GraphRAG:用知识图谱改进大模型检索
大模型很强,但有两个老毛病:幻觉(说得头头是道但全是编的)和知识陈旧(训练完之后发生的事它不知道)。RAG(Retrieval-Augmented Generation,检索增强生成)是当下最主流的补救方案:让模型先查资料再回答。但 RAG 这件事本身还有很深的分化——怎么查差别巨大。本文借 Neo4j 那篇 What is GraphRAG? 的框架,把四种检索范式摆在一起对比,讲清 GraphRAG 解决什么问题,最后用我自己 270 篇博客转出来的知识图谱作为活样本。
先搞懂几个基础概念
什么是 RAG
RAG(Retrieval-Augmented Generation)= 先检索(从知识库里找到相关内容),再生成(让 LLM 基于检索到的内容回答)。它解决的不是"模型不够聪明",而是"模型不知道"。
什么是知识图谱
知识图谱(Knowledge Graph)= 实体(节点)+ 关系(边)。比如:
乔布斯 --创立--> 苹果苹果 --CEO--> 蒂姆·库克乔布斯 --继任者--> 蒂姆·库克
每个节点和边都可以附带属性(时间、出处、置信度等)。它的好处是关系是显式存储的,能直接遍历;向量数据库里的关系是隐式学出来的相似度。
什么是社区检测
社区检测(Community Detection)= 在图上自动找出"内部连接紧密、彼此连接稀疏"的节点群。它不预设分类,主题是从结构里自己涌现出来的:一堆互相引用得密的实体,大概就属于同一个话题。
图里最常用的算法是 Leiden(2019 年,Louvain 的改进版)。它相对 Louvain 的关键优势是保证每个社区内部连通——Louvain 会为了优化模块度把两块互不相连的节点塞进同一个"社区",原论文实测约 25% 的社区存在连接不良、16% 内部完全不连通。Leiden 加了一个细化(refinement)阶段把这类社区拆开,所以它更适合拿去生成"这个社区在讲什么"的摘要。
什么是 GraphRAG
GraphRAG 就是把知识图谱作为检索源的 RAG 方案。检索时不再是"找最相似的文本块",而是"找相关的实体,沿着关系扩展"。
Neo4j 给的定义:
GraphRAG is a powerful retrieval mechanism that improves GenAI applications by taking advantage of the rich context in graph data structures.
四种检索范式对比
这是本文的核心。在实际工程里,“用什么方式查"远比"用什么模型"更影响 RAG 系统的上限。
| 范式 | 检索原理 | 擅长 | 不擅长 |
|---|---|---|---|
| 向量检索 | 把文本和 query 都转成 embedding,找余弦距离最近的文本块 | 语义匹配、同义改写、模糊问题 | 跨段落综合、多跳推理、答案溯源 |
| 标量检索(关键词) | BM25、TF-IDF、正则匹配 | 精确术语、ID、代码片段、英文 | 中文简称/变体、语义相似 |
| GraphRAG | 命中实体节点 → 沿关系遍历 → 拿到关联三元组 | 多跳问题、跨文档综合、可解释性、需要事实链条 | 长文本语义、新兴实体(没在图里)、文本细节 |
| LLM Wiki(基于 LLM 自身知识) | 不查资料,让模型直接回答 | 通用常识、不需要查证的事 | 新事实、私域知识、容易幻觉 |
每种范式具体做什么
向量检索是当下 RAG 的默认选择。把所有文档切片(chunk)转成 embedding 存进向量库(如 Milvus、Pinecone、pgvector),查询时把用户问题也转 embedding,召回 top-k 个最相似的 chunk。它的局限 Neo4j 文章说得很到位:检索结果"in isolation”(孤立片段),缺乏跨片段的上下文;embedding 是黑盒,无法解释"为什么相关"。
标量检索就是经典的关键词匹配。BM25 这类算法不计语义,只看词项在文档里的频率。它对"找出一篇包含 tcp 三次握手 的文章"非常准,但对"段总"的查询无能为力(需要"段永平")。
GraphRAG先把语料抽成实体-关系图,检索时先找到相关的实体,再沿边扩展拿到结构化的三元组,最后喂给 LLM 拼答案。Neo4j 文章特别强调它能解决 multi-hop questions(多跳问题):“follows non-chunk relationships up to 2 hops out”。比如"段永平和巴菲特的投资策略有什么共同点?"——需要先找到段永平和巴菲特两个节点,再各自展开 1-hop,再比较。
LLM Wiki严格说不算 RAG,但它是 baseline 选项:直接让模型基于训练知识回答。便宜、快,但私域内容(你自己的笔记、公司内部文档)和新事实(昨天发生的事)完全没辙。
GraphRAG 能解决什么
Neo4j 文章列了 8 个行业场景(法律合规、投研、生物科技、供应链、欺诈检测等),但本质上是 5 类问题:
- 多跳推理:答案需要串联多个文档的事实。向量检索召回的 top-k chunk 经常不够"远"。
- 跨文档综合:同一实体在多篇文章出现,需要合并观点。GraphRAG 的节点自动汇聚所有引用。
- 可解释性:能给出"答案来自哪几篇文章、哪几个实体"。向量检索只能给一个相似度分数。
- 关系密集型领域:法律(条款之间的引用)、投研(公司之间的关联)、生物(蛋白质相互作用)。这些场景里关系本身就是答案。
- 全局概览:“这批文档主要在讲什么?"——这句话里没有实体可以命中,向量检索和关系遍历都无从下手。它要求系统先看清整张图的结构,而这就是社区检测要解决的问题。
前 4 类靠"实体 + 遍历"就能覆盖,第 5 类需要换一套思路。这也是微软那版 GraphRAG 相对普通图检索最重要的增量。
GraphRAG 的关键设计
一个能跑的 GraphRAG 系统至少要做 5 件事:
1. 实体/关系抽取
通常用 LLM 从原文里抽三元组,prompt 模板大致是:
从下面文本里抽实体关系三元组,每行一个,格式:主语|关系|宾语
文本:
{text}这一步决定了图谱质量。LLM 抽出来的实体名常常带噪声(“X - Y”、“引号短语”),需要在归一化阶段清洗。
2. 实体归一化
同一实体在不同文章里会被叫成不同名字(“段永平” / “段总” / “老段”)。如果不做归一化,图谱里会出现 3 个本应合并的节点。常用方案:fuzzy 匹配(rapidfuzz、编辑距离)或 LLM 归一化。
3. 多跳检索(Local Search)
最简单的版本:关键词命中节点 → 1-hop 扩展 → top-k 三元组喂给 LLM。更复杂的可以加 embedding 排序、按度数做权重。
但这套逻辑有个前提:问题里得有实体。命中不到节点的查询它无能为力,这就轮到下一步。
4. 社区检测与社区摘要(Global Search)
前面三步解决的都是"局部"问题:找到某个实体,沿关系展开,回答"X 和 Y 有什么关系”。还有一类问题根本没有实体可以命中——“我这 270 篇博客主要在聊什么?"——要回答它,得先看清整张图的形状。
做法就是社区检测:用 Leiden 对图谱做层次化聚类。因为 Leiden 是递归跑的,得到的不是一层平面分类,而是一棵社区树:
Level 0 ── 1 个社区(整张图)
└─ Level 1 ── 几个大主题
└─ Level 2 ── 更细的子主题
└─ ...层级越浅,社区越少、主题越泛;层级越深,社区越多、越具体。
聚类本身还不能直接喂给 LLM——节点和边不是人话。所以每个社区要再生成一份 community report(社区摘要):把社区内的实体、关系(以及可选的子社区摘要)交给 LLM,写出一段"这团东西在讲什么、关键实体有哪些、它们之间是什么关系”,并给出重要性排序。子社区的摘要作为父社区的输入,自底向上汇总。
有了这批摘要,检索就从一条路变成两条路:
| 检索模式 | 做法 | 适合的问题 |
|---|---|---|
| Local Search | 从实体出发,取 1-2 hop 邻居 + 相关社区摘要 + 原文片段 | “段永平的投资策略是什么” |
| Global Search | 把选定层级的社区摘要分批各答一轮(map),再汇总成最终答案(reduce) | “这些博客都讨论了哪些主题” |
Global Search 本质是个 map-reduce:不遍历全图,让每个社区先"自报家门"产出一个局部答案,再把高分答案合并。代价是要读几十上百份摘要,比 Local Search 贵得多;但换来的是那些没有具体实体的问题也能答——而在此之前这类问题在检索层面是完全无解的。
5. 与 LLM 结合
拿到子图后怎么拼答案?直接把三元组列表丢给 LLM 也能回答(“用以下知识回答:X 是 Y,X 操作 Z…"),但 LLM 经常对负面指令遵循差,需要兜底逻辑(比如检测 LLM 说"没找到"时自动 fallback 到原始三元组列表)。
实践示例:我的博客知识图谱
光讲理论太空。我把我的 270 篇博客转成了一个知识图谱:
- 实体总数:4301 个节点
- 关系总数:4028 条边
- 覆盖:270 篇文章(每篇都贡献了实体和关系)
- 社区:暂无(这一版没做 Leiden 划分,所以只支持"实体相关"的查询,见后面的局限)
- 抽取方式:每篇文章正文调 LLM 抽三元组,结果缓存到本地(断点续跑,下次只重抽变化的文章)
下面这张图围绕**“乔布斯”**这个实体展开,红色节点是直接命中,蓝色是邻居节点。你可以拖拽、缩放、点击节点看度数,左上角搜索框能定位到任意实体。
围绕"乔布斯"的 2-hop 子图:155 节点 / 169 边。中心是乔布斯(红色),一圈是他相关的人物(蒂姆·库克、约翰·斯考利、皮克斯)和作品(《花花公子》采访、斯坦福演讲),二跳是相关主题(演讲者、推销者、远景、勇气、目的、专注)。
示例问答
下面是几个真实的提问与回答:
Q: 段永平投资策略是什么?
段永平的投资策略核心是"买股票就是买公司”,方法是基于公司价值进行判断,他曾举例判断腾讯值 400 港币。在具体操作上,他强调不要用杠杆、不要做空、不懂不投,并通过分批建仓、设定愿意买的价格、平仓止损等方式控制风险。同时他偏好现金担保看跌策略,认为这优于直接持有正股。长期年化收益率约 18%。
Q: 乔布斯
斯蒂夫·乔布斯是苹果电脑公司的创始人,也是个人电脑领域的梦想家,他引领了计算机硬件和软件产业的发展,被许多人视为激励大师。禅宗深刻地影响了他的人生。沃尔特·艾萨克森撰写的《史蒂夫·乔布斯传》详细记录了他的经历,他本人始终怀有远景并相信未来。在他离开苹果后,蒂姆·库克接替了他的位置。
Q: 我用什么胶片相机?
胶片单反属于单反相机,在取景结构上和电子单反一致,多为自动对焦,使用光学取景。胶片底片是物理实体,可以存放数十年甚至上百年。
可以看到 GraphRAG 的回答有个特点:每条事实都能追溯到原文(节点的 sources 字段记录了哪些文章提到了这个实体),不会出现"模型凭空捏造"的幻觉。这是向量检索做不到的。
局限与展望
这套实现还有几个明显短板,正好对应四种检索范式的互补性:
- LLM 抽取成本:270 篇博客大约消耗 ¥2-3 的 API 费用。如果文章量大(万篇级),要么用更便宜的模型,要么按 mtime 增量更新(我现在做的)。
- LLM 幻觉:回答时 LLM 经常忽略图谱,硬说"图谱里没找到"。我加了 fallback 兜底,但偶尔还是会失败。
- retriever 召回噪声:关键词 fuzzy 在中文场景下偶尔把无关节点拉进子图(比如"咖啡"匹配到"相机")。下一步会加 embedding 检索做 dense retrieval + rerank。
- 没有社区检测(Leiden):这是最缺的一块。我的实现只做了 1-hop 扩展,等于只有 Local Search 的简化版,所以"我这博客讨论了哪些主题"这类全局问题问不了。补上 Leiden 分区 + 社区摘要,才能回答全局概览类的问题。
下一步可能的方向:
- Leiden 社区检测:给图谱补上社区划分 + 社区摘要,把"全局概览"这类问题接住
- MCP Server:把这套检索能力暴露给 Cursor 等 AI 工具,让 AI 写新文章时能直接查询"我以前关于 X 写过什么"
- 向量检索混合:retriever 里加 embedding 通路,做双路召回
- 云端部署:把图谱文件部署到腾讯云函数,让博客前端可以直接调用
完整代码已开源在 GitHub:tanteng/blog-graphrag,包含 6 个核心模块 + 4 个 CLI(build / update / ask / visualize),总共不到 500 行 Python。