์ด๋ฒˆ ํŽธ์€ ํ•œ ๋ช…์˜ ์—์ด์ „ํŠธ๊ฐ€ ๋ชจ๋“  ์ผ์„ ๋‹ค ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ , ์—ญํ• ๋ณ„ ํ•˜์œ„ ์—์ด์ „ํŠธ์—๊ฒŒ ์ผ์„ **์œ„์ž„(delegate)**ํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ฃฌ๋‹ค.
ํ•ต์‹ฌ์€ ManagedAgent ํŒจํ„ด์œผ๋กœ ๊ณ„ํš(Planner) - ์‹คํ–‰(Worker) - ๊ฒ€ํ† (Reviewer) ํ๋ฆ„์„ ๋ถ„๋ฆฌํ•ด, ๊ฒฐ๊ณผ ํ’ˆ์งˆ๊ณผ ์šด์˜ ์•ˆ์ •์„ฑ์„ ๋™์‹œ์— ๋†’์ด๋Š” ๊ฒƒ์ด๋‹ค.

ํ•œ ์ค„ ๊ฒฐ๋ก 

์ž‘์—…์„ ์—ญํ• ๋ณ„๋กœ ์ชผ๊ฐœ๊ณ (Planner/Worker/Reviewer), ์ตœ์ข… ์ถœ๋ ฅ ๊ณ„์•ฝ์„ ๊ณ ์ •ํ•˜๋ฉด ์ดˆ๋ณด์ž๋„ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๋ฉ€ํ‹ฐ์—์ด์ „ํŠธ ์šด์˜์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

flowchart TD
  U[์‚ฌ์šฉ์ž ์š”์ฒญ] --> P[Planner Agent\n์š”๊ตฌ์‚ฌํ•ญ ๋ถ„ํ•ด]
  P --> W[Worker Agent\n๋„๊ตฌ ์‹คํ–‰/์ดˆ์•ˆ ์ƒ์„ฑ]
  W --> R[Reviewer Agent\nํ’ˆ์งˆ/๋ฆฌ์Šคํฌ ์ ๊ฒ€]
  R --> C{ํ†ต๊ณผ?}
  C -- Yes --> F[FINAL: deliverable + checklist + verdict]
  C -- No --> W2[Worker ์žฌ์ž‘์—…]
  W2 --> R

์™œ ManagedAgent๊ฐ€ ์‹ค๋ฌด์—์„œ ์ค‘์š”ํ•œ๊ฐ€?

์ดˆ๋ณด์ž๊ฐ€ ์ฒ˜์Œ ๋งŒ๋“œ๋Š” ์—์ด์ „ํŠธ๋Š” ๋ณดํ†ต โ€œ์งˆ๋ฌธ ํ•˜๋‚˜ โ†’ ๋‹ต๋ณ€ ํ•˜๋‚˜โ€ ๊ตฌ์กฐ๋‹ค.
ํ•˜์ง€๋งŒ ์‹ค์ œ ์—…๋ฌด์—์„œ๋Š” ์•„๋ž˜ ๋ฌธ์ œ๊ฐ€ ์ž์ฃผ ์ƒ๊ธด๋‹ค.

  1. ์š”์ฒญ์ด ๊ธธ์–ด์งˆ์ˆ˜๋ก ๋ˆ„๋ฝ์ด ๋Š˜์–ด๋‚จ
  2. ๋„๊ตฌ ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋ณต๊ตฌ๊ฐ€ ์–ด๋ ค์›€
  3. ๊ฒฐ๊ณผ๋ฌผ ํ’ˆ์งˆ ๊ธฐ์ค€์ด ๋งค๋ฒˆ ํ”๋“ค๋ฆผ

ManagedAgent ํŒจํ„ด์€ ์ด ๋ฌธ์ œ๋ฅผ ์—ญํ•  ๋ถ„๋ฆฌ๋กœ ํ‘ผ๋‹ค.

  • Planner: ํ•ด์•ผ ํ•  ์ผ์„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋กœ ๋ถ„ํ•ด
  • Worker: ๋„๊ตฌ๋ฅผ ์จ์„œ ์‹ค์ œ ์‹คํ–‰
  • Reviewer: ์ถœ๋ ฅ ํ˜•์‹/์ •ํ™•๋„/๋ˆ„๋ฝ์„ ๊ฒ€์‚ฌ

