# ============================================================================= # 企微IT智能服务台 — 自动化 意图识别(含全局意图) # ============================================================================= # 说明:调用 Dify 识别员工诉求命中哪个自动化场景;Dify 未配置时走关键词兜底, # 保证 P0 四个场景在无真实 Dify 环境下也能跑通闭环。 # 复杂场景重构:新增全局意图检测(PAUSE/RESUME_TASK/CORRECT/SUPPLEMENT), # detect() 内部先调全局意图检测,命中则返回全局意图,跳过场景识别。 # ============================================================================= from __future__ import annotations import logging from typing import Any, Dict, Optional from app.integrations.dify import DifyClient, get_dify_client from app.integrations.factory import build_dify_client logger = logging.getLogger(__name__) class IntentRouter: """意图识别路由器(含全局对话控制意图)。""" def __init__(self, db: Any = None, audit: Any = None): self.db = db self.audit = audit async def detect(self, description: str, employee_id: str = "") -> Dict[str, Any]: """识别意图,返回包含 global_intent 的完整结果。 优先检测全局对话控制意图(pause/resume_task/correct/supplement), 命中则直接返回全局意图,跳过场景识别; 未命中则走原有场景识别流程。 Returns: Dict: {global_intent, scenario_key, confidence, corrected_field, old_value, new_value, supplement_field, supplement_value, raw, error} """ # 1. 先检测全局意图 global_result = await self.detect_global_intent(description) if global_result.get("global_intent") is not None: # 命中全局意图 → 直接返回,跳过场景识别 return global_result # 2. 未命中全局意图 → 走原场景识别 client: Optional[DifyClient] = None try: client = await build_dify_client(audit=self.audit) except Exception as e: # noqa: BLE001 logger.warning(f"构建 Dify 客户端失败,转关键词兜底: {e}") if client is None: fb = DifyClient._fallback_intent(description) fb["error"] = "dify_not_configured" # 确保全局意图字段存在 fb.setdefault("global_intent", None) fb.setdefault("corrected_field", None) fb.setdefault("old_value", None) fb.setdefault("new_value", None) fb.setdefault("supplement_field", None) fb.setdefault("supplement_value", None) return fb try: result = await client.detect_intent(description, employee_id) # 确保全局意图字段存在(兼容旧版 Dify 返回) result.setdefault("global_intent", None) result.setdefault("corrected_field", None) result.setdefault("old_value", None) result.setdefault("new_value", None) result.setdefault("supplement_field", None) result.setdefault("supplement_value", None) return result except Exception as e: # noqa: BLE001 logger.warning(f"Dify 意图识别异常,转关键词兜底: {e}") fb = DifyClient._fallback_intent(description) fb["error"] = str(e) fb.setdefault("global_intent", None) fb.setdefault("corrected_field", None) fb.setdefault("old_value", None) fb.setdefault("new_value", None) fb.setdefault("supplement_field", None) fb.setdefault("supplement_value", None) return fb async def detect_global_intent(self, text: str) -> Dict[str, Any]: """检测全局对话控制意图(pause/resume_task/correct/supplement)。 优先调用 Dify(复用现有客户端),Prompt 中增加全局意图判断; Dify 不可用时走关键词兜底(使用 GLOBAL_INTENT_KEYWORDS)。 Returns: Dict: {global_intent, scenario_key, confidence, corrected_field, old_value, new_value, supplement_field, supplement_value, raw, error} """ # 尝试 Dify client: Optional[DifyClient] = None try: client = await build_dify_client(audit=self.audit) except Exception as e: # noqa: BLE001 logger.debug(f"构建 Dify 客户端失败,全局意图转关键词兜底: {e}") if client is not None: try: result = await client.detect_intent(text, "") # detect_intent 已返回 global_intent,直接使用 return result except Exception as e: # noqa: BLE001 logger.warning(f"Dify 全局意图识别异常,转关键词兜底: {e}") # 关键词兜底 return self._keyword_fallback_global(text) def _keyword_fallback_global(self, text: str) -> Dict[str, Any]: """全局意图关键词兜底。""" lower_text = (text or "").lower() try: from app.constants import GLOBAL_INTENT_KEYWORDS for intent, keywords in GLOBAL_INTENT_KEYWORDS.items(): if any(kw.lower() in lower_text for kw in keywords): return { "global_intent": intent, "scenario_key": None, "confidence": 0.6, "corrected_field": None, "old_value": None, "new_value": None, "supplement_field": None, "supplement_value": None, "raw": "", "error": "fallback", } except Exception: # noqa: BLE001 pass return { "global_intent": None, "scenario_key": None, "confidence": 0.0, "corrected_field": None, "old_value": None, "new_value": None, "supplement_field": None, "supplement_value": None, "raw": "", "error": "fallback", }