想象你是一座巨型城市的市长。城市里有成千上万份文件——政策法规、居民反馈、历史档案、研究报告,散落在各个角落。过去你请了一位助理(传统 RAG),你问“公园改造项目进展如何?”,助理只能去翻找文件碎片,找到几段提到“公园”的文字给你,却不知道这些公园分属哪个城区、涉及哪些部门、与哪些历史决策相关。你得到的答案是零散的,没有全局脉络。 现在,你请来了一位新的城市规划师(GraphRAG)。他做了一件了不起的事:先花时间走遍全城,把每一份文件里的关键信息抽出来——“谁是负责人”“哪个部门负责”“这个项目与哪个政策相关”——画成一张巨大的城市知识地图。地图上,每个公园是一个节点,与它相连的还有责任部门、预算来源、居民委员会。当你提问时,规划师不再只翻碎片,而是看一眼地图,告诉你“这三个城区的公园改造项目彼此关联,合并推进可以节省 20% 预算”。这就是 GraphRAG 的本质:给 AI 配一张“城市知识地图”,让它既能精确检索,又能把握全局。

岗位关联

理解 GraphRAG 是 AI 工程师走向高级阶段的必修课。它解决了纯向量检索“只见树木不见森林”的痛点,让你能构建真正理解全局关系的问答系统——这正是政策分析、医学研究、商业决策等高端场景的核心需求。

目录
  1. 可视化演示
  2. 混合检索架构
  3. 实体关系抽取
  4. 图存储结构
  5. 社区检测算法
  6. 多层摘要生成
  7. 全局问答增强
  8. 建库流程管线
  9. 应用场景价值
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示GraphRAG核心流程:从文档抽取实体关系构建知识图谱、Leiden社区检测生成层次化摘要、向量与图路径混合检索实现全局问答,通过步进式交互理解混合增强机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

图谱构建:实体关系抽取 LLM抽取:大模型从文档自动提取实体及关系三元组 三元组(h,r,t):头实体-关系类型-尾实体 G = {(h, r, t) | doc→LLM→triples} 非结构化文本转化为结构化知识图谱

混合检索架构

城市里有两类信息需求。一类是精确查找——“2023 年公园改造预算是多少?”这类问题需要快速定位到具体段落,就像你在图书馆里用搜索引擎找一本书。另一类是全局理解——“公园改造对各城区的影响有何异同?”这类问题需要你读懂整个城市文件之间的关联,就像市长需要翻阅跨部门的报告才能给出判断。

传统 RAG 系统只装备了第一种能力:向量检索。它把文档切成小块,用向量(一串数字)表示每块的语义。你问一个问题,它就转换成向量,然后找出语义最接近的几个文本块。这套机制在“精确找答案”时表现出色,但当你需要跨越多个文档、理清因果链条时,它就力不从心了——因为文档块之间是彼此孤立的,像散落一地的拼图,没有拼图之间的连接线。

GraphRAG 的混合检索架构则聪明得多。它在向量检索的基础上,增加了一条图通道:文档不再是孤立的碎片,而是通过实体关系连接成一张知识网络。当你提问时,两条通道同时工作——向量通道负责找到语义相关的候选片段,图通道则沿着网络中的关系路径,捕捉与问题相关的上下文结构。两路结果汇合后,系统综合排序,给出既精准又有全局视角的回答。

回到城市的比喻:向量通道像你的专属图书管理员,快速翻书找关键词;图通道像城市的规划师,看着地图告诉你这些信息之间的关联。两者配合,才能既找到那本书,又读懂整座城的布局。

score(q,d)=λ⋅simvec(q,d)+(1−λ)⋅simgraph(q,d)\text{score}(q, d) = \lambda \cdot \text{sim}_{\text{vec}}(q, d) + (1-\lambda) \cdot \text{sim}_{\text{graph}}(q, d)
这条公式是混合检索的“总分计算器”:q 是问题,d 是候选文档。sim_vec 衡量语义相似度,sim_graph 衡量图结构上的关联强度,λ 是两者的权重配比。
  1. 语义相似度计算问题与文档的向量余弦距离,衡量“语义上有多像”。
    simvec(q,d)=q⋅d∥q∥∥d∥\text{sim}_{\text{vec}}(q, d) = \frac{q \cdot d}{\|q\| \|d\|}
  2. 图相似度统计问题中实体到文档实体的图路径长度与连通度,衡量“关系上有多近”。
    simgraph(q,d)=∑eq∈q∑ed∈d1dist(eq,ed)\text{sim}_{\text{graph}}(q, d) = \sum_{e_q \in q} \sum_{e_d \in d} \frac{1}{\text{dist}(e_q, e_d)}
  3. 加权融合用 λ 平衡两种信号,λ 大偏重语义,λ 小偏重结构。
    score=λ⋅simvec+(1−λ)⋅simgraph\text{score} = \lambda \cdot \text{sim}_{\text{vec}} + (1-\lambda) \cdot \text{sim}_{\text{graph}}

