RAG 工程与检索系统设计

第19章 GraphRAG:实体、关系与图检索

作者 杨艺韬 · 15,795 字 · 发布于 · 更新于

基于 chunk 的 RAG 看见的是文档,基于图的 RAG 看见的是关系。代价是索引阶段贵一个数量级以上。

本章要点

  • 传统 chunk RAG 擅长"找到相关段落",不擅长回答跨实体关系和多跳推理问题
  • GraphRAG 把知识组织成 实体 + 关系 + 社区 的图结构——检索时走图遍历
  • 三种主流方案:Microsoft GraphRAG(全局社区摘要)、LightRAG(本地 + 全局双路)、Neo4j + LLM 合体(传统图 DB + embedding)
  • 工程成本:索引阶段贵一个数量级以上——每个 chunk 都要过一次 LLM 抽实体和关系,再加社区摘要;在线检索成本与 chunk RAG 相近(Global Search 除外)
  • GraphRAG 不是替代 chunk RAG——是补充,在全局性问题("整个文档集说了什么""最重要的三个主题")和多跳关系上价值显著

19.1 传统 RAG 的盲区

chunk-based RAG 能回答:

  • "什么是企业版 SSO?"(单跳事实)
  • "SSO 价格是多少?"(单跳事实)

不能很好回答:

  • "企业版 SSO 和专业版 SSO 有什么区别?"(两个实体对比)
  • "哪些功能是企业版独有、专业版没有?"(集合运算)
  • "这份产品手册整体讲了什么?"(全局主题)
  • "这个产品问题可能影响哪些下游模块?"(多跳因果)

原因:chunk RAG 的检索是局部的——每个 chunk 独立打分,没有跨 chunk 的关系建模。回答"A 和 B 的区别"需要同时找到 A 和 B 的相关 chunk 并对比——传统 RAG 偶尔做对,做不稳定。

GraphRAG 的解法:在索引阶段用 LLM 从文档里抽出实体和关系、构建知识图谱;检索阶段不只是找 chunk,还遍历图。

flowchart LR
    classDef chunkStyle fill:#fed,stroke:#c60
    classDef graphStyle fill:#def,stroke:#06c
    subgraph CHUNK["chunk RAG"]
        C1[chunk 1]:::chunkStyle
        C2[chunk 2]:::chunkStyle
        C3[chunk 3]:::chunkStyle
    end
    subgraph GR["GraphRAG"]
        E1[实体 A]:::graphStyle
        E2[实体 B]:::graphStyle
        E3[实体 C]:::graphStyle
        E1 -->|关系 r1| E2
        E2 -->|关系 r2| E3
    end
    Q1[单跳事实] --> CHUNK
    Q2[多跳 关系 全局] --> GR

19.2 GraphRAG 的核心组件

一个完整的 GraphRAG 系统包含:

  • 实体抽取:LLM 从每个 chunk 抽出实体(人、产品、概念、地点)
  • 关系抽取:LLM 同时抽出实体间关系("SSO 属于 企业增强包"、"企业版 包含 专业版功能")
  • 图构建:把所有实体和关系合并成一张大图
  • 社区发现:用图聚类算法找出实体集群(讨论同主题的实体集合)
  • 社区摘要:对每个社区用 LLM 写一段总结
  • 混合检索:query 时同时查向量 + 图 + 社区
flowchart TB
    DOCS[(文档集)] --> CHUNK[分块]
    CHUNK --> EXTRACT[LLM 抽取<br/>实体+关系]
    EXTRACT --> GRAPH[构建图]
    GRAPH --> COMM[社区发现<br/>Leiden]
    COMM --> SUMM[LLM 写社区摘要]
    Q[query] --> ROUTE{问题类型}
    ROUTE -->|局部| VEC[向量检索 chunk]
    ROUTE -->|全局| COMM_Q[社区摘要检索]
    ROUTE -->|多跳| GRAPH_Q[图遍历]
    VEC & COMM_Q & GRAPH_Q --> FUSE[融合]
    FUSE --> LLM[生成]

19.3 Microsoft GraphRAG

Microsoft 2024 年开源的 GraphRAG 是影响最大的 GraphRAG 实现,对应论文是 Edge et al. 2024《From Local to Global: A Graph RAG Approach to Query-Focused Summarization》(arXiv:2404.16130)。核心创新:层次化社区摘要。

  • Local Search:针对具体实体的问题——先找实体节点,在邻居里召回,送 LLM 回答。类似传统 RAG 但以实体为中心
  • Global Search:针对全局性问题——把选定层级的全部社区摘要打散,按固定 token 预算分批;每批各自生成一份带有用度打分的部分答案(map),再把得分高的部分答案汇总成全局答案(reduce)。论文里这一步不用向量检索筛社区,而是让该层所有摘要都参与

Global Search 是 chunk RAG 做不到的能力——传统 top-k 拿不到"全量"信息。

社区发现:Leiden 算法

Microsoft 用 Leiden(Traag et al. 2019)做图聚类——每个社区是一组紧密相关的实体。聚类是层次化的,自顶向下越分越细:

  • Level 0:根层,社区数最少、单个社区最大、摘要最概括
  • Level 1:中等社区
  • Level 2 及以下:更细的小社区

层数由数据和算法决定,不固定是三层——论文在两个数据集上都评估了 C0–C3 四层(播客转录数据集上依次是 34、367、969、1310 个社区)。Global Search 在哪一层上做是可选的:层级越高,要读的摘要越少、token 越省;层级越低,摘要越细。

论文实际证明了什么

这一点值得说清楚,因为二手转述里被夸大得很厉害。

论文的实验对象是百万 token 量级语料上的全局性 sensemaking 问题("这个数据集的主要主题是什么"这一类),评价方式是 LLM 做头对头比较,比的维度主要是答案的 comprehensiveness(全面性)和 diversity(多样性)(另有 empowerment,以及作为对照的 directness),结论是 GraphRAG 在前两个维度上明显优于常规向量 RAG 基线;empowerment 上互有胜负,directness 上则是向量 RAG 占优。

也就是说:

  • 论文没有报告"多跳 recall 提升多少点"——它测的根本不是 recall,任何这类数字都不要信
  • 论文的优势场景是全局摘要类问题,不是所有问题
  • 具体优势幅度依赖语料和 judge,不能当作你自己语料上的预期值

成本侧的结论是结构性的、不需要引用别人的数字就能推出来:naive RAG 的离线成本只有 embedding,GraphRAG 的离线成本是"每 chunk 一次 LLM 抽取 + 每社区一次 LLM 摘要"。LLM 的每 token 单价比 embedding 高一到三个数量级(看选哪档模型,§19.8 有算例),所以索引成本必然差一个数量级以上——具体倍数按 §19.8 的算式自己算。

这就是 GraphRAG 的核心权衡——离线算力换在线查询质量。

19.4 LightRAG:更轻量的变体

LightRAG(arXiv:2410.05779,HKU 2024)是对 Microsoft GraphRAG 的简化:

  • 不做层次社区发现——只用 entity + relation
  • 检索时双路:low-level(用具体关键词匹配实体节点,取回实体及其属性和关系)+ high-level(用概括性关键词匹配关系边,汇总多个相关实体的信息,对应更宏观的主题)
  • 论文的主张是在保持效果的前提下降低检索与增量更新的开销——具体降幅看论文自己的实验设定,别当通用结论

省下来的成本主要来自两处,这两处是结构性的:不做层次社区发现(省掉了每个社区一次的摘要调用)、增量更新不必重算社区(新文档只影响局部子图)。LightRAG 的开源实现也更简单,后端换 Neo4j 或 NetworkX 都行。

适合:想体验 GraphRAG 但预算紧、数据规模中等的项目。

19.5 用 Neo4j + 向量 DB 组合

另一条路:不用新框架,直接在 Neo4j 图数据库上加 embedding 检索。

Neo4j 从 5.11 起原生支持 vector index——可以在图节点上挂向量。架构:

  • 文档解析 + 分块(第 5-6 章)
  • LLM 抽实体 + 关系(同 GraphRAG)
  • 实体作 Neo4j node,关系作 edge,chunk 作 (:Chunk) 节点连到相关实体
  • chunk embedding 建向量 index

Cypher 查询例子:

Neo4j 的向量检索不是写在 WHERE 里的相似度表达式(那是 pgvector 的写法),而是通过 db.index.vector.queryNodes 这个过程调用向量索引,再把结果接进后面的图遍历:

// 1) 先用向量索引拿语义上最相关的 chunk
CALL db.index.vector.queryNodes('chunk_embedding_idx', 20, $query_vec)
YIELD node AS c, score

