Files

141 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# 企微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)