GraphRAG 多跳推理实战 2026:为什么基础 RAG 会失败
💡 工具推荐:处理图数据与实体 JSON 时,试试 Evergreen Tools 的 JSON格式化工具、YAML/JSON转换工具 和 正则测试工具,全部免费!
基础 RAG 在 demo 里完美运行,直到你把它部署上线。核心假设是「语义相似 = 相关」,但用户真正问的多跳问题——「Acme 收购的那家公司的 CEO 是谁?」——需要的是 A 连到 B、B 连到 C 的结构化连接,而这些概念通常不在同一个文本块里。2026 年的答案越来越明确:把知识图谱的刚性结构与向量检索的语义能力结合起来,这就是 GraphRAG。本文用 Python 从零实现完整流程。
1. 基础 RAG 为什么会在多跳问题上翻车
假设你的向量数据库里存着一批企业合同。用户问「谁领导 Acme 收购的那家公司?」——回答藏在 Chunk 2:「Sarah Connor 被任命为 BetaTech 的 CEO」。但向量检索只看语义相似度,它会把查询嵌入后找到与「Acme」和「领导」最相似的块,而 Chunk 2 里的「Sarah Connor」和「BetaTech CEO」跟「Acme」没有任何语义重叠,于是答案块永远不会被召回。基础 RAG 还搞不定全局汇总类问题:当答案需要跨几十个块的信息时,top-k 检索天然就漏。
// Why naive vector search fails on multi-hop questions.
// Query: "Who leads the company that Acme Corp acquired?"
// Chunk 2 holds the answer but never gets retrieved,
// because "Sarah Connor" has no semantic overlap with "Acme".
const query = "Who leads the company that Acme Corp acquired?";
const chunks = [
{ id: 1, text: "Acme Corp acquired BetaTech in 2025 for $2.1B..." },
{ id: 2, text: "Sarah Connor was appointed CEO of BetaTech..." }, // <- answer
{ id: 3, text: "BetaTech builds enterprise scheduling software..." },
];
const topK = await vectorSearch(query, chunks, { k: 2 });
// Returns chunk 1 and chunk 3 — chunk 2 is invisible to
// semantic similarity, yet it holds the answer.2. GraphRAG 的核心思路:先建图,再检索
GraphRAG 在摄取阶段就让 LLM 处理文档:从每个块中提取节点(实体)和边(关系),构建知识图谱。这样「Acme Corp -[ACQUIRED]-> BetaTech」和「Sarah Connor -[LEADS]-> BetaTech」就成了显式的结构化事实。检索时不再只靠向量相似度,而是先向量定位起点,再沿图的边做多跳遍历——每一跳都是确定性的关系查询,而不是模糊的语义猜测。
// GraphRAG ingestion: let the LLM extract nodes (entities)
// and edges (relationships) from each chunk to build a
// knowledge graph. This is the key architectural shift.
import { ChatOpenAI } from "@langchain/openai";
const EXTRACT_PROMPT = `Extract all entities and relationships
from the text as JSON. Use the schema:
{ "nodes": [{"name": string, "type": string}],
"edges": [{"from": string, "to": string, "rel": string}] }`;
async function extractGraph(text: string) {
const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const res = await llm.invoke(EXTRACT_PROMPT + "\n\n" + text);
return JSON.parse(res.content as string);
}
// Chunk 2 -> { nodes: [{name: "Sarah Connor", type: "person"},
// {name: "BetaTech", type: "company"}],
// edges: [{from: "Sarah Connor", to: "BetaTech",
// rel: "LEADS"}] }3. 实体抽取:让 LLM 当图构建器
摄取管线的核心是一个结构化的抽取提示词:让 LLM 输出 JSON 格式的节点和边。温度设为 0 保证可复现。抽取质量直接决定图的质量,所以提示词要明确实体类型(person/company)和关系类型(ACQUIRED/LEADS)。这一步本质上是把非结构化文本「编译」成结构化知识,后续所有查询都建立在这张图上。
// Store the graph in Neo4j (or any graph DB) alongside
// the vector index. Entities get both a node and an embedding,
// so you can start from either semantic or structural search.
from neo4j import GraphDatabase
import openai
driver = GraphDatabase.driver("bolt://localhost:7687")
def upsert_entity(tx, name, etype, embedding):
tx.run(
"""MERGE (e:Entity {name: $name})
SET e.type = $etype, e.embedding = $embedding""",
name=name, etype=etype, embedding=embedding,
)
def upsert_relationship(tx, src, dst, rel):
tx.run(
"""MATCH (a:Entity {name: $src}), (b:Entity {name: $dst})
MERGE (a)-[:$rel]->(b)""",
src=src, dst=dst, rel=rel,
)
# Ingest pipeline: for each chunk -> extractGraph() -> upsert
with driver.session() as s:
s.execute_write(upsert_entity, "Acme Corp", "company", emb1)
s.execute_write(upsert_entity, "BetaTech", "company", emb2)
s.execute_write(upsert_relationship, "Acme Corp", "BetaTech", "ACQUIRED")4. 存储:图数据库 + 向量索引双轨并行
知识图谱存进 Neo4j(或其他图数据库),实体同时保留向量嵌入。这样你既可以从语义入口进入(向量检索找到起点实体),也可以从结构入口进入(直接按关系遍历)。双轨设计是 GraphRAG 的弹性来源:向量负责「模糊召回」,图负责「精确连接」。
5. 多跳检索:向量找起点,图走完剩下的路
回答「Acme 收购的公司由谁领导」需要三步:向量检索定位 Acme Corp;沿 ACQUIRED 边走到 BetaTech;再沿 LEADS 边反向找到 Sarah Connor。每一步都是确定性的 Cypher 查询,答案稳定可复现——这正是企业场景最看重的。混合检索器把向量召回和图的 k 跳遍历组合起来,把上下文交给 LLM 生成最终回答。
// Retrieval: first hop via vector search, then traverse
// the graph for the remaining hops. This answers the query
// that defeated naive RAG.
def answer_multihop(query: str) -> str:
# Hop 1: find "Acme Corp" in the vector index
seed = vector_search(query, top_k=5) # -> Acme Corp
# Hop 2: traverse ACQUIRED edge
acquired = graph.query(
"MATCH (a:Entity {name:$name})-[:ACQUIRED]->(t) RETURN t.name",
name=seed[0],
) # -> BetaTech
# Hop 3: traverse LEADS edge
leader = graph.query(
"MATCH (c:Entity {name:$name})<-[:LEADS]-(p:Person) RETURN p.name",
name=acquired[0],
) # -> Sarah Connor
return leader[0]
print(answer_multihop("Who leads the company Acme acquired?"))
# Sarah Connor6. 什么时候该升级到 GraphRAG
如果用户问题集中在单一事实(「合同编号是多少」),基础 RAG 就够了。一旦出现跨实体的关系问题、汇总问题、或者「为什么」类需要推理路径的问题,就该上 GraphRAG。迁移成本不算低——要跑实体抽取、要维护图数据库——但换来的是企业级查询的准确性和可解释性。从一个小型试点域开始,比如只建合同领域的关系图,跑通后再扩展。
// Full pipeline wiring in Python: chunk -> extract ->
// graph + vector -> hybrid retrieval -> LLM answer.
import asyncio
from graphrag import GraphIndex, HybridRetriever
async def main():
docs = load_contracts("contracts/") # corporate contracts
index = GraphIndex(llm="gpt-4o", neo4j_uri="bolt://localhost:7687")
await index.ingest(docs) # extraction + upsert
retriever = HybridRetriever(index)
ctx = retriever.retrieve(
"Who leads the company that Acme Corp acquired?",
vector_k=5, graph_hops=3,
)
answer = await llm_complete(ctx.context_text())
print(answer)
asyncio.run(main())📌 常见问题 FAQ
基础 RAG 为什么回答不了多跳问题?
基础 RAG 假设语义相似即相关,但多跳问题需要的连接(A 收购 B、B 的 CEO 是 C)分散在不同 chunk 里,答案块与查询没有语义重叠,向量检索永远召不回它。
GraphRAG 和向量 RAG 的本质区别是什么?
GraphRAG 在摄取阶段用 LLM 抽取实体和关系构建知识图谱,检索时先向量定位起点、再沿图遍历多跳;向量 RAG 只做语义相似度 top-k 召回。
GraphRAG 需要什么基础设施?
一个图数据库(如 Neo4j)+ 向量索引 + LLM 抽取管线。实体同时存节点和嵌入,向量负责模糊召回,图负责精确连接。
GraphRAG 比基础 RAG 慢吗?
摄取阶段更慢(多一次 LLM 抽取),但查询阶段多跳遍历是确定性的图查询,通常很快。总体是拿摄取成本换查询准确性和可解释性。
什么时候不该用 GraphRAG?
当问题都是单一事实查询(如「合同编号是多少」)、数据规模小、或关系结构不明显时,基础 RAG 更简单便宜。GraphRAG 面向跨实体关系和汇总类问题。