文章目录
- LangChain 知识图谱 + RAG:处理"谁和谁什么关系"这类问题
- 一、向量检索在关系问题上的失败
- 二、GraphRAG 的两条路线
- 三、路线一:Neo4j + Text-to-Cypher
- 3.1 环境
- 3.2 连接与 schema
- 3.3 让模型写 Cypher
- 3.4 安全:只允许读
- 四、路线二:从文档自动抽取实体关系
- 五、混合检索:图 + 向量一起用
- 六、社区摘要(GraphRAG 的核心创新)
- 七、什么时候用知识图谱
- 八、成本与复杂度
- 九、常见坑
- 十、小结
LangChain 知识图谱 + RAG:处理"谁和谁什么关系"这类问题
向量检索擅长"这段话在讲什么",
但不擅长"张三的导师是谁""A 产品依赖哪些服务"这类关系查询。因为关系信息往往散落在多个文档里,
单个 chunk 里根本找不到完整的链条。这篇讲 GraphRAG 的思路,以及什么时候它比向量检索强。
一、向量检索在关系问题上的失败
假设文档里有三段:
[文档1] 张三是李四的学生。 [文档2] 李四是王五的学生。 [文档3] 王五是清华大学的教授。用户问:“张三的师祖是谁?”
向量检索会召回这三段(都跟"学生""师承"相关),
然后让模型推理。简单情况能答对,
但一旦关系链条长一点、文档多一点,就会:
- 召回不全(漏掉中间某一环);
- 上下文塞了太多无关内容;
- 模型推理链条断了。
根本问题:关系不是"相似度"能表达的。
"张三 → 李四 → 王五"是一条路径,需要的是图遍历,不是向量匹配。
二、GraphRAG 的两条路线
| 路线 | 做法 | 适合 |
|---|---|---|
| 图数据库 + Cypher | 把知识存成 Neo4j,用 Text-to-Cypher 查询 | 结构化强、关系明确 |
| 知识图谱索引 | 从文档抽取实体关系建图,检索子图 | 从非结构化文本建图 |
路线一更可控,路线二更自动化。下面都讲。
三、路线一:Neo4j + Text-to-Cypher
3.1 环境
pipinstalllangchain-neo4j neo4j# 需要跑一个 Neo4j 实例(Docker 最方便)dockerrun-p7474:7474-p7687:7687-eNEO4J_AUTH=neo4j/password neo4j3.2 连接与 schema
fromlangchain_neo4jimportNeo4jGraph graph=Neo4jGraph(url="bolt://localhost:7687",username="neo4j",password="password",)# 写入数据graph.query(""" MERGE (a:Person {name: '张三'}) MERGE (b:Person {name: '李四'}) MERGE (c:Person {name: '王五'}) MERGE (a)-[:学生]->(b) MERGE (b)-[:学生]->(c) """)print(graph.schema)3.3 让模型写 Cypher
fromlangchain_neo4jimportGraphCypherQAChain chain=GraphCypherQAChain.from_llm(ChatOpenAI(model="gpt-4o-mini",temperature=0),graph=graph,verbose=True,allow_dangerous_requests=True,# 需要显式开启才执行写操作)chain.invoke({"query":"张三的师祖是谁?"})# 生成的 Cypher:# MATCH (a:Person {name:'张三'})-[:学生*2]->(c) RETURN c.name# 答案:王五[:学生*2]这种可变长度路径查询,正是图数据库的强项——
一条语句就能走两层关系,向量检索完全做不到。
3.4 安全:只允许读
和 Text-to-SQL 一样,绝不能让模型执行任意 Cypher:
defsafe_cypher(cypher:str)->bool:s=cypher.strip().upper()# 只允许 MATCH / RETURN / WITH / WHERE / ORDER / LIMIT 等读操作forbidden=["CREATE","MERGE","DELETE","SET","REMOVE","DROP"]returnnotany(re.search(rf"\b{k}\b",s)forkinforbidden)更稳妥的是用只读账号连 Neo4j。
四、路线二:从文档自动抽取实体关系
没有现成图谱时,可以让 LLM 从文本里抽:
fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_openaiimportChatOpenAI EXTRACT_PROMPT=ChatPromptTemplate.from_template(""" 从下面的文本中抽取实体和它们之间的关系。 要求: - 实体类型限定在:Person(人)、Org(组织)、Product(产品)、Tech(技术) - 关系用简洁的动词短语,如"任职于""依赖于""师从" - 输出 JSON 格式:{{"entities": [{{"name": "", "type": ""}}], "relations": [{{"from": "", "rel": "", "to": ""}}]}} - 只抽取文本中明确提到的,不要推断 文本: {text} """)extract_chain=(EXTRACT_PROMPT|ChatOpenAI(model="gpt-4o-mini",temperature=0)|JsonOutputParser())result=extract_chain.invoke({"text":"张三是李四的学生,李四任职于清华大学。"})# {"entities": [{"name":"张三","type":"Person"}, ...],# "relations": [{"from":"张三","rel":"师从","to":"李四"}, ...]}批量处理整个文档库(注意并发和成本):
results=extract_chain.batch([{"text":d.page_content}fordinchunks],config={"max_concurrency":10},)这一步的成本不低——每个 chunk 一次 LLM 调用。
1000 个 chunk 就是 1000 次。用便宜模型 + 批量 API。
抽完后建图:
defbuild_graph(extractions):forexinextractions:foreinex.get("entities",[]):graph.query("MERGE (n:%s {name: $name})"%e["type"],{"name":e["name"]},)forrinex.get("relations",[]):graph.query("MATCH (a {name: $from}), (b {name: $to}) ""MERGE (a)-[r:RELATED {type: $rel}]->(b)",{"from":r["from"],"to":r["to"],"rel":r["rel"]},)五、混合检索:图 + 向量一起用
实际项目很少有纯图场景,通常是混合的:
defhybrid_retrieve(question:str):# 1. 判断是否关系型问题q_type=classify(question)# 轻量分类ifq_type=="relation":# 走图查询cypher=cypher_chain.invoke({"query":question})graph_result=safe_run(cypher)returnformat_graph_result(graph_result)# 2. 否则走向量检索docs=vector_retriever.invoke(question)returnformat_docs(docs)分类可以用规则先兜一层:
RELATION_PATTERNS=[r"谁.*的(导师|老师|上司|负责人)",r".*和.*的?关系",r".*依赖(哪些|什么)",r".*属于(哪个|什么)",]defis_relation_query(q:str)->bool:returnany(re.search(p,q)forpinRELATION_PATTERNS)六、社区摘要(GraphRAG 的核心创新)
微软提出的 GraphRAG 有个关键做法:
把图按"社区"聚类,预先为每个社区生成摘要。
图 → 社区发现(Leiden 算法)→ 每个社区 → LLM 生成摘要 ↓ 检索时先命中社区,再展开细节好处是能回答**“这批文档整体在讲什么”**这类全局性问题——
这是普通 RAG 完全做不到的(它只能看到局部 chunk)。
# 简化示意communities=detect_communities(graph)# 图算法forcincommunities:summary=llm.invoke(f"总结下面这组实体的共同主题:{c.entities}")store_community(c.id,summary)# 检索全局性问题时defglobal_query(question):# 在所有社区摘要里检索hits=search_community_summaries(question,top_k=5)return"\n".join(h.summaryforhinhits)成本高(建索引时要对大量社区调 LLM),
但适合"给我一份整体分析"这类需求。
七、什么时候用知识图谱
| 场景 | 用不用 |
|---|---|
| 多跳关系查询(“A 的 B 的 C”) | 强烈推荐 |
| 全局性/总结性问题 | 推荐(社区摘要) |
| 实体关系明确的领域(组织架构、产品依赖、医学) | 推荐 |
| 纯文档问答(“报销标准多少”) | ❌ 向量检索更简单 |
| 数据量小、关系少 | ❌ 杀鸡用牛刀 |
| 没有明确实体 | ❌ 建不出图 |
判断依据:你的问题里有多少是"关系型"的。
抽 20 个真实问题数一数,超过 30% 涉及关系,就值得考虑。
八、成本与复杂度
| 项目 | 开销 |
|---|---|
| 图数据库 | 要额外部署维护 Neo4j |
| 建图(抽取) | 每 chunk 一次 LLM 调用 |
| 查询 | Text-to-Cypher 一次 LLM + 图遍历(很快) |
| 维护 | 数据更新要同步图,这是最大成本 |
最大痛点是维护:
文档改了,图要跟着改;实体消歧("张三"和"张三丰"是不是一个人)
到现在也没有完美方案。
如果你的数据更新频繁,谨慎上图谱——
维护成本可能超过收益。
九、常见坑
| 现象 | 原因 | 解法 |
|---|---|---|
| 关系抽不全 | chunk 切太碎,关系跨 chunk | 用更大的 chunk 抽取 |
| 实体重复(“张三”/“张先生”) | 没做消歧 | 建规范化词典;或人工校对 |
| Cypher 生成错误 | schema 没给全 | graph.refresh_schema() |
| 图查询返回空 | 关系方向写反了 | 检查建图时的方向 |
| 建图太慢/太贵 | 每 chunk 都调 LLM | 只对含实体的 chunk 抽;用便宜模型 |
| 文档更新了图没更新 | 没有同步机制 | 建立"文档 → 图"的增量更新流程 |
十、小结
- 向量检索解决不了多跳关系查询,图数据库的可变长路径查询可以;
- 两条路线:Neo4j + Text-to-Cypher(可控,需已有图谱)
vsLLM 抽取实体关系自动建图(自动化,成本高); - ⚠️安全同 Text-to-SQL:只允许读操作,用只读账号;
- 混合方案:先分类,关系型走图、其他走向量;
- 社区摘要能回答"整体在讲什么"这类全局问题,是 GraphRAG 的独有价值;
- 最大成本是维护——数据频繁更新的场景要谨慎;
- 先统计"关系型问题占比",超过 30% 再考虑投入。
下一篇讲增量索引——文档更新了怎么只处理变化的部分。