2792 字
约 9 分钟
15
S20-X17 · GraphRAG 与 GFM-RAG:把检索从"相似段落"搬到"关系结构"

向量检索擅长"这段话和问题很像",不擅长"这三份文档里的实体其实是同一个人,且它们构成了因果链"。当问题需要跨文档串联、或者问的是"整体主题分布"时,切片检索会稳定地给出局部正确、全局错误的答案。GraphRAG 一类的做法是把语料先变成图,再在图上检索;GFM-RAG 则进一步把"在图上推理"这件事训练成一个可迁移的模型。


1. 朴素 RAG 的三处结构性盲区

盲区 具体表现 图谱方案的对应动作
多跳关系断裂 答案需要 A→B→C 三段各自在不同文档 建实体关系边,检索沿边扩展
全局聚合问题 "这批反馈的主要诉求是什么"无法靠 top-k 段落回答 预先聚成社区并生成摘要
实体指代不一致 "老王""王经理""Wang" 检索时互不相识 实体消歧与别名归并
时间/条件约束 只按语义相似度,忽略时间线 关系上带属性,检索时过滤

需要清醒的一点:图谱不是免费的。它用索引期的大量 LLM 调用换查询期的结构能力,适不适合取决于"问题的形态",不取决于"数据量"。


2. 索引管线:五步与各自的失败点

步骤 输入 输出 主要成本 典型失败
切块 原始文档 文本单元(chunk) 低 切断了跨段实体关系
抽取 chunk 实体、关系、可选"主张" LLM 调用,最贵 抽取粒度不一致,同实体多种写法
消歧归并 实体候选 唯一实体 ID 中 过度合并,把两个人并成一个
社区检测 图 层次化社区 低(图算法) 社区过大,摘要失去焦点
社区摘要 社区 + 成员描述 摘要文本 LLM 调用,第二贵 摘要空洞、丢失关键实体名

抽取提示词决定了整张图的质量。实践里最有效的约束不是"抽得更多",而是"限定类型 + 强制给证据"。

EXTRACT_PROMPT = """从下面的文本中抽取实体与关系,只使用给定类型。
实体类型: [PERSON, ORG, PRODUCT, EVENT, METRIC]
关系类型: [WORKS_AT, OWNS, CAUSED, MENTIONED_WITH]

要求:
1. 每个实体必须给出原文中出现的原句作为 evidence。
2. 关系必须两端都是已抽出的实体。
3. 不确定时不要输出,宁缺勿滥。

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

文本:
{chunk}
"""

强制 evidence 字段有两个直接收益:消歧时能按上下文判断是否同一实体;摘要时能引用原文而不是靠模型编。


3. 社区检测:为什么是 Leiden 而不是 Louvain

层次社区检测的作用是给"全局问题"提供可聚合的单位:把图分成多级社区,每级生成摘要,查询时按级别 map-reduce。

维度 Louvain Leiden
社区连通性 可能产生内部不连通社区 保证连通
层次结构 有,但粒度不稳 明确的多级层次
收敛质量 好 通常更优(同规模下模块度更高)
参数 分辨率 resolution 分辨率 + 迭代次数 + 随机种子
import igraph as ig
import leidenalg

g = ig.Graph.from_networkx(nx_graph)          # 需保证边权与节点属性已带上
partition = leidenalg.find_partition(
    g,
    leidenalg.RBConfigurationVertexPartition,
    weights="weight",
    resolution_parameter=1.0,                  # 越大社区越小
    n_iterations=-1,                           # 迭代到收敛
    seed=42,
)
for cid, members in enumerate(partition):
    print(cid, len(members))

resolution_parameter 是唯一需要认真调的参数:调大→社区更细、摘要更多更贵;调小→社区更粗、摘要更泛。它直接决定索引成本,而不是"效果微调"。


4. 三种查询模式:别用一个模式回答所有问题

模式 检索范围 适合的问题 代价
Local 命中实体的邻域(1–2 跳)+ 关联原文块 "某产品谁负责、出过什么问题" 单次查询便宜
Global 社区摘要的 map-reduce "整体主题分布、主要矛盾" 需要遍历多个摘要,贵
DRIFT Local 为主,混入社区上下文 既需细节又需背景 中等,最像"人怎么查资料"
// Local 检索:从命中实体向外扩一跳,带上原文块
MATCH (e:Entity {name: $name})-[r]-(n:Entity)
OPTIONAL MATCH (e)-[:MENTIONED_IN]->(c:Chunk)
RETURN e.name, type(r), n.name, collect(DISTINCT c.text)[0..5] AS evidence
LIMIT 50

注意 LIMIT 的位置:度数高的实体(如"公司""用户")一跳就能扩出上千邻居,不限制会让上下文窗口瞬间爆掉。生产实现里通常还要按边权或提及次数排序后截断,而不是靠数据库返回顺序。


5. GFM-RAG:把"图上推理"训练成模型

GraphRAG 的检索逻辑是规则 + LLM 摘要:先找实体,再扩邻域,再让模型读摘要。GFM-RAG 换了思路——用一个图神经网络直接在实体图上做相关性传播,把"哪些子图与问题相关"学成一个可迁移的打分函数。

