logo

GraphRAG:知识图谱如何补足向量检索

Published on

GraphRAG:知识图谱如何补足向量检索

做 RAG 时,很多人第一反应是把文档切成片段,生成 Embedding,然后用相似度找到最接近的问题。这个方法对“关于退款政策的说明在哪”很有效,但遇到另一类问题就开始吃力:“负责支付系统的团队有哪些?这些团队最近两次发布分别影响了哪些服务?如果服务 A 依赖服务 B,而 B 的负责人又发生了变更,当前应该通知谁?”

这些问题不是单纯找一段相似文字,而是在追踪实体之间的关系。向量检索擅长回答“哪些内容和这个问题像”,知识图谱擅长回答“谁和谁有什么关系、关系经过了几跳、这条路径是否成立”。GraphRAG 的价值,不是把向量数据库换成图数据库,而是让两种检索方式在合适的地方互相补足。

我第一次搭图谱时也有过浪漫想象:把所有文档抽成节点和边,模型就能像查地图一样理解企业知识。后来发现,图谱最难的不是画出一张漂亮的关系图,而是决定哪些关系值得保存、关系从哪份证据来、冲突时相信谁,以及查询成本是否值得。

一、向量检索和图检索各自擅长什么

向量检索:问题 → 相似片段
知识图谱:实体 → 关系 → 实体 → 关系 → 目标

向量检索适合语义相似、答案集中在一段文本里的问题。用户换一种说法,系统仍然有机会召回相关内容。它对开放表达很友好,建设成本也相对低。

图检索适合实体明确、关系重要、多跳路径和全局聚合问题。例如查“某个部门负责的所有系统”,或者找“同时依赖两个有风险组件的服务”。它能显式表达关系类型、方向、时间和权重,不必让模型从多段文本里自己拼图。

图谱也有明显代价:需要定义本体和实体类型,抽取关系会出错,更新和冲突处理复杂,图查询的结果还要回到原文验证。不是所有 RAG 都需要 GraphRAG,只有当关系结构本身决定答案时,增加图层才值得。

二、先定义图谱中的实体和关系

不要一上来让模型“抽取所有知识”。先从一个具体业务问题出发,定义最小本体:

type EntityType = 'person' | 'team' | 'service' | 'document' | 'project'

type RelationType =
  | 'OWNS'
  | 'DEPENDS_ON'
  | 'MEMBER_OF'
  | 'MENTIONS'
  | 'CHANGED_BY'
  | 'RELATED_TO'

type Entity = {
  id: string
  type: EntityType
  canonicalName: string
  aliases: string[]
  sourceRefs: string[]
}

type Relation = {
  id: string
  from: string
  type: RelationType
  to: string
  validFrom?: string
  validTo?: string
  confidence: number
  sourceRefs: string[]
}

sourceRefs 是图谱可信度的关键。一个关系不是因为“模型抽出来了”就变成事实,它必须能回到文档、页码、段落或数据库记录。validFromvalidTo 也不能省略,负责人、系统依赖和项目状态都会变化,图谱不是永恒不变的地图。

本体要尽量小。实体类型越多、关系越细,抽取和维护成本越高;如果查询用不上,先不要加入。图谱建设最容易陷入过度设计:团队花几周定义一套完美 ontology,最后却没有一个真实问题依赖它。

三、从文档抽取关系时,模型只是候选生成器

关系抽取通常经历:文档解析、实体识别、实体归一化、关系候选生成、冲突校验和写入图数据库。模型可以帮助识别候选,但不能单独决定最终事实。

原文
实体识别:支付服务、订单服务
实体归一化:payment-api = 支付服务
关系候选:订单服务 DEPENDS_ON 支付服务
规则 / 数据库 / 人工核验
写入图谱并保存来源
type RelationCandidate = {
  fromText: string
  relation: RelationType
  toText: string
  evidence: string
  confidence: number
  sourceId: string
}

function acceptCandidate(candidate: RelationCandidate) {
  if (candidate.confidence < 0.8) return false
  if (!candidate.evidence.trim()) return false
  return allowedRelationTypes.has(candidate.relation)
}

