# ============================================================================= # 企微IT智能服务台 — 意图匹配器 # ============================================================================= # 说明:复用审批意图识别 Dify 链路(approval_dify_base_url + approval_dify_api_key), # 调用 Dify 意图识别 API,检查返回意图是否在排除列表中。 # 降级处理:Dify 不可用时返回未命中(不影响其他匹配器执行)。 # ============================================================================= import logging from typing import Optional import httpx from app.config import settings from app.services.matchers.base import BaseMatcher, MatchResult logger = logging.getLogger(__name__) # 意图识别 System Prompt _INTENT_SYSTEM_PROMPT = ( "你是IT服务台意图识别引擎,负责分析用户消息的意图类别。\n" "输出约束:只输出意图ID(一个词),不要输出解释性文字。\n" "常见意图ID包括:password_reset, account_unlock, software_install, " "network_issue, hardware_repair, vpn_issue, email_issue, " "approval_request, information_inquiry, complaint, other." ) class IntentMatcher(BaseMatcher): """意图匹配器。 匹配逻辑: 1. 调用 Dify 意图识别 API(复用审批意图链路) 2. 获取用户消息的意图ID 3. 检查意图ID是否在排除列表中 降级处理: - Dify 未配置或不可用 → 返回未命中 - Dify 超时 → 返回未命中 - 返回格式异常 → 返回未命中 Example: condition = "password_reset,account_unlock" message = "我的密码忘了,帮我重置一下" → Dify 返回 "password_reset" → 命中,matched_detail="意图: password_reset" """ async def _recognize_intent(self, message: str) -> Optional[str]: """调用 Dify 意图识别 API。 Args: message: 用户消息文本 Returns: Optional[str]: 识别到的意图ID,失败返回 None """ api_url = settings.approval_dify_base_url api_key = settings.approval_dify_api_key timeout = settings.approval_dify_timeout if not api_url or not api_key: logger.warning("审批意图识别 Dify 未配置,跳过意图匹配") return None try: async with httpx.AsyncClient(timeout=timeout) as client: resp = await client.post( f"{api_url}/v1/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, json={ "model": "intent-recognition", "messages": [ {"role": "system", "content": _INTENT_SYSTEM_PROMPT}, {"role": "user", "content": message}, ], "temperature": 0.1, "max_tokens": 50, }, ) resp.raise_for_status() resp_data = resp.json() content = resp_data["choices"][0]["message"]["content"].strip() # 清理可能的 markdown 包裹 if content.startswith("```"): content = content.strip("`").strip() logger.info("意图识别结果: message=%s, intent=%s", message[:50], content) return content except httpx.TimeoutException: logger.warning("意图识别 Dify 请求超时(%s秒)", timeout) return None except Exception as e: logger.error("意图识别 Dify 调用异常: %s", e) return None async def match( self, message: str, condition: str, context: Optional[dict] = None, ) -> MatchResult: """检查消息意图是否在排除列表中。 Args: message: 用户消息文本 condition: 逗号分隔的意图ID列表 context: 上下文(本匹配器不需要) Returns: MatchResult: 命中时 matched=True, matched_detail="意图: xxx" """ if not message or not condition: return MatchResult(matched=False) excluded_intents = [s.strip() for s in condition.split(",") if s.strip()] if not excluded_intents: return MatchResult(matched=False) # 调用 Dify 意图识别 intent = await self._recognize_intent(message) if intent is None: # Dify 不可用,降级返回未命中 return MatchResult(matched=False) # 检查意图是否在排除列表中(不区分大小写) intent_lower = intent.lower() for excluded in excluded_intents: if excluded.lower() == intent_lower: return MatchResult( matched=True, matched_detail=f"意图: {intent}", match_position="意图识别匹配", ) return MatchResult(matched=False)