bea288e414
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
150 lines
6.3 KiB
Python
150 lines
6.3 KiB
Python
# =============================================================================
|
|
# 企微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",
|
|
}
|