์ฆ‰, โ€œ์ž˜ ์ƒ๊ฐํ•˜๋Š” ๋ชจ๋ธโ€๋ณด๋‹ค ๋จผ์ € โ€œํ”๋“ค๋ฆฌ์ง€ ์•Š๋Š” ์ž‘์—… ๊ตฌ์กฐโ€๋ฅผ ๋งŒ๋“œ๋Š” ์ ‘๊ทผ์ด๋‹ค.


์‹ค์Šต ๋ชฉํ‘œ

  • smolagents ๊ธฐ๋ฐ˜์œผ๋กœ ์—ญํ•  ๋ถ„๋ฆฌ ์—์ด์ „ํŠธ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•œ๋‹ค.
  • ์ถœ๋ ฅ ๊ณ„์•ฝ์„ FINAL: ๋ธ”๋ก์œผ๋กœ ๊ณ ์ •ํ•ด ํŒŒ์‹ฑ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
  • single๊ณผ eval ์‹คํ–‰์œผ๋กœ ์žฌํ˜„์„ฑ์„ ์ ๊ฒ€ํ•œ๋‹ค.

์‹ค์Šต ์ค€๋น„๋ฌผ

  • ๋„๊ตฌ: Python 3.10+, ํ„ฐ๋ฏธ๋„, ๊ฐ€์ƒํ™˜๊ฒฝ(venv)
  • ์ž…๋ ฅ:
    • (์˜จ๋ผ์ธ) OPENAI_API_KEY ๋“ฑ ๋ชจ๋ธ ํ‚ค
    • (์˜คํ”„๋ผ์ธ) ํ‚ค ์—†์ด ์‹คํ–‰ ๊ฐ€๋Šฅ
  • ์ž‘์—… ํด๋” ์˜ˆ์‹œ: ~/hf-managed-agent-lab

1) ํ™˜๊ฒฝ ์ค€๋น„

  • ๋„๊ตฌ: ํ„ฐ๋ฏธ๋„, Python venv
  • ์ž…๋ ฅ: smolagents, litellm
  • ์‹คํ–‰๋ช…๋ น:
mkdir -p ~/hf-managed-agent-lab
cd ~/hf-managed-agent-lab
python3 -m venv .venv
source .venv/bin/activate
pip install -U smolagents litellm
  • ์„ฑ๊ณตํŒ์ •:
    • (.venv) ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋ณด์ž„
    • pip ์—๋Ÿฌ ์—†์ด ์„ค์น˜ ์™„๋ฃŒ

2) ์ตœ์†Œ ๋ฉ€ํ‹ฐ์—์ด์ „ํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

  • ๋„๊ตฌ: ์—๋””ํ„ฐ
  • ์ž…๋ ฅ: ์•„๋ž˜ ์˜ˆ์ œ ์ฝ”๋“œ
  • ์‹คํ–‰๋ช…๋ น:
cat > managed_agent_demo.py <<'PY'
from __future__ import annotations
import argparse
import json
import re
from dataclasses import dataclass
 
 
def planner(query: str) -> list[str]:
    return [
        f"์š”๊ตฌ์‚ฌํ•ญ ํ•ต์‹ฌ ์ •๋ฆฌ: {query}",
        "ํ•„์ˆ˜ ์‚ฐ์ถœ๋ฌผ 3๊ฐœ ์ •์˜(์š”์•ฝ/์ฒดํฌ๋ฆฌ์ŠคํŠธ/ํŒ์ •)",
        "๋ฆฌ์Šคํฌ 2๊ฐœ ์‹๋ณ„",
    ]
 
 
