导语:RAG 的第三次进化
第一代 RAG 用向量相似度检索文本块,第二代加了重排(rerank)与混合检索,但都困在一个问题上:文本块之间没有关系,模型无法理解"谁和谁在什么时候有关联"。GraphRAG 把知识图谱引入 RAG——检索的不再是孤立的文本块,而是带关系的子图。这让多跳推理(multi-hop reasoning)、全局问题(global question)的回答质量显著提升。本专题的 知识图谱构建与应用 已介绍 KG 概念,本文聚焦 GraphRAG 的工程实现。
一句话总结:GraphRAG = 知识图谱(结构)+ 向量检索(语义)+ LLM(生成)——用图谱约束检索范围,用向量补足语义模糊,让答案可溯源、可多跳。
1. 从 RAG 到 GraphRAG
1.1 传统向量 RAG 的三大局限
| 局限 | 表现 | 后果 |
|---|---|---|
| 缺乏关系 | 只检索文本块,无法回答"A 和 B 有什么关系" | 多跳问题答非所问 |
| 全局性差 | 检索受限于 top-k 块,宏观问题无答案 | “总结全部研究主题"类问题失效 |
| 可溯源弱 | 证据是文本片段,无法精确到实体-关系 | 审计与纠错困难 |
1.2 GraphRAG 的两种范式
文本块 RAG(传统):
问题 → 向量化 → 向量库 top-k 文本块 → LLM 生成
图增强 RAG(Text2Cypher):
问题 → LLM 解析 → 实体/意图 → Cypher 查子图 → LLM 生成
图语义 RAG(GraphRAG 原版):
文档 → 抽取实体关系 → 构建图谱 + 社区检测
问题 → 检索相关社区/子图 → LLM 生成
1.3 为什么"图谱 + 向量"优于单一路径
- 向量检索擅长:语义模糊、说法不同但意思相近
- 图谱检索擅长:精确实体、多跳路径、可解释的关系
- 混合策略:向量召回候选实体 → 图谱展开一跳/多跳 → 组合成上下文
一句话总结:GraphRAG 不是抛弃向量检索,而是用图谱给向量检索"加持”——向量负责语义召回,图谱负责结构化扩展。
2. 实体链接与图检索增强
2.1 问题到实体的实体链接
第一步是把用户问题映射到图中的实体:
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def link_entities(question_entities):
"""把 NER/LLM 抽取的实体名链接到图内实体(别名优先)"""
with driver.session() as session:
result = session.run("""
MATCH (a:EntityAlias {alias: $alias})
RETURN a.entityId AS eid, a.entityName AS name, a.type AS type
UNION
MATCH (e:Entity {name: $alias})
RETURN e.id AS eid, e.name AS name, e.type AS type
""", alias=question_entities[0]) # 循环处理每个实体
return [r.data() for r in result]
2.2 子图检索(Cypher 召回)
// 以命中实体为中心,展开 1-2 跳子图
MATCH path = (e:Entity)-[r*1..2]-(neighbor)
WHERE e.id IN $entity_ids
RETURN path
LIMIT $max_paths
// 带权重的关系子图(打分排序)
MATCH (e:Entity)-[r]-(n)
WHERE e.id IN $entity_ids
WITH e, n, r, coalesce(r.confidence, 0.5) AS score
ORDER BY score DESC
LIMIT 50
RETURN e.name, type(r) AS rel, n.name, score
2.3 混合检索:向量 + 图
def hybrid_retrieve(question, top_k_text=5, top_k_graph=20):
# 1. 向量召回:找语义相关的实体描述块
vector_hits = vector_search(question, top_k_text)
# 2. 从文本块中提取实体,链接到图谱
entities = extract_entities(vector_hits + question)
entity_ids = link_to_graph(entities)
# 3. 图谱展开:沿关系扩展 1-2 跳
subgraph = expand_subgraph(entity_ids, top_k_graph)
# 4. 组合上下文(文本块 + 三元组 + 子图描述)
return build_prompt(vector_hits, subgraph)
一句话总结:实体链接是 GraphRAG 的"入口",子图检索是"放大器"——先语义定位实体,再图结构扩展关系。
3. Neo4j Vector Index
3.1 启用向量能力
Neo4j 5.11+ 内置 Vector Index(基于 HNSW 近似最近邻)。容器需安装 GDS 或使用 5.15+ 的内置向量支持:
# Docker 启动时启用
docker run \
--name neo4j-graphrag \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/password \
-e NEO4J_PLUGINS='["apoc"]' \
neo4j:5.15-community
3.2 创建向量索引
// 创建向量索引(L2 或 COSINE 或 DOT 相似度)
CREATE VECTOR INDEX entity_embedding IF NOT EXISTS
FOR (e:Entity) ON (e.embedding)
OPTIONS {
indexConfig: {
`vector.dimensions`: 1536, // 与嵌入模型维度一致
`vector.similarity_function`: 'cosine'
}
}
3.3 相似度查询
// 最近邻查询
WITH $question_embedding AS qvec
CALL db.index.vector.queryNodes("entity_embedding", 10, qvec)
YIELD node, score
RETURN node.name, node.type, score
ORDER BY score DESC
与图谱展开组合:
// 向量召回实体 → 图谱扩展(两跳内)→ 输出子图
CALL db.index.vector.queryNodes("entity_embedding", 5, $qvec)
YIELD node, score AS sem_score
WITH node, sem_score
MATCH (node)-[r*1..2]-(n)
RETURN node.name AS root, type(r) AS rel, n.name AS neighbor, sem_score
LIMIT 100
3.4 嵌入生成与存储
# 生成实体描述嵌入并写入
import openai
from neo4j import GraphDatabase
openai_client = openai.OpenAI()
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def embed(text):
resp = openai_client.embeddings.create(
model="text-embedding-3-small", input=text
)
return resp.data[0].embedding
def index_entity(eid, description):
vec = embed(description)
with driver.session() as session:
session.run(
"MATCH (e:Entity {id: $eid}) SET e.embedding = $vec",
eid=eid, vec=vec
)
一句话总结:Neo4j Vector Index 用 HNSW 提供近似最近邻,维度与相似度函数在索引创建时固定,是 GraphRAG 的语义检索地基。
4. 端到端 GraphRAG 落地
4.1 LangChain 集成
LangChain 提供 Neo4jGraph 与 GraphCypherQAChain,几行代码即可搭建:
from langchain_community.graphs import Neo4jGraph
from langchain_community.chains.graph_qa.cypher import GraphCypherQAChain
from langchain_openai import ChatOpenAI
graph = Neo4jGraph(
url="bolt://localhost:7687",
username="neo4j", password="password",
database="neo4j"
)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = GraphCypherQAChain.from_llm(
llm=llm, graph=graph, verbose=True,
allow_dangerous_requests=True, # 允许 LLM 生成 Cypher
validate_cypher=True # 用 EXPLAIN 验证生成语句
)
answer = chain.invoke("华为和阿里云在 AI 领域有什么关系?")
print(answer)
4.2 自定义流水线:向量 + 图 + LLM
class GraphRAG:
def __init__(self, uri, user, pw, llm_client):
self.driver = GraphDatabase.driver(uri, auth=(user, pw))
self.llm = llm_client
def answer(self, question):
# Step 1: 实体抽取(LLM 解析)
entities = self.extract_entities(question)
# Step 2: 向量召回 + 图扩展 混合检索
qvec = self.embed(question)
graph_context = self.hybrid_search(qvec, entities)
# Step 3: 组织上下文提示
prompt = self.build_prompt(question, graph_context)
# Step 4: 生成(要求给出事实来源)
return self.llm.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
).choices[0].message.content
def hybrid_search(self, qvec, entities):
facts = []
# 向量召回
with self.driver.session() as s:
r = s.run(
"CALL db.index.vector.queryNodes('entity_embedding', 5, $qvec)"
" YIELD node, score RETURN node.name, score",
qvec=qvec
)
facts += [x.data() for x in r]
# 图扩展
r = s.run(
"MATCH (e:Entity)-[rel]-(n) WHERE e.name IN $names"
" RETURN e.name, type(rel) AS rel, n.name LIMIT 30",
names=entities
)
facts += [x.data() for x in r]
return facts
def build_prompt(self, question, facts):
return f"""基于以下知识图谱事实回答。每个事实都是 {head}-{rel}-{tail} 结构。
事实:
{chr(10).join(str(f) for f in facts)}
问题:{question}
要求:如果事实不足,明确回答"知识不足";引用事实编号说明依据。"""
4.3 评测:GraphRAG 需要多跳评测集
# 构建评测集:2-hop 问答(A 与 C 通过 B 关联)
eval_set = [
{
"question": "任正非创立的公司在深圳发布了什么芯片?",
"expected_path": [("任正非","FOUNDED_BY","华为"),("华为","DEVELOPED","昇腾AI芯片")],
"answer_keywords": ["昇腾AI芯片"]
},
# ... 覆盖 1-hop、2-hop、全局三类
]
def evaluate(chain, eval_set):
correct = 0
for item in eval_set:
answer = chain(item["question"])
hit = any(kw in answer for kw in item["answer_keywords"])
correct += hit
print(f"{'✓' if hit else '✗'} {item['question']}")
print(f"Acc: {correct}/{len(eval_set)}")
一句话总结:落地三件套——LangChain 快速起步、自定义流水线控细节、多跳评测集守质量;
validate_cypher是防 LLM 生成坏 Cypher 的安全网。
5. GraphRAG 变体与选型
| 方案 | 原理 | 优点 | 局限 |
|---|---|---|---|
| Text2Cypher | LLM 直接生成 Cypher | 实现简单、查询精确 | 复杂图谱易生成错误语句 |
| 微软 GraphRAG | 社区检测 + 社区摘要 + MapReduce | 全局问题强 | 构建成本高(多次 LLM 调用) |
| LightRAG | 双级检索(低阶/高阶) | 效率高、增量更新 | 依赖实现成熟度 |
| Vector-Graph 混合 | 向量召回 + 图扩展 | 稳定、可控 | 需要实体链接质量 |
| 自研 Cypher 模板 | 预定义模板参数化 | 最稳、可审计 | 覆盖面受模板限制 |
选型建议:
- 查询模式稳定 → 模板化 Cypher + 向量兜底
- 问题开放多变 → Text2Cypher +
validate_cypher - 需要全局综述 → 微软 GraphRAG 的社区摘要思路
一句话总结:GraphRAG 没有银弹——模板、Text2Cypher、社区摘要按"查询确定性"从高到低排列,越开放的问题成本越高。
6. 最佳实践与总结
工程清单:
- 实体链接是质量门:问题实体链接不准,后续全部失真
- 向量维度对齐:
vector.dimensions必须与嵌入模型输出一致 - Cypher 安全:LLM 生成 Cypher 必须
validate_cypher+ 只读账号 - 混合检索有兜底:图检索为空时回退向量检索,再回退原文档
- 证据可溯源:提示词要求 LLM 引用事实编号,答案可核对
- 评测常态化:1-hop/2-hop/全局三类题集,每次改动跑一遍
核心认知:
- GraphRAG 的价值是"可解释的多跳"——关系即证据
- 向量与图谱是互补不是替代——语义召回 + 结构扩展
- 工程化顺序:先建图谱与向量索引,再套 LLM 层,最后做评测
延伸阅读:
- 知识图谱构建实战 — GraphRAG 的图谱底座从哪来
- 知识图谱构建与应用 — KG-RAG 概念原型
- 事务与索引调优 — 向量索引与其他索引的共存设计
- 关联专题:AI/ML 专题 的 LLM 应用与 RAG 模式、数据工程专题 的向量数据管道
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。