""" =========================================== Cypher 执行模块 - 拆分 + 拆分执行 =========================================== 从 GraphRetrieval 抽取出的两个独立纯函数: - split_aggregate_cypher(cypher) 把一条 Cypher 拆成 (path_cypher, aggregate_cypher)。 纯字符串处理,只依赖 re 和 logger,不依赖 self。 - execute_cypher_with_split(cypher, is_aggregate, driver, split_fn=...) 执行 Cypher,支持拆分为 path + aggregate 两个查询。 依赖通过参数注入:driver 负责执行,split_fn 负责拆分 (默认就是上面的 split_aggregate_cypher)。 这样 GraphRetrieval 里的两个方法都可以变成一行委托调用,测试也直接测这两个函数。 """ import re import logging from typing import Callable, Optional, Tuple, Any,List,Dict from neo4j import AsyncGraphDatabase, GraphDatabase from graph_search.result_formatter import format_results,serialize_graph_for_llm logger = logging.getLogger(__name__) def split_aggregate_cypher(cypher: str) -> Tuple[Optional[str], Optional[str]]: """ 这个函数只处理字符串,不需要改为异步。 """ if not cypher or not cypher.strip(): return None, None try: match_match = re.search( r'(?i)\bMATCH\s+(.+?)(?=\s+\b(?:WHERE|WITH|RETURN)\b)', cypher, re.DOTALL ) if not match_match: logger.warning(f"[Cypher处理] 未找到合法的 MATCH 子句。输入片段: {cypher[:100]}...") return None, None full_match_clause = match_match.group(1).strip() where_match = re.search( r'(?i)\bWHERE\s+(.+?)(?=\s+\b(?:WITH|RETURN)\b)', cypher, re.DOTALL ) where_clause = " WHERE " + where_match.group(1).strip() if where_match else "" patterns = [p.strip() for p in full_match_clause.split(',') if p.strip()] if not patterns: logger.warning("[Cypher处理] MATCH 子句中无有效模式") return None, None first_pattern = patterns[0] path_var = "path" path_pattern = f"{path_var} = {first_pattern}" path_pattern = re.sub(r'?', ']-', path_pattern) path_cypher = f"MATCH {path_pattern}{where_clause} RETURN DISTINCT {path_var}" aggregate_cypher = cypher logger.debug(f"[Cypher处理] Path查询: {path_cypher}") return path_cypher, aggregate_cypher except Exception as e: logger.exception(f"[Cypher处理] 生成 path 查询失败: {e}") return None, None # 2. 将执行函数改为 async async def execute_cypher_with_split( cypher: str, is_aggregate: bool, driver: Any, split_fn: Callable[[str], Tuple[Optional[str], Optional[str]]] = split_aggregate_cypher, ) -> Tuple[Any, Optional[Any]]: """ 异步执行 Cypher,支持拆分为 path + aggregate 两个查询。 """ path_cypher, aggregate_cypher = split_fn(cypher) if path_cypher and aggregate_cypher: logger.info("[Cypher拆分] 执行 path 和 aggregate 查询") path_records = None try: # 3. 使用 await 执行 path 查询 path_result = await driver.execute_query(path_cypher) path_records = path_result.records except Exception as e: logger.warning(f"[Cypher拆分] Path 查询失败: {e}") path_records = None try: # 4. 使用 await 执行 aggregate 查询 aggregate_result = await driver.execute_query(aggregate_cypher) records = aggregate_result.records except Exception as e: logger.error(f"[Cypher拆分] Aggregate 查询失败: {e},回退到原始 Cypher") # 回退也需异步执行 result = await driver.execute_query(cypher) return result.records, None return records, path_records else: # 5. 使用 await 执行原始查询 result = await driver.execute_query(cypher) return result.records, None class ResultProcessor: """查询结果处理器 - 保持不变,因为只是数据处理""" def __init__(self): pass def process_results(self, records: List, path_records: Optional[List], is_aggregate: bool = False) -> List[Dict[str, Any]]: if is_aggregate: return self._process_aggregate_results(records, path_records) else: return self._process_detail_results(records, path_records) def _process_aggregate_results(self, records: List, path_records: Optional[List]) -> List[Dict[str, Any]]: aggregate_results = [dict(r) for r in records] aggregate_value = aggregate_results[0].get('result', aggregate_results[0]) if path_records: raw_results = [dict(r) for r in path_records] for res in raw_results: res['result'] = aggregate_value if raw_results: raw_results[0]['_aggregate_value'] = aggregate_value logger.debug(f"[结果处理] 聚合查询,返回 {len(raw_results)} 条path记录") return raw_results else: logger.debug(f"[结果处理] 聚合查询,返回 {len(aggregate_results)} 条聚合结果") return aggregate_results def _process_detail_results(self, records: List, path_records: Optional[List]) -> List[Dict[str, Any]]: value_results = [dict(r) for r in records] if path_records: raw_results = [dict(r) for r in path_records] for i in range(len(raw_results)): if i < len(value_results): raw_results[i]['result'] = value_results[i] else: raw_results[i]['result'] = None logger.debug(f"[结果处理] 明细查询,返回 {len(raw_results)} 条path记录") return raw_results else: logger.debug(f"[结果处理] 明细查询,返回 {len(value_results)} 条明细结果") return value_results def process_neo4j_results(records: List, path_records: Optional[List] = None, is_aggregate: bool = False) -> List[Dict[str, Any]]: """便捷函数""" processor = ResultProcessor() return processor.process_results(records, path_records, is_aggregate) async def excute_cypher(driver,cypher): # driver = AsyncGraphDatabase.driver(URI, auth=AUTH) try: query = """ MATCH (d:设备)-[:发生故障使用维修工作]->(w:维修工作)-[:包含故障]->(f:故障模式)<-[:修理]-(p:维修项目) WHERE d.名称 = '喷油器组件' AND w.名称 = '喷油器组件维修工作指导' AND f.名称 = '喷油器工作不一致导致机座与机架组件功率输出不稳定' AND p.名称 = '排除喷油器雾化不良故障' RETURN w.名称 AS 维修工作名称, f.名称 AS 故障模式, p.名称 AS 维修项目名称 """ # 7. 调用异步函数 records, path_records = await execute_cypher_with_split(cypher, True, driver) results = process_neo4j_results( records=records, path_records=path_records, is_aggregate=True ) # 这里假设 format_results 和 serialize_graph_for_llm 是同步的工具函数 # 如果它们也是异步的,请使用 await path_data = format_results(results) temp_graph_response = { "data": { "nodes": path_data.get("nodes", []), "results": results } } # 假设 serialize_graph_for_llm 是同步的 formatted_results_str = serialize_graph_for_llm(temp_graph_response) response = { "data": { "nodes": path_data.get("nodes", []), "links": path_data.get("links", []), "results": formatted_results_str }, "meta": { "result_count": len(formatted_results_str), "query_type": "graph_search", "cypher": cypher, "query_classification": "graph_search" } } return response finally: # 8. 关闭异步驱动 await driver.close() # ========== 便捷函数和主程序入口 ========== if __name__ == "__main__": # 6. 配置异步驱动 URI = "bolt://192.168.0.46:57687" AUTH = ("neo4j", "zdht123@") # 定义异步主函数 async def main(): # 使用 AsyncGraphDatabase 初始化驱动 driver = AsyncGraphDatabase.driver(URI, auth=AUTH) try: query = """ MATCH (d:设备)-[:发生故障使用维修工作]->(w:维修工作)-[:包含故障]->(f:故障模式)<-[:修理]-(p:维修项目) WHERE d.名称 = '喷油器组件' AND w.名称 = '喷油器组件维修工作指导' AND f.名称 = '喷油器工作不一致导致机座与机架组件功率输出不稳定' AND p.名称 = '排除喷油器雾化不良故障' RETURN w.名称 AS 维修工作名称, f.名称 AS 故障模式, p.名称 AS 维修项目名称 """ # 7. 调用异步函数 records, path_records = await execute_cypher_with_split(query, True, driver) results = process_neo4j_results( records=records, path_records=path_records, is_aggregate=True ) # 这里假设 format_results 和 serialize_graph_for_llm 是同步的工具函数 # 如果它们也是异步的,请使用 await path_data = format_results(results) temp_graph_response = { "data": { "nodes": path_data.get("nodes", []), "results": results } } # 假设 serialize_graph_for_llm 是同步的 formatted_results_str = serialize_graph_for_llm(temp_graph_response) response = { "code": 200, "message": "success", "data": { "nodes": path_data.get("nodes", []), "links": path_data.get("links", []), "results": formatted_results_str }, "meta": { "result_count": len(formatted_results_str), "query_type": "graph_search", "elapsed_ms": 20, "cypher": query, "query_classification": "graph_search" } } print(response) finally: # 8. 关闭异步驱动 await driver.close() # 9. 运行异步主函数 import asyncio asyncio.run(main())