☰
LangChain 知识图谱 + RAG:处理‘谁和谁什么关系‘这类问题
2026/10/14 3:27:02 网站建设 项目流程
个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • 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 neo4j

3.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% 再考虑投入。

下一篇讲增量索引——文档更新了怎么只处理变化的部分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询