# ============================================================================= # 企微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