// 2) 再从这些 chunk 提到的实体出发,做 1-2 跳扩展
MATCH (c)-[:MENTIONS]->(e:Entity)
MATCH (e)-[:RELATED_TO*1..2]-(e2:Entity)<-[:MENTIONS]-(c2:Chunk)
RETURN c, score, e2, collect(DISTINCT c2) AS expanded
ORDER BY score DESC

注意几个容易写错的点:

  • 向量检索用过程调用,不是比较运算符。<=> 是 pgvector 的距离运算符,Cypher 里没有;索引名(这里的 chunk_embedding_idx)必须是你 CREATE VECTOR INDEX 时起的那个名字
  • queryNodes 的第二个参数是 k,先取回 k 个再做图扩展——不是先全图匹配再过滤,顺序反了性能会很难看
  • 多跳用无向 -[:RELATED_TO*1..2]-,除非你的关系语义确实是单向的;写成 -> 会漏掉反向边
  • 注意版本:上面是 5.x 以来的过程调用写法。Neo4j 官方文档说明,从 2026.01 起查询向量索引的首选写法是 Cypher 25 的 SEARCH 子句,db.index.vector.queryNodes 在 2026.04 被标为 deprecated(仍可用);按你所用版本的文档选写法

这种"向量入口 + 图扩展"的组合正是 §19.15 会讲的串联式混合——先用语义找到起点,再用结构探索关系。

适合:团队已有 Neo4j 基础、想复用图数据库能力;对新组件学习成本敏感的。

19.6 实体和关系抽取

整条 GraphRAG 的质量根本上取决于实体/关系抽取的质量。

Prompt 工程

典型抽取 prompt:

从下面的文本中抽取实体和关系。

实体类型:产品、功能、角色、组件、概念
关系类型:包含、属于、依赖、对比、替代、影响

输出 JSON:
{
  "entities": [{"name": "...", "type": "...", "description": "..."}],
  "relations": [{"source": "...", "target": "...", "type": "...", "desc": "..."}]
}

文本:{chunk}

Microsoft GraphRAG 的实际抽取 prompt 连 few-shot 示例在内一百多行——每个实体抽出 name / type / description,每条关系抽出 source / target / description 和一个数值型的 strength(关系强度)。论文里还有 claims(关于实体的事实性断言,如日期、事件)的抽取,在开源实现里是单独的一步,默认关闭。信息密度高,LLM 输出也更可用。

实体对齐

同一个实体在不同 chunk 里可能有不同名字——"Enterprise 版"、"企业版"、"Ent edition"。需要实体对齐(entity resolution):

  • 精确匹配 + 规则:小写、去空格、别名表
  • embedding 相似度:相似度超过阈值视为同实体(阈值要在自己标注的样本上定)
  • LLM 仲裁:LLM 看几个候选,判断是否同一实体

实体对齐做不好——图里一个实体变成几个副本,连接不上,图变支离破碎。这是 GraphRAG 工程的主要难点之一。

关系去重

"SSO 属于企业版" 和 "企业版包含 SSO" 是同一关系的两个方向表达。构图前要归一化:统一方向、统一关系类型名。

实体抽取的质量评估

抽取质量是 GraphRAG 的上限。三个指标做离线评估:

  • Entity Precision:抽出的实体里,真正是目标类型的比例(不是误抽的普通名词)
  • Entity Recall:应该抽出的实体中,被抽到的比例(漏抽率反面)
  • Relation F1:关系的三元组 (source, target, type) 精确匹配的 F1

构造 gold set:挑 20-50 份代表性 chunk,人工标出标准答案的实体 + 关系,和 LLM 结果对比。

上线阈值定多少没有通用答案——它取决于你的 ontology 粒度(类型越细越难抽准)和领域(法律条文比产品文档难得多)。可以确定的只有两条相对关系:Relation F1 通常明显低于 Entity F1(三元组要三个位置都对),以及先把这两个数测出来、再谈图检索的效果——抽取质量是整条链路的上限,抽取只有 0.6,后面怎么调都没用。

Prompt 迭代的技巧

抽取 prompt 的调优有几个固定操作:

  • 逐步给例子:初版 prompt → 跑 10 条看错 → 把错改对作为 few-shot 加回 prompt → 再跑 → 迭代
  • 明确边界:LLM 经常抽出"企业"、"系统"这类过于笼统的实体。prompt 加明确排除项:"不要抽出诸如'公司''系统''东西'等过于笼统的词"
  • 输出 JSON schema:pydantic 的 JSON schema 比自由文本指令更稳健
  • 温度 0:抽取是结构化任务,temperature=0 最稳定,不要用 sampling

19.7 什么问题适合 GraphRAG

判断当前项目是否该上 GraphRAG,问几个问题:

场景 chunk RAG GraphRAG
FAQ 问答 ✓ 不值得
产品文档问答 ✓ 部分值得
"整个文档集讲了什么" ✗ ✓
法律条文多条款关联 一般 ✓
研究文献元分析 一般 ✓
代码库多模块关系 部分 ✓
客服工单归类 ✓ 不需要
企业内员工/项目关系 ✗ ✓

规则:单跳事实问题用 chunk RAG,全局性/多跳/关系型问题用 GraphRAG。多数生产项目两者并用——按 query 类型路由(第 15 章的 routing)。

19.8 GraphRAG 的成本和工程

索引成本:给算式,不给结论

成本会随模型定价变,所以这里给可重算的式子。设语料共 T 个 token、切成 N 个 chunk、抽取 prompt 本身 P token、每个 chunk 抽取输出 O token:

抽取输入 tokens = N × (chunk_tokens + P)     # P 通常远大于 chunk 本身,
                                              # 因为抽取 prompt 有长长的类型定义和 few-shot
抽取输出 tokens = N × O
社区摘要        = 社区数 × (社区内实体关系 tokens + 摘要 prompt) 输入 + 摘要输出

索引成本 ≈ 抽取输入 × 输入单价 + 抽取输出 × 输出单价 + 社区摘要那一项

对照组——chunk RAG 的离线成本只有一项:

embedding 成本 ≈ T × embedding 单价

两边一比就明白差距从哪来:embedding 的单价通常在每百万 token 几美分到十几美分,而 LLM 的输入输出单价在每百万 token 一美元到十几美元——差一到三个数量级;再加上抽取 prompt 让实际处理的 token 数远多于原文 token 数(P 会被重复 N 次)。所以 GraphRAG 的索引成本高一个数量级以上是结构决定的,不是某个实现的问题。

真要估自己的账,按上面的式子代入你选的模型单价算一遍。两个降本抓手也直接体现在式子里:P 越短越省(抽取 prompt 里的 few-shot 是重复 N 次的固定开销,能用 prompt cache 就一定要用;注意各模型有最小可缓存长度,例如 claude-haiku-4-5 是 4096 token,更短的前缀不会被缓存,见第 16 章),抽取模型越便宜越省(抽取是结构化任务,通常不需要最强的模型)。

举一个算例(参数是假设的,单价取各家官方定价页 2026-09-25 的挂牌价):语料 T = 100 万 token,切成 N = 2000 个 500 token 的 chunk,抽取 prompt P = 1500 token,每 chunk 输出 O = 300 token,抽取模型用 claude-haiku-4-5(输入 $1、输出 $5 每百万 token),对照组 embedding 用 OpenAI text-embedding-3-small($0.02 每百万 token):

抽取输入 = 2000 × (500 + 1500) = 400 万 token   → 4 × 1    = 4 美元
抽取输出 = 2000 × 300          =  60 万 token   → 0.6 × 5  = 3 美元
抽取小计 ≈ 7 美元(社区摘要另算)

embedding = 100 万 token                        → 1 × 0.02 = 0.02 美元

光抽取一项就是 embedding 的 7 / 0.02 = 350 倍;换成 text-embedding-3-large($0.13 每百万 token)也还有 7 / 0.13 ≈ 54 倍。单价变了,按同一组式子重算。

在线检索成本

在线检索本身不比 chunk RAG 贵多少——多的是一次图遍历,具体延迟取决于你的图规模、跳数和图存储,自己压测。真正的例外是 Global Search:它要把参与的社区摘要分批,每批各生成一次部分答案,再汇总,LLM 调用数从 1 变成 摘要批次数 + 1——成本和延迟都是倍数级上升。所以生产上 Global Search 必须限制参与的社区数,并且只对确实需要全局视角的 query 开放(§19.15 的路由)。

增量更新

