图 RAG 实战
任何需要关联事实的问题,向量 RAG 都会力不从心。
AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo
问标准向量 RAG 系统一个直接的事实性问题,比如*"我们的差旅政策是什么?",它能完美回答。但如果你问一个多跳问题,比如"我们主要供应商在 X 地区的合规问题,如何影响我们一级产品的交付时间线及相关供应商合同?"*,它就会彻底崩溃。
为什么?因为向量搜索找到的是相似的文本片段,它不理解关系。
如果你构建过检索增强生成(RAG)管线,你一定熟悉这个经典模式:
- 将文档切成多个块
- 在向量存储中嵌入它们
- 检索最接近的片段
- 让模型作答
对于简单问题,这套流程运转得很好。但任何需要关联事实的问题,向量 RAG 都会力不从心。
向量 RAG 擅长找到正确的文本。图 RAG 更擅长找到正确的事实链。
1、真正的问题:断裂的碎片
想象一下问这样一个问题:"谁在影响 Acme 公司的 CEO?"
普通的向量 RAG 系统可能会检索出:
- 一个关于 John Doe 是 CEO 的块
- 一个关于 Acme 公司的块
- 一个关于 Jane Smith 与该公司有关联的块
这些都是有用的碎片,但它们彼此孤立。系统仍然需要把这些点串起来。一个可靠的答案需要一条清晰的事实链:
- John Doe 是 Acme 公司的 CEO。
- Jane Smith 是 Acme 公司的董事会成员。
- 因此,Jane Smith 通过公司董事会网络与 CEO 产生关联。
向量搜索看到的是零散的片段。图 RAG 看到的是底层的关系。
2、图 RAG 有何不同
图 RAG 将非结构化文本转化为结构化网络。它不是只处理文档块,而是映射实体及其关系。
Feature | Vector RAG | Graph RAG
-------------------|---------------------|-------------------------
Storage unit | Text chunks | Entities + relationships
Retrieval method | Semantic similarity | Graph traversal
Best for | Direct lookup | Multi-hop reasoning
Context scope | Local fragment | Connected network
简单来说:
- 向量 RAG 回答的是**"哪些文本相似?"**
- 图 RAG 回答的是**"哪条事实路径可以解释这个问题?"**
3、抽取瓶颈
在深入遍历之前,有必要先正视房间里的大象:构建图非常困难。
遍历和排序算法是确定性的、快速的,但图 RAG 真正的挑战在于,把杂乱无章的非结构化企业文档转化为干净的实体-关系三元组(source, relation, target)。在生产系统中,这第一步依赖于基于 LLM 的抽取管线:解析文本块、识别核心实体、梳理关系动词。
一旦三元组建立起来,图的结构性威力就开始发挥作用。
4、图 RAG 管线如何工作
flowchart LR
Q[User query] --> E[Entity extraction]
E --> G[Graph construction]
G --> T[Traversal + path ranking]
T --> C[Path context]
C --> L[LLM answer generation]
L --> R[Final response]
在查看实现代码之前,我们先拆解图 RAG 查询的概念生命周期:
- 抽取: 原始文本被解析为结构化的实体-关系三元组(
source、relation、target)。 - 构建图: 这些三元组被加载到网络结构中,节点表示实体,边表示关系。
- 遍历: 当查询到来时,系统识别种子实体,并在图连接上按定义的深度行走,找出所有可能的关系路径。
- 评分与排序: 引擎使用 token 重叠度、实体相关性和连接长度来评估候选路径,过滤噪声,隔离出最相关的事实链。
- 上下文化: 胜出的路径被转化为干净、可读的散文,形成高度受约束的提示词上下文。
- 生成: LLM 消化结构化的图事实,生成精确、可验证的答案。
5、实现蓝图(POC)
要看到这个概念流程的实际效果,下面是用 Python 和 NetworkX 构建功能核心的方法:
5.1 抽取结构化事实
核心知识结构从示例事实开始,构建一个节点表示实体、边表示关系的图。
import networkx as nx
SAMPLE_TRIPLES = [
("John Doe", "is CEO of", "Acme Corp"),
("Jane Smith", "sits on board of", "Acme Corp"),
("Jane Smith", "mentors", "John Doe"),
]
def build_graph(triples):
graph = nx.DiGraph()
for source, relation, target in triples:
graph.add_node(source)
graph.add_node(target)
graph.add_edge(source, target, relation=relation)
return graph
5.2 寻找候选路径
构建图之后,系统从查询中识别相关实体,并使用无向遍历行走图结构。
def traverse_graph(graph, seeds, depth=2):
paths = []
seen = set()
undirected = graph.to_undirected()
for seed in seeds:
for target in graph.nodes:
if seed == target:
continue
for path in nx.all_simple_paths(undirected, source=seed, target=target, cutoff=depth):
canonical = tuple(path) if tuple(path) <= tuple(reversed(path)) else tuple(reversed(path))
if canonical in seen:
continue
seen.add(canonical)
paths.append(path)
return paths
5.3 为路径评分
并非每条路径权重相同。候选路径使用查询 token 重叠度、实体相关性和关系匹配分数进行排序。
def score_path(graph, path, query):
query_tokens = set(tokenize(query))
path_nodes = [node.lower() for node in path]
path_rels = []
for i in range(len(path) - 1):
rel, _ = get_edge_relation(graph, path[i], path[i + 1])
path_rels.append(rel.lower())
path_text = " ".join(path_nodes + path_rels)
overlap_score = len(query_tokens.intersection(set(tokenize(path_text)))) * 10
node_score = sum(1 for node in path_nodes if any(token in node for token in query_tokens)) * 5
rel_score = sum(1 for rel in path_rels if any(token in rel for token in query_tokens)) * 8
length_penalty = max(0, len(path) - 2) * 2
connection_bonus = sum(len(node.split()) for node in path_nodes)
return overlap_score + node_score + rel_score + connection_bonus - length_penalty
5.4 把最佳路径转化为上下文
一旦选出最优路径,它就会被转化为干净、无歧义的文本上下文供模型使用。
def path_to_text(graph, path):
lines = []
for i in range(len(path) - 1):
source = path[i]
target = path[i + 1]
relation, reversed_edge = get_edge_relation(graph, source, target)
if reversed_edge:
lines.append(f"{target} {relation} {source}.")
else:
lines.append(f"{source} {relation} {target}.")
return " ".join(lines)
5.5 用选定的路径询问 LLM
结构化的路径上下文被直接喂入 LLM 提示词,以约束最终回答。
def generate_llm_answer(query, context):
prompt = (
"You are a helpful assistant. Use only the graph facts below to answer the query clearly. "
"Do not introduce any new information. "
f"If the answer is not directly supported by these facts, say you don't know.\n\n"
f"Question: {query}\n\n"
"Graph facts:\n"
f"{context}\n\n"
"Answer with a short explanation of the supporting facts:"
)
response = client.responses.create(
model=config["deployment_name"],
input=prompt,
max_output_tokens=250,
temperature=0.1,
)
return response.output_text.strip()
5.6 将其暴露为演示 API
一个极简的 FastAPI 后端把查询端点直接接到推理工作流上。
@app.post("/query")
def query_graph_rag(request: QueryRequest):
query = request.query.strip()
graph = build_graph(SAMPLE_TRIPLES)
answer, path, context, ranked_paths = answer_query(query, graph)
return {
"query": query,
"answer": answer,
"reasoning_path": context,
"path_nodes": path,
"ranked_paths": ranked_paths,
}
6、从原型到生产:规模化扩展
虽然我们概念验证(POC)中使用 NetworkX 和硬编码三元组的内存 Python 脚本非常适合快速原型和机制理解,但将图 RAG 扩展到真实的企业级工作负载,需要一次稳健的架构重构。从玩具图走向处理数百万文档的系统,需要解决关键的基础设施、自动化和运维挑战。
以下是架构设计生产就绪的图 RAG 系统时的核心考量:
6.1 自动化抽取管线(真正的瓶颈)
图 RAG 最困难的部分不是遍历,而是把海量非结构化数据转化为干净、结构化的实体-关系三元组。
- 超越手工三元组: 生产系统需要自动化管线。该管线通常从文档分块开始,把原始文档拆分成可管理的片段。接着是实体与关系抽取阶段,使用 GPT-4o 或 Claude 3.5 Sonnet 这样的强大 LLM 处理这些块,识别核心实体——包括人物、组织和地点——以及连接它们的关系动词。由于原始抽取往往会产生噪声,去重与规范化步骤至关重要,确保代表同一真实事物的不同文本引用(如"Acme Corp"、"Acme Corporation"和"Acme Inc")被解析为单一唯一节点;这种实体解析策略通常需要基于嵌入的聚类或二次 LLM 遍历。最后,对于大规模企业图,扁平存储不足以支撑高效查询,需要分层索引。现代框架采用各种技术在不同层级汇总图社区,例如 Microsoft 的 GraphRAG 方法,以支持快速回答高层级的全局问题。
- 编排: 在大规模下管理这种复杂、通常异步的管线,需要健壮的编排工具。Microsoft GraphRAG SDK、LangChain 或 LlamaIndex 等框架为这些步骤提供了现成组件,或者你可能需要用 Apache Airflow 或 Prefect 构建自定义工作流。
6.2 持久化企业级图存储
本地内存图在重启后就会消失。要处理大规模数据,必须迁移到专用的图数据库。
- 技术选型: 根据你的规模和查询模式评估企业级图数据库。
- 托管解决方案: Azure Cosmos DB for Gremlin / NoSQL 非常适合 Azure 生态内的企业集成,提供完全托管、可扩展的图存储。
- 开源/商业: Neo4j 凭借丰富的查询语言(Cypher),仍然是深度图分析的行业标准。其他选择包括 Amazon Neptune 和 TigerGraph。
- 模式演进: 你最初的抽取模式可能不够用。生产系统需要无需完全重建即可演进图模式的策略。
- 混合存储: 图 RAG 常常与向量 RAG 结合使用。你的架构应该支持图数据库(用于遍历关系)与向量数据库(用于文本块的语义相似度)之间的无缝集成。
6.3 优化检索与延迟管理
NetworkX 遍历的复杂度是 O(N+E),对于需要在亚秒级响应、面对数百万节点的生产 API 来说太慢了。
- 数据库索引: 确保图数据库在节点标签、属性和关系类型上有合适的索引,以加速种子实体查找和邻接节点展开。
- 遍历限制: 生产级遍历引擎必须对遍历的深度(跳数)和每个节点的宽度(展开范围)设置硬限制,防止失控查询导致 API 超时。
- 缓存: 实施激进的缓存策略。缓存常见查询结果、频繁访问的子图,甚至缓存重复问题的 LLM 响应。
6.4 运维化与安全
在生产环境运行 AI 需要严格的运维和安全控制。
- 访问控制(RBAC): 在企业环境中,用户不应有权访问整个图。你需要一个治理层在图数据库上实施行级安全(RLS),确保用户只能遍历他们有权查看的文档所衍生出的关系。
- PII 脱敏: 在把原始文本块发送到公有云 LLM 进行三元组抽取之前,确保有健壮的个人身份信息(PII)脱敏机制,防止数据泄露。
- 可观测性: 你需要对 RAG 管线有完整可见性。这包括记录 LLM 提示词和响应、追踪某个答案所走的精确遍历路径(用于审计),以及监控数据库性能指标。
7、架构总结
下图展示了一个现代、可扩展的图 RAG 架构:自动化抽取、使用混合存储,并以安全的 API 服务形式运行。