def worker(plan: list[str]) -> str:
    summary = "์š”์ฒญ ๋ชฉ์ ์„ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๋‹จ๊ณ„ํ˜• ์‹คํ–‰์•ˆ์„ ์ž‘์„ฑํ–ˆ๋‹ค."
    checklist = [
        "์š”๊ตฌ์‚ฌํ•ญ ๋ˆ„๋ฝ ์—†์Œ",
        "์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ์žฌํ˜„ ๊ฐ€๋Šฅ",
        "๋ฆฌ์Šคํฌ/๋Œ€์‘ ํฌํ•จ",
    ]
    verdict = "READY"
    return f"FINAL: summary={summary} | checklist={';'.join(checklist)} | verdict={verdict}"
 
 
def reviewer(final_text: str) -> dict:
    m = re.search(r"FINAL:\s*summary=(.*?)\s*\|\s*checklist=(.*?)\s*\|\s*verdict=(\w+)", final_text)
    if not m:
        return {"ok": False, "reason": "FINAL format mismatch"}
 
    summary, checklist, verdict = m.groups()
    ok = bool(summary.strip()) and bool(checklist.strip()) and verdict in {"READY", "NEEDS_REVIEW"}
    return {
        "ok": ok,
        "summary": summary.strip(),
        "checklist": [x.strip() for x in checklist.split(";") if x.strip()],
        "verdict": verdict,
    }
 
 
def run_single(query: str) -> dict:
    plan = planner(query)
    draft = worker(plan)
    review = reviewer(draft)
    return {"query": query, "plan": plan, "draft": draft, "review": review}
 
 
def run_eval(path: str) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        items = json.load(f)
 
    if not isinstance(items, list):
        raise ValueError("input JSON root must be list")
 
    results = [run_single(item["query"]) for item in items]
    pass_count = sum(1 for r in results if r["review"].get("ok"))
    score = pass_count / len(results) if results else 0.0
    return {"count": len(results), "pass_count": pass_count, "score": round(score, 2), "pass": score >= 0.67}
 
 
def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--mode", choices=["single", "eval"], required=True)
    ap.add_argument("--query", default="ManagedAgent๋กœ ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ ์ž‘์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ")
    ap.add_argument("--input", default="sample_tasks_day14.json")
    args = ap.parse_args()
 
    if args.mode == "single":
        print(json.dumps(run_single(args.query), ensure_ascii=False, indent=2))
    else:
        print(json.dumps(run_eval(args.input), ensure_ascii=False, indent=2))
 
 
if __name__ == "__main__":
    main()
PY
  • ์„ฑ๊ณตํŒ์ •:
    • managed_agent_demo.py ํŒŒ์ผ ์ƒ์„ฑ ์™„๋ฃŒ

3) ์ƒ˜ํ”Œ ์ž…๋ ฅ ๋งŒ๋“ค๊ธฐ

  • ๋„๊ตฌ: ํ„ฐ๋ฏธ๋„
  • ์ž…๋ ฅ: ํ‰๊ฐ€์šฉ ํƒœ์Šคํฌ 3๊ฐœ
  • ์‹คํ–‰๋ช…๋ น:
cat > sample_tasks_day14.json <<'JSON'
[
  {"query": "๊ณ ๊ฐ ์ธํ„ฐ๋ทฐ ์š”์•ฝ ์ดˆ์•ˆ์„ 3๋‹จ๊ณ„๋กœ ์ •๋ฆฌ"},
  {"query": "์ฃผ๊ฐ„ ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ํŒ€ ๊ณต์šฉ ํฌ๋งท์œผ๋กœ ์ž‘์„ฑ"},
  {"query": "์žฅ์•  ๋ณด๊ณ ์„œ ์ดˆ์•ˆ์— ์›์ธ/์˜ํ–ฅ/๋ณต๊ตฌ ํ•ญ๋ชฉ ์ถ”๊ฐ€"}
]
JSON
  • ์„ฑ๊ณตํŒ์ •:
    • JSON ๋ฃจํŠธ๊ฐ€ [] ๋ฐฐ์—ด

4) ๋‹จ์ผ ์‹คํ–‰

  • ๋„๊ตฌ: managed_agent_demo.py
  • ์ž…๋ ฅ: ์งˆ์˜ 1๊ฐœ
  • ์‹คํ–‰๋ช…๋ น:
