RAG 系统进化论(六):GraphRAG(基于知识图谱的 RAG),从相似文本走向实体关系

本文导读: 当答案分散在多份文档中,并需要沿着人物、产品、组织或事件关系进行推理时,单纯寻找相似文本往往不够。本文将介绍实体与关系抽取、实体消歧、知识图谱、图遍历、局部与全局检索,以及向量检索与图检索怎样配合;同时梳理 Neo4j、LangChain、LangGraph 等技术在 GraphRAG 中分别承担什么职责。
上一篇介绍了 Corrective RAG(纠错式 RAG)和 Self-RAG(自反思式 RAG)。
系统开始检查证据是否相关、是否充分,并在出错时重新检索或拒绝回答。
但无论怎样重排和纠错,传统 RAG 主要检索的仍是一个个独立文本块:
text
问题 → 找到相似 Chunk(文本块)→ 根据 Chunk 回答
如果答案分散在多份文档中,而且需要沿着实体关系连接起来,仅靠文本相似度就不够了。
例如,知识库中分别记录着:
text
供应商资料:
远星电子负责生产 R7 无线接收器。
产品清单:
AX-2048-C 使用 R7 无线接收器。
售后工单:
AX-2048-C 的部分批次出现 E1007 配对故障。
生产记录:
批次 B2407 使用了远星电子 7 月交付的 R7 接收器。
用户问:
text
哪个供应商可能与 AX-2048-C 的 E1007 故障有关?
没有任何一个文本块直接包含完整答案。
系统需要连接一条路径:
text
远星电子
└── 生产 → R7 接收器
└── 使用于 → AX-2048-C
└── 出现 → E1007
这就是 GraphRAG 要解决的问题:
不只查找语义相似的文本,还要理解实体之间怎样连接,并沿关系寻找跨文档证据。
什么是 GraphRAG?
传统向量 RAG 主要保存:
text
Chunk A:[向量]
Chunk B:[向量]
Chunk C:[向量]
GraphRAG 还会从文本中抽取:
text
Entity(实体):
远星电子、R7 接收器、AX-2048-C、E1007、批次 B2407
Relationship(关系):
远星电子 ---生产→ R7
AX-2048-C ---使用→ R7
AX-2048-C ---出现→ E1007
批次 B2407 ---使用→ R7
再将它们保存为 Knowledge Graph(知识图谱):
text
Node(节点)表示实体
Edge(边)表示实体之间的关系
Property(属性)保存名称、类型、时间和来源
把这三个元素组合起来,原本分散在文本中的事实就变成了一张可以沿关系查询的网络:

Node(节点)保存实体,Edge(边)表达实体之间的关系,Property(属性)补充实体的详细信息;每条关系仍然可以关联回原始文档中的证据。
图谱并不一定替代原始文档。
更常见的做法是:
text
图谱负责表达"谁和谁有什么关系"
原始文档负责提供完整语境和可引用证据
向量检索负责从自然语言问题找到可能相关的入口
GraphRAG 的完整过程
GraphRAG 通常分成两个阶段:
text
离线构建:
文档 → 实体与关系抽取 → 实体消歧 → 图谱 → 社区摘要
在线查询:
问题 → 找到入口实体 → 图遍历 → 取回原文证据 → 生成答案

