์ด๋ฒ ํธ์ ์ค๋ฌดํ ๋ฏธ๋ ํ๋ก์ ํธ 1ํธ์ผ๋ก, ๋ค์ด์ค๋ ๊ณ ๊ฐ ๋ฌธ์๋ฅผ ์๋ ๋ถ๋ฅํด
billing / bug / feature / other๋ก ๋ผ์ฐํ
ํ๋ ํฐ์ผ ํธ๋ฆฌ์์ง ์์ด์ ํธ๋ฅผ ๋ง๋ ๋ค.
- ์ด์ ํธ: ๐ค 15. ๋ณธํธ 08
- ๋ค์ ํธ ์๊ณ : ํธ๋ฆฌ์์ง ๊ฒฐ๊ณผ๋ฅผ Slack/Notion/n8n์ผ๋ก ์ ๋ฌํ๋ ์๋ํ ํ์ฅ
ํ ์ค ๋ชฉํ
์ ๋ ฅ(JSON Lines) โ ๋ถ๋ฅ ์์ด์ ํธ ์คํ โ ๊ฒฐ๊ณผ(JSON) ์์ฑ โ ์ฑ๊ณต ํ์ ๊น์ง ํ ๋ฒ์ ์ฌํํ๋ค.
flowchart LR I[tickets.jsonl] --> A[CodeAgent] A --> T[๋ถ๋ฅ ๊ท์น + ์ํ ๊ทผ๊ฑฐ] T --> O[triage_output.json] O --> C[์ ํ๋/ํ์ ์ฒดํฌ] C --> R[์๋๊ฒํ ๋๋ ๋ค์ ์๋ํ]
0) ์ค์ต ๋ฒ์(๊ณ ์ )
- ๋ฒ์ ํฌํจ
- ๋จ์ผ ํ์ผ ์
๋ ฅ(
tickets.jsonl)์ ์ฝ์ด ๋ฌธ์๋ฅผ ์นดํ ๊ณ ๋ฆฌ ๋ถ๋ฅ - ๊ฐ ํฐ์ผ์
category,priority,reasonํ๋ ์์ฑ - ๊ฒฐ๊ณผ๋ฅผ
triage_output.json์ผ๋ก ์ ์ฅ
- ๋จ์ผ ํ์ผ ์
๋ ฅ(
- ๋ฒ์ ์ ์ธ
- DB/Slack/Notion ์ฐ๋
- ๋ค๊ตญ์ด ๋ถ๋ฅ ๊ณ ๋ํ
1) ํ๊ฒฝ ์ค๋น
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: Python 3.10+, ๊ฐ์ํ๊ฒฝ
- ์คํ๋ช ๋ น:
mkdir -p ~/hf-agents-lab16
cd ~/hf-agents-lab16
python3 -m venv .venv
source .venv/bin/activate
pip install -U smolagents litellm- ์ฑ๊ณตํ์ :
(.venv)ํ๋กฌํํธ ํ์pip install์๋ฌ ์์
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(bool(os.getenv("OPENAI_API_KEY")))
PY- ๋ชจ๋ธ ID ์ถ๋ ฅ๋จ
True์ถ๋ ฅ
3) ์ค์ต ์ ๋ ฅ ๋ฐ์ดํฐ ๋ง๋ค๊ธฐ
- ๋๊ตฌ: ํฐ๋ฏธ๋
- ์ ๋ ฅ: ์ํ ํฐ์ผ 6๊ฐ
- ์คํ๋ช ๋ น:
cat > tickets.jsonl <<'JSONL'
{"ticket_id":"T-1001","text":"๊ฒฐ์ ๋ ๋๋๋ฐ ์์์ฆ ๋ฉ์ผ์ด ์ ์์ด์"}
{"ticket_id":"T-1002","text":"์ฑ ์
๋ฐ์ดํธ ํ ๋ก๊ทธ์ธ ๋ฒํผ ๋๋ฅด๋ฉด ์ฑ์ด ๊บผ์ง๋๋ค"}
{"ticket_id":"T-1003","text":"๋์๋ณด๋์ CSV ๋ด๋ณด๋ด๊ธฐ ๊ธฐ๋ฅ ์ถ๊ฐ ๋ถํํฉ๋๋ค"}
{"ticket_id":"T-1004","text":"์๊ธ์ ์
๊ทธ๋ ์ด๋ํ๋๋ฐ ๋ฐ์์ด ์ ๋ฉ๋๋ค"}
{"ticket_id":"T-1005","text":"์ฒจ๋ถํ์ผ ์
๋ก๋ ์ 500 ์๋ฌ๊ฐ ๋ ์"}
{"ticket_id":"T-1006","text":"์๋น์ค ์๊ฐ ์๋ฃ๋ฅผ ๋ฐ์๋ณผ ์ ์๋์?"}
JSONL- ์ฑ๊ณตํ์ :
wc -l tickets.jsonl6 tickets.jsonl์ถ๋ ฅ
4) ์์ด์ ํธ ์ฝ๋ ์์ฑ
- ๋๊ตฌ: ์๋ํฐ/ํฐ๋ฏธ๋
- ์ ๋ ฅ: ์๋ ์ฝ๋
- ์คํ๋ช ๋ น:
cat > lab16_ticket_triage.py <<'PY'
from __future__ import annotations
import json
import os
from pathlib import Path
from smolagents import CodeAgent, LiteLLMModel
INPUT_PATH = Path("tickets.jsonl")
OUTPUT_PATH = Path("triage_output.json")
def load_tickets(path: Path):
rows = []
for line in path.read_text(encoding="utf-8").splitlines():
if line.strip():
rows.append(json.loads(line))
return rows
def build_agent() -> CodeAgent:
model_id = os.getenv("MODEL_ID", "openai/gpt-4o-mini")
model = LiteLLMModel(model_id=model_id)
system_prompt = """
๋๋ ๊ณ ๊ฐ์ง์ ํฐ์ผ ๋ถ๋ฅ ์์ด์ ํธ๋ค.
์นดํ
๊ณ ๋ฆฌ๋ billing/bug/feature/other ์ค ํ๋๋ง ์ฌ์ฉํ๋ค.
priority๋ high/medium/low ์ค ํ๋๋ง ์ฌ์ฉํ๋ค.
๋ฐํ์ ๋ฐ๋์ JSON ๋ฐฐ์ด์ด๋ฉฐ ๊ฐ ์์๋ ์๋ ํค๋ฅผ ํฌํจํ๋ค.
- ticket_id
- category
- priority
- reason (ํ๊ธ 1๋ฌธ์ฅ)
""".strip()
return CodeAgent(model=model, tools=[], system_prompt=system_prompt)
def main() -> None:
tickets = load_tickets(INPUT_PATH)
agent = build_agent()
prompt = f"""
๋ค์ ํฐ์ผ ๋ชฉ๋ก์ ๊ท์น์ ๋ง์ถฐ ๋ถ๋ฅํด.
ํฐ์ผ ๋ชฉ๋ก:
{json.dumps(tickets, ensure_ascii=False)}
""".strip()
result = agent.run(prompt)
# smolagents ์ถ๋ ฅ์ด ๋ฌธ์์ด์ผ ์ ์์ด ๋ณด์
if isinstance(result, str):
start = result.find("[")
end = result.rfind("]")
if start != -1 and end != -1 and end > start:
result = json.loads(result[start:end+1])
else:
raise ValueError("JSON ๋ฐฐ์ด ํ์ฑ ์คํจ")
OUTPUT_PATH.write_text(
json.dumps(result, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"saved: {OUTPUT_PATH}")
print(f"count: {len(result)}")
if __name__ == "__main__":
main()
PY- ์ฑ๊ณตํ์ :
lab16_ticket_triage.pyํ์ผ ์์ฑ- ์ฝ๋์
CodeAgent,LiteLLMModel์กด์ฌ
5) ์คํ ๋ฐ ๊ฒฐ๊ณผ ํ์ธ
- ๋๊ตฌ: Python, ํฐ๋ฏธ๋
- ์
๋ ฅ:
tickets.jsonl - ์คํ๋ช ๋ น:
python lab16_ticket_triage.py
cat triage_output.json- ์ฑ๊ณตํ์ :
saved: triage_output.json์ถ๋ ฅ- ๊ฒฐ๊ณผ ํญ๋ชฉ ์
6 - ๊ฐ ํญ๋ชฉ์
ticket_id/category/priority/reason4๊ฐ ํค ์กด์ฌ
6) ์๋ ๊ฒ์ฆ(์ฑ๊ณต ํ์ ์คํฌ๋ฆฝํธ)
- ๋๊ตฌ: Python
- ์
๋ ฅ:
triage_output.json - ์คํ๋ช ๋ น:
python - <<'PY'
import json
from pathlib import Path
data = json.loads(Path("triage_output.json").read_text(encoding="utf-8"))
allowed_cat = {"billing", "bug", "feature", "other"}
allowed_pri = {"high", "medium", "low"}
assert isinstance(data, list) and len(data) == 6, "๊ฑด์ ๋ถ์ผ์น"
for row in data:
assert set(["ticket_id","category","priority","reason"]).issubset(row.keys()), f"ํค ๋๋ฝ: {row}"
assert row["category"] in allowed_cat, f"category ์ค๋ฅ: {row}"
assert row["priority"] in allowed_pri, f"priority ์ค๋ฅ: {row}"
print("PASS: ํ์/๊ฐ ๊ฒ์ฆ ์๋ฃ")
PY- ์ฑ๊ณตํ์ :
PASS: ํ์/๊ฐ ๊ฒ์ฆ ์๋ฃ์ถ๋ ฅ
ํธ๋ฌ๋ธ์ํ (์ต์ 3๊ฐ)
AuthenticationError/401
- ์์ธ: API ํค ๋ฏธ์ค์ ๋๋ ์๋ชป๋ ํค
- ํด๊ฒฐ:
echo ${OPENAI_API_KEY:+SET}
export OPENAI_API_KEY="์ ์ํค"ValueError: JSON ๋ฐฐ์ด ํ์ฑ ์คํจ
- ์์ธ: ๋ชจ๋ธ์ด ์ค๋ช ๋ฌธ+JSON ํผํฉ ์ถ๋ ฅ
- ํด๊ฒฐ:
- ์์คํ ํ๋กฌํํธ์ โJSON ๋ฐฐ์ด๋ง ์ถ๋ ฅโ ๋ฌธ๊ตฌ ๊ฐํ
- ํ์ ์
json schema์์๋ฅผ ํ๋กฌํํธ์ ์ถ๊ฐ
- ๋ถ๋ฅ ๋ผ๋ฒจ์ด ๊ท์น ์ธ ๊ฐ์ผ๋ก ๋์ด (์:
payment_issue)
- ์์ธ: ํ์ฉ ๋ผ๋ฒจ ์งํฉ ๊ฐ์ ๋ถ์กฑ
- ํด๊ฒฐ:
- ํ๋กฌํํธ์ โํ์ฉ๊ฐ ์ธ ๊ธ์งโ ๋ช ์
- 6๋จ๊ณ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ๋ก ์คํจ ์ฆ์ ํ์ง
- ์๋๊ฐ ๋๋ฆฌ๊ฑฐ๋ ํ์์์ ๋ฐ์
- ์์ธ: ์ ๋ ฅ ํฐ์ผ ์ ์ฆ๊ฐ/๋ชจ๋ธ ์๋ต ์ง์ฐ
- ํด๊ฒฐ:
- ๋ฐฐ์น ๋ถํ (์: 50๊ฐ ๋จ์)
- ์ฌ์๋ ๋ก์ง(๋ฐฑ์คํ) ์ถ๊ฐ
์ด์ ํ์ฅ ํฌ์ธํธ (๋ค์ ํธ ์ฐ๊ฒฐ)
triage_output.json์ n8n Webhook์ผ๋ก POSTpriority=high๋ง ์ฌ๋ ์๋ฆผ ์ฑ๋๋ก ๋ถ๊ธฐ- ๋ถ๋ฅ ์ ํ๋ ์ํ์ (์ ๋ต ๋ผ๋ฒจ) ๋ถ์ฌ ์ฃผ๊ฐ ํ์ง ๋ฆฌํฌํธ ์๋ํ
์ฒดํฌ๋ฆฌ์คํธ
- ๊ฐ์ํ๊ฒฝ/ํจํค์ง ์ค์น ์๋ฃ
- API ํค/๋ชจ๋ธ ID ์ค์ ์๋ฃ
-
tickets.jsonl6๊ฑด ์์ฑ ํ์ธ -
triage_output.json์ ์ฅ ํ์ธ - ์๋ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ PASS
- ํธ๋ฌ๋ธ์ํ 3๊ฐ ์ด์ ์ ๊ฒ ์๋ฃ
์ฐธ๊ณ ๋งํฌ (์ฐ์ ์์)
- https://github.com/huggingface/agents-course
- https://huggingface.co/learn/agents-course
- https://huggingface.co/docs/smolagents
์์ฑํ AI ํ์ฉ ๊ณ ์ง
์ด ๋ฌธ์๋ ์์ฑํ AI๋ฅผ ํ์ฉํด ์ด์์ ์์ฑํ๊ณ , ์ค์ต ์ ์ฐจ/์ฝ๋/๊ฒ์ฆ ๋จ๊ณ๋ฅผ ๊ตฌ์กฐํํ๋ค. ์ต์ข ๋ฐ์ ์ ์ฌ๋ ๊ฒํ ๋ก ๋ช ๋ น ์ฌํ์ฑ, ๋งํฌ ์ ํจ์ฑ, ํฌ๋งท ์ผ๊ด์ฑ์ ์ ๊ฒํ๋ค.