图的增量更新比 chunk 索引复杂:

  • 新加 chunk → 抽实体 → 可能产生新实体或新关系
  • 新实体要做 entity resolution(和已有合并还是独立)
  • 新关系可能影响社区结构 → 社区需要重算
  • 社区摘要可能过时 → 需要重写

多数 GraphRAG 实现不做细粒度增量——定期全量重建(类似第 8 章 §8.6 的周期性重建)。

19.9 RAPTOR:另一条路径

讨论 GraphRAG 时常被一起提的是 RAPTOR(arXiv:2401.18059,Stanford 2024)——不是图、是递归树。

思路:

  • 把所有 chunk 做 embedding 后聚类(论文用 UMAP 降维加高斯混合模型的软聚类,一个 chunk 可以属于多个簇),底层是原 chunk,上层是每个簇的 LLM 摘要
  • 递归往上,每层对上一层的摘要再聚类、再总结,直到无法继续聚类为止
  • 检索时可以把所有层的节点摊平一起查(论文的 collapsed tree,效果优于逐层遍历)——具体问题命中底层 chunk,全局问题命中高层摘要

RAPTOR 和 GraphRAG 的对比:

维度 GraphRAG RAPTOR
结构 图(实体 + 关系) 树(层次摘要)
索引成本 高(每 chunk 抽实体关系) 中(每层聚类 + 摘要)
检索 图遍历 + 向量 多层并行向量
强项 多跳关系 全局性问题
弱项 成本 / 实体对齐 细粒度事实

多数生产场景里 RAPTOR 比 GraphRAG 更快上手——不需要实体对齐这个头号难点。如果你只需要"全局概括"能力、不需要"关系推理",RAPTOR 是更经济的选择。

更多新路线

2024-2026 年类似思路的新研究:

  • HippoRAG(arXiv:2405.14831,NeurIPS 2024):模拟海马体的 personalized PageRank + 向量
  • GraphReader(arXiv:2406.14550,EMNLP 2024):先把长文本组织成图,再让 LLM agent 按问题在图上由粗到细地自主探索
  • LightRAG(本章 §19.4)的持续演化

这些路线都在探索"结构化 + 语义"的不同折中。生产系统不必追最新——等某条路线有 6-12 个月的社区验证再采用。

19.10 GraphRAG 的可观测性

和 chunk RAG 不同的专门指标:

  • entity_count / relation_count:图规模,看增长趋势
  • avg_entity_degree:每个实体的平均邻居数,反映图的密度
  • orphan_entity_rate:没有任何关系的孤立实体比例——关注的是趋势和量级,不是某个固定阈值:这个比例突然上升,通常意味着抽取 prompt 改坏了或实体对齐失效了
  • community_count_by_level:各层次社区数量,Leiden 发现的结构是否稳定
  • global_search_coverage:Global 问题检索到多少社区,过少说明社区摘要漏信息

图质量的人工评估:随机抽 100 条关系,人工判断对不对。这是 GraphRAG 的底线质量门槛。

19.11 GraphRAG 和 Knowledge Graph 的关系

传统 Knowledge Graph(KG)和 GraphRAG 有继承也有差异:

维度 传统 KG GraphRAG
构建 人工为主 / 规则抽取 LLM 自动抽取
关系类型 预定义 schema(类型有限、受控) 开放、LLM 自由发现
规模 受人工构建成本限制 随 LLM 调用预算扩展
更新 手动为主 LLM 自动
查询 结构化 SPARQL / Cypher 自然语言
准确性 高(人工保证) 中(LLM 抽取有错)

GraphRAG 是 "用 LLM 自动化降低 KG 构建成本" 的实验——牺牲一点准确性换规模和速度。对企业来说,有一张不完美但覆盖大的图,胜过一张完美但只覆盖小部分的图。

19.12 GraphRAG 的争议

GraphRAG 社区有几个持续争议:

争议 1:真的比 hybrid RAG 强吗

已有研究在对等条件下系统对比过两者,例如 Han et al. 2025《RAG vs. GraphRAG: A Systematic Evaluation and Key Insights》(arXiv:2502.11371)统一了预处理、检索和生成设置,在问答和 query-based summarization 基准上对比,结论是两者各有擅长,把两者结合的混合策略还能进一步提升。GraphRAG 的价值可能主要在全局问题,对 local 问题未必值得。

争议 2:索引成本值不值得

索引成本比 chunk RAG 高一个数量级以上(§19.8 给了算式,代进自己的语料规模和当期单价就能算出绝对值),这笔钱只有对大企业、高价值知识库才值得花。中小项目用 hybrid RAG 性价比更高。

争议 3:长 context + prompt caching 是否取代

long context(第 16 章)让"一次性把整个文档集送进 LLM"成为可能。配合 prompt caching 成本可控。一些场景下这比 GraphRAG 简单。

结论:GraphRAG 是工具、不是答案

没有"应该用 GraphRAG"的普适结论——要看具体问题类型、规模、预算。实操建议:

  • 先跑 hybrid RAG,看哪类问题是瓶颈
  • 如果瓶颈是全局 / 多跳 / 关系型,考虑 GraphRAG
  • 小规模先试 LightRAG / LLM-driven Neo4j
  • 大规模再上 Microsoft GraphRAG

19.13 GraphRAG 的实操入门路径

很多团队看到 Microsoft GraphRAG 就直接"上最复杂的"——结果成本超预算、效果没达到预期。理性的入门路径是分阶段验证。

阶段 1:评估是否需要

先看业务里的 badcase——有多少是多跳 / 全局 / 关系型问题?

  • < 10% → 继续优化 chunk RAG,不用 GraphRAG
  • 10-30% → 考虑 LightRAG 轻量方案
  • 30% → 值得上 Microsoft GraphRAG

上面三条线是决策规则的示意,不是实测出来的临界点:真正要量的是「这类 query 占业务的比例」这个数,而分界画在哪里取决于 GraphRAG 在你这里的建图成本、以及 chunk RAG 在这类 query 上还剩多少优化空间。不要"听说 GraphRAG 好就上"——先量化收益来源。

阶段 2:小规模 POC

选 1000-5000 份代表性文档,用 LightRAG 跑一次完整 pipeline。关注:

  • 实体抽取质量(人工抽 100 条看对不对)
  • Local search 对具体问题答对率 vs chunk RAG
  • Global search 对全局问题答对率 vs chunk RAG
  • 总索引成本(美元)和时间

POC 结果决定要不要扩规模。

阶段 3:生产化

POC 效果好再上 Microsoft GraphRAG 全量:

  • 选好 LLM(抽取用 Haiku 级,社区摘要用 Sonnet 级)
  • 建立实体对齐规则 + 同义词表
  • 规划增量更新策略(定期重建 vs 局部更新)
  • 把 Local / Global 两种检索路径都接入业务
  • 监控图质量指标(entity count、avg degree、orphan rate 等)

阶段 4:和 chunk RAG 并存

生产 GraphRAG 几乎永远和 chunk RAG 并存——query router 按类型分发(第 15 章)。具体问题走 chunk RAG,全局 / 多跳走 GraphRAG。一套 RAG 里两条路径,按需路由。

19.14 图的演化管理与错误溯源

GraphRAG 上线后很快会碰到两个"第二天问题":图会随时间失效(实体改名、关系废除、新实体进入),以及 GraphRAG 答错时比 chunk RAG 更难排查(错误可能来自抽取 / 对齐 / 图遍历 / 社区摘要里任何一环)。这两件事不处理,GraphRAG 很快就从"更聪明"变成"更难维护"。

图随时间演化的三种变化

flowchart LR
    classDef change fill:#fed,stroke:#c60
    classDef action fill:#def,stroke:#06c
    E[图的变化]:::change
    E --> C1[实体演化<br/>改名/合并/拆分]:::change
    E --> C2[关系失效<br/>旧依赖废除]:::change
    E --> C3[新实体进入<br/>产品/人员/概念]:::change
    C1 --> A1[别名表 + 历史引用]:::action
    C2 --> A2[关系带时间戳]:::action
    C3 --> A3[增量抽取 + resolution]:::action
  • 实体演化:产品"专业版"被重命名为"Pro 版"。旧 chunk 提 "专业版",新 chunk 提 "Pro 版"——是同一实体的两个时态
  • 关系失效:"A 模块依赖 B 库" 在 2025 年成立,2026 年 A 模块重构后不再依赖——旧 chunk 和新 chunk 里的关系相冲突
  • 新实体进入:新产品 / 新功能 / 新人员不断上线——图必须容纳,且不能产生"看似相同的独立实体"

