์ด๋ฒ ํธ์ ํ ๋ช
์ ์์ด์ ํธ๊ฐ ๋ชจ๋ ์ผ์ ๋ค ์ฒ๋ฆฌํ์ง ์๊ณ , ์ญํ ๋ณ ํ์ ์์ด์ ํธ์๊ฒ ์ผ์ **์์(delegate)**ํ๋ ๊ตฌ์กฐ๋ฅผ ๋ค๋ฃฌ๋ค.
ํต์ฌ์ ManagedAgent ํจํด์ผ๋ก ๊ณํ(Planner) - ์คํ(Worker) - ๊ฒํ (Reviewer) ํ๋ฆ์ ๋ถ๋ฆฌํด, ๊ฒฐ๊ณผ ํ์ง๊ณผ ์ด์ ์์ ์ฑ์ ๋์์ ๋์ด๋ ๊ฒ์ด๋ค.
- ์ด์ ํธ: ๐ค 12. ๋ณธํธ 06
- ์ต๊ทผ ์ค์ตํธ: ๐ค 13. ์ค์ตํธ 03
ํ ์ค ๊ฒฐ๋ก
์์ ์ ์ญํ ๋ณ๋ก ์ชผ๊ฐ๊ณ (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๊ฐ ์ค๋ฌด์์ ์ค์ํ๊ฐ?
์ด๋ณด์๊ฐ ์ฒ์ ๋ง๋๋ ์์ด์ ํธ๋ ๋ณดํต โ์ง๋ฌธ ํ๋ โ ๋ต๋ณ ํ๋โ ๊ตฌ์กฐ๋ค.
ํ์ง๋ง ์ค์ ์
๋ฌด์์๋ ์๋ ๋ฌธ์ ๊ฐ ์์ฃผ ์๊ธด๋ค.
- ์์ฒญ์ด ๊ธธ์ด์ง์๋ก ๋๋ฝ์ด ๋์ด๋จ
- ๋๊ตฌ ํธ์ถ ์คํจ ์ ๋ณต๊ตฌ๊ฐ ์ด๋ ค์
- ๊ฒฐ๊ณผ๋ฌผ ํ์ง ๊ธฐ์ค์ด ๋งค๋ฒ ํ๋ค๋ฆผ
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 ๋ฃจํธ๊ฐ
[]๋ฐฐ์ด
- JSON ๋ฃจํธ๊ฐ
4) ๋จ์ผ ์คํ
- ๋๊ตฌ:
managed_agent_demo.py - ์ ๋ ฅ: ์ง์ 1๊ฐ
- ์คํ๋ช ๋ น:
python3 managed_agent_demo.py --mode single --query "์ฃผ๊ฐ ์ด์ ๋ฆฌํฌํธ ์๋ํ ํ๋ก์ฐ ์ค๊ณ"- ์ฑ๊ณตํ์ :
- ์ถ๋ ฅ JSON์
plan,draft,review์กด์ฌ review.ok = true
- ์ถ๋ ฅ JSON์
5) ๋ฐฐ์น ํ๊ฐ ์คํ
- ๋๊ตฌ:
sample_tasks_day14.json - ์ ๋ ฅ: ํ์คํฌ 3๊ฐ
- ์คํ๋ช ๋ น:
python3 managed_agent_demo.py --mode eval --input sample_tasks_day14.json- ์ฑ๊ณตํ์ :
score๊ฐ ๊ณ์ฐ๋จpass = true๋ฉด ์ฌํ ๊ฐ๋ฅํ ์ต์ ํ์ง ์ถฉ์กฑ
์ค๋ฌด ์ ์ฉ ํฌ์ธํธ (๋ฐ๋ก ์จ๋จน๊ธฐ)
- ์ถ๋ ฅ ๊ณ์ฝ ๊ณ ์ (
FINAL:)- ๋์ค์ n8n/์คํฌ๋ฆฝํธ์์ ํ์ฑํ๊ธฐ ์ฝ๋ค.
- ์ญํ ๋ณ ์คํจ ๋ก๊ทธ ๋ถ๋ฆฌ
- Planner ์คํจ vs Worker ์คํจ๋ฅผ ๊ตฌ๋ถํ๋ฉด ๋ณต๊ตฌ ์๊ฐ์ด ์ค์ด๋ ๋ค.
- Reviewer๋ฅผ ํ์ง ๊ฒ์ดํธ๋ก ์ฌ์ฉ
- ๋ฐฐํฌ ์ ์๋ ์ ๊ฒ ๋จ๊ณ๋ก ๋ถ์ด๊ธฐ ์ข๋ค.
- ์คํ๋ผ์ธ fallback ์ ์ง
- API ์ฅ์ /์๊ธ ์ด์ ์์๋ ๊ธฐ๋ณธ ํ์ดํ๋ผ์ธ์ ํ ์คํธํ ์ ์๋ค.
์์ฃผ ๋งํ๋ ๋ฌธ์ ์ ํด๊ฒฐ
-
input JSON root must be list- ์์ธ: JSON ๋ฃจํธ๋ฅผ
{}๋ก ์์ฑ - ํด๊ฒฐ:
[]๋ฐฐ์ด๋ก ์์
- ์์ธ: JSON ๋ฃจํธ๋ฅผ
-
FINAL format mismatch- ์์ธ: Worker ์ถ๋ ฅ ํ์์ด ๊ณ์ฝ๊ณผ ๋ค๋ฆ
- ํด๊ฒฐ:
FINAL: summary=... | checklist=... | verdict=...ํ์ ๊ฐ์
-
ํ๊ธ ๊ฒฝ๋ก ์คํ ์คํจ
- ์์ธ: ๊ฒฝ๋ก ์ธ์ฉ ๋๋ฝ
- ํด๊ฒฐ: ๊ฒฝ๋ก ์ ์ฒด๋ฅผ ํฐ๋ฐ์ดํ๋ก ๊ฐ์ผ๋ค
์ฒดํฌ๋ฆฌ์คํธ
-
single์คํ์์review.ok=trueํ์ธ -
eval์คํ์์score๊ธฐ๋ก - ํ ๊ธฐ์ค์ผ๋ก
verdict์กฐ๊ฑด(READY/NEEDS_REVIEW) ํฉ์ - ์ด์ ํ์ดํ๋ผ์ธ(n8n/cron)์
FINALํ์ ์ฐ๊ฒฐ
์ฐธ๊ณ ๋งํฌ (์ฐ์ ์์)
- https://github.com/huggingface/agents-course
- https://huggingface.co/learn/agents-course
- https://huggingface.co/docs/smolagents
์์ฑํ AI ํ์ฉ ๊ณ ์ง
์ด ๋ฌธ์๋ ์์ฑํ AI๋ฅผ ํ์ฉํด ์ด์ ์์ฑ, ์ค์ต ์ ์ฐจ ๊ตฌ์กฐํ, ์์ ์ฝ๋ ์ ๋ฆฌ๋ฅผ ์ํํ์ผ๋ฉฐ, ์ต์ข ๋ฐํ ์ ์ฌ๋์ด ๋ช ๋ น ์ฌํ์ฑ๊ณผ ๋งํฌ ์ ํจ์ฑ์ ์ ๊ฒํ๋ค.