P1-2: 统一Dify调用点 - 删除detect-intent死链路(approval/byod) + routing_service共享client修复连接泄漏 + 前端死代码清理

This commit is contained in:
Simon
2026-07-18 01:05:20 +08:00
parent af392f2bf0
commit aeb4e0cf39
5 changed files with 54 additions and 424 deletions
-205
View File
@@ -8,7 +8,6 @@
# =============================================================================
import asyncio
import json
import logging
import os
from typing import Optional
@@ -332,34 +331,6 @@ class ApprovalUrgeRequest(BaseModel):
sp_no: str
class ApprovalDetectIntentRequest(BaseModel):
"""审批意图检测请求"""
text: str
employee_id: Optional[str] = None
class ApprovalDetectIntentResponse(BaseModel):
"""审批意图检测响应
Attributes:
is_approval_request: 是否为审批请求(原字段,语义不变)
confidence: 置信度(0.0~1.0)(原字段,语义不变)
approval_type: 审批类型(原字段,语义不变)
source: 结果来源 — dify(Dify识别) / keyword_prefilter(关键词预过滤未命中) / fallback(降级兜底)
intent_type: 意图大类(新增)— approval/it_consult/non_it_routing/chitchat
business_category: 非IT业务类别(新增)— 行政/人力资源/财务/法务/行政-物业,仅 non_it_routing 时有值
routing_confidence: 路由置信度(新增)— 0.0~1.0,≥0.7 触发名片推荐
"""
is_approval_request: bool
confidence: float
approval_type: Optional[str] = None
source: str # "dify" | "keyword_prefilter" | "fallback"
# 以下为 v3 统一意图识别新增字段(向后兼容:原审批逻辑只读取前4个字段)
intent_type: str = "chitchat"
business_category: Optional[str] = None
routing_confidence: float = 0.0
# =============================================================================
# 企微API调用辅助函数
# =============================================================================
@@ -973,179 +944,3 @@ async def get_approval_keywords():
return success_response(data=keywords)
# =============================================================================
# 审批意图识别(Dify + 关键词预过滤 + 降级兜底)
# =============================================================================
def _keyword_prefilter(text: str) -> bool:
"""关键词预过滤:检查文本是否包含审批相关关键词(v2.0 收窄版)。
v2.0 变更(2026-07-13):
- 不再合并 APPROVAL_TEMPLATES 的 keywords(包含"借用""升级""外联"等泛化词)
- 仅使用 APPROVAL_PREFILTER_KEYWORDS(强意图词 + 复合专有词)
- 模板 keywords 仍保留在 KEYWORD_TO_APPROVAL_TYPE 中,仅用于 Dify 不可用时的降级兜底
Args:
text: 用户消息文本
Returns:
bool: 是否包含审批关键词
"""
if not text:
return False
lower_text = text.lower()
# v2.0: 仅使用预过滤关键词列表,不合并模板 keywords
return any(kw.lower() in lower_text for kw in APPROVAL_PREFILTER_KEYWORDS)
def _fallback_detect(text: str) -> tuple[bool, float, Optional[str]]:
"""关键词兜底:Dify 不可用时通过关键词匹配判断审批意图。
遍历 KEYWORD_TO_APPROVAL_TYPE 映射,命中第一个关键词即返回对应审批类型。
置信度取 0.6(略低于阈值,但预过滤已通过说明有审批关键词)。
Args:
text: 用户消息文本
Returns:
tuple: (is_approval_request, confidence, approval_type)
"""
lower_text = (text or "").lower()
approval_type: Optional[str] = None
for kw, atype in KEYWORD_TO_APPROVAL_TYPE.items():
if kw.lower() in lower_text:
approval_type = atype
break
# 预过滤已通过(说明有审批关键词),兜底返回 is_approval_request=True
return True, 0.6, approval_type
async def _call_dify_approval_intent(text: str, employee_id: str = "") -> dict:
"""调用 Dify 审批意图识别应用(Dify 原生 API)。
直接调用 Dify 原生 /v1/chat-messages 接口,绕过 Dify2OpenAI 代理。
Dify2OpenAI 代理会将 JSON 响应序列化为 "[object Object]" 字符串,
导致后端无法解析。使用原生 API 可获得正确的 JSON 响应。
Dify 应用的 System Prompt 已在 Dify 后台配置好,后端只需把用户消息传过去。
返回 JSON: {"is_approval_request": bool, "confidence": float, "approval_type": str|null}
Args:
text: 用户消息文本
employee_id: 员工 ID(可选,传给 Dify 的 user 字段)
Returns:
dict: {"is_approval_request": bool, "confidence": float, "approval_type": str|None}
Raises:
Exception: Dify 调用失败或响应解析失败
"""
base_url = settings.approval_dify_base_url
api_key = settings.approval_dify_api_key
timeout = settings.approval_dify_timeout
if not base_url or not api_key:
raise ValueError("Dify 审批意图识别应用未配置(APPROVAL_DIFY_BASE_URL / APPROVAL_DIFY_API_KEY")
# 构建请求 URLbase_url + /v1/chat-messagesDify 原生 API
url = f"{base_url.rstrip('/')}/v1/chat-messages"
body = {
"inputs": {}, # Dify 应用的输入变量(无自定义变量时为空)
"query": text, # 用户消息文本
"response_mode": "blocking", # 阻塞模式,等待完整响应
"user": employee_id or "approval_detection", # 用户标识
}
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
async with httpx.AsyncClient(timeout=httpx.Timeout(timeout)) as client:
response = await client.post(url, json=body, headers=headers)
response.raise_for_status()
data = response.json()
# 解析 Dify 原生响应:answer 字段包含 AI 返回的文本(JSON 字符串)
answer = data.get("answer", "")
parsed = json.loads(answer)
return {
"is_approval_request": bool(parsed.get("is_approval_request", False)),
"confidence": float(parsed.get("confidence", 0.0)),
"approval_type": parsed.get("approval_type"),
# v3 统一意图识别新增字段(向后兼容:旧 Prompt 无这些字段时取默认值)
"intent_type": str(parsed.get("intent_type", "chitchat")),
"business_category": parsed.get("business_category"),
"routing_confidence": float(parsed.get("routing_confidence", 0.0)),
}
@router.post("/approval/detect-intent")
async def detect_approval_intent(request: ApprovalDetectIntentRequest):
"""审批意图检测端点。
流程:
1. 关键词预过滤 — 未命中直接返回 false(避免每条消息都调 Dify)
2. 命中关键词 → 调用 Dify 审批意图识别应用
3. Dify 调用失败 → 降级为关键词匹配(兜底)
Args:
request: 包含 text(用户消息)和可选的 employee_id
Returns:
ApprovalDetectIntentResponse: 检测结果
"""
text = request.text or ""
# 1. 关键词预过滤
if not _keyword_prefilter(text):
return success_response(data=ApprovalDetectIntentResponse(
is_approval_request=False,
confidence=0.0,
approval_type=None,
source="keyword_prefilter",
intent_type="chitchat",
business_category=None,
routing_confidence=0.0,
))
# 2. 调用 Dify 意图识别
try:
result = await _call_dify_approval_intent(text, request.employee_id or "")
# 检查置信度阈值
threshold = settings.approval_confidence_threshold
is_approval = result["is_approval_request"] and result["confidence"] >= threshold
logger.info(
f"审批意图检测(Dify): is_approval={is_approval}, "
f"confidence={result['confidence']}, type={result.get('approval_type')}, "
f"intent_type={result.get('intent_type')}, "
f"business_category={result.get('business_category')}, "
f"routing_confidence={result.get('routing_confidence')}"
)
return success_response(data=ApprovalDetectIntentResponse(
is_approval_request=is_approval,
confidence=result["confidence"],
approval_type=result.get("approval_type"),
source="dify",
intent_type=result.get("intent_type", "chitchat"),
business_category=result.get("business_category"),
routing_confidence=result.get("routing_confidence", 0.0),
))
except Exception as e:
logger.warning(f"Dify 审批意图识别失败,降级为关键词匹配: {e}")
# 3. 降级为关键词匹配
is_approval, confidence, approval_type = _fallback_detect(text)
logger.info(
f"审批意图检测(兜底): is_approval={is_approval}, "
f"confidence={confidence}, type={approval_type}"
)
return success_response(data=ApprovalDetectIntentResponse(
is_approval_request=is_approval,
confidence=confidence,
approval_type=approval_type,
source="fallback",
intent_type="approval" if is_approval else "chitchat",
business_category=None,
routing_confidence=0.0,
))