不处理这些演化,图会越来越脏,最终答案里夹杂过时的关系断言。

三种演化管理策略

策略 A:定期全量重建

最简单粗暴——每周或每月清空图,全部重新抽取。

  • 优点:永远和文档最新状态一致,无需复杂版本逻辑
  • 缺点:每次重建都要把 §19.8 那笔索引账重付一遍,且重建期间查询不可用(除非做蓝绿切换)
  • 适合:文档量中等、业务变化快、预算允许

策略 B:时间戳化的边

每条关系带 valid_from / valid_to 时间戳。查询时按当前时间过滤。

MATCH (a)-[r:DEPENDS_ON]->(b)
WHERE r.valid_from <= date() AND (r.valid_to IS NULL OR r.valid_to > date())
RETURN a, r, b
  • 优点:历史查询可用("2025 年 6 月时 A 依赖什么"),增量更新友好
  • 缺点:LLM 抽取要给出时间戳或用 chunk 的 publish_date 代理,复杂度增加
  • 适合:有明确时间语义的领域(法律条款、合同版本、产品迭代)

策略 C:版本化 snapshot

整张图定期拍快照,保留 N 个版本。查询默认走最新,可指定历史版本。

  • 优点:能做"对比两个时间点的知识图"分析,回滚快
  • 缺点:存储成本翻倍以上
  • 适合:合规需求强的领域(金融、法律、医疗)

多数生产项目混合:每月全量重建作主路径 + 关键实体带时间戳作微调。

GraphRAG 错误溯源的五层回路

当 GraphRAG 答错,问题可能出在任何一环。一套系统性的排查顺序:

层级 检查项 工具 / 做法
L1:抽取 抽取结果漏了或抽错某实体/关系? 翻抽取日志、对比原 chunk
L2:对齐 同一实体被当成两个?或不同实体被合并? 查实体 resolution 历史
L3:图结构 关键关系是否在图里?方向是否正确? Cypher 查实体的邻居列表
L4:检索 query 路由到对的路径(local/global)了吗?召回的社区对不对? 看 retrieval trace
L5:生成 context 有足够证据但 LLM 没用上? 看 prompt 和 output

按这个顺序查有个理由:L2(实体对齐)是 GraphRAG 特有且最容易出问题的一层——"看起来答的是一个实体其实是另一个",或"看似无关的两个实体其实应该合并"。chunk RAG 完全没有这一层,所以团队的排查直觉里也没有它,往往一路查到 L5 才想起回头看对齐。先查 L1-L2,再往下。

调试必备的三个日志字段

每次 GraphRAG 查询 trace 至少记录:

  • 抽取来源:查询涉及的每个实体/关系是从哪个 chunk 抽出来的(source_chunk_id + line_range)
  • 对齐历史:这个实体节点是由哪些别名 / 原始出现合并而来(resolved_from: [...])
  • 检索路径:query 走了 local / global 哪条路、命中哪些社区

没这三个字段,GraphRAG 错答基本无法定位——只能靠"重建图重跑一次看是否复现",效率极低。

错误的优先级:先修哪些

GraphRAG 错答按业务影响分三档:

  • 高优先级:事实错(把 A 的属性说成 B 的)—— 可能涉及合规,必须立刻修
  • 中优先级:关系缺失(该关联没关联上)—— 功能降级但不危险
  • 低优先级:无关关系被召回(答案有噪声)—— 影响体验,可累积修

修单个错不一定值得——如果一类错是抽取 prompt 问题,改 prompt 比改图更经济。调试完要回到源头改,不要只改表现。

19.15 混合 RAG:GraphRAG 与 chunk RAG 的路由与融合

§19.13 入门路径最后一步是 "GraphRAG 和 chunk RAG 并存"——这不是说同时部署两个系统就完事。如何让每次查询走对的路径,两路结果如何融合,失败时如何兜底——这是 GraphRAG 上线后的实际工程挑战,前 14 节没展开。

三种混合模式

flowchart TB
    classDef route fill:#fed,stroke:#c60
    classDef chain fill:#def,stroke:#06c
    classDef par fill:#efe,stroke:#080
    M[混合模式]
    M --> A[路由式<br/>query 选一条路]:::route
    M --> B[串联式<br/>先 chunk 再 graph]:::chain
    M --> C[并行式<br/>两路独立 RRF 融合]:::par
    A --> A1[延迟最低 / 需要分类器]
    B --> B1[chunk 找入口 graph 多跳扩展]
    C --> C1[recall 最高 / 成本翻倍]

路由式:最常见。Query classifier 识别类型,事实型走 chunk RAG,全局型/多跳型走 GraphRAG。延迟最低、成本可控,但依赖 classifier 准确率。

串联式:chunk RAG 先召回 top-k 作为"锚点 chunk",从锚点提取实体,在 graph 里扩展多跳邻居,最终把 chunk + 图扩展合并送 LLM。适合"先找到起点再探索关系"的场景(复杂产品问题排查、研究文献综述)。

并行式:两路独立跑,各自 top-k,用 RRF(第 13 章)融合。recall 最高,但 GraphRAG 的延迟和成本不可忽略,每次都跑浪费严重——通常只在用户显式要求"深度答案"时启用。

Query classifier 的设计

路由的核心是 classifier。输入 query、输出 chunk | graph | both。三种实现:

  • 规则 + 模板:识别关键词("整体""对比""关系""原因""哪些")匹配到 graph 类;默认走 chunk。零延迟、零成本,能覆盖多少取决于你的 query 表达有多集中——去日志里统计一下这些词的出现率就知道
  • 小模型分类器:小型文本分类模型做二分类,训练数据来自历史 query + 人工标注。毫秒级推理,准确率要自己在留出集上测
  • LLM 判断:直接问 LLM "这个 query 是否需要图推理?"。最灵活,但要多付一次 LLM 往返的延迟和成本,高 QPS 下不划算

生产推荐:规则优先覆盖常见模式、小模型处理剩余、LLM 兜底极端长尾。三层叠加成本可控。

回退机制

混合 RAG 的任一路径都可能失败——GraphRAG 服务宕了、classifier 误判、graph 没相关实体。三级降级:

  • L1 路径失败:如 GraphRAG 超时或 error rate > 5%,新请求自动降级到 chunk RAG,带 warning tag
  • L2 结果为空:GraphRAG 选的路径但召回为空,自动退到 chunk RAG 重跑
  • L3 质量过低:GraphRAG 返回的答案 grounding 分数 < 0.5,触发 chunk RAG 二次验证,取置信高的一路输出

降级代价是延迟翻倍(失败路径 + 成功路径)——但比"给用户一个错答案"好。

三种模式的成本结构

延迟和成本的绝对值必须自己压测(它们取决于你的图规模、模型、部署),但相对结构是可以推出来的,而且这才是选型要看的东西:

模式 LLM 调用数 延迟构成 相对成本
纯 chunk RAG 1 检索 + 生成 基准
纯 GraphRAG(local) 1 图遍历 + 生成 ≈ 基准(多一次图查询)
纯 GraphRAG(global) 摘要批次数 + 1 多次生成串行/并行 倍数级
路由式混合 1(+ classifier) 走哪条算哪条 ≈ 基准
串联式混合 1 检索 + 图扩展 + 生成 ≈ 基准(延迟叠加)
并行式混合 2 两路取慢的那条 两倍起

结论直接从这张表读出来:

  • 路由式性价比最高——每次只走一条路,成本贴近纯 chunk RAG,代价是需要一个准的 classifier
  • 并行式是最贵的——每次都跑两路,成本翻倍且 P99 由慢的那路决定。只在用户显式要"深度答案"时开
  • 真正贵的不是"图",是 Global Search 的多次生成——把它和"GraphRAG 贵"混为一谈会做出错误决策:local search 其实并不贵

要对比的话,自己跑:同一批 query、同一个模型、分别走三种模式,记 P50 / P99 和实际 token 用量。别人的表格换个语料就不成立。

可观测性的特殊要求

混合 RAG 的监控要分路径拆,否则平均指标掩盖单路问题:

  • classifier_accuracy_per_type:classifier 对各类 query 的判准率
  • route_p99_latency by route:chunk / graph 各自的 P99
  • downgrade_rate:从 L1 → L2 → L3 降级的触发频率
  • per_route_cost_share:chunk RAG 和 GraphRAG 在总成本里的占比,和预算对比
  • answer_quality_by_route:按路径分的答对率,哪路拉低了整体

没有分路径视图,出问题时只知道"整体质量降了",无法归因到具体路径。