python3 managed_agent_demo.py --mode single --query "์ฃผ๊ฐ„ ์šด์˜ ๋ฆฌํฌํŠธ ์ž๋™ํ™” ํ”Œ๋กœ์šฐ ์„ค๊ณ„"
  • ์„ฑ๊ณตํŒ์ •:
    • ์ถœ๋ ฅ JSON์— plan, draft, review ์กด์žฌ
    • review.ok = true

5) ๋ฐฐ์น˜ ํ‰๊ฐ€ ์‹คํ–‰

  • ๋„๊ตฌ: sample_tasks_day14.json
  • ์ž…๋ ฅ: ํƒœ์Šคํฌ 3๊ฐœ
  • ์‹คํ–‰๋ช…๋ น:
python3 managed_agent_demo.py --mode eval --input sample_tasks_day14.json
  • ์„ฑ๊ณตํŒ์ •:
    • score๊ฐ€ ๊ณ„์‚ฐ๋จ
    • pass = true๋ฉด ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ํ’ˆ์งˆ ์ถฉ์กฑ

์‹ค๋ฌด ์ ์šฉ ํฌ์ธํŠธ (๋ฐ”๋กœ ์จ๋จน๊ธฐ)

  1. ์ถœ๋ ฅ ๊ณ„์•ฝ ๊ณ ์ • (FINAL:)
    • ๋‚˜์ค‘์— n8n/์Šคํฌ๋ฆฝํŠธ์—์„œ ํŒŒ์‹ฑํ•˜๊ธฐ ์‰ฝ๋‹ค.
  2. ์—ญํ• ๋ณ„ ์‹คํŒจ ๋กœ๊ทธ ๋ถ„๋ฆฌ
    • Planner ์‹คํŒจ vs Worker ์‹คํŒจ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋ฉด ๋ณต๊ตฌ ์‹œ๊ฐ„์ด ์ค„์–ด๋“ ๋‹ค.
  3. Reviewer๋ฅผ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ๋กœ ์‚ฌ์šฉ
    • ๋ฐฐํฌ ์ „ ์ž๋™ ์ ๊ฒ€ ๋‹จ๊ณ„๋กœ ๋ถ™์ด๊ธฐ ์ข‹๋‹ค.
  4. ์˜คํ”„๋ผ์ธ fallback ์œ ์ง€
    • API ์žฅ์• /์š”๊ธˆ ์ด์Šˆ ์‹œ์—๋„ ๊ธฐ๋ณธ ํŒŒ์ดํ”„๋ผ์ธ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ž์ฃผ ๋ง‰ํžˆ๋Š” ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ

  1. input JSON root must be list

    • ์›์ธ: JSON ๋ฃจํŠธ๋ฅผ {}๋กœ ์ž‘์„ฑ
    • ํ•ด๊ฒฐ: [] ๋ฐฐ์—ด๋กœ ์ˆ˜์ •
  2. FINAL format mismatch

    • ์›์ธ: Worker ์ถœ๋ ฅ ํ˜•์‹์ด ๊ณ„์•ฝ๊ณผ ๋‹ค๋ฆ„
    • ํ•ด๊ฒฐ: FINAL: summary=... | checklist=... | verdict=... ํ˜•์‹ ๊ฐ•์ œ
  3. ํ•œ๊ธ€ ๊ฒฝ๋กœ ์‹คํ–‰ ์‹คํŒจ

    • ์›์ธ: ๊ฒฝ๋กœ ์ธ์šฉ ๋ˆ„๋ฝ
    • ํ•ด๊ฒฐ: ๊ฒฝ๋กœ ์ „์ฒด๋ฅผ ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ผ๋‹ค

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

  • single ์‹คํ–‰์—์„œ review.ok=true ํ™•์ธ
  • eval ์‹คํ–‰์—์„œ score ๊ธฐ๋ก
  • ํŒ€ ๊ธฐ์ค€์œผ๋กœ verdict ์กฐ๊ฑด(READY/NEEDS_REVIEW) ํ•ฉ์˜
  • ์šด์˜ ํŒŒ์ดํ”„๋ผ์ธ(n8n/cron)์— FINAL ํŒŒ์„œ ์—ฐ๊ฒฐ

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

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

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

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