feat: ai-first intent detection with keyword fallback

This commit is contained in:
2026-03-01 17:09:05 +08:00
parent 4a07f9c726
commit 00c80c3bec
5 changed files with 117 additions and 24 deletions

View File

@@ -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()