feat: ai-first intent detection with keyword fallback
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,6 +32,13 @@ EMOTION_TEMPLATES = {
|
||||
|
||||
_template_embeddings: dict = {}
|
||||
|
||||
|
||||
@dataclass
|
||||
class IntentDecision:
|
||||
intent: str = ""
|
||||
source: str = "none" # embedding / keyword / none
|
||||
score: float = 0.0
|
||||
|
||||
def _get_embedding(text: str, cache_key: str = None) -> Optional[list]:
|
||||
"""调用 embedding API,失败返回 None。cache_key 用于缓存模板向量"""
|
||||
model = os.getenv("EMBEDDING_MODEL", "")
|
||||
@@ -66,10 +74,16 @@ def _cosine_sim(a: list, b: list) -> float:
|
||||
|
||||
|
||||
def detect_intent_embedding(msg: str) -> Optional[str]:
|
||||
"""用 embedding 检测意图,未配置或失败返回 None"""
|
||||
"""用 embedding 检测意图,未配置或失败返回 None。"""
|
||||
decision = detect_intent_embedding_decision(msg)
|
||||
return decision.intent or None
|
||||
|
||||
|
||||
def detect_intent_embedding_decision(msg: str) -> IntentDecision:
|
||||
"""返回 embedding 意图决策(含分值)。"""
|
||||
msg_emb = _get_embedding(msg)
|
||||
if not msg_emb:
|
||||
return None
|
||||
return IntentDecision()
|
||||
best_intent, best_score = "", 0.0
|
||||
for intent, template in INTENT_TEMPLATES.items():
|
||||
tpl_emb = _get_embedding(template, cache_key=f"intent_{intent}")
|
||||
@@ -79,7 +93,9 @@ def detect_intent_embedding(msg: str) -> Optional[str]:
|
||||
if sim > best_score:
|
||||
best_score = sim
|
||||
best_intent = intent
|
||||
return best_intent if best_score > 0.6 else None
|
||||
if best_score > 0.6:
|
||||
return IntentDecision(intent=best_intent, source="embedding", score=float(best_score))
|
||||
return IntentDecision()
|
||||
|
||||
|
||||
def detect_emotion_embedding(msg: str) -> Optional[str]:
|
||||
@@ -112,9 +128,32 @@ def detect_intent_keywords(msg: str) -> str:
|
||||
return "砍价"
|
||||
if any(k in m for k in ["改", "修改", "不满意"]):
|
||||
return "修改"
|
||||
if any(k in m for k in ["多少钱", "价格", "报价", "多钱"]):
|
||||
if any(k in m for k in ["多少钱", "价格", "报价", "多钱", "收费", "怎么收费", "咋收费"]):
|
||||
return "询价"
|
||||
if any(k in m for k in ["在吗", "你好", "有人"]):
|
||||
return "打招呼"
|
||||
return ""
|
||||
|
||||
|
||||
def detect_intent(msg: str) -> IntentDecision:
|
||||
"""
|
||||
AI 意图判定 + 规则兜底:
|
||||
1) 有 embedding 配置时先走 embedding。
|
||||
2) 失败/低置信时回退关键词规则。
|
||||
"""
|
||||
text = (msg or "").strip()
|
||||
if not text:
|
||||
return IntentDecision()
|
||||
|
||||
try:
|
||||
emb_decision = detect_intent_embedding_decision(text)
|
||||
except Exception:
|
||||
emb_decision = IntentDecision()
|
||||
if emb_decision.intent:
|
||||
return emb_decision
|
||||
|
||||
kw_intent = detect_intent_keywords(text)
|
||||
if kw_intent:
|
||||
return IntentDecision(intent=kw_intent, source="keyword", score=0.0)
|
||||
return IntentDecision()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user