置信度不能盲信模型自己给出的数字。它更适合做排序和人工抽检优先级,真正的接受规则还要结合句法证据、来源等级、时间和领域约束。比如“可能依赖”与“正式依赖”表达的关系强度不同,不能都写成同一条确定边。

四、实体归一化比抽取更容易被低估

同一个实体可能有简称、旧名、大小写差异、不同语言名称或环境后缀。若没有实体归一化,图谱会出现多个“支付服务”,查询路径被分裂,结果看起来不完整。

type EntityAlias = {
  alias: string
  entityId: string
  source: 'manual' | 'directory' | 'model' | 'rule'
  confidence: number
}

function canonicalKey(name: string) {
  return name
    .normalize('NFKC')
    .trim()
    .toLowerCase()
    .replace(/[ _-]+/g, '')
}

归一化可以结合目录、唯一 ID、规则和人工维护。不要只用字符串相似度合并实体,两个名字相似的服务可能属于不同团队;错误合并会制造比漏关系更危险的路径。

实体合并也要可回滚。保存别名来源、合并理由和版本,发现错误时可以拆分或恢复。图谱中的错误关系会被多跳查询放大,一条错误边可能让几十个问题得到看似合理的错误答案。

五、GraphRAG 的查询流程

一个实用的 GraphRAG 查询通常不是“图或向量二选一”,而是先判断问题结构,再分别取证:

用户问题
问题分类与实体识别
   ├─ 语义检索 → 相关文档片段
   ├─ 图查询 → 实体、关系和多跳路径
   └─ 结构化过滤 → 时间、权限、状态
       结果合并与去重
       回到原文核验
       生成带引用的回答

图查询适合先缩小实体范围,向量检索适合补充关系证据和自然语言上下文。两边结果必须带来源和版本,不能把图数据库返回的边直接当成无需验证的事实。

type RetrievalBundle = {
  entities: Entity[]
  relations: Relation[]
  passages: Array<{
    text: string
    sourceId: string
    score: number
  }>
}

async function retrieveForQuestion(question: string) {
  const entities = await entityResolver.resolve(question)
  const graph = await graphStore.expand(entities, { maxHops: 2, limit: 50 })
  const passages = await vectorStore.search(question, { limit: 8 })

  return mergeAndDeduplicate({ entities, ...graph, passages })
}

maxHopslimit 是必要的护栏。无限扩展图谱会产生大量无关邻居,既增加查询时间,也把上下文变成一张噪声地图。多跳不是越多越聪明,通常两到三跳已经足以解决很多业务问题。

六、图查询要回答“为什么”,不只是“是什么”

GraphRAG 的回答最好带一条可读路径:

订单服务
  ── DEPENDS_ON ──> 支付服务
  ── OWNS ────────> 结算团队
  ── CHANGED_BY ──> 张三

每条边都关联原文证据,最终答案可以说明“根据哪份架构文档,订单服务依赖支付服务;根据哪条组织记录,结算团队负责支付服务”。用户不只需要一个结论,也需要知道结论如何被推出来。

如果图谱中存在冲突,例如一份旧文档说 A 负责,最新目录说 B 负责,查询层应该按时间、来源等级和有效状态处理,而不是把两条边都交给模型让它自己猜。冲突本身也可以作为回答的一部分:说明资料不一致,并请求人工确认。

七、社区发现适合全局问题,但要谨慎解释

当用户问“组织里有哪些主题社区”“哪些系统形成了紧密依赖簇”,单条路径查询不够,可以使用图的社区发现、连通分量或中心性分析,先找出全局结构,再让模型总结。

图谱
社区 / 簇分析
每个簇的实体、关系和代表文档
模型生成摘要

这种方法适合总结大型知识库,但聚类结果不是业务事实。图算法会受边权、时间窗口、缺失数据和实体合并影响,不能把“在同一个簇里”直接解释成“有正式组织关系”。生成摘要时要明确这是统计发现还是来源明确的业务关系。

八、权限必须同时存在于图和文本检索

图谱会保存实体、关系和来源,任何一层泄露都可能暴露内部信息。查询时要根据用户身份过滤实体、关系和原文片段;不能先查全图,再让模型负责隐藏不该显示的结果。

