你问向量 RAG「小明和小红是什么关系?」,它往往答不上来——因为它只会把问题 embedding 后找字面最像的句子,而「关系」往往藏在多段文字的连接里。GraphRAG 的思路是:先把文档里的实体(人/公司/概念)和它们之间的关系抽出来,建成一张知识图谱,再让检索在「图」上做多跳推理。本文用 LlamaIndex 的 PropertyGraphIndex 跑通一条本地、无需 Neo4j、可复现的最小链路。
先搞懂:向量 RAG 和图 RAG 差在哪?
| 维度 | 向量 RAG | GraphRAG(本文) |
|---|---|---|
| 检索单位 | chunk(文本块) | 实体 + 关系(节点 + 边) |
| 擅长 | 「某段话讲了什么」 | 「谁和谁什么关系、怎么连起来」 |
| 多跳推理 | 弱(靠 chunk 重叠碰运气) | 强(沿边遍历) |
| 代价 | 低 | 抽取成本高、需更强模型 |
别盲目上 GraphRAG。如果你的问答只是「某份文档里某段原话」,向量 RAG 又快又便宜;只有碰到「关系 / 多跳 / 全局总结」类问题,图检索才明显占优。本文聚焦后者。
Step 1:准备环境与数据
pip install "llama-index>=0.11" "llama-index-llms-openai"
mkdir data && cd data
# 放几段 txt/md,内容最好是「人物/公司/概念 + 它们之间关系」的叙事
# 例:小明创办了 X 公司;X 公司投资了 Y 项目;小红是 Y 的项目负责人……
gpt-4o-mini 或更强的模型;太小的本地模型抽关系会大量失真,反而得到一张「假图谱」。Step 2:用 PropertyGraphIndex 建本地图谱
from llama_index.core import PropertyGraphIndex, SimpleDirectoryReader
from llama_index.core.graph_stores import SimplePropertyGraphStore
from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-4o-mini") # 需 OPENAI_API_KEY
docs = SimpleDirectoryReader("./data").load_data()
index = PropertyGraphIndex.from_documents(
docs,
llm=llm,
graph_store=SimplePropertyGraphStore(), # 本地内存图,无需 Neo4j
show_progress=True,
)
版本敏感点。PropertyGraphIndex 与 SimplePropertyGraphStore 的 import 路径、参数在不同 llama-index 版本有变动。跑前先 pip show llama-index-core 看版本,若 import 报错,按官方对应版本文档微调(常见是 llama_index.core.graph_stores 路径调整)。
Step 3:看看图谱到底抽出了什么
建完别急着查,先确认抽取质量——这是 GraphRAG 最容易翻车的地方:
rel_map = index.property_graph_store.get_rel_map()
for k, v in list(rel_map.items())[:10]:
print(k, "->", v)
理想输出类似:小明 --创办--> X公司、X公司 --投资--> Y项目。如果关系稀稀拉拉或全是噪声,说明文档太短 / 模型太弱 / prompt 没讲清要抽什么。
Step 4:关系检索查询(对比向量 RAG 答不上来的问题)
query_engine = index.as_query_engine()
print(query_engine.query("小明和 Y 项目之间有什么关系?"))
模型会沿「小明→创办→X公司→投资→Y项目」这条边做多跳,给出「小明通过创办的 X 公司投资了 Y 项目」这类回答。换成纯向量 RAG,这种跨越 3 段文本的关系往往拼不出来。
Step 5:让检索在图谱上做多跳推理
想更可控,可以单独用 retriever 看它从图里取回了哪些子图,再喂给模型:
retriever = index.as_retriever(include_text=False)
nodes = retriever.retrieve("小红负责什么?")
for n in nodes:
print(n.get_content())
include_text=False 让它只回图谱里的「实体-关系」片段,更聚焦关系本身;需要原文佐证时再开 True。Step 6:进阶——换 Neo4j 图库(生产级、可 Cypher)
本地 SimplePropertyGraphStore 重启即丢、不能多人并发。生产环境把图存进 Neo4j:
from llama_index.graph_stores.neo4j import Neo4jPropertyGraphStore
graph_store = Neo4jPropertyGraphStore(
username="neo4j", password="你的密码",
url="bolt://localhost:7687", refresh_schema=False)
index = PropertyGraphIndex.from_documents(
docs, llm=llm, graph_store=graph_store, show_progress=True)
# 之后可用 Cypher 直接查图,也支持更复杂的图算法
Neo4j 需要单独起服务(Docker 一条命令即可),并暴露 7687/7474 端口。本地演示阶段完全不必上它,等图谱要持久化、要 Cypher 查询、要并发访问时再迁。
Step 7:与 Agent 结合——把图谱当成一个工具
把上面的 query engine 包成一个工具,Agent 就能「想查关系时查关系、想读原文时读原文」:
from llama_index.core.tools import QueryEngineTool
graph_tool = QueryEngineTool.from_defaults(
query_engine=index.as_query_engine(),
name="knowledge_graph",
description="当用户问实体之间的关系、谁和谁有什么关联时使用",
)
# 把 graph_tool 交给你的 Agent(如 LlamaIndex Agent / LangChain)即可
常见问题与避坑
| 现象 | 原因 & 解决 |
|---|---|
| 关系抽得很少 / 很乱 | 文档太短或模型太弱;换更强模型、加长文档、明确抽取目标 |
| import PropertyGraphIndex 报错 | 版本不对应;按当时 llama-index 文档调整路径与参数 |
| 回答还是像向量检索 | 问题本就是「原文复述」类,GraphRAG 优势不在;混用向量+图双路检索 |
| 成本飙升 | 抽取每个 chunk 都过 LLM;控制文档量、用便宜模型做初抽、强模型做精修 |