评估混合 RAG

评估要针对每类 query 跑对应路径的 gold set,不能混在一起跑。典型做法:

  • gold_chunk_rag:纯事实类 query,只用 chunk RAG 评估
  • gold_graphrag:多跳/全局类 query,只用 GraphRAG 评估
  • gold_mixed:真实流量分布采样,走完整混合 pipeline 评估

最终业务看 gold_mixed——它反映真实用户体验。分路径 gold 用于 debug。

常见坑

  • 只监控整体 recall:混合 RAG 整体 recall 好看,其实 chunk 那路掉了 5 点,被 graph 那路拉起来,下次 graph 也掉时就崩
  • classifier 训练集偏斜:用历史 query 训 classifier,但历史 query 分布和未来流量不同,尤其新产品上线后
  • GraphRAG 降级后没告警:GraphRAG 持续 failing,系统在自动降级 chunk RAG,但团队不知道,GraphRAG 等于白建
  • 并行式打不住成本:默认并行跑两路,每次查询的成本翻倍且延迟由慢的那路决定,又没有路由 / 降级的保护

何时真的需要混合

和 §19.7 呼应——并不是所有 RAG 都要混合。判断是否值得:

  • chunk RAG 已跑通且稳定 6+ 个月 → 可以考虑加 GraphRAG
  • badcase 归因显示 "多跳 / 全局" 类占比 > 15% → 值得加(示意线,和 §19.13 的分档一样按自己的成本收益定)
  • 团队有 ML + 运维能力维护两条路径 → 否则复杂度压过收益
  • 业务能承受多出来的那笔离线索引成本(§19.8 给了算式;它比纯 chunk RAG 高一个数量级以上,但只发生在建图和重建时,不是每次查询)

满足这四条才上混合。否则先把 chunk RAG 做到极致,比过早上 GraphRAG 收益高。

19.16 Community 摘要的质量工程:global search 的命根

§19.3 Microsoft GraphRAG 的核心创新是 community 摘要 + global search——它针对的是"整个文档集说了什么"这类 chunk RAG 结构上答不好的问题(论文实际验证了什么,§19.3 已经讲清楚,别给它安一个 recall 提升的数字)。但这套机制的命根是 community 摘要的质量。摘要写得差,global search 就只是"另一种普通检索",失去 GraphRAG 的独特价值。这一节把摘要这一步的工程细节讲清楚——它往往被低估,但却决定 global search 能否真正"全局"。

Community 摘要是什么

flowchart TB
    classDef ent fill:#fed,stroke:#c60
    classDef comm fill:#def,stroke:#06c
    classDef sum fill:#efe,stroke:#080
    E[实体 + 关系]:::ent
    E --> LEIDEN[Leiden 聚类]
    LEIDEN --> C1[Level-0 社区<br/>几十个]:::comm
    LEIDEN --> C2[Level-1 社区<br/>几百个]:::comm
    LEIDEN --> C3[Level-2 社区<br/>几千个]:::comm
    C1 & C2 & C3 --> LLM[LLM 写摘要]
    LLM --> S[社区摘要<br/>每个社区 200-500 字]:::sum
  • 一个 community 是一组紧密相关的实体(关系密度高)
  • 每个社区的 摘要 是 LLM 根据这些实体 + 关系生成的 200-500 字描述
  • Global search 时(按论文的做法)选定层级的社区摘要分批送 LLM,每批产生一份部分答案,最后汇总——不是先做向量检索筛社区

摘要是社区的"自然语言门面"——全局查询看的是摘要,不是原始实体关系。摘要好,global search 能抓到"整份数据的宏观脉络";摘要差,抓到的是"几十个无关的一句话"。

好摘要的四个特征

  • 信息密度高:200-500 字里涵盖 5-15 个核心实体和它们的关系,不堆砌琐碎
  • 主题聚焦:一段摘要讲一个主题,不是实体列表串联
  • 可检索性强:摘要里出现的关键词符合用户 query 的表达——便于向量和 BM25 召回
  • 层次感:Level-0 摘要是高层概括,Level-2 摘要是具体事实——不同 query 命中不同层

写不出这四点的摘要等于没写。

层次化摘要的组织

Microsoft GraphRAG 用 Leiden 算法的多层次社区——每一层都生成摘要。查询时用哪一层是可选的,下面是按问题粒度分层使用的思路:

Level-0 (粗): 本文档集涉及 AI 基础设施,包括向量库、LLM、Agent 架构三大主题...
Level-1 (中): 向量库部分涵盖 Qdrant、Milvus、pgvector 三款方案的选型...
Level-2 (细): Qdrant 是 Rust 实现,支持 HNSW 索引和 payload 过滤...

三层摘要对应三类 query:

  • "这份数据整体讲什么" → 命中 Level-0
  • "向量库有哪些选择" → 命中 Level-1
  • "Qdrant 的索引算法" → 命中 Level-2

Prompt 设计

社区摘要的 prompt 往往写得很长——Microsoft GraphRAG 自带的社区报告 prompt 连示例在内有一百多行。核心要素:

你是一个技术领域的分析师。下面是一组紧密相关的实体和关系、请写一段
200-500 字的摘要,描述这些实体涉及的主题。

要求:
1. 用概括的语言,不要罗列实体名
2. 重点突出:核心主题、关键实体间的关系、这个主题的代表性发现
3. 避免冗余:同一实体不重复提,同一关系只说一次
4. 保留可检索的关键词:实体名、技术术语、产品名

实体:
{entities}

关系:
{relations}

示例好摘要:
{few_shot_good_examples}

示例坏摘要(不要这样写):
{few_shot_bad_examples}

输出 JSON: {"summary": "...", "key_topics": [...], "core_entities": [...]}

Few-shot 示例是关键——LLM 对"好摘要"的直觉没经过这个 prompt 是不一致的。人工标 5-10 个好坏例子,LLM 能稳定学到。

评估摘要质量

评估社区摘要的独立指标(不是 global search 最终答对率,是摘要本身):

  • Coverage:社区里的核心实体 / 关系是否都被提及,人工抽检比例
  • Conciseness:摘要长度 / 信息量比例,超长低密度摘要扣分
  • Topic coherence:多个评估员读摘要能否一致识别主题
  • Retrievability:用模拟 query 检索,摘要能否被命中

人工抽检 100 个摘要,评这四维——摘要质量低于阈值触发重新生成。

摘要的更新策略

图在变(§19.14),摘要也要跟着变。两种更新策略:

  • 完全重生成:图结构变了,所有摘要重写。成本高(每个社区一次 LLM 调用),但质量最好
  • 增量更新:只对变化大的社区重写,小变化的保留。触发条件可以按社区实体数或关系数的变化比例来设,阈值在自己的数据上标定

生产推荐:每月一次完全重建 + 日级增量——平衡成本和质量。

摘要的常见坑

  • 摘要过短:50 字的摘要没信息——向量检索命中率低,无法覆盖社区全貌
  • 摘要像实体列表:"涉及 A、B、C、D、E 实体"——LLM 偷懒、没有抽象、没有主题
  • Level-0 摘要不够高层:应该是"全数据集概览",却写成"某具体主题"——global 命中效果差
  • 没有层次区分:三层摘要写得一样,层次化没起作用
  • 摘要不更新:数据变了摘要没变,global search 答的是过期信息

摘要 vs RAPTOR 的异曲同工

§19.9 讲过 RAPTOR 的递归层次摘要——其实和 GraphRAG 的社区摘要结构同构:

  • RAPTOR:用层次聚类组织,从 chunk 层层向上摘要
  • GraphRAG:用图社区聚类组织,从实体层层向上摘要

两者都在解决同样的问题:"对大规模数据的宏观理解"。选哪个看:

  • 有明确实体关系结构 → GraphRAG 社区摘要
  • 主要是非结构化文本 → RAPTOR 层次摘要
  • 两者结合:高层用 RAPTOR 聚类,低层用 GraphRAG 关系——极少数大项目会试

摘要质量的投入产出

社区摘要在整个索引成本里占多少,按 §19.8 的算式能自己算出来:它等于社区数 × 每社区一次 LLM 调用,而实体抽取是chunk 数 × 每 chunk 一次 LLM 调用。别想当然地认为社区数远小于 chunk 数——各层社区都要写摘要,论文的播客数据集切成 1669 个 chunk,C0–C3 四层社区合计却有 2680 个(34 + 367 + 969 + 1310)。所以摘要这一项要按式子实算,不能当零头;但不管它占多少,这笔钱都值得花在质量上:

  • 好摘要让 global search 真正"全局"
  • 好摘要让人工审核员能用简短时间理解每个社区
  • 好摘要是 GraphRAG 相对 chunk RAG 的核心差异化

