์ด๋ฒ ํธ์ ์ค๋ฌดํ ๋ฏธ๋ ํ๋ก์ ํธ/ํธ์ฆ์จ์ผ๋ก, ์ฌ๋ด ์ ์ฑ ๋ฌธ์(ํ๋ถ/์๊ธ์ /๋ณด์)๋ฅผ ์ฝ๊ณ ์ง๋ฌธ์ ๋ตํ๋ FAQ ์์ด์ ํธ๋ฅผ ๋ง๋ ๋ค.
- ์ด์ ํธ: ๐ค 18. ๋ณธํธ 10
- ๋ค์ ํธ ์๊ณ : FAQ ์๋ต ํ์ง ์ ์ํ(์ ํ์ฑ/๊ทผ๊ฑฐ/๊ธ์ง์ด) ์๋ ๋ฆฌํฌํธ
ํ ์ค ๋ชฉํ
๋ก์ปฌ ์ ์ฑ ํ์ผ(JSON) + ์ง๋ฌธ์ (JSONL) + smolagents CodeAgent๋ก ๋ต๋ณ ์์ฑ ํ, ํ์/๊ทผ๊ฑฐ/์ ํ๋ ๊ธฐ์ค์ ์๋ ๊ฒ์ฆํ๋ค.
flowchart LR A[policy_kb.json] --> B[Python Tool: search_policy] C[questions.jsonl] --> D[CodeAgent] B --> D D --> E[answers.json] E --> F[validator.py] F --> G{PASS/FAIL}
0) ์ค์ต ๋ฒ์(๊ณ ์ )
- ๋ฒ์ ํฌํจ
- ์ ์ฑ
KB ์กฐํ ๋๊ตฌ 1๊ฐ(
search_policy)๋ฅผ ๋ง๋ค์ด ์์ด์ ํธ์ ์ฐ๊ฒฐ - ์ง๋ฌธ 8๊ฑด์ ๋ํด
answer,evidence(๊ทผ๊ฑฐ policy_id) ์ถ๋ ฅ - ์๋ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ๋ก PASS/FAIL ํ๋จ
- ์ ์ฑ
KB ์กฐํ ๋๊ตฌ 1๊ฐ(
- ๋ฒ์ ์ ์ธ
- ๋ฒกํฐDB/์๋ฒ ๋ฉ
- ์ธ๋ถ API ์ฐ๋(n8n, Slack)
- ๋ค๊ตญ์ด ๋ต๋ณ ํ๋
1) ํ๊ฒฝ ์ค๋น
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: Python 3.10+, ๊ฐ์ํ๊ฒฝ
- ์คํ๋ช ๋ น:
mkdir -p ~/hf-agents-lab19
cd ~/hf-agents-lab19
python3 -m venv .venv
source .venv/bin/activate
pip install -U smolagents litellm- ์ฑ๊ณตํ์ :
(.venv)ํ๋กฌํํธ๊ฐ ๋ณด์python -V์คํ ์ ๋ฒ์ ์ถ๋ ฅpip show smolagents๊ฒฐ๊ณผ๊ฐ ์กด์ฌ
2) ๋ชจ๋ธ ํค ์ค์
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: API ํค, ๋ชจ๋ธ ID
- ์คํ๋ช ๋ น:
export OPENAI_API_KEY="YOUR_API_KEY"
export MODEL_ID="openai/gpt-4o-mini"- ์ฑ๊ณตํ์ :
echo "$MODEL_ID"
python - <<'PY'
import os
print("OPENAI_API_KEY set:", bool(os.getenv("OPENAI_API_KEY")))
PY- ๋ชจ๋ธ ID ๋ฌธ์์ด ์ถ๋ ฅ
OPENAI_API_KEY set: True์ถ๋ ฅ
3) ์ ์ฑ KB์ ์ง๋ฌธ์ ๋ง๋ค๊ธฐ
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: ์ ์ฑ 6๊ฐ + ์ง๋ฌธ 8๊ฐ
- ์คํ๋ช ๋ น:
cat > policy_kb.json <<'JSON'
[
{"policy_id":"P-REFUND-7D","topic":"refund","content":"๊ฒฐ์ ํ 7์ผ ์ด๋ด, ์ฌ์ฉ๋ 10% ๋ฏธ๋ง์ด๋ฉด ํ๋ถ ๊ฐ๋ฅํ๋ค."},
{"policy_id":"P-REFUND-EXC","topic":"refund","content":"๋์งํธ ๋ค์ด๋ก๋ ์ํ์ ๊ฒฐ์ ์ฆ์ ์ฌ์ฉ์ผ๋ก ๊ฐ์ฃผ๋์ด ํ๋ถ ๋์์์ ์ ์ธ๋๋ค."},
{"policy_id":"P-PLAN-UP","topic":"plan","content":"์๊ธ์ ์
๊ทธ๋ ์ด๋๋ ์ฆ์ ๋ฐ์๋๋ฉฐ, ์ฐจ์ก์ ์ผํ ๊ณ์ฐ๋๋ค."},
{"policy_id":"P-PLAN-DOWN","topic":"plan","content":"์๊ธ์ ๋ค์ด๊ทธ๋ ์ด๋๋ ๋ค์ ๊ฒฐ์ ์ฃผ๊ธฐ๋ถํฐ ์ ์ฉ๋๋ค."},
{"policy_id":"P-SEC-MFA","topic":"security","content":"๊ด๋ฆฌ์ ๊ณ์ ์ MFA๋ฅผ ๋ฐ๋์ ํ์ฑํํด์ผ ํ๋ค."},
{"policy_id":"P-SEC-LOG","topic":"security","content":"๋ณด์ ๋ก๊ทธ๋ ์ต์ 90์ผ๊ฐ ๋ณด๊ดํ๋ค."}
]
JSON
cat > questions.jsonl <<'JSONL'
{"qid":"Q1","question":"๊ฒฐ์ 3์ผ ์ง๋ฌ๊ณ ๊ฑฐ์ ์ ์ผ๋๋ฐ ํ๋ถ ๊ฐ๋ฅํด?","expected_policy":"P-REFUND-7D"}
{"qid":"Q2","question":"๋ค์ด๋ก๋ํ ๋ฆฌํฌํธ ์๋๋ฐ ๋ฐ๋ก ํ๋ถ๋ผ?","expected_policy":"P-REFUND-EXC"}
{"qid":"Q3","question":"์๊ธ์ ์ฌ๋ฆฌ๋ฉด ์ธ์ ๋ฐ์๋ผ?","expected_policy":"P-PLAN-UP"}
{"qid":"Q4","question":"์๊ธ์ ๋ด๋ฆฌ๋ฉด ๋ฐ๋ก ๋ด๋ ค๊ฐ?","expected_policy":"P-PLAN-DOWN"}
{"qid":"Q5","question":"๊ด๋ฆฌ์ ๊ณ์ ์ 2๋จ๊ณ ์ธ์ฆ ํ์์ผ?","expected_policy":"P-SEC-MFA"}
{"qid":"Q6","question":"๋ณด์ ๋ก๊ทธ๋ ์ผ๋ง๋ ๋ณด๊ดํด์ผ ํด?","expected_policy":"P-SEC-LOG"}
{"qid":"Q7","question":"ํ๋ถ์ ๋ฌด์กฐ๊ฑด 30์ผ ์ด๋ด๋ฉด ๋ผ?","expected_policy":"P-REFUND-7D"}
{"qid":"Q8","question":"์
๊ทธ๋ ์ด๋ ์ฐจ์ก ๊ณ์ฐ ๋ฐฉ์์?","expected_policy":"P-PLAN-UP"}
JSONL- ์ฑ๊ณตํ์ :
python - <<'PY'
import json
print('kb:', len(json.load(open('policy_kb.json'))))
print('q :', sum(1 for _ in open('questions.jsonl')))
PYkb: 6,q : 8์ถ๋ ฅ
4) ์์ด์ ํธ ์ฝ๋ ์์ฑ
- ๋๊ตฌ: ์๋ํฐ/ํฐ๋ฏธ๋
- ์ ๋ ฅ: ์๋ ์ฝ๋
- ์คํ๋ช ๋ น:
cat > lab19_policy_faq_agent.py <<'PY'
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import List, Dict, Any
from smolagents import CodeAgent, LiteLLMModel, tool
KB_PATH = Path("policy_kb.json")
Q_PATH = Path("questions.jsonl")
OUT_PATH = Path("answers.json")
KB: List[Dict[str, Any]] = json.loads(KB_PATH.read_text(encoding="utf-8"))
@tool
def search_policy(query: str) -> str:
"""์ง๋ฌธ(query)์ ๊ฐ์ฅ ๊ด๋ จ ๋์ ์ ์ฑ
3๊ฐ๋ฅผ ๋ฌธ์์ด๋ก ๋ฐํํ๋ค."""
q = query.lower()
scored = []
for row in KB:
txt = (row["topic"] + " " + row["content"]).lower()
score = 0
for token in ["ํ๋ถ", "refund", "์๊ธ์ ", "upgrade", "down", "๋ณด์", "mfa", "๋ก๊ทธ"]:
if token in q and token in txt:
score += 1
if score == 0:
# ํ ํฐ์ด ํ๋๋ ์ ๋ง์ผ๋ฉด ์ฝํ ๊ธฐ๋ณธ์ ์
score = 0.1
scored.append((score, row))
top = [r for _, r in sorted(scored, key=lambda x: x[0], reverse=True)[:3]]
return json.dumps(top, ensure_ascii=False)
def load_questions() -> List[Dict[str, Any]]:
items = []
for line in Q_PATH.read_text(encoding="utf-8").splitlines():
if line.strip():
items.append(json.loads(line))
return items
def build_agent() -> CodeAgent:
model = LiteLLMModel(model_id=os.getenv("MODEL_ID", "openai/gpt-4o-mini"))
system_prompt = """
๋๋ ์ ์ฑ
FAQ ๋ต๋ณ ์์ด์ ํธ๋ค.
๋ฐ๋์ search_policy ๋๊ตฌ๋ฅผ ๋จผ์ ํธ์ถํด ๊ทผ๊ฑฐ๋ฅผ ์ฐพ๋๋ค.
์ถ๋ ฅ์ JSON ๋ฐฐ์ด๋ง ๋ฐํํ๋ค.
๊ฐ ์์ ํค:
- qid
- answer (ํ๊ธ 1~2๋ฌธ์ฅ)
- evidence (policy_id 1๊ฐ)
๊ธ์ง:
- KB์ ์๋ ์ ์ฑ
์ ์ฌ์ค์ฒ๋ผ ๋จ์
- evidence ๋๋ฝ
""".strip()
return CodeAgent(model=model, tools=[search_policy], system_prompt=system_prompt)
def main() -> None:
questions = load_questions()
agent = build_agent()
prompt = f"""
์๋ ์ง๋ฌธ ๋ชฉ๋ก์ ๋ต๋ณํด.
์ง๋ฌธ ๋ชฉ๋ก:
{json.dumps(questions, ensure_ascii=False)}
๋ฐ๋์ JSON ๋ฐฐ์ด๋ง ์ถ๋ ฅํด.
""".strip()
result = agent.run(prompt)
if isinstance(result, str):
s = result.find("[")
e = result.rfind("]")
if s != -1 and e != -1 and e > s:
result = json.loads(result[s:e+1])
else:
raise ValueError("JSON ๋ฐฐ์ด ํ์ฑ ์คํจ")
OUT_PATH.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"saved: {OUT_PATH}")
print(f"count: {len(result)}")
if __name__ == "__main__":
main()
PY- ์ฑ๊ณตํ์ :
lab19_policy_faq_agent.py์์ฑsearch_policy๋๊ตฌ ์ ์ ํ์ธ
5) ์คํ ๋ฐ ์ถ๋ ฅ ํ์ธ
- ๋๊ตฌ: Python, ํฐ๋ฏธ๋
- ์
๋ ฅ:
policy_kb.json,questions.jsonl - ์คํ๋ช ๋ น:
python lab19_policy_faq_agent.py
cat answers.json- ์ฑ๊ณตํ์ :
saved: answers.jsoncount: 8- ๊ฐ ํญ๋ชฉ์
qid/answer/evidenceํค ์กด์ฌ
6) ์๋ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ(์ฑ๊ณต ํ์ )
- ๋๊ตฌ: Python
- ์
๋ ฅ:
answers.json,questions.jsonl - ์คํ๋ช ๋ น:
cat > validator.py <<'PY'
import json
from pathlib import Path
answers = json.loads(Path("answers.json").read_text(encoding="utf-8"))
expected = [json.loads(x) for x in Path("questions.jsonl").read_text(encoding="utf-8").splitlines() if x.strip()]
emap = {x["qid"]: x["expected_policy"] for x in expected}
assert isinstance(answers, list), "answers must be list"
assert len(answers) == len(expected), "์ง๋ฌธ/์๋ต ๊ฑด์ ๋ถ์ผ์น"
for row in answers:
assert {"qid", "answer", "evidence"}.issubset(row.keys()), f"ํค ๋๋ฝ: {row}"
assert row["qid"] in emap, f"์ ์ ์๋ qid: {row['qid']}"
assert isinstance(row["answer"], str) and len(row["answer"].strip()) > 0, f"๋น answer: {row}"
assert isinstance(row["evidence"], str) and row["evidence"].startswith("P-"), f"evidence ํ์ ์ค๋ฅ: {row}"
# ๋จ์ ์ ๋ต๋ฅ
ok = sum(1 for row in answers if row["evidence"] == emap[row["qid"]])
acc = ok / len(expected)
print(f"accuracy={acc:.2f} ({ok}/{len(expected)})")
assert acc >= 0.75, "์ ๋ต๋ฅ 0.75 ๋ฏธ๋ง"
print("PASS: ํ์/๊ทผ๊ฑฐ/์ ๋ต๋ฅ ๊ฒ์ฆ ์๋ฃ")
PY
python validator.py- ์ฑ๊ณตํ์ :
accuracy=...์ถ๋ ฅPASS: ํ์/๊ทผ๊ฑฐ/์ ๋ต๋ฅ ๊ฒ์ฆ ์๋ฃ์ถ๋ ฅ
ํธ๋ฌ๋ธ์ํ (์ต์ 3๊ฐ)
AuthenticationError/401 Unauthorized
- ์์ธ: API ํค ๋๋ฝ/๋ง๋ฃ
- ํด๊ฒฐ:
echo ${OPENAI_API_KEY:+SET}
export OPENAI_API_KEY="์ ์ํค"ValueError: JSON ๋ฐฐ์ด ํ์ฑ ์คํจ
- ์์ธ: ๋ชจ๋ธ์ด ์ค๋ช ๋ฌธ + JSON์ ํผํฉ ์ถ๋ ฅ
- ํด๊ฒฐ:
- ์์คํ
ํ๋กฌํํธ์
JSON ๋ฐฐ์ด๋ง ์ถ๋ ฅ๋ฐ๋ณต ๋ช ์ - ๊ฒฐ๊ณผ์์
[~]๊ตฌ๊ฐ ์ถ์ถ ๋ณด์ ์ ์ง(ํ์ฌ ์ฝ๋ ๋ฐ์)
- ์์คํ
ํ๋กฌํํธ์
evidence๊ฐP-ํ์์ด ์๋
- ์์ธ: ๋ชจ๋ธ์ด ์ ์ฑ ID ๋์ ์์ฐ์ด๋ฅผ ๋ฐํ
- ํด๊ฒฐ:
- ํ๋กฌํํธ์
evidence๋ policy_id 1๊ฐ๊ฐ์ validator.py์์ ํ์ ๊ฒ์ฆ์ผ๋ก ์ฆ์ ์คํจ ์ฒ๋ฆฌ
- ํ๋กฌํํธ์
- ์ ๋ต๋ฅ ์ด 0.75 ๋ฏธ๋ง์ผ๋ก FAIL
- ์์ธ:
search_policyํ ํฐ ๋งค์นญ ๋จ์ํ๋ก ํ์์จ ์ ํ - ํด๊ฒฐ:
- ๋์์ด ํ ํฐ ์ถ๊ฐ(์: ์ ๊ทธ๋ ์ด๋/์ํฅ, ๋ค์ด๊ทธ๋ ์ด๋/ํํฅ)
- top-k๋ฅผ 3โ4๋ก ์กฐ์ ํ ์ฌํ๊ฐ
ModuleNotFoundError: smolagents
- ์์ธ: ๊ฐ์ํ๊ฒฝ ๋ฏธํ์ฑํ ๋๋ ์ค์น ๋๋ฝ
- ํด๊ฒฐ:
source .venv/bin/activate
pip install -U smolagents litellm์ด์ ํ์ฅ ํฌ์ธํธ (๋ค์ ํธ ์ฐ๊ฒฐ)
- ์ ์ฑ
KB๋ฅผ Markdown ํด๋์์ ์๋ ์์งํด
policy_kb.json๋ก ๋น๋ accuracy์ถ์ด๋ฅผ ์ฃผ๊ฐ ๋ฆฌํฌํธ๋ก ์ ์ฅ(ํ์ง ํ๊ท ๊ฐ์)- ์คํจ ์ผ์ด์ค๋ฅผ ๋ณ๋ ํ๋ก ๋ณด๋ด ์ฌ๋ ๊ฒํ (HITL) ์ ์ฉ
์ฒดํฌ๋ฆฌ์คํธ
- ํ๊ฒฝ/ํจํค์ง ์ค์น ์๋ฃ
- API ํค/๋ชจ๋ธ ์ค์ ์๋ฃ
- KB/์ง๋ฌธ์ ํ์ผ ์์ฑ ์๋ฃ
-
answers.json์์ฑ ํ์ธ -
validator.pyPASS ํ์ธ - ํธ๋ฌ๋ธ์ํ 3๊ฐ ์ด์ ์ ๊ฒ ์๋ฃ
์ฐธ๊ณ ๋งํฌ (์ฐ์ ์์)
- https://github.com/huggingface/agents-course
- https://huggingface.co/learn/agents-course
- https://huggingface.co/docs/smolagents
์์ฑํ AI ํ์ฉ ๊ณ ์ง
์ด ๋ฌธ์๋ ์์ฑํ AI๋ก ์ด์์ ์์ฑํ ๋ค, ์ฌ๋ ๊ฒํ ๋ฅผ ํตํด ์ค์ต ์ฌํ์ฑ(๋ช ๋ น/์ ๋ ฅ/์ฑ๊ณตํ์ ), ๋งํฌ ์ ํจ์ฑ, ํฌ๋งท ์ผ๊ด์ฑ์ ์ ๊ฒํด ํ์ ํ๋ค.