多路召回+Graph RAG调研和实践

简介

多路召回简单来说就是指的通过多种途径——包括从已有的Qdrant、Memgraph甚至是web上等去拿到与query相关的上下文信息的过程。

Graph RAG这里有一些文档可以看看:

  1. memgraph+spacy构建知识图谱:https://memgraph.com/blog/extract-entities-build-knowledge-graph-memgraph-spacy
  2. 从知识图谱到 GraphRAG:探索属性图的构建和复杂的数据检索实践:https://cloud.tencent.com/developer/article/2441475
  3. 利用 LlamaIndex 和 Memgraph 构建知识图谱并查询:https://www.llamaindex.ai/blog/constructing-a-knowledge-graph-with-llamaindex-and-memgraph
  4. 一篇RAG+知识图谱的博客:https://qiankunli.github.io/2024/07/12/llm_graph.html

步骤及调研

其实简单来说,分为两大步:

  1. 知识图谱的构建
  2. 如何去查询

知识图谱的创建

构建知识图谱,这里有几种方式可供参考:

  1. 利用 LlamaIndex 和 Memgraph 构建知识图谱并支持向量查询:https://www.llamaindex.ai/blog/constructing-a-knowledge-graph-with-llamaindex-and-memgraph
    1. 但是Llama_Index这里有几个问题:
    2. 严重依赖open ai的api,无法用mass的key。
    3. 这里所用到的索引需要在本地去维护,用起来体验非常不好,感觉就不是一个生产环境能正常使用的东西(具体可以去点开这个链接看看代码)。
  2. 调用from langchain_experimental.graph_transformers import LLMGraphTransformer
    1. 这个会自动地将非结构化的document转化成graphDocument,这里会依赖LLM去自动的生成实体和关系,然后可以调用memgraph的api插入到memgraph中

查询

几种查询方式:

  1. LLMSynonymRetriever:用户查询->同义词、关键词,关键词搜索,找到最近的节点及其邻居。唯一的缺点是,它使用关键词搜索,不太可靠
  2. VectorContextRetriever:使用嵌入和余弦相似性,进行向量相似性搜索,以检索相关的节点。
  3. Text2Cypher: LLM 根据用户查询生成 Cypher语句,当然不一定准确,准确性换灵活性的方式,需要微调模型
  4. CypherTemplateRetriever:带有特定参数的 Cypher 模板。对于用户查询,我们会使用 LLM 来填充这些参数,以创建用于检索的 Cypher 查询。

实践及效果

知识图谱的创建

其中从非结构化数据到LPG建模这一步直接调用的langchain_experimental.graph_transformers中的**LLMGraphTransformer这个库为进行LPG(属性图,Property Graph)**帮我们做了这几件事:

  1. 概述:定义任务和目标。
  2. 节点标注:规定节点类型的一致性和基本性。
  3. 关系标注:规定关系类型的一般性和永恒性。
  4. 共指消解:实体消歧,确保实体一致性。
  5. 严格合规:要求严格遵守规则。

这里会提取出graphDocument(其中包含实体、实体的关系和原有的引用的文本、引用文本与实体的关系)

同时,为了方便后续的向量相似度的检索,这里手动再对graphDocument中各node(包括实体和Document)做了embedding的属性的注入,并建立了索引https://memgraph.com/docs/querying/vector-search

之后调用接口,将这一系列graphDocument通过cypher语句在memgraph中创建,自此完成知识库的创建。

查询方式

  1. Text2Cypher/CypherTemplateRetriever

方案描述

  • 从查询中抽取实体名称
  • 填充到通用的 Cypher 查询模板
  • 在 Memgraph 中执行查询
  • 局限:仅返回实体间关系,未利用向量相似度

这个prompt template内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
elf.cypher_prompt = """
你是一个Cypher查询生成专家。根据用户查询和图数据库模式,生成一个Memgraph Cypher查询来检索相关节点。

指令:
1. 首先,分析查询以确定感兴趣的实体类型(例如,人物、组织、地点、文档等)。
2. 生成的Cypher语句严格遵循示例,将具体的实体名字进行替换。
3. 确保查询语法正确且安全(避免注入)。

示例:

查询: "爱因斯坦的成就"
Cypher: MATCH (n)-[r]-(m)
WHERE any(key in keys(n) WHERE toString(n[key]) CONTAINS '爱因斯坦')
RETURN n, r, m

查询: "苹果公司和微软的关系"
Cypher: MATCH (n)-[r]->(m)
WHERE any(key in keys(n) WHERE toString(n[key]) CONTAINS '苹果')
AND
any(key in keys(m) WHERE toString(m[key]) CONTAINS '微软')
RETURN n,r,m

当前查询:{query}

请只返回Cypher查询语句,不要有其他解释。
"""