省摘要钱 = 失去 GraphRAG 的独特价值 = 不如直接用 chunk RAG。所以要么认真做摘要质量、要么不上 GraphRAG——中间态是浪费。

19.17 Ontology 设计:实体和关系类型的体系建构

§19.6 讨论了实体抽取的 prompt 工程——但跳过了一个上游决策:实体和关系应该有哪些类型?这叫 ontology(本体论)。看起来抽象,但实际是 GraphRAG 最关键的 upfront 决策——ontology 错了,后续所有抽取 / 检索 / 归因都偏了。传统 KG 项目在 ontology 上投入巨大——GraphRAG 让这块变轻了(LLM 能学),但不等于不重要。

Ontology 是什么

在 GraphRAG 里,ontology 是:

  • 实体类型清单:哪些类型是实体(人、组织、产品、概念、地点、事件……)
  • 关系类型清单:实体间有哪些关系(属于、包含、依赖、对比、替代、影响……)
  • 约束规则:哪些类型间可以有哪些关系("人" 可以 "属于" "组织",但不能 "包含" "产品")
flowchart TB
    classDef type fill:#fed,stroke:#c60
    classDef ent fill:#def,stroke:#06c
    classDef rel fill:#efe,stroke:#080
    O[Ontology]
    O --> T[类型系统]:::type
    T --> E1[实体类型]:::ent
    T --> E2[关系类型]:::rel
    T --> C[约束规则]:::type
    E1 --> E11[人 / 组织 / 产品 / 概念 / 地点]
    E2 --> E21[属于 / 包含 / 依赖 / 对比]
    C --> C1[人 -属于- 组织 ✓]
    C --> C2[产品 -包含- 功能 ✓]

三种 ontology 路线

路线 A:开放 ontology(Open)

LLM 自由生成实体和关系类型,不预定义:

  • 优点:灵活、覆盖新概念
  • 缺点:同义类型冗余("依赖" 和 "需要" 被 LLM 视为不同关系),图质量低

Microsoft GraphRAG 接近这条路线,但并不完全开放:实体类型默认给了一个小列表(organization、person、geo、event),关系则不分类型,只写自由文本描述和强度分。

路线 B:预定义 ontology(Closed)

业务专家提前定义所有实体 / 关系类型,LLM 只能从列表选:

  • 优点:一致性高、可复用
  • 缺点:新概念覆盖不到、设计成本高

传统 KG / 法律 / 医疗领域常用。

路线 C:混合 ontology(Hybrid)

预定义核心类型 + 允许 LLM 生成长尾类型,定期合并进核心:

  • 优点:兼顾覆盖和一致性
  • 缺点:实现复杂、需要持续维护

本专栏推荐的路线。

粒度决策

Ontology 的粒度是核心取舍:

粒度 实体类型示例 优点 缺点
粗 实体 / 概念 / 关系 简单、稳定 区分度低
中 人 / 组织 / 产品 / 事件 / 概念 平衡 普适
细 CEO / 客户 / 员工 / 部门 / 产品线 / SKU 业务贴合 维护重

类型数量没有通用的甜点值,要在试跑(见下面的设计流程)里看两件事:类型太少时,同一类里混进了语义差很远的实体;类型太多时,LLM 在相近类型之间摇摆,一致性下降。

领域特化

不同领域的 ontology 差异巨大:

技术文档:

  • 实体:产品、组件、技术术语、错误码
  • 关系:依赖、替代、兼容、弃用

法律:

  • 实体:法律条款、案例、当事人、法院
  • 关系:引用、反驳、修正、适用于

医疗:

  • 实体:疾病、症状、药品、治疗、基因
  • 关系:引起、治疗、副作用、拮抗

金融:

  • 实体:公司、产品、人员、市场、指标
  • 关系:持股、合作、竞争、上下游

起步时先去找领域里已有的标准本体或受控词表参考——医疗有 SNOMED CT、UMLS,生命科学有 Gene Ontology,法律领域也有若干立法与判例的元数据标准。别从零设计:这些体系里的类型划分是领域内多年争论的结果,哪怕你只借鉴它的顶层分类,也比自己拍脑袋强。

设计流程

构建 ontology 的推荐流程:

  1. 业务 workshop:3-5 位领域专家,列出关键实体和关系
  2. 初版规范:列出初版的实体类型和关系类型,写成表格或 RDF
  3. LLM 辅助扩展:让 LLM 对样本文档抽取,对比预定义列表,补充遗漏
  4. 试跑验证:小样本 100 篇文档,跑抽取,人工审查一致性
  5. 迭代:根据抽取结果调整列表(合并同义、拆分过粗)
  6. 固化 v1:投入生产、监控

初版一般要几周(经验估算,视领域复杂度而定),之后定期 review,例如每季度一次。

Ontology 的演化

业务在变、ontology 也要变:

  • 新产品上线:加新产品实体类型
  • 法规更新:加新关系类型
  • 同义识别:"依赖" 和 "需要" 合并为一个

版本化很重要——每次变更带版本号,旧图带旧版本 tag,新抽取用新版本,查询时可指定版本。

{
    "ontology_version": "v2.3",
    "entity_types": [...],
    "relation_types": [...],
    "changelog": [
        {"ver": "v2.3", "change": "合并 '依赖' 和 '需要' 为 'depends_on'"},
        {"ver": "v2.2", "change": "新增实体类型 '合规要求'"}
    ]
}

Ontology 的约束规则

除了列表、约束让图质量更高:

constraints:
  - source_type: Person
    relation: works_at
    target_type: Organization  # 只允许人 -工作于- 组织
  
  - source_type: Product
    relation: depends_on
    target_type: [Product, Technology]  # 产品可依赖产品或技术
  
  - source_type: Person
    relation: contains  # 禁止人 "包含" 某对象
    target_type: null

LLM 抽取时违反约束 → 该关系被拒绝 / 标为 pending review。

实体对齐 (entity resolution) 的上游

§19.6 讲过实体对齐——但好的 ontology 能大幅减少对齐需求:

  • 预定义类型让同类实体走同一 resolution rule
  • "企业版" 和 "Enterprise" 通过产品类型的 alias 表快速合并
  • LLM 在预定义类型下更容易识别 "这是同一个实体"

这就是ontology 和实体对齐的协同——共同决定图质量。

Ontology 的 prompt 注入

把 ontology 注入 LLM 抽取 prompt:

请从文本中抽取实体和关系。

允许的实体类型:
- 产品(Product)
- 功能(Feature)
- 技术(Technology)
- 公司(Company)

允许的关系类型:
- 包含(contains): 产品 → 功能
- 依赖(depends_on): 产品 → 技术 / 产品
- 开发(developed_by): 产品 → 公司

请严格按列表选、不要自由创造。

文本:{chunk}

LLM 按列表抽,类型一致性显著提升。

Ontology 的反模式

  • 太细:实体类型多到 LLM 无法稳定区分,一致性反而差
  • 太粗:寥寥几个类型,区分度不足,检索精度低
  • 不更新:业务变了 ontology 没跟,抽取偏差累积
  • 没有约束规则:类型有但 LLM 乱连,图质量差
  • 英中混用:类型名混用中英,"Person" 和 "人" 指同一类但对不上

Ontology 的投入回报

构建 + 维护 ontology 的投入(经验估算,视领域和团队而定):

  • 初版:2-4 人周
  • 维护:每季度 1-2 人天
  • 和 LLM 抽取联动:prompt 里加 ontology 约束,几行代码

收益是结构性的,不需要具体数字来论证:

  • 抽取一致性提升——LLM 从固定列表里选类型,比自由生成稳定得多,这是机制决定的
  • 实体对齐的工作量下降——同类实体走同一套 resolution 规则和别名表,不用为每个实体单独判断
  • 社区发现更干净——类型一致的图,聚类结果才有语义
  • 下游归因明确——错答能定位到"是哪个类型抽错了",而不是笼统的"抽取有问题"

先投 ontology、再优化抽取 prompt——顺序反了事倍功半:ontology 一改,之前调好的抽取 prompt 和已经抽出来的图全要重来。

19.18 GraphRAG 的专门评估:普通 eval 框架不够

