intentfinder-api / intent_classifier.py
youngryong's picture
deploy: IntentFinder API (HF Docker Space)
21bdc64
Raw
History Blame Contribute Delete
3.34 kB
# Claude Haiku๋กœ ํด๋Ÿฌ์Šคํ„ฐ์˜ ์˜๋„ยทํ…Œ๋งˆ๋ช…์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋งˆ์ผ€ํŒ… ์ œ์•ˆ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ƒ์„ฑํ•˜๋Š” ๋ชจ๋“ˆ
import os
import json
import anthropic
MODEL = "claude-haiku-4-5"
USD_TO_KRW = 1400 # ๋น„์šฉ ํ™˜์‚ฐ์šฉ ๋Œ€๋žต๊ฐ’
INTENT_LABELS = {
"info": "์ •๋ณด ํƒ์ƒ‰ํ˜•",
"transactional": "๊ตฌ๋งค/๊ฑฐ๋ž˜ํ˜•",
"navigational": "๋ธŒ๋žœ๋“œ/๋‚ด๋น„๊ฒŒ์ด์…˜ํ˜•",
"mixed": "ํ˜ผํ•ฉํ˜•",
}
SYSTEM = """๋„ˆ๋Š” ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๋Š” ๋งˆ์ผ€ํŒ… ๋ถ„์„๊ฐ€๋‹ค. ๊ฐ ํด๋Ÿฌ์Šคํ„ฐ์— ๋Œ€ํ•ด ๋‘ ๊ฐ€์ง€๋ฅผ ํŒ๋‹จํ•œ๋‹ค.
1. intent โ€” ๊ฒ€์ƒ‰ ์˜๋„๋ฅผ ์ •ํ™•ํžˆ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜
- info: ๋ฐฉ๋ฒ•ยท์ถ”์ฒœยท๋น„๊ตยทํ›„๊ธฐยท๊ฐ€์ด๋“œ ๋“ฑ ์ •๋ณด ํƒ์ƒ‰
- transactional: ๊ตฌ๋งคยท๊ฐ€๊ฒฉยท์ตœ์ €๊ฐ€ยท์ฃผ๋ฌธ ๋“ฑ ๊ฑฐ๋ž˜
- navigational: ํŠน์ • ๋ธŒ๋žœ๋“œยท์‚ฌ์ดํŠธ๋กœ ์ด๋™
- mixed: ์œ„๊ฐ€ ์„ž์—ฌ ํ•˜๋‚˜๋กœ ๋‹จ์ •ํ•˜๊ธฐ ์–ด๋ ค์›€
2. theme โ€” ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” 2~6์–ด์ ˆ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด ํ…Œ๋งˆ ๊ทธ๋ฃน๋ช… (์˜ˆ: "์บ ํ•‘ ๊ฐ€๊ตฌ", "์ดˆ๋ณด ์ž…๋ฌธ ๊ฐ€์ด๋“œ", "๋™๊ณ„ ๋‚œ๋ฐฉ")
๊ทธ๋ฆฌ๊ณ  ์ „์ฒด ํด๋Ÿฌ์Šคํ„ฐ ํŒจํ„ด์„ ๋ณด๊ณ  ์ค‘์†Œ๊ธฐ์—…(SMB)์ด ๋ฐ”๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋งˆ์ผ€ํŒ… ์•ก์…˜ ์ œ์•ˆ 1๊ฐœ๋ฅผ 2~3๋ฌธ์žฅ์œผ๋กœ ๊ตฌ์ฒด์ ์œผ๋กœ ์ž‘์„ฑํ•œ๋‹ค.
๋ฐ˜๋“œ์‹œ JSON๋งŒ ์ถœ๋ ฅํ•œ๋‹ค. ํ˜•์‹:
{"clusters":[{"cluster_id":0,"intent":"transactional","theme":"์บ ํ•‘ ๊ฐ€๊ตฌ"}],"marketing_suggestion":"..."}"""
def _client() -> anthropic.Anthropic:
return anthropic.Anthropic() # ANTHROPIC_API_KEY๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋กœ๋“œ
def classify_clusters(clusters: list[dict]) -> dict:
# ํด๋Ÿฌ์Šคํ„ฐ์— intent/intent_label/theme๋ฅผ ์ฑ„์šฐ๊ณ , ๋„๋„› ๋น„์ค‘ยท๋งˆ์ผ€ํŒ… ์ œ์•ˆยทํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰์„ ๋ฐ˜ํ™˜
if not clusters:
return {"intent_breakdown": {}, "marketing_suggestion": None, "usage": None}
listing = "\n".join(
f"{c['cluster_id']}: {c['cluster_label']} โ€” {', '.join(c['top_keywords'])}"
for c in clusters
)
res = _client().messages.create(
model=MODEL,
max_tokens=1500,
system=SYSTEM,
messages=[{"role": "user", "content": f"๋‹ค์Œ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๋ถ„์„ํ•ด๋ผ.\n{listing}"}],
)
text = next(b.text for b in res.content if b.type == "text").strip()
if text.startswith("```"):
text = text.split("```")[1].lstrip("json").strip()
parsed = json.loads(text)
by_id = {item["cluster_id"]: item for item in parsed.get("clusters", [])}
for c in clusters:
item = by_id.get(c["cluster_id"], {})
intent = item.get("intent", "mixed")
if intent not in INTENT_LABELS:
intent = "mixed"
c["intent"] = intent
c["intent_label"] = INTENT_LABELS[intent]
c["theme"] = item.get("theme") or c["cluster_label"] # ํ…Œ๋งˆ๋ช…, ์—†์œผ๋ฉด ๋Œ€ํ‘œ ํ‚ค์›Œ๋“œ
breakdown: dict[str, int] = {}
for c in clusters:
breakdown[c["intent"]] = breakdown.get(c["intent"], 0) + c["total_search_volume"]
usd = res.usage.input_tokens / 1e6 * 1 + res.usage.output_tokens / 1e6 * 5
return {
"intent_breakdown": breakdown,
"marketing_suggestion": parsed.get("marketing_suggestion"),
"usage": {
"input_tokens": res.usage.input_tokens,
"output_tokens": res.usage.output_tokens,
"cost_usd": round(usd, 5),
"cost_krw": round(usd * USD_TO_KRW, 1),
},
}