维度 GraphRAG(摘要路线) GFM-RAG(图基础模型路线)
索引期 需为每份语料生成社区摘要(LLM 密集) 构图后主要由 GNN 编码,摘要非必需
查询期 规则扩邻域 + LLM 阅读 GNN 前向传播,输出子图相关性
迁移性 换语料要重跑全部索引 论文报告可跨未见图零样本/少样本迁移
多跳能力 受限于显式扩跳数与截断 由传播层数与注意力决定
可解释性 高(摘要可读) 中(要看节点权重)

机制上有两层:先按查询与实体的初步相似度选出候选子图(否则全图前向不可行),再用 GNN 在候选子图上做若干轮消息传递,最后按节点/边得分裁剪出用于生成的知识。论文给出的跨数据集数值请按原文核对,这里只谈结构。

# 推理骨架:候选子图 -> GNN 传播 -> 子图裁剪 -> 交给生成模型
def retrieve(question, graph, encoder, gnn, top_k=8):
    q = encoder.encode(question)
    seeds = graph.search_entities(q, limit=64)          # 粗筛,避免全图前向
    sub = graph.subgraph(seeds, hops=2)                  # 局部子图
    scores = gnn.propagate(sub, q, rounds=3)             # 相关性在边上传播
    keep = sub.prune(scores, ratio=0.3)                  # 丢掉低分节点与边
    return keep.to_context(max_tokens=top_k * 512)

三个工程约束:种子数量决定召回上限;hops 决定多跳能力也决定噪声;prune ratio 决定上下文预算。三者一起动会互相掩盖问题,调参时要一次只动一个。


6. 成本与参数:先算账再动手

项目 量级来源 说明
抽取调用数 ≈ chunk 数 每块一次 LLM 调用,是索引成本主项
摘要调用数 ≈ 社区数(多级累加) 层次越细,调用越多
图存储 实体数 + 关系数 图数据库或带索引的关系表均可
查询延迟 扩跳、GNN 层数、生成长度 扩跳是最容易失控的一项
增量更新 新文档涉及的社区 全量重建代价高,需支持局部重算
# 索引配置的关键项(各家实现字段名不同,按所用版本核对)
chunk:
  size: 1200
  overlap: 120
extract:
  model: EXTRACT_MODEL
  max_entities_per_chunk: 40
community:
  algorithm: leiden
  resolution: 1.0
  levels: 2
summary:
  model: SUMMARY_MODEL
  max_tokens: 600

resolution 与 levels 是成本旋钮而不是精度旋钮:levels: 2 意味着摘要调用量大致翻倍。第一版索引建议只做一级,确认查询收益后再加层级。


7. 排错:六种典型问题

现象 根因 处理
全局问题答得很空 社区摘要缺少实体名与数字 摘要提示词强制保留关键实体与量级
同一实体在多处重复 消歧缺失,别名未归并 加别名表 + 上下文相似度合并
多跳问题只答对一半 扩跳被 LIMIT 截断在中间节点 按边权排序后截断,或提高跳数并裁剪
检索到大量无关节点 高 degree 实体污染邻域 对超级节点做降权或排除
索引跑了一夜没完 抽取并发太低 / 调用重试风暴 提高并发、加缓存、按文档分片
新增文档后答案变差 只补了图没更新社区摘要 触发受影响社区的局部重建

第四条是图谱检索最常见的"看起来像模型退化、实际是图结构问题"的现象。处理方式不是换更大的模型,而是在构图阶段就给超级节点打标,检索时限制其贡献。


8. 边界:哪些场景不值得上图

场景 更合适的方案 理由
单文档问答、事实查找 切片 + 向量检索 关系结构用不上,索引成本白花
语料每天大量新增 向量库 + 元数据过滤 图谱增量成本远高于收益
需要精确引用原文 原文块直取 摘要会引入不可核对的内容
实时性要求秒级索引 流式向量化 抽取 + 社区检测无法做到秒级
只有几十份短文档 全文检索 结构复杂度超过数据本身

判断标准可以简化成一句话:问题里有没有"之间""整体""为什么"这类跨实体、跨文档的诉求。有,图结构值得;只问"是什么",向量检索通常更便宜也更准。


9. 自测

  1. 朴素 RAG 在哪两类问题上会稳定失败?为什么向量相似度救不了?
  2. 索引管线里哪两步是 LLM 成本主项,各自与什么数量成正比?
  3. 为什么选 Leiden 而不是 Louvain?resolution_parameter 调大意味着什么?
  4. Global 与 Local 检索的输入范围差在哪?各自适合什么问题?
  5. GFM-RAG 为什么要先"粗筛候选子图"再做 GNN 传播?
  6. 超级节点(高 degree 实体)会给检索带来什么污染,通常怎么处理?
S20-X17 · GraphRAG 与 GFM-RAG:把检索从"相似段落"搬到"关系结构"
https://www.clxhxhhr.top/posts/6031/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。