์ด๋ฒˆ ํŽธ์€ **โ€œํ•˜๋‚˜์˜ ์š”์ฒญ์„ ์—ญํ• ๋ณ„ ์„œ๋ธŒ ์—์ด์ „ํŠธ๋กœ ๋ถ„ํ•ดํ•ด, ๊ทผ๊ฑฐ ํฌํ•จ ๋ฆฌ์„œ์น˜ ํŒฉ(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')))
PY
  • OPENAI_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 ํŒŒ์ผ ์ƒ์„ฑ
    • ์ฝ”๋“œ์— CodeAgent 3๊ฐœ(์‹œ์žฅ/๊ธฐ์ˆ /์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ) ํ™•์ธ

4) ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ ์ €์žฅ

  • ๋„๊ตฌ: Python
  • ์ž…๋ ฅ: TOPIC ๋ฌธ์ž์—ด(์ฝ”๋“œ ์ƒ๋‹จ)
  • ์‹คํ–‰๋ช…๋ น:
python lab25_multiagent_research_pack.py
cat research_pack.json
  • ์„ฑ๊ณตํŒ์ •:
    • saved: research_pack.json ์ถœ๋ ฅ
    • JSON์— executive_summary 5๊ฐœ ๋ผ์ธ
    • specialists 2๊ฐœ ๋ธ”๋ก + 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๊ฐœ ์ด์ƒ)

  1. 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
  • ์กฐ์น˜: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์žฌ์„ค์ • ํ›„ ์žฌ์‹คํ–‰
  1. DuckDuckGoSearchTool ImportError
  • ์›์ธ: ํŒจํ‚ค์ง€ ๋ฒ„์ „ ๋ถˆ์ผ์น˜
  • ์กฐ์น˜:
pip install -U smolagents duckduckgo-search
python - <<'PY'
from smolagents import DuckDuckGoSearchTool
print('ok')
PY
  1. JSON ํŒŒ์‹ฑ ์‹คํŒจ (json.loads)
  • ์›์ธ: ๋ชจ๋ธ ์ถœ๋ ฅ์— ์„ค๋ช… ๋ฌธ์žฅ/์ฝ”๋“œ๋ธ”๋ก์ด ์„ž์ž„
  • ์กฐ์น˜:
    • ํ”„๋กฌํ”„ํŠธ์— JSON๋งŒ ๋ฐ˜ํ™˜ ์œ ์ง€
    • strip_code_fence()๋กœ ์ฝ”๋“œ๋ธ”๋ก ์ œ๊ฑฐ ํ›„ ํŒŒ์‹ฑ
  1. source_count mismatch ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ์‹คํŒจ
  • ์›์ธ: ๋ณ‘ํ•ฉ ์‹œ source_count ๊ณ„์‚ฐ ๋ˆ„๋ฝ/์˜ค๋ฅ˜
  • ์กฐ์น˜:
    • ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ ํ”„๋กฌํ”„ํŠธ์— ์ „์ฒด source ํ•ฉ ๊ทœ์น™ ๊ณ ์ •
    • ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ ์‹คํŒจ ์‹œ ์žฌ์‹คํ–‰ ์ „ specialists[].sources ๊ฐœ์ˆ˜ ๋จผ์ € ์ ๊ฒ€
  1. ๊ฒ€์ƒ‰ ํ’ˆ์งˆ ๋‚ฎ์Œ(์ค‘๋ณต/์•ฝํ•œ ์ถœ์ฒ˜)
  • ์›์ธ: TOPIC์ด ๋„ˆ๋ฌด ๋„“๊ฑฐ๋‚˜ ๋ชจํ˜ธํ•จ
  • ์กฐ์น˜:
    • TOPIC์„ ์‚ฐ์—…/์ง€์—ญ/๊ธฐ๊ฐ„์œผ๋กœ ์ขํ˜€ ์žฌ์‹คํ–‰
    • ์˜ˆ: 2026๋…„ ํ•œ๊ตญ SaaS ๊ธฐ์—…์˜ AI ์—์ด์ „ํŠธ ์šด์˜ ์‚ฌ๋ก€ 3๊ฐ€์ง€

์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • 3๊ฐœ ์—์ด์ „ํŠธ(์‹œ์žฅ/๊ธฐ์ˆ /์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ) ์‹คํ–‰ ์„ฑ๊ณต
  • research_pack.json ์ €์žฅ ์™„๋ฃŒ
  • 5์ค„ ์š”์•ฝ + ์—ญํ• ๋ณ„ ์ธ์‚ฌ์ดํŠธ/์ถœ์ฒ˜ ํ˜•์‹ ์ถฉ์กฑ
  • ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ์Šคํฌ๋ฆฝํŠธ PASS
  • ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… 3๊ฐœ ์ด์ƒ ์žฌํ˜„/์ •๋ฆฌ

์ฐธ๊ณ  ๋งํฌ (์šฐ์„ ์ˆœ์œ„)

  1. https://github.com/huggingface/agents-course
  2. https://huggingface.co/learn/agents-course
  3. https://huggingface.co/docs/smolagents

์ƒ์„ฑํ˜• AI ํ™œ์šฉ ๊ณ ์ง€

์ด ๋ฌธ์„œ๋Š” ์ƒ์„ฑํ˜• AI๋กœ ์ดˆ์•ˆ์„ ์ž‘์„ฑํ•˜๊ณ , ์‚ฌ๋žŒ์ด ์‹ค์Šต ์žฌํ˜„์„ฑ(๋„๊ตฌ/์ž…๋ ฅ/์‹คํ–‰๋ช…๋ น/์„ฑ๊ณตํŒ์ •), ๋งํฌ ์œ ํšจ์„ฑ, ๋ฌธ์„œ ํ˜•์‹, ๋‚ด๋ถ€ ๋งํฌ๋ฅผ ๊ฒ€ํ† ํ•ด ์ตœ์ข… ํ™•์ •ํ–ˆ๋‹ค.