701 lines
28 KiB
Python
701 lines
28 KiB
Python
"""
|
||
真正的图RAG检索模块
|
||
基于图结构的知识推理和检索,而非简单的关键词匹配
|
||
"""
|
||
|
||
import json
|
||
import logging
|
||
from collections import defaultdict, deque
|
||
from typing import List, Dict, Tuple, Any, Optional, Set
|
||
from dataclasses import dataclass
|
||
from enum import Enum
|
||
|
||
from langchain_core.documents import Document
|
||
from neo4j import GraphDatabase
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class QueryType(Enum):
|
||
"""查询类型枚举"""
|
||
ENTITY_RELATION = "entity_relation" # 实体关系查询:A和B有什么关系?
|
||
MULTI_HOP = "multi_hop" # 多跳查询:A通过什么连接到C?
|
||
SUBGRAPH = "subgraph" # 子图查询:A相关的所有信息
|
||
PATH_FINDING = "path_finding" # 路径查找:从A到B的最佳路径
|
||
CLUSTERING = "clustering" # 聚类查询:和A相似的都有什么?
|
||
|
||
@dataclass
|
||
class GraphQuery:
|
||
"""图查询结构"""
|
||
query_type: QueryType
|
||
source_entities: List[str]
|
||
target_entities: List[str] = None
|
||
relation_types: List[str] = None
|
||
max_depth: int = 2
|
||
max_nodes: int = 50
|
||
constraints: Dict[str, Any] = None
|
||
|
||
@dataclass
|
||
class GraphPath:
|
||
"""图路径结构"""
|
||
nodes: List[Dict[str, Any]]
|
||
relationships: List[Dict[str, Any]]
|
||
path_length: int
|
||
relevance_score: float
|
||
path_type: str
|
||
|
||
@dataclass
|
||
class KnowledgeSubgraph:
|
||
"""知识子图结构"""
|
||
central_nodes: List[Dict[str, Any]]
|
||
connected_nodes: List[Dict[str, Any]]
|
||
relationships: List[Dict[str, Any]]
|
||
graph_metrics: Dict[str, float]
|
||
reasoning_chains: List[List[str]]
|
||
|
||
class GraphRAGRetrieval:
|
||
"""
|
||
真正的图RAG检索系统
|
||
核心特点:
|
||
1. 查询意图理解:识别图查询模式
|
||
2. 多跳图遍历:深度关系探索
|
||
3. 子图提取:相关知识网络
|
||
4. 图结构推理:基于拓扑的推理
|
||
5. 动态查询规划:自适应遍历策略
|
||
"""
|
||
|
||
def __init__(self, config, llm_client):
|
||
self.config = config
|
||
self.llm_client = llm_client
|
||
self.driver = None
|
||
|
||
# 图结构缓存
|
||
self.entity_cache = {}
|
||
self.relation_cache = {}
|
||
self.subgraph_cache = {}
|
||
|
||
def initialize(self):
|
||
"""初始化图RAG检索系统"""
|
||
logger.info("初始化图RAG检索系统...")
|
||
|
||
# 连接Neo4j
|
||
try:
|
||
self.driver = GraphDatabase.driver(
|
||
self.config.neo4j_uri,
|
||
auth=(self.config.neo4j_user, self.config.neo4j_password)
|
||
)
|
||
# 测试连接
|
||
with self.driver.session() as session:
|
||
session.run("RETURN 1")
|
||
logger.info("Neo4j连接成功")
|
||
except Exception as e:
|
||
logger.error(f"Neo4j连接失败: {e}")
|
||
return
|
||
|
||
# 预热:构建实体和关系索引
|
||
self._build_graph_index()
|
||
|
||
def _build_graph_index(self):
|
||
"""构建图索引以加速查询"""
|
||
logger.info("构建图结构索引...")
|
||
|
||
try:
|
||
with self.driver.session() as session:
|
||
# 构建实体索引 - 修复Neo4j语法兼容性问题
|
||
entity_query = """
|
||
MATCH (n)
|
||
WHERE n.nodeId IS NOT NULL
|
||
WITH n, COUNT { (n)--() } as degree
|
||
RETURN labels(n) as node_labels, n.nodeId as node_id,
|
||
n.name as name, n.category as category, degree
|
||
ORDER BY degree DESC
|
||
LIMIT 1000
|
||
"""
|
||
|
||
result = session.run(entity_query)
|
||
for record in result:
|
||
node_id = record["node_id"]
|
||
self.entity_cache[node_id] = {
|
||
"labels": record["node_labels"],
|
||
"name": record["name"],
|
||
"category": record["category"],
|
||
"degree": record["degree"]
|
||
}
|
||
|
||
# 构建关系类型索引
|
||
relation_query = """
|
||
MATCH ()-[r]->()
|
||
RETURN type(r) as rel_type, count(r) as frequency
|
||
ORDER BY frequency DESC
|
||
"""
|
||
|
||
result = session.run(relation_query)
|
||
for record in result:
|
||
rel_type = record["rel_type"]
|
||
self.relation_cache[rel_type] = record["frequency"]
|
||
|
||
logger.info(f"索引构建完成: {len(self.entity_cache)}个实体, {len(self.relation_cache)}个关系类型")
|
||
|
||
except Exception as e:
|
||
logger.error(f"构建图索引失败: {e}")
|
||
|
||
def understand_graph_query(self, query: str) -> GraphQuery:
|
||
"""
|
||
理解查询的图结构意图
|
||
这是图RAG的核心:从自然语言到图查询的转换
|
||
"""
|
||
prompt = f"""
|
||
作为图数据库专家,分析以下查询的图结构意图,并将自然语言问题映射到**已有图结构**上。
|
||
|
||
已知图中大致有以下节点和关系:
|
||
- 节点类型:
|
||
- Recipe:菜谱节点,包含 name、description、cuisineType(如"川菜")、category、tags、prepTime、cookTime 等属性
|
||
- Ingredient:食材节点,包含 name、category(如"蔬菜"、"蛋白质" 等)
|
||
- Category:菜品分类(如"川菜"、"家常菜"、"素菜")
|
||
- CookingStep:烹饪步骤
|
||
- 主要关系:
|
||
- (Recipe)-[:REQUIRES]->(Ingredient)
|
||
- (Recipe)-[:BELONGS_TO_CATEGORY]->(Category)
|
||
- (Recipe)-[:CONTAINS_STEP]->(CookingStep)
|
||
|
||
请根据上述图结构分析下面的查询:
|
||
|
||
查询:{query}
|
||
|
||
请识别:
|
||
1. 查询类型:
|
||
- entity_relation: 询问实体间的直接关系(如:鸡肉和胡萝卜能一起做菜吗?)
|
||
- multi_hop: 需要多跳推理(如:鸡肉配什么蔬菜?需要:鸡肉→菜品→食材→蔬菜)
|
||
- subgraph: 需要完整子图(如:川菜有什么特色?需要川菜相关的完整知识网络)
|
||
- path_finding: 路径查找(如:从食材到成品菜的制作路径)
|
||
- clustering: 聚类相似性(如:和宫保鸡丁类似的菜有哪些?)
|
||
|
||
2. source_entities:
|
||
- 只包含在图中**很有可能有对应节点**的具体实体名称
|
||
- 优先选择:菜系(如"川菜")、具体菜名(如"宫保鸡丁")、食材名(如"鸡肉"、"豆腐")
|
||
- 不要把抽象概念或约束(如"糖尿病饮食限制"、"具体川菜菜品"、"健康饮食"、"30分钟内")放进 source_entities
|
||
|
||
3. target_entities:
|
||
- 只在确实需要限制「路径终点」时填写
|
||
- 同样只能使用可能出现在 Recipe / Ingredient / Category 节点上的名称(如"蔬菜"、"素菜"、具体菜名)
|
||
- 如果不确定目标实体怎么映射到图中,请返回空列表 []
|
||
|
||
4. relation_types:本次推理中希望优先考虑的关系类型列表
|
||
- 例如:["REQUIRES", "BELONGS_TO_CATEGORY"]
|
||
|
||
5. max_depth:建议的图遍历深度(1-3 之间的整数)
|
||
|
||
6. constraints:可选的**属性级约束**,用于表达图结构之外的过滤条件,例如:
|
||
- 健康/饮食限制(如"糖尿病"、"低糖")
|
||
- 时间限制(如"30分钟内")
|
||
- 口味偏好(如"清淡"、"少油")
|
||
用一个字典描述,例如:
|
||
{{
|
||
"health": ["糖尿病", "低糖"],
|
||
"time": {{"max_minutes": 30}},
|
||
"style": ["川菜"]
|
||
}}
|
||
|
||
示例1:
|
||
查询:"鸡肉配什么蔬菜好?"
|
||
期望分析:这是 multi_hop 查询,需要通过"鸡肉→使用鸡肉的菜品→这些菜品使用的蔬菜"的路径推理。
|
||
|
||
返回JSON示例:
|
||
{{
|
||
"query_type": "multi_hop",
|
||
"source_entities": ["鸡肉"],
|
||
"target_entities": ["蔬菜"],
|
||
"relation_types": ["REQUIRES", "BELONGS_TO_CATEGORY"],
|
||
"max_depth": 3,
|
||
"constraints": {{}}
|
||
}}
|
||
|
||
示例2:
|
||
查询:"适合糖尿病人吃的低糖川菜有哪些,并且制作时间不超过30分钟?"
|
||
期望分析:
|
||
- 图中可以直接对应的实体:主要是菜系 "川菜"
|
||
- 糖尿病/低糖/30分钟 属于属性级约束,不能当作节点
|
||
- 可以使用 subgraph 或 multi_hop,以 "川菜" 为核心实体,结合属性约束做后续过滤
|
||
|
||
返回JSON示例:
|
||
{{
|
||
"query_type": "subgraph",
|
||
"source_entities": ["川菜"],
|
||
"target_entities": [],
|
||
"relation_types": ["BELONGS_TO_CATEGORY", "REQUIRES"],
|
||
"max_depth": 2,
|
||
"constraints": {{
|
||
"health": ["糖尿病", "低糖"],
|
||
"time": {{"max_minutes": 30}}
|
||
}}
|
||
}}
|
||
|
||
请严格返回一个合法的 JSON 对象,不要包含任何多余的说明文字。
|
||
"""
|
||
|
||
try:
|
||
response = self.llm_client.chat.completions.create(
|
||
model=self.config.llm_model,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
temperature=0.1,
|
||
max_tokens=1000
|
||
)
|
||
|
||
result = json.loads(response.choices[0].message.content.strip())
|
||
|
||
return GraphQuery(
|
||
query_type=QueryType(result.get("query_type", "subgraph")),
|
||
source_entities=result.get("source_entities", []),
|
||
target_entities=result.get("target_entities", []),
|
||
relation_types=result.get("relation_types", []),
|
||
max_depth=result.get("max_depth", 2),
|
||
max_nodes=50
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询意图理解失败: {e}")
|
||
# 降级方案:默认子图查询
|
||
return GraphQuery(
|
||
query_type=QueryType.SUBGRAPH,
|
||
source_entities=[query],
|
||
max_depth=2
|
||
)
|
||
|
||
def multi_hop_traversal(self, graph_query: GraphQuery) -> List[GraphPath]:
|
||
"""
|
||
多跳图遍历:这是图RAG的核心优势
|
||
通过图结构发现隐含的知识关联
|
||
"""
|
||
logger.info(f"执行多跳遍历: {graph_query.source_entities} -> {graph_query.target_entities}")
|
||
|
||
paths = []
|
||
|
||
if not self.driver:
|
||
logger.error("Neo4j连接未建立")
|
||
return paths
|
||
|
||
try:
|
||
with self.driver.session() as session:
|
||
# 构建多跳遍历查询
|
||
source_entities = graph_query.source_entities
|
||
target_keywords = graph_query.target_entities or []
|
||
max_depth = graph_query.max_depth
|
||
|
||
# 根据查询类型选择不同的遍历策略
|
||
if graph_query.query_type == QueryType.MULTI_HOP:
|
||
# 根据是否有目标关键词动态拼接过滤条件
|
||
target_filter_clause = ""
|
||
if target_keywords:
|
||
target_filter_clause = """
|
||
AND ANY(kw IN $target_keywords WHERE
|
||
(target.name IS NOT NULL AND (toString(target.name) CONTAINS kw OR kw CONTAINS toString(target.name))) OR
|
||
(target.category IS NOT NULL AND (toString(target.category) CONTAINS kw OR kw CONTAINS toString(target.category)))
|
||
)"""
|
||
|
||
cypher_query = f"""
|
||
// 多跳推理查询
|
||
UNWIND $source_entities as source_name
|
||
MATCH (source)
|
||
WHERE source.name CONTAINS source_name OR source.nodeId = source_name
|
||
|
||
// 执行多跳遍历
|
||
MATCH path = (source)-[*1..{max_depth}]-(target)
|
||
WHERE NOT source = target{target_filter_clause}
|
||
|
||
// 计算路径相关性
|
||
WITH path, source, target,
|
||
length(path) as path_len,
|
||
relationships(path) as rels,
|
||
nodes(path) as path_nodes
|
||
|
||
// 路径评分:短路径 + 高度数节点 + 关系类型匹配
|
||
WITH path, source, target, path_len, rels, path_nodes,
|
||
(1.0 / path_len) +
|
||
(REDUCE(s = 0.0, n IN path_nodes | s + COUNT {{ (n)--() }}) / 10.0 / size(path_nodes)) +
|
||
(CASE WHEN ANY(r IN rels WHERE type(r) IN $relation_types) THEN 0.3 ELSE 0.0 END) as relevance
|
||
|
||
ORDER BY relevance DESC
|
||
LIMIT 20
|
||
|
||
RETURN path, source, target, path_len, rels, path_nodes, relevance
|
||
"""
|
||
|
||
params = {
|
||
"source_entities": source_entities,
|
||
"relation_types": graph_query.relation_types or []
|
||
}
|
||
if target_keywords:
|
||
params["target_keywords"] = target_keywords
|
||
|
||
result = session.run(cypher_query, params)
|
||
|
||
for record in result:
|
||
path_data = self._parse_neo4j_path(record)
|
||
if path_data:
|
||
paths.append(path_data)
|
||
|
||
elif graph_query.query_type == QueryType.ENTITY_RELATION:
|
||
# 实体间关系查询
|
||
paths.extend(self._find_entity_relations(graph_query, session))
|
||
|
||
elif graph_query.query_type == QueryType.PATH_FINDING:
|
||
# 最短路径查找
|
||
paths.extend(self._find_shortest_paths(graph_query, session))
|
||
|
||
except Exception as e:
|
||
logger.error(f"多跳遍历失败: {e}")
|
||
|
||
logger.info(f"多跳遍历完成,找到 {len(paths)} 条路径")
|
||
return paths
|
||
|
||
def extract_knowledge_subgraph(self, graph_query: GraphQuery) -> KnowledgeSubgraph:
|
||
"""
|
||
提取知识子图:获取实体相关的完整知识网络
|
||
这体现了图RAG的整体性思维
|
||
"""
|
||
logger.info(f"提取知识子图: {graph_query.source_entities}")
|
||
|
||
if not self.driver:
|
||
logger.error("Neo4j连接未建立")
|
||
return self._fallback_subgraph_extraction(graph_query)
|
||
|
||
try:
|
||
with self.driver.session() as session:
|
||
# 简化的子图提取(不依赖APOC)
|
||
cypher_query = f"""
|
||
// 找到源实体
|
||
UNWIND $source_entities as entity_name
|
||
MATCH (source)
|
||
WHERE source.name CONTAINS entity_name
|
||
OR source.nodeId = entity_name
|
||
|
||
// 获取指定深度的邻居
|
||
MATCH (source)-[r*1..{graph_query.max_depth}]-(neighbor)
|
||
WITH source, collect(DISTINCT neighbor) as neighbors,
|
||
collect(DISTINCT r) as relationships
|
||
WHERE size(neighbors) <= $max_nodes
|
||
|
||
// 计算图指标
|
||
WITH source, neighbors, relationships,
|
||
size(neighbors) as node_count,
|
||
size(relationships) as rel_count
|
||
|
||
RETURN
|
||
source,
|
||
neighbors[0..{graph_query.max_nodes}] as nodes,
|
||
relationships[0..{graph_query.max_nodes}] as rels,
|
||
{{
|
||
node_count: node_count,
|
||
relationship_count: rel_count,
|
||
density: CASE WHEN node_count > 1 THEN toFloat(rel_count) / (node_count * (node_count - 1) / 2) ELSE 0.0 END
|
||
}} as metrics
|
||
"""
|
||
|
||
result = session.run(cypher_query, {
|
||
"source_entities": graph_query.source_entities,
|
||
"max_nodes": graph_query.max_nodes
|
||
})
|
||
|
||
record = result.single()
|
||
if record:
|
||
return self._build_knowledge_subgraph(record)
|
||
|
||
except Exception as e:
|
||
logger.error(f"子图提取失败: {e}")
|
||
|
||
# 降级方案:简单邻居查询
|
||
return self._fallback_subgraph_extraction(graph_query)
|
||
|
||
def graph_structure_reasoning(self, subgraph: KnowledgeSubgraph, query: str) -> List[str]:
|
||
"""
|
||
基于图结构的推理:这是图RAG的智能之处
|
||
不仅检索信息,还能进行逻辑推理
|
||
"""
|
||
reasoning_chains = []
|
||
|
||
try:
|
||
# 1. 识别推理模式
|
||
reasoning_patterns = self._identify_reasoning_patterns(subgraph)
|
||
|
||
# 2. 构建推理链
|
||
for pattern in reasoning_patterns:
|
||
chain = self._build_reasoning_chain(pattern, subgraph)
|
||
if chain:
|
||
reasoning_chains.append(chain)
|
||
|
||
# 3. 验证推理链的可信度
|
||
validated_chains = self._validate_reasoning_chains(reasoning_chains, query)
|
||
|
||
logger.info(f"图结构推理完成,生成 {len(validated_chains)} 条推理链")
|
||
return validated_chains
|
||
|
||
except Exception as e:
|
||
logger.error(f"图结构推理失败: {e}")
|
||
return []
|
||
|
||
def adaptive_query_planning(self, query: str) -> List[GraphQuery]:
|
||
"""
|
||
自适应查询规划:根据查询复杂度动态调整策略
|
||
"""
|
||
# 分析查询复杂度
|
||
complexity_score = self._analyze_query_complexity(query)
|
||
|
||
query_plans = []
|
||
|
||
if complexity_score < 0.3:
|
||
# 简单查询:直接邻居查询
|
||
plan = GraphQuery(
|
||
query_type=QueryType.ENTITY_RELATION,
|
||
source_entities=[query],
|
||
max_depth=1,
|
||
max_nodes=20
|
||
)
|
||
query_plans.append(plan)
|
||
|
||
elif complexity_score < 0.7:
|
||
# 中等复杂度:多跳查询
|
||
plan = GraphQuery(
|
||
query_type=QueryType.MULTI_HOP,
|
||
source_entities=[query],
|
||
max_depth=2,
|
||
max_nodes=50
|
||
)
|
||
query_plans.append(plan)
|
||
|
||
else:
|
||
# 复杂查询:子图提取 + 推理
|
||
plan1 = GraphQuery(
|
||
query_type=QueryType.SUBGRAPH,
|
||
source_entities=[query],
|
||
max_depth=3,
|
||
max_nodes=100
|
||
)
|
||
plan2 = GraphQuery(
|
||
query_type=QueryType.MULTI_HOP,
|
||
source_entities=[query],
|
||
max_depth=3,
|
||
max_nodes=50
|
||
)
|
||
query_plans.extend([plan1, plan2])
|
||
|
||
return query_plans
|
||
|
||
def graph_rag_search(self, query: str, top_k: int = 5) -> List[Document]:
|
||
"""
|
||
图RAG主搜索接口:整合所有图RAG能力
|
||
"""
|
||
logger.info(f"开始图RAG检索: {query}")
|
||
|
||
if not self.driver:
|
||
logger.warning("Neo4j连接未建立,返回空结果")
|
||
return []
|
||
|
||
# 1. 查询意图理解
|
||
graph_query = self.understand_graph_query(query)
|
||
logger.info(f"查询类型: {graph_query.query_type.value}")
|
||
|
||
results = []
|
||
|
||
try:
|
||
# 2. 根据查询类型执行不同策略
|
||
if graph_query.query_type in [QueryType.MULTI_HOP, QueryType.PATH_FINDING]:
|
||
# 多跳遍历 / 路径查找
|
||
paths = self.multi_hop_traversal(graph_query)
|
||
results.extend(self._paths_to_documents(paths, query))
|
||
|
||
elif graph_query.query_type in [QueryType.SUBGRAPH, QueryType.CLUSTERING]:
|
||
# 子图提取 / 聚类查询:都视为“围绕核心实体的局部知识网络”
|
||
subgraph = self.extract_knowledge_subgraph(graph_query)
|
||
|
||
# 图结构推理
|
||
reasoning_chains = self.graph_structure_reasoning(subgraph, query)
|
||
|
||
results.extend(self._subgraph_to_documents(subgraph, reasoning_chains, query))
|
||
|
||
elif graph_query.query_type == QueryType.ENTITY_RELATION:
|
||
# 实体关系查询(可以视为一跳 / 少量跳的路径查询)
|
||
paths = self.multi_hop_traversal(graph_query)
|
||
results.extend(self._paths_to_documents(paths, query))
|
||
|
||
# 3. 图结构相关性排序
|
||
results = self._rank_by_graph_relevance(results, query)
|
||
|
||
logger.info(f"图RAG检索完成,返回 {len(results[:top_k])} 个结果")
|
||
return results[:top_k]
|
||
|
||
except Exception as e:
|
||
logger.error(f"图RAG检索失败: {e}")
|
||
return []
|
||
|
||
# ========== 辅助方法 ==========
|
||
|
||
def _parse_neo4j_path(self, record) -> Optional[GraphPath]:
|
||
"""解析Neo4j路径记录"""
|
||
try:
|
||
path_nodes = []
|
||
for node in record["path_nodes"]:
|
||
path_nodes.append({
|
||
"id": node.get("nodeId", ""),
|
||
"name": node.get("name", ""),
|
||
"labels": list(node.labels),
|
||
"properties": dict(node)
|
||
})
|
||
|
||
relationships = []
|
||
for rel in record["rels"]:
|
||
relationships.append({
|
||
"type": type(rel).__name__,
|
||
"properties": dict(rel)
|
||
})
|
||
|
||
return GraphPath(
|
||
nodes=path_nodes,
|
||
relationships=relationships,
|
||
path_length=record["path_len"],
|
||
relevance_score=record["relevance"],
|
||
path_type="multi_hop"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"路径解析失败: {e}")
|
||
return None
|
||
|
||
def _build_knowledge_subgraph(self, record) -> KnowledgeSubgraph:
|
||
"""构建知识子图对象"""
|
||
try:
|
||
central_nodes = [dict(record["source"])]
|
||
connected_nodes = [dict(node) for node in record["nodes"]]
|
||
relationships = [dict(rel) for rel in record["rels"]]
|
||
|
||
return KnowledgeSubgraph(
|
||
central_nodes=central_nodes,
|
||
connected_nodes=connected_nodes,
|
||
relationships=relationships,
|
||
graph_metrics=record["metrics"],
|
||
reasoning_chains=[]
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"构建知识子图失败: {e}")
|
||
return KnowledgeSubgraph(
|
||
central_nodes=[],
|
||
connected_nodes=[],
|
||
relationships=[],
|
||
graph_metrics={},
|
||
reasoning_chains=[]
|
||
)
|
||
|
||
def _paths_to_documents(self, paths: List[GraphPath], query: str) -> List[Document]:
|
||
"""将图路径转换为Document对象"""
|
||
documents = []
|
||
|
||
for i, path in enumerate(paths):
|
||
# 构建路径描述
|
||
path_desc = self._build_path_description(path)
|
||
|
||
doc = Document(
|
||
page_content=path_desc,
|
||
metadata={
|
||
"search_type": "graph_path",
|
||
"path_length": path.path_length,
|
||
"relevance_score": path.relevance_score,
|
||
"path_type": path.path_type,
|
||
"node_count": len(path.nodes),
|
||
"relationship_count": len(path.relationships),
|
||
"recipe_name": path.nodes[0].get("name", "图结构结果") if path.nodes else "图结构结果"
|
||
}
|
||
)
|
||
documents.append(doc)
|
||
|
||
return documents
|
||
|
||
def _subgraph_to_documents(self, subgraph: KnowledgeSubgraph,
|
||
reasoning_chains: List[str], query: str) -> List[Document]:
|
||
"""将知识子图转换为Document对象"""
|
||
documents = []
|
||
|
||
# 子图整体描述
|
||
subgraph_desc = self._build_subgraph_description(subgraph)
|
||
|
||
doc = Document(
|
||
page_content=subgraph_desc,
|
||
metadata={
|
||
"search_type": "knowledge_subgraph",
|
||
"node_count": len(subgraph.connected_nodes),
|
||
"relationship_count": len(subgraph.relationships),
|
||
"graph_density": subgraph.graph_metrics.get("density", 0.0),
|
||
"reasoning_chains": reasoning_chains,
|
||
"recipe_name": subgraph.central_nodes[0].get("name", "知识子图") if subgraph.central_nodes else "知识子图"
|
||
}
|
||
)
|
||
documents.append(doc)
|
||
|
||
return documents
|
||
|
||
def _build_path_description(self, path: GraphPath) -> str:
|
||
"""构建路径的自然语言描述"""
|
||
if not path.nodes:
|
||
return "空路径"
|
||
|
||
desc_parts = []
|
||
for i, node in enumerate(path.nodes):
|
||
desc_parts.append(node.get("name", f"节点{i}"))
|
||
if i < len(path.relationships):
|
||
rel_type = path.relationships[i].get("type", "相关")
|
||
desc_parts.append(f" --{rel_type}--> ")
|
||
|
||
return "".join(desc_parts)
|
||
|
||
def _build_subgraph_description(self, subgraph: KnowledgeSubgraph) -> str:
|
||
"""构建子图的自然语言描述"""
|
||
central_names = [node.get("name", "未知") for node in subgraph.central_nodes]
|
||
node_count = len(subgraph.connected_nodes)
|
||
rel_count = len(subgraph.relationships)
|
||
|
||
return f"关于 {', '.join(central_names)} 的知识网络,包含 {node_count} 个相关概念和 {rel_count} 个关系。"
|
||
|
||
def _rank_by_graph_relevance(self, documents: List[Document], query: str) -> List[Document]:
|
||
"""基于图结构相关性排序"""
|
||
return sorted(documents,
|
||
key=lambda x: x.metadata.get("relevance_score", 0.0),
|
||
reverse=True)
|
||
|
||
def _analyze_query_complexity(self, query: str) -> float:
|
||
"""分析查询复杂度"""
|
||
complexity_indicators = ["什么", "如何", "为什么", "哪些", "关系", "影响", "原因"]
|
||
score = sum(1 for indicator in complexity_indicators if indicator in query)
|
||
return min(score / len(complexity_indicators), 1.0)
|
||
|
||
def _identify_reasoning_patterns(self, subgraph: KnowledgeSubgraph) -> List[str]:
|
||
"""识别推理模式"""
|
||
return ["因果关系", "组成关系", "相似关系"]
|
||
|
||
def _build_reasoning_chain(self, pattern: str, subgraph: KnowledgeSubgraph) -> Optional[str]:
|
||
"""构建推理链"""
|
||
return f"基于{pattern}的推理链"
|
||
|
||
def _validate_reasoning_chains(self, chains: List[str], query: str) -> List[str]:
|
||
"""验证推理链"""
|
||
return chains[:3]
|
||
|
||
def _find_entity_relations(self, graph_query: GraphQuery, session) -> List[GraphPath]:
|
||
"""查找实体间关系"""
|
||
return []
|
||
|
||
def _find_shortest_paths(self, graph_query: GraphQuery, session) -> List[GraphPath]:
|
||
"""查找最短路径"""
|
||
return []
|
||
|
||
def _fallback_subgraph_extraction(self, graph_query: GraphQuery) -> KnowledgeSubgraph:
|
||
"""降级子图提取"""
|
||
return KnowledgeSubgraph(
|
||
central_nodes=[],
|
||
connected_nodes=[],
|
||
relationships=[],
|
||
graph_metrics={},
|
||
reasoning_chains=[]
|
||
)
|
||
|
||
def close(self):
|
||
"""关闭资源连接"""
|
||
if hasattr(self, 'driver') and self.driver:
|
||
self.driver.close()
|
||
logger.info("图RAG检索系统已关闭") |