第 20 章的评估框架基于 chunk RAG 设计——对 GraphRAG 部分适用、部分不够。GraphRAG 有三层独有的质量(抽取质量、图结构质量、社区摘要质量)——每层都需要专门评估。通用 RAG 评估工具基本不覆盖这几层:以本专栏核对的 Ragas 0.4.3 为例,和实体沾边的只有 ContextEntityRecall(比较检索到的 context 与参考答案中的实体重合度)和生成测试集用的 KnowledgeGraph(src/ragas/testset/graph.py),都不是用来评估建图质量的——这部分得自建。这节给 GraphRAG 评估的专门方法。

为什么普通 eval 不够

flowchart TB
    classDef general fill:#def,stroke:#06c
    classDef graphDef fill:#fed,stroke:#c60
    G[普通 RAG eval]:::general
    G --> G1[recall / NDCG]:::general
    G --> G2[faithfulness]:::general
    S[GraphRAG 专有]:::graphDef
    S --> S1[实体抽取质量]:::graphDef
    S --> S2[关系抽取质量]:::graphDef
    S --> S3[图连通性 / 密度]:::graphDef
    S --> S4[Community 摘要质量]:::graphDef
    S --> S5[Global search 覆盖]:::graphDef
    S --> S6[Multi-hop 推理正确性]:::graphDef

普通 eval 只测最终答案——GraphRAG 要分层评估找到根因。

实体抽取的评估

Gold set 构造:

[
  {
    "text": "企业版支持 SSO 和 LDAP、由身份团队维护",
    "expected_entities": [
      {"name": "企业版", "type": "product"},
      {"name": "SSO", "type": "feature"},
      {"name": "LDAP", "type": "feature"},
      {"name": "身份团队", "type": "team"}
    ]
  }
]

指标:

  • Entity Precision:抽出的实体里,对的比例
  • Entity Recall:应抽的实体里,抽到的比例
  • Type Accuracy:类型标对的比例

这三个数的目标值由你的 ontology 粒度和领域决定,不要照搬别人的门槛。要看的是:先测出基线,然后每次改 prompt 或换模型时这三个数分别怎么动——Precision 掉说明抽出了太多噪声实体(prompt 的排除项要加强),Recall 掉说明漏抽(要补 few-shot),Type Accuracy 掉说明类型定义有歧义(回去改 ontology)。

关系抽取的评估

关系是三元组 (subject, predicate, object):

[
  {
    "text": "企业版支持 SSO 和 LDAP、由身份团队维护",
    "expected_relations": [
      {"s": "企业版", "p": "supports", "o": "SSO"},
      {"s": "企业版", "p": "supports", "o": "LDAP"},
      {"s": "身份团队", "p": "maintains", "o": "企业版"}
    ]
  }
]

指标:

  • Relation F1:三元组精确匹配
  • Relation 方向:s 和 o 是否颠倒(常见错误)

关系比实体难得多——三元组要三个位置同时对,而且方向错是高频错误。所以 Relation F1 通常明显低于 Entity F1,这是正常的,不要按同一个标准苛求。真正要盯的是"方向错"这一类:它在 F1 上只扣一点,但在图里会造成完全错误的遍历方向。

图结构质量

图本身的统计指标:

  • 节点数 / 边数:量级,和预期对比
  • 平均度:每个节点平均连几条边,太低说明图稀疏
  • Orphan rate:孤立节点(度 = 0)比例,同 §19.10,看趋势而不是套固定阈值
  • 最大连通分量占比:图是否一个大块还是碎成多个小块
  • 度分布:是否 power law(少数节点高度连接、多数节点少连接)

不健康的图:

  • Orphan rate 明显偏高或突然上升:很多实体"孤立",说明抽取或 resolution 漏了
  • 最大连通分量只占节点的一小部分:图太碎,global search 覆盖低

Community 摘要的评估

§19.16 讨论过——指标:

  • Coverage:摘要覆盖社区核心实体的比例
  • Conciseness:信息密度 = 关键点 / 总字数
  • Topic coherence:读摘要能否一致识别主题(人工或 LLM-judge)
  • Retrievability:模拟 query 检索社区摘要的命中率

每月抽样 50-100 个社区做人工 review。

Global search 的评估

Global search 的 gold set 和普通 query 不同——要 全局性问题:

[
  {
    "query": "这个产品集合的整体架构思路是什么",
    "expected_themes": ["分布式", "事件驱动", "多租户"],
    "expected_top_level_communities": ["架构概览", "核心服务"]
  }
]

指标:

  • Theme coverage:答案提到的核心主题数 / 期望数
  • Cross-community consistency:多个社区的贡献在答案里协调吗
  • Depth vs breadth 平衡:答案既概览又有关键细节

普通 recall 不适用——全局问题没有单一 gold chunk。

Multi-hop 推理的评估

GraphRAG 的卖点之一——多跳推理:

[
  {
    "query": "影响企业版 SSO 升级的上下游团队有哪些",
    "expected_reasoning_chain": [
      "企业版 SSO -> 由 身份团队 维护",
      "身份团队 -> 依赖 基础设施团队 的 IAM 服务",
      "企业版 SSO -> 影响 客户成功团队 的 onboarding 流程"
    ],
    "expected_entities": ["身份团队", "基础设施团队", "客户成功团队"]
  }
]

指标:

  • Multi-hop correctness:推理链是否正确
  • Relevant entity recall:应出现的实体出现比例
  • No spurious entities:没出现不相关实体

Multi-hop 是 GraphRAG 和 chunk RAG 最大差异——专项评估才能测出价值。

和 chunk RAG 的对比评估

GraphRAG 的核心问题:"比 chunk RAG 好多少"——必须双系统对比:

def compare_gr_and_chunk(gold_set):
    results = {"graphrag": [], "chunk_rag": []}
    for query in gold_set:
        gr_answer = graphrag.query(query)
        cr_answer = chunk_rag.query(query)
        
        gr_score = evaluate(gr_answer, query.expected)
        cr_score = evaluate(cr_answer, query.expected)
        
        results["graphrag"].append(gr_score)
        results["chunk_rag"].append(cr_score)
    
    # 按 query 类型分桶看
    for qtype in ["fact", "multi-hop", "global"]:
        gr_mean = mean(r for r, q in zip(results["graphrag"], gold_set) if q.type == qtype)
        cr_mean = mean(r for r, q in zip(results["chunk_rag"], gold_set) if q.type == qtype)
        print(f"{qtype}: GraphRAG={gr_mean:.2f}, ChunkRAG={cr_mean:.2f}, Diff={gr_mean-cr_mean:+.2f}")

这里不给示例分数——具体数值完全取决于语料和 gold set。要看的是三类 query 上差值的符号和量级,预期模式是这样的:

  • Fact 类:两者接近,GraphRAG 可能还略差一点(多绕了一层实体,反而可能引入噪声)
  • Multi-hop 类:GraphRAG 应该明显领先——如果不领先,说明图或实体对齐有问题,先回 §19.14 排查,不要急着调检索
  • Global 类:GraphRAG 应该领先最多,因为 chunk RAG 在这类问题上根本没有对应机制

按 query 类型分桶看,才看得出 GraphRAG 的真实价值——整体均值会被占比最大的那类(通常是 fact)拉平,让你误以为 GraphRAG "没什么用"。

评估的资源投入

GraphRAG 评估比普通 RAG 重(以下人力为经验估算):

  • Gold set 构造:500-2000 条多类型、2-4 人周
  • 人工 review(社区摘要 / 多跳):每月 2-3 人天
  • 自动化 pipeline:2-3 人周

高门槛——是否值得投看 GraphRAG 对业务多重要。不投入评估的 GraphRAG 几乎一定失败——因为没数据证明比 chunk RAG 好,团队无法说服自己坚持投入。

评估的运营节奏

  • Daily:gold set 自动回归,抓 regression
  • Weekly:per-query-type 细分指标趋势
  • Monthly:社区摘要抽样人工 review
  • Quarterly:Gold set 扩充,cover 新 badcase

这个节奏让 GraphRAG 质量可控演化——不是"投入后放任"。

评估工具链

自建工具(原因见本节开头:通用评估框架不评建图质量):

  • Entity/relation evaluator:基于 gold set 的精确匹配
  • Graph stats dashboard:Grafana 看图指标
  • Community summary reviewer:UI 让人工 review
  • GR vs ChunkRAG comparison UI:对比两系统对同一 query 的答复

初期建设约 2-3 人月(经验估算)。看似多——GraphRAG 没这些等于盲跑。

GraphRAG 评估的反模式

  • 只测最终答案:好答案也可能是 chunk RAG 的功劳,不是 graph 的
  • 不分 query 类型:fact / multi-hop / global 混在一起,均值掩盖差异
  • 忽略图结构:最终答案好但图质量差,长期不可持续
  • 不对比 chunk RAG:不知道 GraphRAG 有没有意义
  • Gold set 太小:100 条 gold 的结论不够信