type GraphAccess = {
  tenantId: string
  allowedGroups: string[]
  visibleClassifications: Array<'public' | 'internal' | 'restricted'>
}

async function queryGraph(question: string, access: GraphAccess) {
  const entities = await entityResolver.resolve(question)
  return graphStore.expand(entities, {
    maxHops: 2,
    filter: {
      tenantId: access.tenantId,
      groups: access.allowedGroups,
      classifications: access.visibleClassifications,
    },
  })
}

图谱中的聚合信息也可能泄露。即使隐藏了具体员工姓名,返回“某团队负责所有支付系统”也可能是敏感信息。权限策略要覆盖节点、边、路径和聚合结果。

九、更新和冲突是图谱的长期难题

文档会修改、服务会下线、负责人会变更,图谱必须支持增量更新和历史版本。每条边都应该能被撤销、替换或标记过期;删除源文档时,相关关系不能继续作为当前事实返回。

源文档变化
重新解析受影响实体和关系
比较边的来源、时间和内容哈希
   ├─ 新增 → 写入
   ├─ 修改 → 新版本替换
   ├─ 删除 → 失效或软删除
   └─ 冲突 → 标记并进入审核

不要每次全量重建整张图,规模大以后成本和停机风险都很高;也不要只追加新边而不处理旧边,最终查询会得到多个互相矛盾的当前状态。图谱需要类似数据库的迁移、索引和回滚机制。

十、什么时候不该上 GraphRAG

如果问题主要是“找到和问题相关的一段说明”,普通向量或混合检索通常更简单。以下情况也要谨慎:数据量很小、关系变化极快、实体没有稳定 ID、关系无法可靠抽取、没有人维护本体、或者团队没有能力评测多跳答案。

GraphRAG 的额外成本包括关系抽取、实体对齐、图存储、查询编排、权限过滤和冲突维护。如果它没有显著提高关键任务的召回或答案完整性,就不值得仅仅因为概念热门而增加一层系统。

可以先做一个窄实验:选一种关系、一个业务问题、几十份文档,比较纯向量检索和向量加图查询的 Recall、引用准确率、延迟与维护成本。用结果决定是否扩大,不要从“全公司知识图谱”开始。

十一、如何评测 GraphRAG

评测要分别看实体识别、关系抽取、图查询和最终答案:

type GraphRagReport = {
  entityResolutionAccuracy: number
  relationPrecision: number
  relationRecall: number
  pathValidity: number
  answerFaithfulness: number
  citationAccuracy: number
  p95LatencyMs: number
  graphMaintenanceCost: number
}

测试集应包含单跳、多跳、关系不存在、关系冲突、时间变化和权限边界问题。尤其要测试“图谱里没有这条边”时系统是否诚实拒答,而不是让模型根据相似文档脑补一条关系。

人工评测时,不只看最后结论,还要检查路径是否真实存在、每条边的来源是否支持、时间是否正确。一个结论碰巧答对,但路径包含一条虚构边,系统仍然不可靠。

十二、我的总结:图谱不是向量检索的替代品

GraphRAG 最有价值的地方,是把“相似内容”之外的关系结构带回了问答系统。向量检索负责从开放文本中找到相关证据,知识图谱负责表达实体、方向、时间和多跳路径,二者结合后,系统才有机会回答那些“需要把几份资料连起来”的问题。

但图谱不是自动长出来的真相。实体会重名,关系会抽错,文档会过期,权限会变化,查询路径也可能因为一条错误边而走向错误结论。模型可以帮助抽取和总结,却不能替代来源、版本、校验和权限。

我现在判断是否采用 GraphRAG,只看一个标准:关系结构是否真的决定任务成败。如果答案依赖多跳关系、全局连接或实体约束,图层值得认真建设;如果只是找相似段落,先把普通 RAG 的解析、切分、召回和评测做好。

好的 GraphRAG 不是让架构图变得更复杂,而是让系统在回答“为什么”“通过谁”“依赖什么”“发生过哪些变化”时,有一条可以追溯的路径。把每条关系都当成需要证据的主张,把每次查询都当成需要权限和版本的事实,知识图谱才会从一张漂亮的图,变成真正能帮助模型少猜、让用户能核验的知识基础设施。

🤪 您也可以编辑此页: