Files
wecom_it_smart_desk/backend/app/services/graph_query_service.py
T

194 lines
6.2 KiB
Python
Raw 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智能服务台 — 图谱查询服务
# =============================================================================
# 说明:基于Neo4j知识图谱的智能问答服务。
# 用户问题 → 图谱查询 → 直接返回解决方案(毫秒级响应)
# 未命中 → 降级到Dify流程
#
# 核心功能:
# 1. 关键词提取(jieba分词 + 停用词过滤)
# 2. Neo4j模糊匹配Issue节点
# 3. 查找关联的Action解决方案
# 4. 返回最高匹配的SolutionResult
# =============================================================================
import logging
from typing import List, Optional
from pydantic import BaseModel
logger = logging.getLogger(__name__)
# 停用词列表(常见的无意义词汇)
STOPWORDS = {
"", "", "", "", "", "", "", "", "", "",
"", "", "一个", "", "", "", "", "", "", "",
"", "", "", "没有", "", "", "自己", "", "",
"什么", "怎么", "如何", "为什么", "请问", "帮忙", "帮助",
}
class SolutionResult(BaseModel):
"""图谱查询结果 - 解决方案"""
issue_name: str = ""
issue_uuid: str = ""
solution: str = ""
action_name: str = ""
confidence: float = 0.0
class GraphQueryService:
"""图谱查询服务 - 根据用户问题查找解决方案"""
def __init__(self, neo4j_client):
"""初始化图谱查询服务
Args:
neo4j_client: Neo4jClient实例
"""
self.neo4j_client = neo4j_client
self._keyword_cache: dict = {} # 简单缓存
async def find_solution_by_question(
self, question: str, timeout_ms: int = 100
) -> Optional[SolutionResult]:
"""根据用户问题查找解决方案(图谱查询主入口)
流程:
1. 提取问题关键词
2. 遍历关键词查询Neo4j图谱
3. 获取匹配的Issue及关联的Action
4. 返回置信度最高的解决方案
Args:
question: 用户问题文本
timeout_ms: 查询超时时间(毫秒),默认100ms
Returns:
Optional[SolutionResult]: 匹配的解决方案,未命中返回None
"""
if not question or not question.strip():
return None
# 1. 提取关键词
keywords = self._extract_keywords(question)
if not keywords:
logger.info(f"图谱查询:无法提取关键词 question={question[:50]}")
return None
logger.info(f"图谱查询:keywords={keywords}, question={question[:50]}")
# 2. 遍历关键词查询图谱(按置信度排序)
for kw in keywords:
try:
result = await self._query_by_keyword(kw)
if result:
logger.info(
f"图谱命中: keyword={kw}, issue={result.issue_name}, "
f"confidence={result.confidence}"
)
return result
except Exception as e:
logger.warning(f"图谱查询异常(降级继续): keyword={kw}, error={e}")
continue
logger.info(f"图谱未命中: question={question[:50]}")
return None
def _extract_keywords(self, text: str) -> List[str]:
"""从文本中提取关键词
使用jieba分词 + 停用词过滤
Args:
text: 输入文本
Returns:
List[str]: 关键词列表(按出现顺序)
"""
# 尝试导入jieba,如果失败则使用简单分词
try:
import jieba
words = jieba.lcut(text)
except ImportError:
# 降级:简单按空格和标点分词
words = text.replace("", " ").replace("", " ").replace("", " ").split()
# 过滤停用词和短词
keywords = [
w.strip()
for w in words
if w.strip() and len(w.strip()) >= 2 and w.strip() not in STOPWORDS
]
# 返回前5个关键词(避免查询过多)
return keywords[:5]
async def _query_by_keyword(self, keyword: str) -> Optional[SolutionResult]:
"""根据单个关键词查询解决方案
Args:
keyword: 搜索关键词
Returns:
Optional[SolutionResult]: 匹配的解决方案
"""
# 调用neo4j_client的模糊搜索方法
if not hasattr(self.neo4j_client, "find_issues_by_keyword"):
logger.warning("neo4j_client没有find_issues_by_keyword方法")
return None
issues = await self.neo4j_client.find_issues_by_keyword(keyword, limit=5)
if not issues:
return None
# 获取第一个Issue的详细信息和关联的Action
issue = issues[0]
# 查询关联的Action解决方案
actions = await self.neo4j_client.find_actions_by_issue(issue.uuid)
if not actions:
return None
action = actions[0]
# 构建返回结果
return SolutionResult(
issue_name=issue.name,
issue_uuid=issue.uuid,
solution=action.description or action.name,
action_name=action.name,
confidence=0.8, # 简化处理,默认0.8
)
# 全局单例
_graph_query_service: Optional[GraphQueryService] = None
async def get_graph_query_service(neo4j_client=None) -> Optional[GraphQueryService]:
"""获取GraphQueryService单例(异步版本)
Args:
neo4j_client: Neo4jClient实例,不传则自动获取
Returns:
Optional[GraphQueryService]: 图谱查询服务实例,Neo4j不可用时返回None
"""
global _graph_query_service
if _graph_query_service is None:
if neo4j_client is None:
from app.services.neo4j_client import get_neo4j_client
neo4j_client = await get_neo4j_client()
# 如果neo4j_client为None(图服务不可用),直接返回None
if neo4j_client is None:
return None
_graph_query_service = GraphQueryService(neo4j_client)
return _graph_query_service