7.1 POC 系统设计一览
对于 POC,我们把事情保持得非常简单。

7.2 结果
在完全接线的浏览器 UI 中,概念验证展示:
- 用户输入的查询
- 用于生成回答的选定推理路径
- 遍历引擎考虑过的候选路径排名
- 基于清晰关系事实的最终模型回答

8、何时使用向量 RAG vs 图 RAG
以下情况使用向量 RAG:
- 问题是直接的事实性问题
- 答案完全包含在单个段落内
- 查询速度和实现简单性最重要
以下情况使用图 RAG:
- 答案依赖于跨独立文档的多跳推理
- 实体间的关系比表面文本相似度更重要
- 可审计性、可解释性和显式推理路径是硬性要求
图 RAG 在法律合规、财务审计、医疗诊断和复杂供应链追踪等领域表现出色,这些领域里结构上下文与原始文本同样关键。
9、为什么这很重要
经典 RAG 管线虽然强大,但本质上是文档中心的。
图 RAG 引入了结构化推理能力,让系统能够明确陈述:
- "这些分散的事实是相互关联的。"
- "这是最相关的关系路径。"
- "这条具体的边序列支撑了这个结论。"
这就是单纯检索相似文本与真正检索可解释答案之间的决定性区别。
10、结束语
这个 Graph RAG 演示的完整可运行 POC 在这个仓库中,如果你想在本地运行它或把它作为起点。
在本地运行它需要几样东西:
- Python 3.10+ 以及
requirements.txt中的包。 - 一个兼容 OpenAI 的模型或 Azure OpenAI 部署。设置你的
OPENAI_API_KEY,或者设置 Azure 对应的端点、部署名和密钥。 - 本项目启动命令:
uvicorn web_app:app --reload
原文链接:Graph RAG in Action: Why Standard RAG Fails at Complex Queries (And How Graph RAG Fixes It)
汇智网翻译整理,转载请标明出处