WIP-CHECKPOINT[auth-refactor]: 固化工程师崩溃前部分成果 + 同树其他未提交WIP(仅源码,不含密钥/二进制)-- 待重激活工程师续作

This commit is contained in:
Simon
2026-07-07 21:52:11 +08:00
parent 242c1967ff
commit fab75760e0
203 changed files with 21504 additions and 3345 deletions
+140
View File
@@ -0,0 +1,140 @@
# =============================================================================
# 企微IT智能服务台 — 阶段5 自动化 Dify 客户端
# =============================================================================
# 说明:Dify 承担意图识别与 AI 编排。
# 1. 意图识别:根据员工消息判断命中哪个自动化场景
# password_reset / software_install / virus_dispose / terminal_locate
# 2. AI 编排(可选):生成处置方案草案
#
# 认证:Dify 开放 API 使用 Bearer TokenAPI Key)。
# 配置:AUTOMATION_DIFY_BASE_URL / AUTOMATION_DIFY_API_KEY(来自 settings)。
#
# 容错:若 Dify 未配置或无结构化输出,detect_intent 走关键词兜底,
# 保证 P0 四个场景在无真实 Dify 环境下也能演示闭环。
# =============================================================================
from __future__ import annotations
import json
import logging
import re
from typing import Any, Dict, Optional
from app.config import settings
from app.integrations.base import BaseClient, BaseClientError
logger = logging.getLogger(__name__)
class DifyClient(BaseClient):
"""Dify API 客户端(意图识别 / AI 编排)。"""
system_name = "dify"
def __init__(
self,
api_key: str,
base_url: str,
timeout: Optional[float] = None,
audit=None,
):
super().__init__(base_url=base_url, timeout=timeout, audit=audit)
self.api_key = api_key
def _headers(self) -> Dict[str, str]:
return {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
async def chat_completions(
self,
query: str,
user: str = "automation",
conversation_id: str = "",
response_mode: str = "blocking",
) -> Dict[str, Any]:
"""调用 Dify Chat 补全(兼容 OpenAI Chat Completions 格式)。
Returns:
Dict: {"answer": str, "conversation_id": str, ...}
"""
body = {
"inputs": {},
"query": query,
"user": user,
"response_mode": response_mode,
}
if conversation_id:
body["conversation_id"] = conversation_id
return await self.request(
"POST", "/v1/chat-messages",
json_data=body, headers=self._headers(), event="dify.chat",
)
async def detect_intent(self, message: str, employee_id: str = "") -> Dict[str, Any]:
"""意图识别:把员工消息发给 Dify,期望返回结构化场景意图。
解析策略:
1. 优先尝试从 answer 中解析 JSONscenario_key + confidence
2. 失败则走关键词兜底(无 Dify 结构化输出时也能跑通 P0)
Returns:
Dict: {"scenario_key": str|None, "confidence": float, "raw": str, "error": str}
"""
try:
data = await self.chat_completions(query=message, user=employee_id or "automation")
answer = data.get("answer", "")
except BaseClientError as e:
logger.warning(f"Dify 意图识别失败,转关键词兜底: {e}")
fb = self._fallback_intent(message)
fb["error"] = str(e)
return fb
intent = self._parse_intent(answer)
if intent["scenario_key"] is None:
# 关键词兜底
fb = self._fallback_intent(message)
fb["raw"] = answer
return fb
return intent
@staticmethod
def _parse_intent(answer: str) -> Dict[str, Any]:
"""尝试从 Dify 返回中解析 JSON 意图。"""
try:
m = re.search(r"\{.*\}", answer, re.DOTALL)
if m:
obj = json.loads(m.group(0))
return {
"scenario_key": obj.get("scenario_key"),
"confidence": float(obj.get("confidence", 0.0)),
"raw": answer,
}
except Exception:
pass
return {"scenario_key": None, "confidence": 0.0, "raw": answer}
@staticmethod
def _fallback_intent(message: str) -> Dict[str, Any]:
"""关键词兜底意图识别(无 Dify 结构化输出时使用)。"""
text = (message or "").lower()
rules = [
(("密码", "重置", "password", "忘密码", "修改密码"), "password_reset"),
(("安装", "软件", "install", "software", "wps", "office", "下载"), "software_install"),
(("病毒", "杀毒", "virus", "勒索", "木马", "火绒", "huorong"), "virus_dispose"),
(("定位", "终端", "电脑在哪", "locate", "terminal", "找电脑"), "terminal_locate"),
]
for keywords, key in rules:
if any(k in text for k in keywords):
return {"scenario_key": key, "confidence": 0.75, "raw": ""}
return {"scenario_key": None, "confidence": 0.0, "raw": ""}
async def get_dify_client(audit=None) -> Optional[DifyClient]:
"""从 settings 构建 Dify 客户端;未配置返回 None。"""
base_url = settings.automation_dify_base_url
api_key = settings.automation_dify_api_key
if not base_url or not api_key:
return None
return DifyClient(api_key=api_key, base_url=base_url, audit=audit)