这种效果非常差,原因分析如下:

  1. 实体匹配困难:自然语言难以准确映射到知识库中真实存在的实体,导致什么都查询不到

  2. 即使查询到了,当返回实体的相关关系时,会因为以下原因导致效果不好:

    1. **关系提取不完整:**实体之间的有效关系并未被有效提取,在建库时被遗漏(且这种通过LLM as Extractor的方式无法避免这个过程
    2. Chunk大小悖论:另外实验发现(同时在另一篇博客中有人提到这个问题)对于同一个样本数据集,通过llm进行抽取实体的方式,抽取实体的数量与chunk token的大小呈一定程度上的反比关系。简单来说,就是chunk_size为600比chunk_size为2000能够抽出的实体和关系要多不少。但将chunk_size减小也并非银弹,又会面临实体消歧的问题。)
    3. 信息压缩损失:将原始文本压缩为图结构导致信息丢失
  3. VectorContextRetriever

相似度索引引入实体查询反而相对于原信息而言引入了噪声

mamgraph官方的向量查询:https://memgraph.com/docs/querying/vector-search

在总结了上面的查询的一些教训之后,这里引入向量查询,并优化了整体查询流程。步骤如下:

1
query → 向量检索 → seed 实体 → 图扩展 → 扩展实体 → 回捞 chunks → 排序截断 → 最终证据
  1. query → 向量检索
    1. 先在向量库里取一个很小的 top-k(比如 3–10)
    2. 这些 chunks 只是 “入口”,不是最后要给 LLM 的全部证据
  2. 入口 chunks → seed 实体
    1. 每个入口 chunk 包含哪些实体/与哪些实体相关联?
    2. 合并去重,得到一批 seed 实体,也就是知识图谱上的起点节点
  3. 在图上扩展(有限半径)
    1. 从 seed 实体出发,最多走 1–2 跳
    2. 每跳只取 top-N 邻居(理想状态时按边权/共现频次/置信度排序,但这里就按照关系取随机选取+向量相似度的实体)
    3. 得到一批扩展实体:包括
      • 跟 seed实体 语义上像的实体
      • 以及“虽然不像但强关联”的实体(即他们之间存在关系的实体,这才是 GraphRAG 的价值)
  4. 扩展实体 → 回捞 chunks
    1. 对每个扩展实体,找它出现过的证据 chunks
    2. 合并去重,得到一批候选 chunks
  5. 排序 + 控制 token 预算 → 选出最终证据 chunks
    1. 按 query 相关性、实体覆盖度、文档权重等重排
    2. 按 token 上限截断,比如最多 6000 tokens
    3. 得到 10~20 个左右的“精华 chunk”

把这些 chunk 拼进 prompt → 让 LLM 回答

这种方式从理论上避免了查询方式1中的几个问题:

  1. 保证查准率::这里直接通过相似度查询,是保证能够查询到对应相似的文本+实体的
  2. 信息完整性:
    1. 返回原始文本而非压缩的关系表示
    2. 避免信息提取过程中的损失
  3. 关联发现:这里采用通过与chunk的相似度和实体相似度多重匹配+实体扩展且再查询的方式进行查询,在实体之间的有效关系并未被有效提取,最大程度保证了返回相关内容的完整性,即使存在关系提取不完整的情况也能够返回有效信息。
  4. 避免了原有文本再切分成多chunk时导致的实体重复的问题(通过实体相似度扩展实现)

效果

在知识库创建和选择采用查询方式2后,实际上实测下来:

  1. 计算资源消耗巨大:embedding获取,chunk检索,实体检索,实体扩展,chunk再查询,排序
  2. 响应时间过长:在agentic的模式下,平均耗时非常长(是普通rag的三倍以上时间,出现大量超时)
  3. 评估结果较差:eval出来的结果也弱于原有rag的方式

对于当前graph rag和原有的rag而言,由于返回的都是原有的chunk,所以可以认为只有当graph rag检索到比原有rag更完整、更相关的chunk时其效果才会优于原有rag

假设简单粗暴地认为通过多路召回(原有rag检索和graph rag两种方式)获取到的eval指标是二者的加权,那么我们引入graph rag的理由肯定是当分别单独进行eval时,至少graph rag的指标要好于原有rag的指标,否则就没有理由引入rag:

  1. Graph rag返回的上下文相较于原有上下文而言,就变成了“噪声”
  2. Graph rag会消耗大量资源和时间

总结

关键发现

  1. GraphRAG 非银弹:不能盲目认为引入图结构就能提升效果
  2. 引入噪声风险:在当前的多路召回场景下,GraphRAG 返回的上下文可能成为"噪声",降低回答质量
  3. 成本效益问题:显著的资源消耗和时间成本 vs 不确定的性能提升

知识图谱的正确定位

知识图谱 ≠ 答案源

知识图谱 = 导航工具

  • 核心原则:知识图谱应作为**“引导谁该被召回"的导航工具,而不是直接的"真相来源”**
  • 实际内容:真正输入给 LLM 的应是原始文本 chunks,而非图结构压缩信息
  • 信息损失警示:将非结构化文本压缩为知识图谱会导致严重的信息损失

RAG和Graph RAG之间的关系

RAG 与 GraphRAG 的关系 = 互补而非替代

  1. RAG 优势:
    1. 直接、快速
    2. 保留原始信息
    3. 技术成熟稳定
  2. GraphRAG 优势:
    1. 发现深层关联
    2. 支持复杂的推理路径
    3. 实体关系的显式表示