实体关系抽取

城市规划师开始绘制城市地图之前,必须先做的事是“认人认事”。他阅读每一份文件,圈出关键实体——人名、机构名、地名、项目名——然后判断它们之间的关系。比如他从一份文件中读到“住建局拨款 500 万用于朝阳公园改造”,他就画一个节点“住建局”,再画一个节点“朝阳公园”,然后用一条边“拨款”连接两者。

这一步在 GraphRAG 中叫“实体关系抽取”。通常由一个 LLM 来完成:把文本块输入给模型,让它输出结构化的三元组——(头实体,关系,尾实体)。比如上文那个例子就是:(住建局,拨款给,朝阳公园)。三个要素缺一不可:实体是图上的节点,关系是边,三元组是构成整个知识图谱的最小单元。

抽取完成后,还有一个关键环节叫“指代消解”。同一实体在不同文档中可能有不同写法,比如“住建局”“市住建局”“住房和城乡建设局”其实指向同一个机构。如果不去重,地图上就会有两个节点代表同一个机构,导致后续分析出现偏差。LLM 需要识别这些别名并把它们合并成同一个节点。

这些三元组就像地图上的一个个“街区标记”。当城市里所有的文件都被这样标注后,一张庞大的、彼此关联的城市知识地图就浮现出来了。规划师手里拿的不再是散乱的文件堆,而是一张能看到“哪个机构负责哪个项目、项目之间有什么联系”的活地图。

(头实体,关系,尾实体)∈G(\text{头实体}, \text{关系}, \text{尾实体}) \in \mathcal{G}
三元组是知识图谱的基本造句单位:像“(小明,喜欢,读书)”一样,主语-谓语-宾语的结构,“主语”和“宾语”是节点,“谓语”是它们之间的边。
  1. 实体识别从文本中找出具有特定含义的词,比如人名、地名、组织名。
    Entity(wi)∈{人名,地名,组织名,⋯ }\text{Entity}(w_i) \in \{\text{人名}, \text{地名}, \text{组织名}, \cdots\}
  2. 关系分类判断两个实体之间的语义关系类型,如“负责”“位于”“合作”。
    Relation(ei,ej)→rk\text{Relation}(e_i, e_j) \rightarrow r_k
  3. 三元组成立当实体存在且关系被确认,一条知识边就建立了。
    (ei,rk,ej)∈G(e_i, r_k, e_j) \in \mathcal{G}

图存储结构

地图画好了,得找个大柜子把地图收好,方便随时查阅。图存储结构就是这张地图的“档案柜”。它专门设计来高效存储一组组三元组数据,让你能随时从任意一个节点出发,沿着边快速找到邻居节点。

常见的图数据库(如 Neo4j)把实体存成“节点”,每个节点可以携带属性——比如“朝阳公园”节点上有“面积:120 公顷”“建成时间:2005 年”。关系则存成“边”,边上同样可以标注属性,比如“拨款”边上有“金额:500 万”“年份:2023”。这种结构让图检索非常高效:从“住建局”出发,一步就能找到所有它拨款过的公园。

与传统关系型数据库(如 MySQL)相比,图数据库在“多跳查询”上优势巨大。传统数据库需要多次 JOIN 操作,查询“住建局拨款过的公园所在城区的负责人”这样的三级关系时,SQL 会变得冗长且性能低下。图数据库则天然支持这种“沿边走”的查询方式,一步一个脚印,就像按图索骥。

回到城市比喻,图数据库就是那座城市档案馆的“关系索引柜”。普通文件柜只能按编号取文件,而关系索引柜能告诉你每个机构、每个项目、每个人之间千丝万缕的联系——资料员查找时不是翻箱倒柜,而是顺着索引线一路追寻。

query: A→r1B→r2C→r3D\text{query: } A \xrightarrow{r_1} B \xrightarrow{r_2} C \xrightarrow{r_3} D
多跳查询就是沿着图上的边一步步走:从 A 节点出发,走过关系 r₁ 到 B,再走 r₂ 到 C,最后走 r₃ 到 D——每走一步,信息就更丰富一层。
  1. 起点定义确定查询的起始节点,比如“住建局”。
    v0=Av_0 = A
  2. 关系遍历沿着一条关系边走到下一个节点。
    v1=Neighbor(v0,r1)v_1 = \text{Neighbor}(v_0, r_1)
  3. 多跳累积重复遍历多次,得到经多步关联的远端节点。
    vn=Neighbor(vn−1,rn)v_n = \text{Neighbor}(v_{n-1}, r_n)

社区检测算法

知识图谱建好了,但新的问题出现了:整个城市的节点可能有几十万个,关系有上百万条。面对这么庞大的网络,市长想快速知道:“这座城市里有哪些相对独立的业务板块?”比如在政策文档图谱中,医保相关的文件彼此关联紧密,教育相关的文件又是另一个密集群体——这些密集连接的节点群,就是“社区”。

社区检测算法就是自动找出这些密集连接区域的方法。GraphRAG 常用的 Leiden 算法,其核心思想是优化“模块度”——一个衡量社区划分质量的指标。模块度高意味着社区内部的边尽可能密集,社区之间的边尽可能稀疏。Leiden 算法通过迭代地移动节点、合并社区,逐步找到模块度最高的划分方案。

Leiden 算法的一大优势是它支持层次化社区检测。你可以先得到粗粒度的社区划分(比如“教育”“医疗”“基建”),然后每个社区内部又可以继续细分(“高等教育”“职业教育”“基础教育”)。这种层次结构完美契合了后续生成多层摘要的需求——先概括大方向,再深入小细节。

δ(ci,cj)={1ci=cj0cieqcj\delta(c_i, c_j) = \begin{cases} 1 & c_i = c_j \\ 0 & c_i eq c_j \end{cases}

用城市的例子来说,社区检测就像给城市做“功能区划分”。规划师看着地图上密密麻麻的连接线,发现医院、卫健委、药品供应商之间线特别密,就划出一个“医疗区”;学校、教育局、培训机构之间线特别密,就划出一个“教育区”。这些区域不是硬性划分的功能区,而是从真实关系网络中自然浮现出来的“生态圈”。

Q=12m∑ij[Aij−kikj2m]δ(ci,cj)Q = \frac{1}{2m} \sum_{ij} \left[ A_{ij} - \frac{k_i k_j}{2m} \right] \delta(c_i, c_j)
模块度 Q 是社区划分质量的“体检指标”:A_ij 表示节点 i 和 j 之间实际有没有边,k_i 是 i 的度(连接数),δ 判断两者是否在同一社区。Q 越高,说明社区内部连接越紧密,划分越合理。
  1. 边的实际存在判断节点 i 和 j 之间是否有边相连,有则 A_ij=1,否则为 0。
    Aij∈{0,1}A_{ij} \in \{0, 1\}
  2. 随机期望在随机连接假设下,i 和 j 之间期望的边数。
    kikj2m\frac{k_i k_j}{2m}
  3. 同社区判定δ 函数只在 i、j 属于同一社区时为 1,否则为 0。

多层摘要生成

社区检测完成后,规划师手里有了一个层次化地图:顶层有几个大区,每个大区里又有若干小区。但光有地图还不够——市长可没时间逐一查看每个节点的细节。他需要的是每个区域的核心概况。这就是“社区摘要”要做的事:对每个社区,用 LLM 生一段简洁的文字,概括这个社区在讲什么。

生成社区摘要时,系统把社区内所有节点的相关信息聚在一起,让 LLM 提炼要点。比如“医疗区”的社区摘要可能是:“该社区涵盖医保政策调整、公立医院改革、药品集中采购三大主题,核心关注点是控费与服务质量平衡。”这个摘要只花了 LLM 一次调用,但汇总了几十个节点的信息。

社区摘要生成后,系统会做一个关键操作:把摘要当成“新的节点”,在更高层级继续聚类和再摘要。低层社区的摘要被聚合成中层社区摘要,中层再聚合成高层全局摘要。每一层都忠实浓缩下一层的信息,最终形成一个“金字塔”式的摘要体系。顶层摘要就是对整个知识库的全景概览。

这种多层摘要就像给城市写了一本“分级指南”。市长出差时带上最薄的顶层摘要——一页纸看懂全城要务;部门主管看中层摘要——了解本领域动态;基层办事员看底层摘要——掌握具体项目细节。无论哪一层决策者,都能找到适合自己的“信息密度”。

S1⊂S2⊂⋯⊂SL\mathcal{S}_1 \subset \mathcal{S}_2 \subset \cdots \subset \mathcal{S}_L
这个包含链表示摘要的层级关系:最底层 S₁,包含最具体的社区细节;最顶层 S_L,是最宏观的全局概述。每一层都是对下一层的浓缩与提炼。
  1. 底层社区摘要每个最细粒度社区生成一段摘要。
    S1={s1,1,s1,2,⋯ ,s1,n}\mathcal{S}_1 = \{s_{1,1}, s_{1,2}, \cdots, s_{1,n}\}
  2. 逐层上卷将下一层摘要合并,再次提炼成上一层摘要。
    Sl+1=Summarize(Sl)\mathcal{S}_{l+1} = \text{Summarize}(\mathcal{S}_l)
  3. 全局摘要形成反复上卷直至顶层,得到覆盖全知识库的摘要。
    SL=Summarize(SL−1)\mathcal{S}_L = \text{Summarize}(\mathcal{S}_{L-1})

全局问答增强

现在,市长要问一个真正的宏观问题了:“过去五年,我市在民生领域的投入趋势和政策效果如何?”传统 RAG 面对这种问题会抓瞎——答案散布在几百个文档块的子串里,每个块只讲一个局部,谁都无法单独回答这个问题。

GraphRAG 的全局问答增强机制则展示了它的王牌能力。系统采用 Map-Reduce 策略:Map 阶段,把全局摘要(顶层+中层)与用户问题一起交给 LLM,让它基于摘要给出部分回答;Reduce 阶段,把各部分的回答合并起来,再次交给 LLM,生成一个综合性的全局答案。

关键一步是在 Map 阶段要做“相关性筛序”——不是所有摘要都与问题相关,系统需要筛选出与问题主题相关的社区摘要,避免不相关的内容干扰回答质量。这就像市长问民生问题,规划师不会把军事工业的摘要也拿来讲,而是迅速定位到民政、财政、教育等相关的社区摘要。

回到城市比喻:全局问答增强像一场“多部门联席会议”。传统 RAG 是随机抓一个路人问路,而 GraphRAG 是召集民政、财政、教育等相关部门的代表(社区摘要)一起开会,综合各方意见后给出一个统筹性的答复。市长得到的不是某个部门的片面之词,而是跨部门整合后的全局研判。

Answerglobal=LLM(⋃i∈top-kSummaryi∪Question)\text{Answer}_{\text{global}} = \text{LLM}\left( \bigcup_{i \in \text{top-}k} \text{Summary}_i \cup \text{Question} \right)
全局答案由 LLM 基于“最相关的 k 个社区摘要 + 用户问题”综合生成——摘要提供了宏观上下文,问题提供了具体诉求,两者结合让模型能跳出局部碎片,做出全局判断。
  1. 筛选相关摘要从众多社区摘要中挑出与问题最相关的 k 个。
    top-k=Rank(Summaries,Question)\text{top-}k = \text{Rank}(\text{Summaries}, \text{Question})
  2. 合并上下文将这 k 个摘要与问题组合成一个增强提示。
    Context=[s1,s2,⋯ ,sk,q]\text{Context} = [s_1, s_2, \cdots, s_k, q]
  3. 生成最终答案LLM 基于合并后的上下文输出综合回答。
    Answer=LLM(Context)\text{Answer} = \text{LLM}(\text{Context})

建库流程管线

理解了各个模块,让我们把视线拉高,看看从一堆原始文档到 GraphRAG 系统正式服务,中间经历了怎样的流水线。这条流水线(Pipeline)是工程落地的骨架,分为三个阶段:文本处理、图谱构建、索引整合。

第一阶段是文本处理。原始文档需要进行清洗(去除页眉页脚、乱码字符)、分块(按语义或固定长度切成适当大小的块)。分块质量直接决定后续抽取效果:块太大,LLM 抽取关系时容易丢失细节;块太小,上下文不完整导致实体识别不准。经验上,按段落或章节切分效果较好,块大小通常在 600—1200 个 token 之间。

第二阶段是图谱构建。对每个文本块执行实体关系抽取,将所有三元组汇总去重,存入图数据库。同时,系统还要为每个文本块生成向量表示,存入向量数据库。注意此处图库和向量库是同步并行的:同一份文档,既以“节点+边”的形式存在于图库,也以“向量”的形式存在于向量库。这就是“混合检索”的底层支撑。

第三阶段是索引整合。图库中的社区检测、摘要生成都在这个阶段完成,最终形成一个多层次的信息检索体系。建库完成后,系统还需要支持增量更新——当新文档进来,不需要重建整张图,而是对新增文本走一遍抽取、入图、更新社区的流程。这就像城市扩建新区时,不需要推翻原有的城市规划图,只需在图上补充新区域。

这条流水线就像一座城市的建设周期:选址规划(清洗分块)→ 基础施工(抽取建图)→ 功能分区(社区检测)→ 信息发布(摘要生成)。每一步都有明确的产出物,每一步的质量都影响最终系统的表现。工程上,GraphRAG 的价值不仅仅在于算法新颖,更在于这条流水线让知识处理变成了标准化、可复用的流程。

Pipeline=Clean→Chunk→Extract→Graph+Vector→Community→Summary\text{Pipeline} = \text{Clean} \rightarrow \text{Chunk} \rightarrow \text{Extract} \rightarrow \text{Graph+Vector} \rightarrow \text{Community} \rightarrow \text{Summary}
这串箭头展示了 GraphRAG 的标准建库链路:六个环节环环相扣,前一步的输出就是后一步的输入,构成一条完整的流水线。
  1. 清洗分块将原始文档清洗为规范文本,再切成适合处理的块。
    Blocks=Chunk(Clean(Docs))\text{Blocks} = \text{Chunk}(\text{Clean}(\text{Docs}))
  2. 抽取建图从每个块中抽取三元组,并生成向量,分别存入图库与向量库。
    G←Extract(Blocks),V←Embed(Blocks)\mathcal{G} \leftarrow \text{Extract}(\text{Blocks}), \quad \mathcal{V} \leftarrow \text{Embed}(\text{Blocks})
  3. 社区与摘要在图库上运行社区检测,生成各层摘要。
    S←Summarize(Community(G))\mathcal{S} \leftarrow \text{Summarize}(\text{Community}(\mathcal{G}))

应用场景价值

了解了 GraphRAG 的原理和工程实现,最后一个问题是:这盏灯在哪些场景能派上大用场?答案是那些“答案藏在全局关系里”的场景,而不仅仅是“答案在某段文本里”的场景。

政策评估是 GraphRAG 的第一大主场。一份新政策的影响评估,需要综合历年政策文本、财政预算数据、地方执行报告、民众反馈等多源信息。政策分析师提问“数字乡村政策对中西部地区的实际带动效应如何?”,GraphRAG 能从图谱中找出数字乡村相关的基础设施项目、财政拨款记录、地方案例,通过社区摘要提供宏观判断依据,再结合具体文本来佐证。

医学研究是另一类典型场景。研究者想探索“某种药物在特定人群中的不良反应关联因素”,这类问题横跨临床记录、药物说明书、文献报道、基因数据多个知识域。GraphRAG 的知识图谱天然适合做跨域关联——从“药物”节点出发,沿“副作用”边找到“不良反应”,再沿“患者特征”边找到“老年人群”,一次多跳查询就能勾勒出关联图。传统 RAG 则需要在海量片段中逐一拼凑。

商业决策同样受益。企业做市场进入决策时,需要综合分析竞品动态、用户评价、供应链状况、政策环境。GraphRAG 可以构建一个行业知识图谱,CEO 问“我们进入东南亚市场的最大风险是什么?”,系统综合社区摘要给出涵盖政策、竞品、供应链的全局风险清单。

城市知识的价值不在于哪条街道多繁华,而在于街道之间如何连通。GraphRAG 在政策、医学、商业中的价值正在于此——它把散落的知识连成网,让决策者在面对复杂问题时,既能看到树木的细节,又能看到森林的全貌。

Value=f(全局性问题占比,知识关联密度,时效要求)\text{Value} = f(\text{全局性问题占比}, \text{知识关联密度}, \text{时效要求})
这个公式总结了 GraphRAG 的价值条件:全局性问题越多、知识之间的关联越密集、对信息完整性的要求越高,GraphRAG 的价值就越突出。
  1. 全局性问题占比问题中需要跨文档、跨主题整合的越多,GraphRAG 优势越明显。
    GlobalRatio=NglobalNtotal\text{GlobalRatio} = \frac{N_{\text{global}}}{N_{\text{total}}}
  2. 知识关联密度知识库中实体间关系越丰富,图结构越能发挥导航作用。
    Density=2∣E∣∣V∣(∣V∣−1)\text{Density} = \frac{2|E|}{|V|(|V|-1)}
  3. 综合效能价值与两个因素正相关,衡量系统在场景中的适配度。
    Value∝GlobalRatio×Density\text{Value} \propto \text{GlobalRatio} \times \text{Density}

AI 应用场景

政策多源分析助手

阶段:建库管线 + 全局问答增强

解决的问题:传统政策分析需要人工阅读数百份政策文件、执行报告、统计数据,耗时长且容易遗漏跨文件的关联。政策分析员总在问“这份新政策与过去哪些政策相关联?各地执行效果的差异受哪些因素驱动?”——纯向量检索只能找到片段,无法自动连接政策之间的脉络。

具体案例:某省级政策研究机构用 GraphRAG 构建了一个政策知识库,收录近十年 2 万余份政策文件及配套解读。分析员提问“碳达峰政策对高耗能行业的地方配套措施有哪些异同?”,系统通过图谱找到碳达峰枢纽节点,沿“配套”“约束”“补贴”等关系边展开,结合社区摘要输出各省措施对比报告。原本 3 天的调研缩短为 2 小时。

医学文献关联发现

阶段:混合检索架构 + 社区检测算法

解决的问题:医学文献中药物、疾病、基因、不良反应之间的关联是高度网络化的,但传统检索只能返回“含有同一关键词”的文献,无法回答“哪些药物之间可能存在未知的相互作用”这类结构性问题。医学科研人员需要把文献藏在关系网络中的线索找出来。

具体案例:某医学研究院将 50 万篇糖尿病领域文献构建成知识图谱,图中有药物、靶点、信号通路、临床指标四类节点。研究者提问“SGLT2 抑制剂与心衰风险降低之间的可能通路有哪些?”,系统通过图通道沿“结合→调节→影响”路径找到 3 条候选通路,并经社区摘要提供背景支持。该线索已作为后续临床研究的前瞻方向。

企业竞争情报驾驶舱

阶段:图存储结构 + 全局问答增强

解决的问题:企业商业决策需要整合竞品技术专利、用户评论、供应链伙伴、行业政策等信息,这些信息分布在跨源异构数据中,传统 BI 报表只能展示预设维度,无法实时回答“我们相对于主要竞争对手的差异化优势在哪里”这类开放问题。

具体案例:一家新能源汽车企业构建了行业知识图谱,涵盖 1.2 万家供应商、300 个竞品车型、8000 项专利技术。产品战略总监提问“今年第三季度我们与比亚迪在智能驾驶技术上的差距是否在缩小”,系统综合专利时间线、技术社区摘要、供应商动态,输出多维对比分析,其中发现了两个传统报表无法暴露的技术短板。

Python 演示

python109 行
"""GraphRAG 核心流程演示:从文本到知识图谱、社区检测与全局问答。 使用纯 Python 实现简化版,聚焦核心逻辑,不依赖外部图数据库。""" from collections import defaultdict  class SimpleGraphRAG:    def __init__(self):        self.entities = set()          # 实体集合        self.relations = defaultdict(list)  # (头实体, 关系) -> [尾实体]        self.communities = {}          # 实体 -> 社区ID        self.summaries = {}            # 社区ID -> 社区摘要     def extract_triples(self, text):        """模拟 LLM 从文本中抽取三元组 (头实体, 关系, 尾实体)。         实际项目中此处调用 LLM API,这里用规则做演示。        """        triples = []        # 简化规则:按 "、"/"," 分句,寻找包含 "负责"/"拨款"/"影响" 的句子        sentences = [s for s in text.replace(",", "。").split("。") if s]        keyword_pairs = [            ("负责", "负责"), ("拨款", "拨款给"), ("影响", "影响"),            ("位于", "位于"), ("合作", "合作于"),        ]        for sent in sentences:            for kw, rel in keyword_pairs:                if kw in sent:                    parts = sent.split(kw)                    if len(parts) == 2:                        head = parts[0].strip()[:8]                        tail = parts[1].strip()[:8]                        if head and tail:                            triples.append((head, rel, tail))        return triples     def build_graph(self, docs):        """建库:抽取三元组,构建图结构。"""        for doc in docs:            for triple in self.extract_triples(doc):                head, rel, tail = triple                self.entities.add(head)                self.entities.add(tail)                if (head, rel) not in self.relations or tail not in self.relations[(head, rel)]:                    self.relations[(head, rel)].append(tail)     def detect_communities(self):        """社区检测:简化的连通分量法,划分密集连接区域。"""        visited = set()        comm_id = 0        for node in self.entities:            if node in visited:                continue            stack = [node]            visited.add(node)            while stack:                cur = stack.pop()                self.communities[cur] = comm_id                for (head, rel), tails in self.relations.items():                    if head == cur:                        for t in tails:                            if t not in visited:                                visited.add(t)                                stack.append(t)                    if cur in tails and (t := head) not in visited:                        visited.add(t)                        stack.append(t)            comm_id += 1     def generate_summaries(self):        """生成社区摘要:汇总每个社区的实体作为关键词组。"""        comm_entities = defaultdict(list)        for entity, comm in self.communities.items():            comm_entities[comm].append(entity)        for comm, entities in comm_entities.items():            self.summaries[comm] = "社区%d 涵盖实体: %s" % (                comm, "、".join(entities[:5]))     def global_query(self, question):        """全局问答:结合社区摘要回答宏观问题。"""        relevant = []        for comm_id, summary in self.summaries.items():            if any(word in summary for word in question.replace(",", "")):                relevant.append(summary)        if not relevant:            relevant = list(self.summaries.values())[:3]        context = ";".join(relevant)        answer = "基于[%s] 的全局理解,%s 需要综合多社区信息作答。" % (context, question)        return answer  if __name__ == "__main__":    docs = [        "住建局拨款给朝阳公园改造。财政局拨款给教育局建设新校区。",        "卫健委负责全市公立医院改革。医保局拨款给公立医院。",        "住建局负责城市绿化项目。朝阳公园位于朝阳区。",        "教育局合作于多所高校。财政局拨款给高校科研项目。",    ]    rag = SimpleGraphRAG()    rag.build_graph(docs)    rag.detect_communities()    rag.generate_summaries()    print("实体数:", len(rag.entities))    print("关系数:", len(rag.relations))    print("社区数:", len(rag.summaries))    answer = rag.global_query("财政资金在各领域如何分配?")    print("全局问答:", answer)
目的:用约 100 行 Python 实现 GraphRAG 的核心链路——三元组抽取、图构建、社区检测、摘要生成、全局问答,让读者脱离复杂依赖亲手跑通流程。
  1. self.relations = defaultdict(list)用字典存储“(头实体,关系)→ 尾实体列表”,天然表达图结构。
  2. def extract_triples(self, text)模拟 LLM 的抽取能力;生产环境中这里替换为 LLM API 调用。
  3. if (head, rel) not in self.relations or tail not in self.relations[(head, rel)]去重逻辑,防止同一三元组重复入库。
  4. def detect_communities(self)用连通分量法找出密集连接区域,是 Leiden 算法的简化替代。
  5. stack = [node]深度优先遍历的起点,从当前实体出发探索整个社区。
  6. for (head, rel), tails in self.relations.items()遍历所有关系边,找出与当前节点直接相连的邻居。
  7. comm_entities[comm].append(entity)按社区 ID 聚合实体,为生成摘要做准备。
  8. self.summaries[comm] = "社区%d 涵盖实体: %s" % (...)用实体列表作为摘要的简化表示,真实场景由 LLM 总结。
  9. def global_query(self, question)全局问答的简化版,通过关键词匹配相关社区摘要。
  10. answer = "基于[%s] 的全局理解..."模拟 LLM 基于上下文的综合回答。

常见误区

误区GraphRAG 就是给普通 RAG 加一个知识图谱插件,反正最终都是检索文档拼答案,效果差不多。

正解两者有本质差异。传统 RAG 的检索单元是“文本块”,它们彼此独立,像一筐土豆。GraphRAG 的检索单元包括“实体”和“关系”组成的图结构,像一张城市的交通网。更重要的是,GraphRAG 引入了社区摘要机制——它是“先提炼再回答”,而非“先检索再拼凑”。面对全局性问题,传统 RAG 只能堆砌局部片段,GraphRAG 则能基于宏观摘要给出有洞察力的回答。这不是锦上添花,而是从“找信息”到“读信息”的范式跃迁。

为何常见:因为两者的文本输入和最终输出形式很像,初学者只看到“检索-回答”的表层流程,忽略了中间层——图结构和社区摘要——带来的根本性变化。

误区既然有了知识图谱,大模型直接看图结构就行,向量检索不是多余的吗?

正解恰恰相反,向量检索是 GraphRAG 不可或缺的一只脚。图结构擅长表达“关系”,但在处理语义模糊的查询时力不从心——比如“最近关于健康城市有哪些讨论”,“健康城市”可能对应图谱中多个分散节点。向量检索擅长捕捉语义相似度,能把模糊的提问映射到相关实体上,然后图结构再接力展开关联。两者是“先定位再导航”的关系:向量负责找到入口,图负责深入探索。

为何常见:人们容易高估“新方法”而低估“老方法”,看到 GraphRAG 的图结构便误以为向量检索已被替代,忽视了两者实际上是互补协同的关系。

误区社区检测就是对文档做聚类嘛,把相似内容的文档分成一组,和文本聚类有什么区别?

正解文本聚类依据的是内容相似度——词频、向量距离等特征,聚类对象是“文本”,结果是一堆相似文章的合集。社区检测依据的是图结构连通性——密集连接程度,检测对象是“实体节点”,结果是一个关系密切的实体网络。比如“住建局”和“朝阳公园”在内容上毫无相似性,但在图结构中通过“拨款”边紧密相连,会属于同一社区。社区检测发现的不是“像”的关系,而是“联系在一起”的关系——这是一种更深层的结构洞察。

为何常见:因为“物以类聚”的直觉让初学者自然联想到聚类,而社区检测的对象已从“特征相似”转变为“关系紧密”,这个跃迁需要理解图论的基本视角。

误区社区摘要就是拿社区内实体列表让 LLM 随便写一段就行,重点是图结构和检索算法。

正解社区摘要的质量直接决定了全局问答的天花板。如果摘要写得笼统或偏颇,上层摘要会被逐级放大失真,最终全局回答的质量会断崖式下降——“垃圾进,垃圾出”在多层摘要链路中被指数级放大。实践上需要精心设计摘要提示词、控制输入上下文大小、采用多轮迭代提炼。微软的 GraphRAG 论文用了大量篇幅讨论摘要生成策略,可见其重要性。摘要不是陪衬,而是全局问答的“信息压舱石”。

为何常见:人们天然关注“检索算法”这种听起来高技术含量的话题,却低估了“写摘要”这种偏工程和 prompt 设计的工作,忽视了它在多层结构中的级联影响。

面试题

Q1. GraphRAG 中“社区摘要”最核心的作用是什么?

单选题 · mid

  • A.减少图谱存储空间
  • B.提供超越局部片段的宏观上下文← 正确答案
  • C.加速实体关系抽取速度
  • D.替代向量检索的作用

答案:B。社区摘要的意义在于把局部信息聚合为宏观上下文,让系统能回答需要全局视野的问题。它不是用存储空间(A)、抽取速度(C)或替代向量检索(D),而是新增了一条理解全局的通道。

Q2. GraphRAG 相比传统 RAG 最大的架构差异是什么?

单选题 · easy

  • A.使用了更大的语言模型
  • B.增加了知识图谱作为中间层← 正确答案
  • C.不再需要向量数据库
  • D.不需要文本分块处理

答案:B。GraphRAG 的架构核心是在文本与检索之间引入知识图谱这一中间层,让信息以实体和关系的形式组织。它不排斥向量库(B 错误),不依赖更大模型(A 错误),依然需要文本分块(D 错误)。

Q3. 三元组 “(住建局,拨款给,朝阳公园)” 中,“拨款给” 在图结构中表示什么?

单选题 · mid

  • A.一个实体节点
  • B.一条关系边← 正确答案
  • C.一个属性值
  • D.一个社区标签

答案:B。三元组中头实体和尾实体是节点(A 错误),两个实体之间的“关系”对应图中的边(B 正确)。它不是属性值或社区标签(C、D 错误),而是连接两个节点的桥梁——图结构正是靠这些边承载关联语义。

Q4. Leiden 算法在 GraphRAG 中的作用有哪些?

多选题 · hard

  • A.优化模块度以发现密集连接区域← 正确答案
  • B.支持层次化社区结构检测← 正确答案
  • C.替代 LLM 生成社区摘要
  • D.为多层摘要提供社区划分依据← 正确答案

答案:ABD。Leiden 算法通过优化模块度发现社区(A 正确),支持从粗到细的层次划分(B 正确),社区划分结果直接为多层摘要提供边界(D 正确)。它只负责结构发现,不涉及文本生成,因此不能替代 LLM(C 错误)。

Q5. GraphRAG 建库流程中,文本分块大小的选择应如何考虑?

单选题 · mid

  • A.越小越好,避免上下文过长
  • B.越大越好,保留完整语义
  • C.过大丢失抽取细节,过小上下文不完整,需权衡← 正确答案
  • D.分块大小不影响最终效果

答案:C。分块大小是权衡决策——太大时 LLM 抽取实体关系容易丢失细节(A 反向错误),太小时上下文不足导致实体识别不准(B 反向错误),分块大小显著影响抽取质量(D 错误)。经验上 600—1200 token 较为合理(C 正确)。

Q6. 关于 GraphRAG 的混合检索,以下说法正确的是哪些?

多选题 · hard

  • A.向量通道负责语义相似匹配← 正确答案
  • B.图通道负责结构路径探索← 正确答案
  • C.两路结果需要融合排序← 正确答案
  • D.混合检索只用于建库阶段

答案:ABC。混合检索贯穿建库与查询全流程(D 错误)。向量通道做语义匹配(A 正确),图通道沿关系路径探索(B 正确),最终两路结果需要加权融合、综合排序(C 正确),这正是混合检索架构的核心设计。

Q7. GraphRAG 的 Map-Reduce 全局问答中,Reduce 阶段的输入是什么?

单选题 · easy

  • A.原始文档片段
  • B.所有社区摘要的集合
  • C.各社区摘要独立回答的合并结果← 正确答案
  • D.用户重新输入的提问

答案:C。Map 阶段让各社区摘要独立回答(A、B 错误),Reduce 阶段把这些部分回答合并起来再让 LLM 提炼(C 正确)。用户提问在第一阶段已经输入,不会在 Reduce 阶段重新输入(D 错误)。

Q8. 在处理“某药物对老年人群的长期影响”这类问题时,GraphRAG 的什么特性最能体现优势?

单选题 · mid

  • A.向量检索的精确匹配
  • B.多跳关系路径的跨域关联← 正确答案
  • C.社区检测的实时性
  • D.文本分块的均匀性

答案:B。这类问题横跨药物、人群、时间等多个知识域,GraphRAG 从“药物”节点出发,沿“副作用”“患者特征”等边多跳遍历(B 正确),形成跨域关联链。向量检索只能做语义匹配(A 错误),社区检测和分块不是核心优势来源(C、D 错误)。

Q9. 关于 GraphRAG 的增量更新,正确的做法有哪些?

多选题 · hard

  • A.只对新文档做抽取和入图← 正确答案
  • B.无需重新做全库社区检测
  • C.需要更新受影响的社区摘要← 正确答案
  • D.必须重建整张知识图谱

答案:AC。增量更新只处理新增文档(A 正确),并更新受影响边界内的社区摘要(C 正确)。但新节点可能改变社区结构,通常需要局部重跑社区检测(B 错误),重建全图则成本过高绝不可取(D 错误)。

Q10. GraphRAG 中最适合用“全局问答”解决的问题是哪种?

单选题 · easy

  • A.“合同中第三条的违约金是多少”
  • B.“过去五年各区域政策效果的整体差异”← 正确答案
  • C.“这份报告共分几个章节”
  • D.“某个具体产品的库存数量”

答案:B。“过去五年各区域政策效果的整体差异”需要跨时间、跨区域、跨政策综合对比,答案分布在大量文档中且依赖宏观视角(B 正确)。其余三个问题都能在局部文本中直接找到精确答案(A、C、D 错误),传统 RAG 已能胜任。