GraphRAG 是否值得的最终 check

GraphRAG 上线半年后,用评估数据回答:

  • Multi-hop / global query 占业务的比例?(> 15% 是示意线,见 §19.13)
  • GraphRAG 在这些 query 上比 chunk RAG 好多少?差距要大到值回索引成本——把上面那个占比乘以差距,和 §19.8 算出来的索引账放在一起比
  • 投入的人力和成本是否可持续?
  • 业务侧是否感知到质量提升?

四项都 yes——继续。某项 no——考虑退回纯 chunk RAG,专注做好 hybrid。

不要"试了 GraphRAG 就必须留着"——评估数据说话。

19.19 知识图谱的可视化与人工编辑

GraphRAG 的 graph 是 LLM 抽出来的,不完美,需要人 review 和修。可视化加人工编辑界面就是这件事的基础设施——没有它,§19.18 讲的那套评估即使把 badcase 归因到某个错误节点,也不知道怎么改、改不快。

flowchart TB
    classDef need fill:#fed,stroke:#c60
    N[可视化的必需]
    N --> N1[理解图结构<br/>人类擅长视觉]:::need
    N --> N2[发现异常<br/>孤立节点 / 错连接]:::need
    N --> N3[人工编辑<br/>修错误 / 补遗漏]:::need
    N --> N4[向业务展示<br/>证明 graph 价值]:::need
    N --> N5[debug 复盘<br/>为什么答错]:::need

工具选型与必备功能

起点推荐 Neo4j Browser——即使生产后端不是 Neo4j,把图导出成 Cypher 导入一个 Neo4j 实例就能用,交互也最成熟;规模上来后再自建。其余选项各有定位:Gephi 是学术界主流的专业图可视化,Cytoscape 灵活但上手陡峭,D3.js / vis.js 自建投入大但完全可控,Linkurious / Graphistry 一类商业工具面向企业级,其中 Graphistry 是 GPU 加速的大图方案。同类还有 Neo4j Bloom(Neo4j 的商用可视化)、Memgraph Lab、以及做静态图的老牌 Graphviz。选型看规模、预算和定制需求,没有银弹。

必备功能是一组固定清单:节点和边的渲染(按类型着色)、交互(点击展开邻居、拖拽、缩放)、按名字搜索定位、按类型和属性筛选,以及局部视图(以某实体为中心显示 N 跳邻居,防止整图爆炸)。进阶的有社区着色、图随时间变化的 playback、两版 graph 的 diff、按检索频率放大节点的热力图。

还要同时支持两种输出模式:interactive 用于日常探索和编辑,static export(PDF / PNG 快照)用于发邮件和写报告——专业用户要前者,管理层要后者。

一个值得跟的方向是 AI 协助编辑:用自然语言下指令(「把企业版和专业版归为兄弟关系」)、AI 标红可疑节点和边、自动发现重复并建议 review。这类能力还在快速演进,选型时以各工具当期文档为准。

人工编辑:四类操作与冲突处理

日常编辑基本落在四类场景里。修错抽取:LLM 把「企业版」和「企业用户」识别成同一实体,人工打开节点、从 alias 列表移除、另建新节点。补遗漏关系:抽取漏了「企业版 → 依赖 → SSO 模块」,人工找到两个节点、拖拽连接、标注关系类型。合并重复实体:「企业版」和「企业 版」(多一个空格)成了两个实体,选中后 merge,选定 primary 名、其余转 alias。拆分过度合并:LLM 把 iPhone 15 和 iPhone 16 都归到「iPhone」,粒度太粗,选中后 split、按属性分开。

Graph 改动要像代码一样可追溯:

{
  "edit_id": "edit-abc",
  "timestamp": "2026-04-25T10:00",
  "editor": "alice@example.com",
  "action": "merge_entities",
  "before": ["entity-456", "entity-789"],
  "after": "entity-456",
  "reason": "duplicate entities from LLM extraction"
}

谁改的、什么时候、什么动作(create / update / delete / merge / split)、前后状态、理由——类似 git commit,可追溯也可回滚。

真正的难点在人工编辑和自动抽取的冲突。三条 pipeline 汇到同一个 graph:LLM 从新文档抽取覆盖大多数,人工 review 争议 case(每月几百条),人工编辑修 bug(每周几十条)。冲突长这样:LLM 抽出「企业版 → 依赖 → SSO」,人工改成「企业版 → 使用 → SSO」,之后 LLM 从新文档又抽出「依赖」——要不要覆盖人工的判断?答案是不覆盖:给人工改过的边打上 manually_edited: true,自动抽取绕开;再定期 review 冲突列表由人判决;同时把人工修正反馈进抽取 prompt,让下次抽得更准。

改动权限按 graph 的重要性分级:非关键的内部 graph 团队成员都能编辑;生产关键 graph 要双人 review 加 approval;医疗、法律这类合规敏感 graph 需要专家审批并留审计记录。

UI 工程的两条硬约束:规模与隐私

规模。千万实体的 graph 不可能全量渲染,浏览器会挂。四条约束是必须的:每次只显示几百个节点的局部视图、缩放时把节点聚合成 community、由搜索驱动而不是「打开就是全图」、按重要性分层采样。

隐私。graph 里含敏感信息,内部工具同样有合规要求,不能「内部就随便看」。要做权限控制(谁能看全图、谁只能看子图),对非授权人员展示时 mask 掉 PII,并记录谁看了哪部分。

实现上建议前端用 React / Vue 渲染,和后端解耦,只调 Graph API 做实体与关系的增删改查;多人同时查看和编辑(类似 Figma 的实时协作)在治理团队超过两三人后价值明显。按经验估算,基础功能约 1–2 人月,协作和审批再加 1 人月,之后每季度 1 人周维护。

让业务方也能用

一个常见反模式:graph 只有工程师看得懂——Cypher 查询、命令行。业务方看不到,价值就不被认可。好的做法是给业务方一套不需要懂 Cypher 的界面,只需要懂自己的业务。

这里有个能形成闭环的设计:graph 里某个实体错了,根源往往是某份文档写错了。点击节点显示「这个实体来自哪些文档」并链接过去,业务方看到后直接去修文档——graph 层的可视化驱动了文档层的治理。

Graph 治理本身是多角色协作:graph owner 定策略、批准重要变更,领域专家提供知识并参与 review,工程师负责工具和 pipeline,合规负责审计与合规要求。graph 是团队工作,不是 LLM 一家之言。

投入产出上,小团队用 Neo4j Browser 就够,大团队值得自建。收益不在于某个效率倍数,而在于有没有这条路径:没有可视化和编辑界面时,"改一个错误实体"这件事要写 Cypher、要人懂图 schema,实际结果是没人去改;有了界面之后,领域专家才可能真正参与 graph 治理。这是从 0 到 1 的差别,不是效率优化。

19.20 跨专栏关联:图与向量的融合

图检索和向量检索的融合是 2024-2026 年 IR 领域的热点。图 DB 一侧已经原生加了向量能力(如 §19.5 的 Neo4j 向量索引);向量 DB 一侧则更多是在自身之上用实体、关系的向量来组织图式检索(例如 Milvus 3.0.1 的 README 就列了一篇 Graph RAG with Milvus 教程)。两条路线正在靠拢。

《LangGraph 设计与实现》讨论的 agent graph 和本章的 knowledge graph 是两种不同的图——一个是控制流图(agent 怎么决策),一个是数据图(知识怎么组织)。大型 AI 系统里两者并存:agent 在 knowledge graph 上"游走"查询、做推理决策。

19.21 本章小结

  1. chunk RAG 不擅长全局 / 多跳 / 关系型问题,GraphRAG 补这个短板
  2. GraphRAG 核心组件:实体抽取 / 关系抽取 / 图构建 / 社区发现 / 社区摘要 / 混合检索
  3. Microsoft GraphRAG 影响最大但索引贵;LightRAG 是轻量替代;Neo4j + vector 是复用现有栈的方案
  4. 实体对齐是工程核心难点——做不好图支离破碎
  5. 适用判断:单跳事实用 chunk RAG,全局 / 多跳 / 关系型用 GraphRAG
  6. 多数项目不需要 GraphRAG——先跑 hybrid RAG 看瓶颈再决定
  7. GraphRAG 是 LLM 自动化降低传统 KG 构建成本的实验

下两章讲 RAG 评估和成本延迟优化——进入第六部分生产化话题。