主题
11.7 宏观问题答不了怎么办?—— 知识图谱与 GraphRAG
痛点场景:用户问“把过去三年公司技术架构的演进脉络总结一下”,或者“这本书的核心价值观是什么”。普通向量 RAG 往往只返回若干局部相似的片段,容易遗漏跨文档关系。原因在于向量检索主要比较局部相似度,而这类问题需要组织更大范围内的概念与关系。
痛点:向量 RAG 的“一叶障目”
- 点状检索:向量检索每次只能捞出“碎片”,就像抽屉里翻便签——你抽到“张三主导支付重构”,抽不到它和“弃用 Oracle”“引入 TiDB”之间的关系;
- 关系丢失:知识以“张三 →(主导)→ 支付系统 →(迁移至)→ TiDB”这样的关系链存在,但切块把这条链切断了;
- 全局问题:“总结全书主旨”“梳理架构演进”没有任何一个单独切块能回答。
💡 比喻:向量 RAG 是散落一桌的便签纸;GraphRAG 是刑警队墙上钉着红线、把嫌疑人与资金流向串起来的“案件线索图”——哪怕两个实体从未出现在同一份文档里,顺着关系也能推理出全貌。
思路:微软 GraphRAG 的四步流水线
微软开源了 GraphRAG(论文 arXiv:2404.16130),核心流程:
- 抽取:用 LLM 从每个切块里抽出实体(人/系统/概念)、关系(“负责”“依赖”“弃用”)、协变量(事实主张);
- 建图:实体对齐去重后,形成“节点 + 边”的知识图谱;
- Leiden 社区发现:把关系紧密的实体自动聚成“朋友圈”(社区),例如【交易与资金社区】【身份认证社区】;
- 社区摘要:让 LLM 给每个社区写一份浓缩“研报”,全局问题就直接在这些研报上做 Map-Reduce 汇总。
代码实现一:LLM 抽取实体与关系(LangChain LLMGraphTransformer)
python
from langchain_core.documents import Document
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
transformer = LLMGraphTransformer(
llm=llm,
allowed_nodes=["人物", "系统", "技术组件", "组织"], # 白名单:控制抽取范围
allowed_relationships=["负责", "依赖", "重构成", "部署于"], # 关系白名单
)
doc = Document(page_content="""
张三领导基础架构部,2025 年主导将老旧的 MySQL 订单库重构为 TiDB 分布式集群。
该 TiDB 集群部署于阿里云上海可用区,被结算系统和风控系统直接依赖。
李四负责监控该集群的实时告警。
""")
graph_docs = transformer.convert_to_graph_documents([doc])
print("=== 抽取到的节点 ===")
for node in graph_docs[0].nodes:
print(f"[{node.type}] {node.id}")
print("\n=== 抽取到的关系 ===")
for rel in graph_docs[0].relationships:
print(f"{rel.source.id} --({rel.type})--> {rel.target.id}")💡 白名单的意义:不限制的话 LLM 可能抽出几百种五花八门的关系类型,导致图不可控。先白名单约束,是工程化的关键一步。
代码实现二:轻量社区发现 + 全局问答(自己体会“全局视野”)
微软 GraphRAG 内部用 Leiden 算法。这里我们用同样思路做一次最小演示:把关系图聚成社区 → 给社区写摘要 → 全局问题基于摘要汇总。
python
import networkx as nx
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 1. 构造一个小图谱(节点=系统,边=依赖关系)
G = nx.Graph()
edges = [
("订单服务", "支付网关"), ("订单服务", "库存服务"), ("支付网关", "结算系统"),
("结算系统", "风控系统"), ("用户中心", "OAuth2"), ("用户中心", "JWT认证"),
]
G.add_edges_from(edges)
# 2. 用标签传播做社区发现(轻量版“朋友圈”聚类;生产用 Leiden)
communities = list(nx.community.label_propagation_communities(G))
print("=== 发现的社区 ===")
for idx, comm in enumerate(communities):
print(f"社区{idx}: {sorted(comm)}")
# 3. 每个社区生成“研报摘要”
summarize = ChatPromptTemplate.from_template(
"下面是一组相互依赖的系统,请用一句话总结这一组系统的共同业务定位:\n{members}\n"
)
reports = []
for comm in communities:
summary = (summarize | llm).invoke({"members": ", ".join(sorted(comm))}).content
reports.append(f"[社区] {sorted(comm)}\n{summary}")
print(f"\n--- 社区研报 ---\n{summary}")
# 4. 全局问题:直接基于所有社区研报汇总(这就是 Global Search 的核心)
global_question = "这个系统的整体架构分成了哪几个部分,各自职责是什么?"
final_qa = ChatPromptTemplate.from_template(
"请基于以下各子系统的社区研报,回答用户的全局性问题:\n{reports}\n\n问题:{question}\n回答:"
)
answer = (final_qa | llm).invoke({"reports": "\n\n".join(reports), "question": global_question}).content
print(f"\n=== 全局回答 ===\n{answer}")代码实现三:入库 Neo4j 并用 Cypher 查询
python
# 前置:docker run -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:5
from langchain_community.graphs import Neo4jGraph
graph = Neo4jGraph(url="bolt://localhost:7687", username="neo4j", password="password")
# graph.add_graph_documents(graph_docs) # 把上面抽取的图文档写入
# Cypher 查询示例:找“谁依赖了 TiDB 集群”
cypher = """
MATCH (target {id: 'TiDB 分布式集群'})<-[:依赖]-(upstream)
RETURN upstream.id AS upstream_component
"""
# result = graph.query(cypher)
# print(result)
print("(在 Neo4j Desktop / Browser 中执行上面的 Cypher 即可可视化图谱)")💡 工程选型提示:如果只是几十万条以内的“中等图谱”,完全可以用 networkx + 社区摘要自己搭轻量版;只有图谱达到百万级节点、需要复杂图查询时,才值得引入 Neo4j 这类专业图数据库。
进阶:两条“便宜版 GraphRAG”
痛点承接:微软 GraphRAG 的“Leiden 社区发现 + 社区摘要”效果确实好,但它在索引阶段要反复调用 LLM 给海量实体关系做加工——先抽实体、再抽关系,最后还要给每个社区写一份“研报”。语料一大、更新一勤,这笔“建图税”中小项目根本付不起。于是一批“便宜版 GraphRAG”出现了:不追求给整张图写完整研报,而是用更省的办法留住一点全局视野。
LightRAG:双层关键词索引,把“写研报”这一步省掉
LightRAG 的改动不大却很有效:建图时除了抽取实体,还额外抽取主题关键词,形成“实体关键词 + 主题关键词”的双层关键词索引,检索时两条线一起走。
| 检索模式 | 怎么走 | 适合的问题 |
|---|---|---|
| local | 从命中的具体实体向外扩散 | “某实体和谁有关系” |
| global | 从主题关键词往下捞 | “整体有哪些主题” |
| hybrid | 两层结果混合 | 大多数场景的默认选项 |
一句话:它保留了图的骨架,砍掉了“逐个社区写研报”这道最贵的工序,所以建图成本显著低于微软 GraphRAG,适合“预算有限但要一点全局视野”的项目。仓库:HKUDS/LightRAG。
🧩 配套脚本:
code/s07_graphrag.py的dual_keyword_search()用「实体层 0.6 + 主题层 0.4」的双层打分演示了这一思路。
HippoRAG:检索时才在图上“扩散”,像海马体做记忆索引
HippoRAG 借鉴了神经科学里海马体做记忆索引的理论:知识不靠提前写摘要,而是在检索的那一刻用**个性化 PageRank(PPR)**在图上做多跳扩散——从一个入口实体出发,顺着关系一圈圈把相关节点“激活”。因此它特别擅长“跨文档多跳问答”(A 文档说了甲、B 文档说了乙,答案要把甲和乙连起来才成立),而且因为没有社区摘要这一步,成本远低于微软 GraphRAG。仓库:OSU-NLP-Group/HippoRAG。
🧩 配套脚本:
code/s07_graphrag.py的personalized_pagerank_hits()用个性化 PageRank 演示了这种从入口实体出发的多跳扩散。
四条路线怎么选
| 方案 | 索引成本 | 多跳能力 | 全局总结能力 | 维护复杂度 | 适合规模 |
|---|---|---|---|---|---|
| 微软 GraphRAG | 最高(逐社区写研报) | 强 | 最强 | 高(实体对齐 + 社区重算) | 语料稳定、预算充足的大中项目 |
| LightRAG | 低(只抽实体 + 关键词) | 中 | 中(主题层兜底) | 中 | 预算有限、语料常更新的中小项目 |
| HippoRAG | 低(建图后在线扩散) | 最强(PPR 多跳) | 弱 | 中 | 以跨文档多跳问答为主 |
| 纯向量 RAG | 最低(只做嵌入) | 无 | 无 | 最低 | 单点事实问答、小知识库 |
💡 选型结论:先问问题类型,再谈技术选型。要“全局主题总结”且预算充足 → 微软 GraphRAG;要“便宜一点但仍保留全局感” → LightRAG;要“跨文档多跳推理” → HippoRAG;绝大多数单点事实问答 → 纯向量或混合检索就够了。
再收一次边界:不是所有问题都走图
后文 ## GraphRAG 不是一个固定算法 那一节已经强调过“不是所有问题都走图”,这里再收一遍:图的价值在“关系与多跳”。如果问题只是“报销标准是多少”“某条款怎么规定”,在图上跑一圈只是平白增加建图费用和查询延迟——纯事实问答上图是纯亏,这类问题继续走关键词 + 向量混合检索即可。
GraphRAG 不是一个固定算法,也不是所有问题都走图
微软 GraphRAG 当前提供多种查询方式,像医院分诊不同科室:
| 方法 | 主要上下文 | 适合的问题 | 不适合的问题 |
|---|---|---|---|
| Basic | 普通 Top-K 文本单元 | 单点事实、直接条款 | 全局主题归纳 |
| Local | 目标实体、邻居、关系与关联文本 | “谁依赖谁”“某实体有什么关系” | 无明确实体的全局题 |
| Global | 分层社区报告 Map-Reduce | “主要主题是什么”“整体如何演进” | 简单事实题,成本不划算 |
| DRIFT | 社区起步,再沿局部证据迭代 | 既要广度又要追到细节的探索题 | 低延迟、低预算问答 |
生产系统应先做问题路由,普通事实题继续走混合检索。若所有问题都走 GraphRAG,建图与查询成本会吞掉收益。官方也提供 Standard 与更便宜但图更嘈杂的 FastGraphRAG 索引方法,应按实体保真需求选择。GraphRAG 方法说明
建图最难的不是画边,而是实体对齐
同一个系统可能写作“用户中心”“User Center”“IAM”;同名“支付服务”也可能属于不同地区。若不消歧,会出现两个相反问题:
- 没合并:一个实体裂成多个节点,关系链断掉;
- 错合并:两个不同实体被揉成一个,图上产生不存在的关系。
工程上要保存实体的规范名、别名、类型、来源 Chunk、抽取置信度与时间范围。低置信边不能直接进入关键决策;抽样人工审核发现的错误要回流到 Prompt、白名单与别名表。
图谱也有版本和保鲜期
“订单服务依赖 MySQL”在迁移 TiDB 后会过期。边应带 valid_from、valid_to 和来源,新索引完成后重新生成受影响的社区报告。不能只增不删,否则图会把历史关系当成当前事实。
更新成本通常包括:文本切块 → 实体关系抽取 → 实体对齐 → 社区发现 → 社区报告生成。GraphRAG 官方资料指出,标准流程的图抽取占索引成本的大头;因此应支持增量更新、受影响社区重算和索引版本回滚。
GraphRAG 要单独评估“图有没有帮忙”
除了最终答案,还应测:实体识别准确率、别名合并错误率、关系正确率、证据来源覆盖、Global/Local 路由准确率,以及相对普通 RAG 的质量增益、索引费用和查询延迟。最有说服力的实验不是“GraphRAG 得了 90 分”,而是同一评测集上:Basic、混合检索、Global、Local、DRIFT 各自在哪类问题胜出。
配套脚本新增了可解释的 Basic/Local/Global/DRIFT 路由基线和实体规范化函数;它们用于讲清决策边界,不冒充完整 GraphRAG 实现。
图中的关系也需要证据和版本
每条实体和关系应保留来源文档、原文位置、抽取时间与版本。两个同名实体是否为同一对象,不能只凭名称合并;可以结合组织、时间和人工规则处理。
GraphRAG 适合跨文档关系、整体主题和多跳问题。对直接查条款编号的问题,关键词或向量检索通常更简单。评估时应与不使用图谱的基线比较,确认图确实增加了可回答信息,而不是只增加了构建成本。