第一步:抽取实体和关系
Entity Extraction(实体抽取)从文档中识别人、组织、产品、零部件、错误码、地点和事件。
Relationship Extraction(关系抽取)识别实体之间的连接。
例如:
text
原文:
AX-2048-C 使用远星电子生产的 R7 无线接收器。
实体:
AX-2048-C,类型为 Product(产品)
远星电子,类型为 Supplier(供应商)
R7,类型为 Component(零部件)
关系:
远星电子 ---生产→ R7
AX-2048-C ---使用→ R7
抽取时还要保存 Provenance(来源信息),也就是这条关系来自哪份文档、哪一段文字。
否则图谱只能给出结论,无法证明结论来自哪里。
ts
async function extractGraphFacts(
chunk: DocumentChunk,
) {
// 从文本中识别产品、供应商、零部件、故障等业务实体
const entities = await extractEntities(chunk.content);
// 只在已识别实体之间抽取受支持的关系类型
const relationships = await extractRelationships(
chunk.content,
entities,
);
// 为每个实体和关系记录原始文档、段落与更新时间
return attachProvenance(
{ entities, relationships },
chunk.metadata,
);
}
关系类型应该预先设计或受到约束。
如果任由模型生成任意关系名称,图谱中可能同时出现:
text
生产、制造、供应、提供、负责生产
这些词可能描述同一种关系,却无法稳定查询。
第二步:Entity Resolution 合并同一个实体
Entity Resolution(实体解析或实体消歧)判断不同名称是否代表同一个对象。
例如:
text
星云定制键盘
AX-2048-C
AX2048C
定制版 2048
它们可能都是同一个产品。
如果不合并,图谱会出现多个孤立节点,查询关系时就会遗漏信息。
实体消歧常结合:
- 业务唯一 ID;
- 名称规范化;
- 别名字典;
- 型号和组织编码;
- 文本上下文;
- 人工审核。
确定性 ID 比模型猜测更可靠。只有缺少唯一标识时,才需要使用相似度或模型辅助判断。
ts
async function resolveEntity(
candidate: ExtractedEntity,
) {
// 优先通过产品 ID、供应商编码等稳定标识查找现有实体
const exactMatch = await findByBusinessId(candidate);
if (exactMatch) return exactMatch;
// 对名称进行大小写、空格、符号和别名规范化
const normalizedName = normalizeEntityName(candidate.name);
// 使用类型和上下文寻找可能相同的候选节点
const possibleMatches = await findSimilarEntities(
normalizedName,
candidate.type,
);
// 置信度不足时保留为待审核项,避免错误合并污染整张图
return decideOrQueueForReview(candidate, possibleMatches);
}
错误合并比重复节点更危险。
如果把两个不同供应商合并成一个,后续所有关系和答案都可能被错误连接。
第三步:构建 Knowledge Graph
完成抽取和消歧后,系统把实体和关系写入知识图谱。
每条边除了起点、终点和关系类型,还应保存:
text
来源文档
原文片段
生效时间
更新时间
置信度
数据权限
这样,图谱中的一条关系不是没有出处的事实,而是一条可以回查的证据索引。
ts
async function updateKnowledgeGraph(
documents: Document[],
) {
// 将文档切成适合抽取事实的文本块
const chunks = splitForGraphExtraction(documents);
// 从每个文本块中抽取实体、关系和来源
const extractedFacts = await Promise.all(
chunks.map(extractGraphFacts),
);
// 合并别名和重复实体,获得稳定的图节点
const resolvedFacts = await resolveAllEntities(
extractedFacts.flat(),
);
// 增量写入节点和边,并删除已经失效的关系版本
await upsertGraphFacts(resolvedFacts);
}
第四步:沿图关系进行多跳检索
Graph Traversal(图遍历)从一个或多个入口节点出发,沿关系查找相邻节点。
例如,用户问:
text
哪个供应商可能与 AX-2048-C 的 E1007 故障有关?
系统可以:
text
第一跳:AX-2048-C ---使用→ R7 接收器
第二跳:R7 接收器 ---由...生产→ 远星电子
第三跳:AX-2048-C ---出现→ E1007
这类需要跨越多条边才能得到答案的问题称为 Multi-hop Question(多跳问题)。
图遍历不能无限扩散。
跳数越多,候选节点增长越快,也越容易把无关关系加入上下文。因此通常要限制:
- 允许的关系类型;
- 最大跳数;
- 实体类型;
- 时间范围;
- 权限范围;
- 每一跳保留的候选数量。
ts
async function retrieveGraphEvidence(
question: string,
) {
// 从问题中识别产品、故障码等入口实体
const seedEntities = await identifySeedEntities(question);
// 只沿与问题有关的关系类型执行有限跳数遍历
const subgraph = await traverseGraph(seedEntities, {
relations: ["USES", "PRODUCED_BY", "HAS_ERROR"],
maxHops: 3,
});
// 根据图中的来源标识取回原始文档片段
const sourceChunks = await loadSourceEvidence(subgraph);
// 返回关系路径和原文证据,而不是只返回节点名称
return { subgraph, sourceChunks };
}
第五步:Local Search 与 Global Search
GraphRAG 不只适合多跳事实查询,还可以处理全局性问题。
Local Search:围绕具体实体查找
Local Search(局部搜索)从问题中的具体实体出发,查找相邻关系和原文证据。
适合:
text
AX-2048-C 使用哪个供应商的接收器?
E1007 还影响了哪些产品?
供应商远星电子关联了哪些故障批次?
Global Search:理解整个知识库的主题
Global Search(全局搜索)处理需要总结大量文档的问题:
text
过去半年主要产品故障集中在哪些类型?
哪些供应链问题影响了最多产品?
售后知识库中有哪些反复出现的风险主题?
直接把整张图和所有文档交给大模型不可行。
常见做法是先使用 Community Detection(社区发现)识别连接紧密的实体群组,再为每个群组生成 Community Summary(社区摘要)。
text
社区一:无线接收器、配对错误、固件版本
社区二:轴体供应商、按键失灵、特定生产批次
社区三:电池供应商、温度异常、运输条件
全局查询先检索相关社区摘要,再汇总多个社区的结论。
ts
async function globalGraphSearch(
question: string,
) {
// 根据问题检索最相关的社区摘要,避免读取整张知识图谱
const communities = await searchCommunitySummaries(
question,
{ limit: 8 },
);
// 分别根据每个社区的实体、关系和来源形成局部结论
const partialAnswers = await Promise.all(
communities.map((community) =>
summarizeCommunityForQuestion(question, community),
),
);
// 合并局部结论,并保留每个结论对应的社区和原始来源
return combineGlobalEvidence(partialAnswers);
}
GraphRAG 解决了什么?
1. 连接跨文档知识
实体和关系把分散在不同文档中的事实连接起来。
2. 支持多跳问题
系统可以沿"产品---零部件---供应商---批次---故障"路径寻找证据,而不是依赖某个文本块直接包含完整答案。
3. 支持全局总结
社区发现和社区摘要让系统能够回答整个知识库的主题、趋势和风险问题。
4. 让推理路径更明确
答案可以展示经过了哪些实体和关系,并回到对应原文,而不只是给出若干相似文本。
向量检索和图检索怎样配合?
GraphRAG 不是"用图数据库替换向量数据库"。
两者适合解决不同问题:
| 检索方式 | 更擅长 |
|---|---|
| Vector Retrieval(向量检索) | 根据自然语言找到语义相近的文本或入口实体 |
| Graph Retrieval(图检索) | 沿明确关系查找邻居和多跳路径 |
| Hybrid Vector-Graph Retrieval(向量与图混合检索) | 先语义定位,再扩展关系,最后取回原文 |
一个常见流程是:
text
用户问题
↓
向量检索找到相关 Chunk 和实体
↓
从实体进入知识图谱
↓
沿允许关系扩展一到三跳
↓
取回关系对应的原始文档
↓
根据文本证据生成答案
ts
async function answerWithGraphRAG(
question: string,
) {
// 使用向量检索寻找语义相关文本和可能的入口实体
const semanticSeeds = await vectorSearch(question);
// 将文本中的实体映射到知识图谱中的稳定节点
const graphSeeds = await linkChunksToGraph(
semanticSeeds,
);
// 沿与问题有关的关系进行受限扩展
const graphEvidence = await retrieveGraphEvidenceFromSeeds(
question,
graphSeeds,
);
// 取回原文并与关系路径一起组成可引用上下文
const evidence = await combineTextAndGraphEvidence(
semanticSeeds,
graphEvidence,
);
// 根据证据回答,并展示关键关系路径和文档来源
return generateGraphGroundedAnswer(question, evidence);
}
实现 GraphRAG 需要哪些技术栈?
GraphRAG 不是安装一个组件就能获得的能力。
它通常由文档解析、实体关系抽取、实体消歧、图数据存储、图查询、检索编排和结果追踪等多层技术组成:
| 技术层 | 负责解决的问题 | JS / TS 中可以使用的技术 |
|---|---|---|
| 文档解析与切块 | 从 PDF、网页和业务文档中得到可处理的文本块 | LangChain Document Loaders(文档加载器)、@langchain/textsplitters;复杂 PDF 可以接入 Unstructured 解析服务 |
| 实体与关系抽取 | 把自然语言转成结构化的实体和关系 | 大模型的 Structured Output(结构化输出)、LangChain withStructuredOutput()、Zod 数据结构校验 |
| 实体消歧 | 合并别名、重复名称和同一业务对象 | 业务唯一 ID、别名字典、Elasticsearch(全文搜索引擎)、向量相似度和人工审核 |
| 图谱存储与查询 | 保存节点、边和属性,并执行多跳查询 | Neo4j 图数据库、neo4j-driver、Cypher(图查询语言) |
| 向量与图混合检索 | 先用语义找到入口,再沿图关系扩展 | @langchain/neo4j、Neo4j Vector Index(向量索引)和 Full-text Index(全文索引) |
| 图算法 | 发现社区、中心节点和重要关系 | Neo4j GDS(图数据科学库)、Graphology(JavaScript 图计算库) |
| 工作流编排 | 串联抽取、消歧、入库、检索和失败重试 | LangGraph 的 StateGraph(状态图工作流) |
| 前端图谱展示 | 在浏览器中交互式展示节点和关系 | Cytoscape.js、Sigma.js;它们负责可视化,不负责持久化存储 |
| 评测与追踪 | 定位抽取错误、查询错误和回答错误 | LangSmith;当流程开始包含多个节点和模型调用时再接入 |
可以把这些技术在系统中的位置理解为:
text
LangChain Document Loaders
↓
Text Splitter(文本切分)
↓
LLM + Structured Output + Zod
↓
Entity Resolution(实体消歧)
↓
Neo4j + Cypher
↓
向量检索 + 图遍历
↓
LangGraph 编排回答流程
↓
LangSmith 追踪每一步结果
Elasticsearch、BM25 和 IK 在这里处于什么位置?
它们不是图数据库,也不负责保存实体关系,而是 GraphRAG 的辅助检索层。
- Elasticsearch(简称 ES)负责建立关键词索引和检索实体候选;
- BM25 是关键词相关性排序算法,适合匹配产品型号、错误码和专有名词;
- IK Analyzer(IK 中文分词器)负责把中文问题切成适合检索的词语。
它们可以在进入图谱前帮助系统找到入口实体:
text
用户问题
↓
IK 中文分词
↓
Elasticsearch + BM25 找到实体名称、别名或相关文档
↓
把结果映射到知识图谱节点
↓
使用 Cypher 沿关系继续查询
如果项目已经使用 Elasticsearch,可以直接复用这套能力;如果数据量不大,Neo4j 的全文索引和向量索引已经够用,就不必为了 GraphRAG 单独增加 Elasticsearch。
图数据库和图组件怎样选择?
Neo4j 是学习和实现第一版 GraphRAG 时比较直接的选择。
它使用 Property Graph(属性图)模型保存节点、关系和属性,使用 Cypher 查询关系路径,同时还可以保存向量索引和全文索引。对于 JS / TS 项目,可以使用:
bash
pnpm add @langchain/core @langchain/community @langchain/textsplitters
pnpm add @langchain/langgraph @langchain/neo4j neo4j-driver zod
这里需要特别区分:
neo4j-driver是 Neo4j 官方 JavaScript 驱动,负责执行 Cypher、写入节点和遍历关系;@langchain/neo4j是 LangChain 的 Neo4j 集成,可以把文档向量和关键词索引接入检索流程;@langchain/langgraph负责组织"抽取---检查---入库---检索---回答"的状态和分支;- Cytoscape.js、Sigma.js 只负责浏览器中的图谱展示,不能代替图数据库。
除了 Neo4j,还可以根据现有基础设施选择其他图数据库:
| 技术 | 更适合的场景 |
|---|---|
| Memgraph | 需要低延迟、实时更新,并希望继续使用类似 Cypher 的查询方式 |
| NebulaGraph | 图数据规模较大,需要分布式存储和遍历 |
| Amazon Neptune | 系统部署在 AWS,希望使用托管式图数据库 |
| Apache AGE | 已经大量使用 PostgreSQL,希望在现有数据库中增加图查询能力 |
| RDF Store + SPARQL | 强调本体、行业标准和语义互操作的知识图谱 |
第一版不需要同时引入这些数据库。对本系列的 JS / TS 示例来说,选择 Neo4j 就足够完整:
text
文档处理:LangChain.js
结构化抽取:大模型 + withStructuredOutput() + Zod
图谱存储:Neo4j
图查询:neo4j-driver + Cypher
混合检索:@langchain/neo4j
流程编排:LangGraph
前端展示:Cytoscape.js(可选)
链路追踪:LangSmith(需要监测时接入)
有哪些现成的 GraphRAG 方案?
Microsoft GraphRAG 是较完整的现成实现之一。
它已经覆盖实体与关系抽取、Community Detection(社区发现)、Community Report(社区报告)、Local Search(局部搜索)和 Global Search(全局搜索)等环节,适合用来理解标准 GraphRAG 流程,或者快速验证全局检索效果。
不过,它的完整工具链更偏向 Python。JS / TS 项目不一定要重新实现它的全部内部流程,可以把离线构图作为独立任务或服务,前端和 Node.js 应用只调用构建结果。
另一种方式是使用 Neo4j 作为基础设施,根据自己的业务关系模型搭建 GraphRAG。这种方案没有一次性封装所有步骤,但实体类型、关系规则、数据权限和检索路径更容易按业务定制,也更适合和 LangChain.js、LangGraph 组合。
因此,本系列后面的伪代码默认采用下面这条路线:
LangChain.js 负责文档与模型调用,Neo4j 负责图存储和查询,LangGraph 负责流程编排,LangSmith 在需要监测时负责追踪。
GraphRAG 不适合所有问题
如果用户只问:
text
E1007 是什么意思?
一次关键词或向量检索就可能得到答案。
为这类问题构建图谱会增加不必要的抽取、存储和维护成本。
GraphRAG 更适合:
- 答案分散在多份文档;
- 问题经常涉及实体关系;
- 需要多跳查找或影响分析;
- 需要对整个语料进行全局总结;
- 关系本身具有业务价值。
GraphRAG 还留下了什么问题?
图谱构建成本高
实体抽取、关系抽取、消歧、社区发现和摘要生成都需要额外计算。
抽取错误会沿图传播
一条错误关系可能影响多条查询路径。图谱不能只构建一次,还要持续更新、验证和删除失效关系。
全局摘要可能丢失细节
社区摘要提高了查询效率,也可能压缩掉重要例外,因此最终结论仍应回到原始来源。
文本仍然不是知识的全部
很多企业资料存在于:
- PDF 页面布局;
- 扫描件;
- 数据表格;
- 产品结构图;
- 故障截图;
- 图表和流程图。
如果只抽取纯文本,表格行列关系和图片信息仍然会丢失。
下一篇:Multimodal RAG(多模态 RAG)
下一篇进入 Multimodal RAG。
系统将从只检索文本:
text
问题 → 文本 Chunk → 答案
扩展到同时处理:
text
文本 + 表格 + 图片 + 页面布局 + 扫描文档
GraphRAG 让系统理解知识之间的关系。
Multimodal RAG 要解决的是:
当关键知识不只存在于文字中,系统怎样找到并理解表格、图片和完整页面?