141 lines
5.2 KiB
Python
141 lines
5.2 KiB
Python
# =============================================================================
|
||
# 企微IT智能服务台 — 阶段5 自动化 Dify 客户端
|
||
# =============================================================================
|
||
# 说明:Dify 承担意图识别与 AI 编排。
|
||
# 1. 意图识别:根据员工消息判断命中哪个自动化场景
|
||
# (password_reset / software_install / virus_dispose / terminal_locate)
|
||
# 2. AI 编排(可选):生成处置方案草案
|
||
#
|
||
# 认证:Dify 开放 API 使用 Bearer Token(API 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 中解析 JSON(scenario_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)
|