์ด๋ฒ ํธ์ **โํ๋์ ์์ฒญ์ ์ญํ ๋ณ ์๋ธ ์์ด์ ํธ๋ก ๋ถํดํด, ๊ทผ๊ฑฐ ํฌํจ ๋ฆฌ์์น ํฉ(JSON)์ ๋ง๋๋ ์ค์ตโ**์ด๋ค.
ํต์ฌ์ ์๋ 4๊ฐ์ง๋ฅผ ์ฌํํ๋ ๊ฒ์ด๋ค.
-
Orchestrator + Specialist(์์ฅ/๊ธฐ์ ) 2๊ฐ ์ญํ ๊ตฌ์ฑ
-
์น ๊ฒ์ ๊ธฐ๋ฐ ๊ทผ๊ฑฐ ์์ง
-
๊ตฌ์กฐํ ๊ฒฐ๊ณผ(JSON) ์ ์ฅ
-
ํ์ง ๊ฒ์ดํธ + ํธ๋ฌ๋ธ์ํ ์ผ๋ก ์ฌ์คํ ๊ฐ๋ฅ ์ํ ํ๋ณด
-
์ด์ ํธ: ๐ค 24. ๋ณธํธ 14
-
๋ค์ ํธ ์๊ณ : ์ค์ตํธ 07-2 (ํ๊ฐ ์์ด์ ํธ ์ถ๊ฐ, ์๋ ์ฌ์๋ ๋ฃจํ)
flowchart TD A[์ฌ์ฉ์ ์ฃผ์ ์ ๋ ฅ] --> B[Orchestrator CodeAgent] B --> C[์์ฅ ๋ถ์ Specialist] B --> D[๊ธฐ์ ๋ถ์ Specialist] C --> E[๊ทผ๊ฑฐ 2๊ฐ ์ด์ ๋ฐํ] D --> F[๊ทผ๊ฑฐ 2๊ฐ ์ด์ ๋ฐํ] E --> G[Orchestrator๊ฐ ๋ฆฌ์์น ํฉ JSON ๋ณํฉ] F --> G G --> H[research_pack.json ์ ์ฅ] H --> I{ํ์ง ๊ฒ์ดํธ ํต๊ณผ?} I -- Yes --> J[์๋ฃ] I -- No --> K[ํ๋กฌํํธ/ํ๊ฒฝ ์์ ํ ์ฌ์คํ] K --> B
0) ์ค์ต ๋ชฉํ
์
๋ ฅ ์ฃผ์ 1๊ฐ๋ฅผ ๋ฃ์ผ๋ฉด, ๋ฉํฐ์์ด์ ํธ๊ฐ ์์ฅ/๊ธฐ์ ๊ด์ ์์ฝ๊ณผ ์ถ์ฒ๋ฅผ ๋ชจ์ research_pack.json์ผ๋ก ์ ์ฅํ๋ค.
1) ํ๊ฒฝ ์ค๋น
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: Python 3.10+
- ์คํ๋ช ๋ น:
mkdir -p ~/hf-agents-lab25
cd ~/hf-agents-lab25
python3 -m venv .venv
source .venv/bin/activate
pip install -U smolagents litellm duckduckgo-search- ์ฑ๊ณตํ์ :
python -V
pip show smolagents | head -n 5
pip show duckduckgo-search | head -n 5- Python ๋ฒ์ ์ด ์ถ๋ ฅ๋๊ณ
smolagents,duckduckgo-search์ ๋ณด๊ฐ ๋ณด์ด๋ฉด ์ฑ๊ณต
2) ๋ชจ๋ธ/API ์ค์
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: API ํค, ๋ชจ๋ธ ID
- ์คํ๋ช ๋ น:
export OPENAI_API_KEY="YOUR_API_KEY"
export MODEL_ID="openai/gpt-4o-mini"- ์ฑ๊ณตํ์ :
python - <<'PY'
import os
print('MODEL_ID =', os.getenv('MODEL_ID'))
print('OPENAI_API_KEY set =', bool(os.getenv('OPENAI_API_KEY')))
PYOPENAI_API_KEY set = Trueํ์ธ
3) ๋ฏธ๋ ํ๋ก์ ํธ ์ฝ๋ ์์ฑ
- ๋๊ตฌ: ์๋ํฐ/ํฐ๋ฏธ๋
- ์ ๋ ฅ: ์๋ ์ฝ๋
- ์คํ๋ช ๋ น:
cat > lab25_multiagent_research_pack.py <<'PY'
from __future__ import annotations
import json
import os
from datetime import datetime
from pathlib import Path
from smolagents import CodeAgent, DuckDuckGoSearchTool, LiteLLMModel
TOPIC = "2026๋
ํ๊ตญ B2B ์กฐ์ง์ด ๋์
๊ฐ๋ฅํ AI ์์ด์ ํธ ์ด์ ํจํด 3๊ฐ์ง"
OUT = Path("research_pack.json")
def strip_code_fence(text: str) -> str:
t = text.strip()
if t.startswith("```"):
lines = t.splitlines()
if lines and lines[0].startswith("```"):
lines = lines[1:]
if lines and lines[-1].startswith("```"):
lines = lines[:-1]
t = "\n".join(lines).strip()
return t
def run_specialist(agent: CodeAgent, role: str, topic: str) -> dict:
prompt = f"""
๋๋ {role} Specialist๋ค.
์ฃผ์ : {topic}
์น ๊ฒ์ ๊ธฐ๋ฐ์ผ๋ก ์๋ JSON๋ง ๋ฐํํ๋ผ:
{{
"role": "{role}",
"insights": ["...", "...", "..."],
"sources": [
{{"title": "...", "url": "..."}},
{{"title": "...", "url": "..."}}
]
}}
๊ท์น:
- insights๋ ์ ํํ 3๊ฐ
- sources๋ ์ต์ 2๊ฐ
- URL์ ์ค์ http/https ์ฃผ์
- ์ฐพ์ง ๋ชปํ๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ๋ช
์
"""
raw = agent.run(prompt)
return json.loads(strip_code_fence(str(raw)))
def main() -> None:
model = LiteLLMModel(model_id=os.getenv("MODEL_ID", "openai/gpt-4o-mini"))
tool = DuckDuckGoSearchTool()
market_agent = CodeAgent(
model=model,
tools=[tool],
max_steps=6,
additional_authorized_imports=["json"],
)
tech_agent = CodeAgent(
model=model,
tools=[tool],
max_steps=6,
additional_authorized_imports=["json"],
)
market = run_specialist(market_agent, "์์ฅ ๋ถ์", TOPIC)
tech = run_specialist(tech_agent, "๊ธฐ์ ๋ถ์", TOPIC)
orchestrator_prompt = f"""
๋ค์ ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณํฉํด ์ต์ข
๋ฆฌ์์น ํฉ JSON๋ง ๋ฐํํ๋ผ.
[์์ฅ ๋ถ์]
{json.dumps(market, ensure_ascii=False)}
[๊ธฐ์ ๋ถ์]
{json.dumps(tech, ensure_ascii=False)}
ํ์:
{{
"topic": "...",
"executive_summary": ["...", "...", "...", "...", "..."],
"specialists": [...],
"source_count": 0
}}
๊ท์น:
- executive_summary๋ ์ ํํ 5์ค
- specialists์๋ ์ 2๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ๋ฃ๊ธฐ
- source_count๋ ์ ์ฒด source ๊ฐ์ ํฉ
"""
orchestrator = CodeAgent(
model=model,
tools=[],
max_steps=4,
additional_authorized_imports=["json"],
)
final_obj = json.loads(strip_code_fence(str(orchestrator.run(orchestrator_prompt))))
final_obj["generated_at"] = datetime.now().isoformat(timespec="seconds")
OUT.write_text(json.dumps(final_obj, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"saved: {OUT}")
print(json.dumps({
"topic": final_obj.get("topic"),
"summary_count": len(final_obj.get("executive_summary", [])),
"source_count": final_obj.get("source_count"),
}, ensure_ascii=False))
if __name__ == "__main__":
main()
PY- ์ฑ๊ณตํ์ :
lab25_multiagent_research_pack.pyํ์ผ ์์ฑ- ์ฝ๋์
CodeAgent3๊ฐ(์์ฅ/๊ธฐ์ /์ค์ผ์คํธ๋ ์ดํฐ) ํ์ธ
4) ์คํ ๋ฐ ๊ฒฐ๊ณผ ์ ์ฅ
- ๋๊ตฌ: Python
- ์
๋ ฅ:
TOPIC๋ฌธ์์ด(์ฝ๋ ์๋จ) - ์คํ๋ช ๋ น:
python lab25_multiagent_research_pack.py
cat research_pack.json- ์ฑ๊ณตํ์ :
saved: research_pack.json์ถ๋ ฅ- JSON์
executive_summary5๊ฐ ๋ผ์ธ specialists2๊ฐ ๋ธ๋ก +source_count๊ฐ ์กด์ฌ
5) ํ์ง ๊ฒ์ดํธ(์ฌํ ์ฒดํฌ)
- ๋๊ตฌ: Python
- ์
๋ ฅ:
research_pack.json - ์คํ๋ช ๋ น:
python - <<'PY'
import json
from urllib.parse import urlparse
obj = json.load(open('research_pack.json', encoding='utf-8'))
assert isinstance(obj.get('executive_summary'), list), 'executive_summary must be list'
assert len(obj['executive_summary']) == 5, 'executive_summary must be 5 lines'
assert isinstance(obj.get('specialists'), list) and len(obj['specialists']) == 2, 'specialists must be 2'
total = 0
for sp in obj['specialists']:
assert len(sp.get('insights', [])) == 3, f"{sp.get('role')} insights must be 3"
assert len(sp.get('sources', [])) >= 2, f"{sp.get('role')} sources must be >=2"
for s in sp['sources']:
u = s.get('url', '')
p = urlparse(u)
assert p.scheme in ('http', 'https') and p.netloc, f'invalid url: {u}'
total += len(sp['sources'])
assert obj.get('source_count') == total, 'source_count mismatch'
print('PASS: ํ์ง ๊ฒ์ดํธ ํต๊ณผ')
PY- ์ฑ๊ณตํ์ :
PASS: ํ์ง ๊ฒ์ดํธ ํต๊ณผ์ถ๋ ฅ
ํธ๋ฌ๋ธ์ํ (3๊ฐ ์ด์)
AuthenticationError๋๋ ๋ชจ๋ธ ํธ์ถ ์คํจ
- ์์ธ: API ํค ๋ฏธ์ค์ /์คํ
- ์ ๊ฒ:
python - <<'PY'
import os
print('OPENAI_API_KEY set =', bool(os.getenv('OPENAI_API_KEY')))
print('MODEL_ID =', os.getenv('MODEL_ID'))
PY- ์กฐ์น: ํ๊ฒฝ๋ณ์ ์ฌ์ค์ ํ ์ฌ์คํ
DuckDuckGoSearchToolImportError
- ์์ธ: ํจํค์ง ๋ฒ์ ๋ถ์ผ์น
- ์กฐ์น:
pip install -U smolagents duckduckgo-search
python - <<'PY'
from smolagents import DuckDuckGoSearchTool
print('ok')
PY- JSON ํ์ฑ ์คํจ (
json.loads)
- ์์ธ: ๋ชจ๋ธ ์ถ๋ ฅ์ ์ค๋ช ๋ฌธ์ฅ/์ฝ๋๋ธ๋ก์ด ์์
- ์กฐ์น:
- ํ๋กฌํํธ์
JSON๋ง ๋ฐํ์ ์ง strip_code_fence()๋ก ์ฝ๋๋ธ๋ก ์ ๊ฑฐ ํ ํ์ฑ
- ํ๋กฌํํธ์
source_count mismatchํ์ง ๊ฒ์ดํธ ์คํจ
- ์์ธ: ๋ณํฉ ์
source_count๊ณ์ฐ ๋๋ฝ/์ค๋ฅ - ์กฐ์น:
- ์ค์ผ์คํธ๋ ์ดํฐ ํ๋กฌํํธ์
์ ์ฒด source ํฉ๊ท์น ๊ณ ์ - ๊ฒ์ฆ ์คํฌ๋ฆฝํธ ์คํจ ์ ์ฌ์คํ ์
specialists[].sources๊ฐ์ ๋จผ์ ์ ๊ฒ
- ์ค์ผ์คํธ๋ ์ดํฐ ํ๋กฌํํธ์
- ๊ฒ์ ํ์ง ๋ฎ์(์ค๋ณต/์ฝํ ์ถ์ฒ)
- ์์ธ: TOPIC์ด ๋๋ฌด ๋๊ฑฐ๋ ๋ชจํธํจ
- ์กฐ์น:
TOPIC์ ์ฐ์ /์ง์ญ/๊ธฐ๊ฐ์ผ๋ก ์ขํ ์ฌ์คํ- ์:
2026๋ ํ๊ตญ SaaS ๊ธฐ์ ์ AI ์์ด์ ํธ ์ด์ ์ฌ๋ก 3๊ฐ์ง
์ฒดํฌ๋ฆฌ์คํธ
- 3๊ฐ ์์ด์ ํธ(์์ฅ/๊ธฐ์ /์ค์ผ์คํธ๋ ์ดํฐ) ์คํ ์ฑ๊ณต
-
research_pack.json์ ์ฅ ์๋ฃ - 5์ค ์์ฝ + ์ญํ ๋ณ ์ธ์ฌ์ดํธ/์ถ์ฒ ํ์ ์ถฉ์กฑ
- ํ์ง ๊ฒ์ดํธ ์คํฌ๋ฆฝํธ PASS
- ํธ๋ฌ๋ธ์ํ 3๊ฐ ์ด์ ์ฌํ/์ ๋ฆฌ
์ฐธ๊ณ ๋งํฌ (์ฐ์ ์์)
- https://github.com/huggingface/agents-course
- https://huggingface.co/learn/agents-course
- https://huggingface.co/docs/smolagents
์์ฑํ AI ํ์ฉ ๊ณ ์ง
์ด ๋ฌธ์๋ ์์ฑํ AI๋ก ์ด์์ ์์ฑํ๊ณ , ์ฌ๋์ด ์ค์ต ์ฌํ์ฑ(๋๊ตฌ/์ ๋ ฅ/์คํ๋ช ๋ น/์ฑ๊ณตํ์ ), ๋งํฌ ์ ํจ์ฑ, ๋ฌธ์ ํ์, ๋ด๋ถ ๋งํฌ๋ฅผ ๊ฒํ ํด ์ต์ข ํ์ ํ๋ค.