第 18 章:RAG 与知识增强
本章系统介绍检索增强生成(Retrieval-Augmented Generation, RAG)的完整技术栈:从基础 Pipeline 设计、评估体系,到文档解析(MinerU)、多模态 RAG(RAGAnything),再到知识图谱增强(Neo4j、GraphRAG、LightRAG),最后讲解 Agent 记忆系统(Memory-RAG、Mem0)。
18.1 RAG Pipeline 全流程
RAG 系统的完整架构:索引阶段与查询阶段的协作,以及 Rerank 的核心价值。
本节目标:掌握 RAG 系统的完整架构,理解各模块的职责与协作关系。
18.1.1 RAG 的动机
LLM 的核心局限:
- 知识截止问题:训练数据有时效性,无法感知最新信息
- 幻觉问题:对于模型知识边界之外的内容,模型倾向于"编造"看似合理但实际错误的答案
- 私有知识问题:企业内部文档、个人笔记等无法纳入预训练
RAG 的核心思路:在推理阶段动态注入相关知识,让 LLM 基于检索到的证据进行回答,而非依赖参数记忆。这相当于为 LLM 配备了一个可以随时查阅的"外部资料库"。
18.1.2 标准 RAG Pipeline
一个完整的 RAG 系统由索引阶段和查询阶段两部分组成:
【索引阶段 - 离线】
原始文档(PDF/HTML/Markdown/...)
↓ 文档解析 + 分块(Chunking)
文本块(Chunks)
↓ Embedding Model
向量索引(Vector Index)
【查询阶段 - 在线】
用户查询(Query)
↓ Query Embedding
向量检索(Top-K Chunks)
↓ Rerank(Cross-Encoder 精排)
精选上下文(Top-N,N << K)
↓ Prompt 组装(System Prompt + Context + Query)
LLM 生成
↓
最终答案索引阶段的关键决策包括分块策略(chunk size、overlap)、Embedding 模型选择、向量数据库选型等。查询阶段的核心在于检索质量和上下文组装方式。
18.1.3 Rerank 的必要性
Rerank 模型(重排序模型)通常基于 Cross-Encoder(交叉编码器)架构。与向量检索(Embedding)分别处理 Query 和 Document 不同,Rerank 模型的特点是**"成对输入,打分输出"**。
初步检索(Bi-Encoder)快速但粗糙,Rerank(Cross-Encoder)慢但精准。两阶段设计实现了高召回 + 高精度 + 低成本的平衡。
Rerank 解决的三个核心问题:
1. 弥补向量检索的语义损失
Bi-Encoder 将一段几百字的文本压缩成一个固定长度的向量(如 768 维),必然会丢失很多细节。更关键的是,它无法处理复杂语义:
- "我不喜欢苹果"和"我喜欢苹果"在向量空间可能靠得很近,但意思完全相反
- 向量相似度基于"模糊的语义接近",容易被关键词误导
Cross-Encoder 将 Query 和 Document 拼接后送入模型,逐字逐句地对比分析。这种方式计算量大、速度慢,但能精准识别逻辑关系、否定词和细微差别,从而把真正相关的文档排到前面。
2. 解决"Lost in the Middle"问题
LLM 倾向于关注 Prompt 开头和结尾的信息,而常常忽略中间的信息(即 "Lost in the Middle" 现象)。
检索回来的 Top 10 文档中,最正确的那条可能排在第 7 位。如果直接喂给 LLM,它被放在中间,很容易被模型"视而不见",导致幻觉。Rerank 按相关性重新排序,强制将最相关文档(Gold chunks)置于 Prompt 最前面(或最后面),确保 LLM 能够"看清"关键证据。
3. 在召回率与精准率之间权衡
初排(Bi-Encoder): Top-50/100 → 目标是宁滥勿缺,保证召回率
Rerank(Cross-Encoder):Top-3/5 → 目标是精准过滤,降低噪音
LLM 生成: 基于精选上下文 → 低 Token 成本,高回答质量Rerank 充当了过滤器:既享受了大规模检索的广度,又保证了输入给 LLM 的数据的纯度,实现了高召回 + 高精度 + 低成本。
18.2 RAG 评估指标
EM、F1、Recall@K 等 RAG 评估指标的计算方式与 RAGAS 端到端评估框架。
本节目标:掌握 RAG 系统的标准评估指标,理解其计算方式与适用场景。
18.2.1 答案质量指标
EM(Exact Match):预测答案与标准答案完全匹配的比例。
直觉:最严格的指标,一个字不差才算对。适合评估事实类问答("X 的首都是哪里?"),但对开放式问题过于苛刻。
F1 Score:在词元(token)级别计算预测答案与标准答案的重叠度。
其中:
(预测词元中正确的比例) (标准答案词元被覆盖的比例)
直觉:比 EM 更宽容,允许措辞不同但意思一致。例如标准答案是"Albert Einstein",预测为"Einstein",EM 为 0 但 F1 > 0。
18.2.2 检索质量指标
- Recall@K:标准答案所在 Chunk 在 Top-K 检索结果中的命中率
- MRR(Mean Reciprocal Rank):相关文档首次出现位置的倒数均值
- NDCG(Normalized Discounted Cumulative Gain):考虑排序位置的综合评估
18.2.3 端到端评估框架
现代 RAG 评估框架(如 RAGAS)将评估分为多个维度,实现对 RAG 系统各环节的独立评估:
| 指标 | 评估目标 | 核心问题 |
|---|---|---|
| Faithfulness | 答案是否忠实于检索到的上下文 | 模型有没有"编造"检索结果中没有的信息 |
| Answer Relevancy | 答案是否与问题相关 | 模型是否跑题了 |
| Context Precision | 检索到的上下文是否都与问题相关 | 检索有没有引入噪音 |
| Context Recall | 标准答案所需信息是否都在检索结果中 | 检索有没有遗漏 |
18.3 文档解析:MinerU
MinerU 文档解析工具的使用方法与核心输出格式在 RAG 中的作用。
本节目标:掌握 MinerU 的使用方法,理解其核心输出格式在 RAG 中的作用。
18.3.1 MinerU 简介
MinerU 是一个专为学术文档、技术报告等复杂版面设计的文档解析工具,能将 PDF 等格式转换为适合 RAG 摄入的结构化数据。相比简单的文本提取工具,MinerU 能正确识别和分离文本、公式、表格、图片等不同类型的内容。
18.3.2 基础用法
# 命令行解析
mineru -p input_file.pdf -o output_dir -m auto-m 参数控制解析模式:
auto:自动选择(推荐)txt:纯文本模式,速度快但不处理图像ocr:OCR 模式,适合扫描件
18.3.3 核心输出格式
MinerU 的核心输出包含两个部分:
1. Markdown 文件(图文交错格式)
# 论文标题
## 摘要
这是摘要文本...

## 1 引言
...这个 Markdown 文件保留了文档的层级结构,图片被提取到 images/ 子目录。
2. x_content_list.json(结构化内容列表)
[
{"type": "text", "content": "这是正文段落..."},
{"type": "equation", "img_path": "equations/eq_1.png", "latex": "E=mc^2"},
{"type": "table", "img_path": "tables/table_1.png", "html": "<table>..."},
{"type": "image", "img_path": "figures/fig_1.png", "caption": "图1:..."}
]x_content_list.json 是 RAG pipeline 的直接摄入格式。每个元素对应一个可检索的内容单元,包含四种类型:
| 类型 | 说明 | 附加字段 |
|---|---|---|
text | 正文段落 | content |
equation | 数学公式(通过 OCR) | img_path |
table | 表格(通过 OCR) | img_path |
image | 图片 | img_path |
这种结构化输出使得下游系统可以按类型筛选(如只检索文本,或同时检索表格和图像),也为多模态 RAG(18.5 节)提供了基础。
18.4 论文检索应用
基于 ChromaDB 向量数据库构建论文语义检索系统的实践方案。
本节目标:了解基于向量数据库构建论文检索系统的实践方案。
18.4.1 向量数据库:ChromaDB
ChromaDB 是轻量级的本地向量数据库,适合中小规模的文档检索场景。数据存储在本地 SQLite 文件(chroma.sqlite3)中:
import chromadb
# 本地持久化
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("papers")
# 添加文档
collection.add(
documents=["论文摘要文本..."],
metadatas=[{"title": "论文标题", "arxiv_id": "2xxx.xxxxx"}],
ids=["paper_001"]
)
# 语义检索
results = collection.query(
query_texts=["attention mechanism transformer"],
n_results=5
)ChromaDB 内置了默认的 Embedding 模型,也支持自定义 Embedding 函数。对于学术检索场景,建议使用专门为科学文献优化的 Embedding 模型。
18.4.2 论文检索应用案例
ICLR 论文检索系统(github.com/wenhangao21/ICLR26_Paper_Finder)展示了完整的学术 RAG 流程:
- 数据准备:批量收集会议论文的标题、摘要、关键词等元数据
- Embedding 索引:将论文摘要编码为向量并存入 ChromaDB
- 语义检索:用户输入自然语言查询,系统返回语义最相关的论文列表
- 过滤增强:支持按研究方向、评分等元数据进行二次筛选
这种方案的优势在于部署简单(纯 Python、本地存储),适合个人或小团队的学术检索需求。
18.5 RAGAnything:多模态 RAG
多模态 RAG 的挑战与 RAGAnything 通过 VLM 统一多模态内容到向量空间的方案。
本节目标:理解多模态 RAG 的挑战与 RAGAnything 的解决方案。
18.5.1 多模态 RAG 的挑战
传统 RAG 只处理纯文本,但现实文档包含多种模态:
- 图表(Chart/Figure):数据趋势、实验结果可视化,包含大量无法通过纯文本表达的信息
- 表格(Table):结构化数据,简单转为文本会丢失行列关系
- 公式(Equation):数学表达式,语义在符号关系中,而非文字描述
- 图像(Image):流程图、示意图、架构图等
多模态检索的本质挑战:如何将不同模态的内容映射到统一的语义空间,使得用户的文本查询能准确命中非文本内容。
18.5.2 RAGAnything 方案
RAGAnything 将多模态内容统一为可检索单元,核心思路是通过 VLM(Vision Language Model,如 GPT-4o、Gemini)为非文本内容生成自然语言描述,将所有模态统一到文本语义空间后进行检索:
原始文档
↓ MinerU 解析
多类型 Chunk(text / table / image / equation)
↓ 模态专用处理
├── text: 直接文本 Embedding
├── table: HTML → VLM 生成文本描述 → Embedding
├── image: VLM 生成描述(caption + 内容分析)→ Embedding
└── equation: LaTeX → VLM 转数学语义描述 → Embedding
↓ 统一向量索引
多模态检索
↓ 多模态上下文组装(文本 + 图片 + 表格混合)
LLM/VLM 生成(支持图文混合上下文)这种方案依赖 MinerU(18.3 节)的结构化输出作为前端,将文档的多模态内容分类提取后,分别进行模态专用处理。检索阶段在统一的向量空间中操作,生成阶段则可以将原始的非文本内容(如图片)直接注入到多模态 LLM 的上下文中。
18.6 知识图谱基础与 KG for RAG
知识图谱的三元组抽象、LLM 自动构建流程,及 KG-based RAG 对多跳推理的增强。
本节目标:理解知识图谱的构建流程,掌握 KG 在 RAG 中的增强作用。
18.6.1 为什么需要知识图谱
当使用文本 Embedding 模型处理非结构化文本时,在面对需要理解多个实体间连接的复杂多跳问题,或需要过滤、排序和聚合等结构化操作的问题时,其表现会明显不足。
向量 RAG 在以下场景表现不足:
- 多跳推理(Multi-hop Reasoning):回答需要连接多个实体的关系链,例如"谁是 X 公司的 CEO 的母校的校长?"——需要至少三次关系跳转
- 计数与聚合:向量检索无法支持结构化查询,如"列出所有发表过 3 篇以上论文的作者"
- 关系推理:理解实体间的明确关系,而非模糊语义相似度
18.6.2 知识图谱的核心抽象
:节点集合(Nodes),代表实体——Person、Organization、Location、Concept 等 :边集合(Edges),代表关系—— WORKS_AT、SPOUSE、FIELD_OF_RESEARCH等
每条边是一个三元组 (Marie Curie, AWARDED, Nobel Prize)。
知识图谱的构建流程:
非结构化文本
↓ LLM 信息抽取(Entity & Relation Extraction)
(实体, 关系, 实体) 三元组
↓ 去重 + 合并
图数据库存储(Neo4j 等)
↓ 查询接口
GraphRAG / Text2Cypher18.6.3 LLM Graph Transformer
LangChain 提供了 LLMGraphTransformer,通过 LLM 自动从非结构化文本中抽取图结构。它的核心设计是将"图抽取"的输出 Schema(节点、关系及其属性的结构)作为一个 Tool 绑定到模型,让模型按 Schema 返回结构化结果。
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain.chat_models import init_chat_model
from langchain_core.documents import Document
llm = init_chat_model(model='gpt-4.1-mini', model_provider='openai')
transformer = LLMGraphTransformer(llm=llm)
text = """Marie Curie, 7 November 1867 - 4 July 1934, was a Polish and
naturalised-French physicist and chemist who conducted pioneering research
on radioactivity. She was the first woman to win a Nobel Prize.
Her husband, Pierre Curie, was a co-winner of her first Nobel Prize."""
documents = [Document(page_content=text)]
graph_documents = await transformer.aconvert_to_graph_documents(documents)
# 输出示例
# Nodes: [Node(id='Marie Curie', type='Person'),
# Node(id='Pierre Curie', type='Person'),
# Node(id='Nobel Prize', type='Award'),
# Node(id='Radioactivity', type='Concept')]
# Relationships: [Relationship(Marie Curie -> Radioactivity, RESEARCH),
# Relationship(Marie Curie -> Nobel Prize, AWARDED),
# Relationship(Marie Curie -> Pierre Curie, SPOUSE)]关键可配置项:
| 参数 | 说明 | 示例 |
|---|---|---|
allowed_nodes | 限制节点类型 | ["Person", "Organization", "Location", "Award"] |
allowed_relationships | 限制关系类型,支持三元组格式 | [("Person", "WORKS_AT", "Organization")] |
node_properties | 是否提取节点属性;True 让 LLM 自主决定 | True 或 ["birth_date", "nationality"] |
ignore_tool_usage | 禁用 Tool-binding 模式,改用纯 Prompt 格式 | True(兼容不支持 Tool 的模型) |
限制节点和关系类型可以显著提高抽取一致性。例如指定 allowed_nodes=["Person", "Organization"] 后,LLM 不会把同一个人物节点有时标记为 Person、有时标记为 Scientist。
18.6.4 KG 抽取 Prompt 的关键设计原则
LLMGraphTransformer 底层使用的 System Prompt 包含几个重要的设计原则:
节点标签一致性:使用基础类型(Person),避免具体类型(Mathematician、Scientist)。这确保了不同上下文中同一类型的实体使用统一的标签。
节点 ID 可读性:禁止使用整数作为 Node ID,必须使用文本中出现的名称或可读标识符。
共指消解(Coreference Resolution):当同一实体被不同名称引用时("John Doe" / "Joe" / "he"),始终使用最完整的标识符("John Doe")作为实体 ID。这是保证图谱一致性的关键。
关系泛化:使用通用且无时间性的关系类型(PROFESSOR),而非具体的瞬时动作(BECAME_PROFESSOR)。
18.6.5 信息抽取:LangExtract
除了 LLMGraphTransformer,Google 的 LangExtract 提供了另一种从文本中提取结构化信息的方案,具有三个核心特性:
- 智能分块(Smart Chunking):具备上下文感知能力的文本分割,尊重句子和段落边界,避免粗暴切分破坏语义完整性
- 并行处理(Parallel Processing):通过可配置工作线程数的线程池进行并发处理,适合大规模数据抽取
- 多轮抽取(Multi-Pass Extraction):对同一文本执行多轮抽取以提高召回率,适合"大海捞针"场景;冲突采用"首轮优先"(first-pass-wins)规则裁决
18.6.6 KG-based RAG vs Chunk-based RAG
| 维度 | Chunk-based(Naive RAG) | KG-based RAG | 核心价值 |
|---|---|---|---|
| 数据形态 | 碎片化的"点"(Vector Chunks) | 连通的"网"(Graph: Entities & Relations) | 完整性:将离散信息重组为结构化知识 |
| 推理方式 | 隐式推理:靠 LLM 在有限上下文内基于碎片硬推 | 显式推理:图结构多跳路径游走和关联发现 | 深度与可解释性:逻辑链条清晰 |
| 全局统计 | 极弱:受 Top-K 截断,存在幸存者偏差 | 较强:支持结构化聚合和全局主题提取 | 宏观洞察:能回答"总结"、"趋势"类问题 |
| 抗噪能力 | 弱:易被字面相似但语义无关的文档干扰 | 强:实体唯一 ID 和图谱聚类天然过滤噪音 | 精准度:同名异义词消歧 |
18.7 Neo4j 实践
Neo4j 图数据库的基本操作:Cypher 查询语言、LangChain 集成与 Text2Cypher。
本节目标:掌握 Neo4j 图数据库的基本操作,能构建和查询知识图谱。
18.7.1 Neo4j 基础
Neo4j 是原生图数据库,同时支持关键词索引和向量索引,是 GraphRAG 的主流存储后端。
本地部署:
- 安装 neo4j-desktop
- 配置
neo4j.conf,设置server.bolt.listen_address=0.0.0.0:7687 - 通过
http://localhost:7474/browser/访问 Web 管理界面 - 安装 APOC 插件(Advanced Procedures on Cypher),支持图合并等高级操作
18.7.2 Cypher 查询语言
Cypher 是 Neo4j 的声明式图查询语言。基础语法:
-- 查看所有节点(限制数量)
MATCH (n) RETURN n LIMIT 25
-- 查看节点及其关系
MATCH (n)-[r]->(m)
RETURN n, r, m
LIMIT 25
-- 查询特定标签的节点
MATCH (p:Person)
WHERE p.name = "Albert Einstein"
RETURN p
-- 查询特定标签的文档节点(baseEntityLabel 场景)
MATCH (d:Document)
RETURN d
LIMIT 50
-- 清空图数据库
MATCH (n)
DETACH DELETE n18.7.3 LangChain + Neo4j 集成
from langchain_neo4j import Neo4jGraph
graph = Neo4jGraph(
url="neo4j://localhost:7687",
username="neo4j",
password="your_password",
refresh_schema=False
)
# 将图文档存入 Neo4j
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True, # 为实体添加基础标签
include_source=True # 保留原始文档来源
)两个关键参数的作用:
baseEntityLabel=True:为所有节点添加统一的__Entity__标签。这使得可以对所有实体建立统一的向量索引,支持混合检索(向量检索 + 图遍历)。大多数图数据库通过索引优化数据导入和检索。include_source=True:在图中保留 Document 节点,并用MENTIONS关系连接实体节点。这实现了从答案回溯到原始文档来源的可解释性。
底层合并机制:Neo4j 使用 MERGE / apoc.merge.* 按 id(以及关系三元组)去重合并节点与边,所以多文档插入时会自动合并重复实体/关系,不需要手工聚合。
18.7.4 Text2Cypher
Text2Cypher 是将自然语言查询转换为 Cypher 语句的技术,让用户可以用自然语言查询知识图谱:
from langchain_neo4j import GraphCypherQAChain
chain = GraphCypherQAChain.from_llm(
llm=llm,
graph=graph,
verbose=True
)
result = chain.invoke("Who did Einstein collaborate with on statistics?")
# LLM 自动生成 Cypher 查询:
# MATCH (p:Person {name: "Albert Einstein"})-[:COLLABORATE_WITH]->(c:Person)
# RETURN c.name
# 然后执行查询并返回自然语言答案18.7.5 图可视化
抽取的知识图谱可以通过 pyvis 库进行交互式可视化:
from pyvis.network import Network
net = Network(height="1200px", width="100%", directed=True,
notebook=False, bgcolor="#222222", font_color="white")
# 添加节点和边
for node in graph_documents[0].nodes:
net.add_node(node.id, label=node.id, title=node.type, group=node.type)
for rel in graph_documents[0].relationships:
net.add_edge(rel.source.id, rel.target.id, label=rel.type.lower())
# 配置物理引擎(ForceAtlas2 布局)
net.set_options('{"physics": {"forceAtlas2Based": {...}, "solver": "forceAtlas2Based"}}')
net.save_graph("knowledge_graph.html")生成的 HTML 文件可在浏览器中直接打开,支持拖拽、缩放等交互操作,便于直观检查抽取结果的质量。
18.8 LangGraph + GraphRAG 集成
微软 GraphRAG 的社区发现与层次摘要机制,及其与 LangChain/Neo4j 的集成实践。
本节目标:理解微软 GraphRAG 框架的核心原理及其与 Neo4j/LangChain 的集成方式。
18.8.1 GraphRAG 核心原理
需要区分两个概念:
- KG-based RAG(Graph RAG):通用概念,指任何利用知识图谱增强检索的 RAG 方案
- GraphRAG(微软):一个特定的 standalone solution,核心创新在于社区发现 + 层次摘要
GraphRAG 从原文抽取实体/关系 → 建知识图 → 做社区划分 → 为社区生成"报告/摘要"。查询时做局部/全局检索(含动态社区选择)。
GraphRAG 的核心步骤:
- 实体/关系抽取:从原始文档提取三元组
- 图构建:建立实体关系网络
- 社区发现(Community Detection):对图进行 bottom-up 聚类,识别语义相关的实体群组。图被用来 "Create a bottom-up clustering that organizes the data hierarchically into semantic clusters"
- 社区摘要生成:为每个社区生成结构化摘要报告(Community Report),实现对数据的全局理解(holistic understanding)
- 查询:
- Local Search:从查询相关实体出发,在局部子图中检索
- Global Search:遍历所有社区摘要,回答全局性问题("文档的主要主题是什么?")
18.8.2 GraphRAG vs KG-based RAG 对比
| 特性 | KG-based RAG | GraphRAG(微软) |
|---|---|---|
| 图结构用途 | 实体-关系检索 | 社区发现 + 摘要生成 |
| 全局理解 | 有限 | 强(bottom-up 层次摘要) |
| 增量更新 | 支持 | 需重新社区发现(成本高) |
| 时间语义 | 需手动添加 | 不内建 |
18.8.3 LangChain 集成实践
完整的 LangChain + Neo4j GraphRAG 集成流程:
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_neo4j import Neo4jGraph, GraphCypherQAChain
from langchain.chat_models import init_chat_model
from langchain.document_loaders import WikipediaLoader
from langchain.text_splitter import TokenTextSplitter
# 1. 加载与分块
raw_documents = WikipediaLoader(query="Elizabeth I").load()
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])
# 2. LLM Graph Transformer 抽取
llm = init_chat_model(model='gpt-4.1-mini', model_provider='openai')
graph_transformer = LLMGraphTransformer(llm=llm)
graph_documents = await graph_transformer.aconvert_to_graph_documents(documents)
# 3. 存入 Neo4j
graph = Neo4jGraph(url="neo4j://localhost:7687", username="neo4j", password="...")
graph.add_graph_documents(graph_documents, baseEntityLabel=True, include_source=True)
# 4. Text2Cypher QA
chain = GraphCypherQAChain.from_llm(llm=llm, graph=graph)
result = chain.invoke({"query": "What did Marie Curie research?"})18.8.4 Temporal Knowledge Graph
知识图谱的一大挑战是处理时序信息。GraphRAG 本身不内建时间语义;是否维护"什么时候发生/有效"取决于在抽取与图模式(schema)中有没有把时间编码进去。
两种主流方案:
方案一:关系时间戳
直接在实体之间的关系(边)上附加时间戳,明确该关系在何时有效:
CREATE (a:Person {name: "Alice"})
-[:CEO_OF {start: "2020", end: "2023"}]->
(c:Company {name: "Acme"})方案二:版本化节点 + PREVIOUS 关系
当实体属性变化时,创建新节点代表更新后的状态,用 PREVIOUS 关系连接新旧节点,形成可追溯的版本链:
(CEO_2023:Role {holder: "Bob"})-[:PREVIOUS]->(CEO_2020:Role {holder: "Alice"})18.9 LightRAG 框架
LightRAG 的图索引与双层检索范式:Local/Global 查询模式及增量更新优势。
本节目标:深入理解 LightRAG 的图索引机制和双层检索范式,掌握其核心实现细节。
18.9.1 LightRAG 核心思想
LightRAG(香港大学 HKUDS)提出了两大核心创新:基于图的文本索引和双层检索范式。

1. 图索引(Graph-based Text Indexing)
- Extract(结构化):LLM 从文本块中抽取实体和关系
- Prof / Profiling(语义化):LLM 为每个实体和关系生成语义描述
- Dedupe(去重合并):相同实体的多个描述通过
<SEP>拼接或 LLM 摘要合并
2. 双层检索(Dual-level Retrieval Paradigm)
查询处理阶段,LLM 从用户查询中同时提取两类关键词:
| 层级 | 关键词类型 | 检索目标 | 示例 |
|---|---|---|---|
| 低层级(Low-level) | 具体实体名称 | 特定实体的直接关系(局部) | "Alice"、"iPhone 15" |
| 高层级(High-level) | 宏观主题/概念 | 跨实体的关系模式(全局) | "conflict"、"collaboration" |
检索后获取三种信息:Entities(实体节点)、Relations(关系描述)、Original Text(关联的原始文本片段),最终组装为上下文交给 LLM 生成答案。
18.9.2 LightRAG vs GraphRAG
增量更新的优势是 LightRAG 相比 GraphRAG 最显著的工程优势:
对于新文档
而 GraphRAG 在新增文档后需要重新进行社区发现(Community Detection),这是一个耗时的全局操作。
18.9.3 核心文件结构
LightRAG 的工作目录包含三类存储:
working_dir/
├── graph_chunk_entity_relation.graphml # 核心图结构(GraphML 格式)
│
├── kv_store_full_docs.json # 原始文档仓库
├── kv_store_text_chunks.json # 切分后的文本块(检索基本单位)
├── kv_store_full_entities.json # 实体详情(名称、类型、描述)
├── kv_store_full_relations.json # 关系详情(来源、目标、描述)
│
├── vdb_entities.json # 实体向量索引(NanoVectorDB)
├── vdb_relationships.json # 关系向量索引
├── vdb_chunks.json # 文本块向量索引(传统 RAG 用)
│
└── kv_store_llm_response_cache.json # LLM 响应缓存重要设计:LightRAG 对 Edge(关系)也做了 Embedding,这是其区别于其他 KG-RAG 方案的独特之处——全局检索通过关系向量库检索宏观主题,而不仅仅依赖实体。
18.9.4 查询流程详解
当调用 await rag.aquery("Who acquired FooBar?") 时,系统的工作流程:
- 关键词匹配/向量搜索:在
vdb_entities.json中找 "FooBar" 和 "Acquired" 相关的实体 - 图遍历:在
graph_chunk_entity_relation.graphml中找到这些实体节点,获取其邻居关系(发现 "ExampleCorp") - 补充细节:在
kv_store_text_chunks.json中拿出相关的原始文本块作为上下文 - 生成答案:把所有信息组装后交给 LLM 生成最终回复
18.9.5 四种查询模式
from lightrag import LightRAG, QueryParam
# naive: 仅向量检索(传统 RAG),query 与 chunks_vdb 匹配
result = await rag.aquery("Who is Alice?", param=QueryParam(mode="naive"))
# local: 实体邻域检索
# 低层级关键词 → 实体向量库搜索 → 图一度邻居遍历
result = await rag.aquery("Who is Alice?", param=QueryParam(mode="local"))
# global: 关系模式检索
# 高层级关键词 → 关系向量库搜索 → 相关实体获取
result = await rag.aquery("What are the main themes?", param=QueryParam(mode="global"))
# hybrid: local + global(纯图谱检索,不含 chunk 向量检索)
result = await rag.aquery("...", param=QueryParam(mode="hybrid"))
# mix: hybrid + naive(最全面,图谱 + 向量检索,成本最高)
result = await rag.aquery("...", param=QueryParam(mode="mix"))Local 与 Global 的检索差异:
- Local Query:基于实体(Entity)的邻域检索。LLM 从 Query 中提取具体实体名称(Low-level Keywords),在
entities_vdb中检索相似实体,然后在图中进行一跳遍历获取直接关系。关注细节和具体实体的直接关联。 - Global Query:基于关系(Relationship)的全局检索。LLM 提取抽象主题(High-level Keywords),在
relationships_vdb中检索相似的边,再获取这些边两端的实体。关注宏观主题和跨实体的连接模式。
18.9.6 实体合并机制
当多个文档中出现同一实体时,LightRAG 采用 Upsert(Update + Insert)策略合并:
节点(实体)合并:
# 若实体 Alice 已存在
# Chunk 1 描述:"An engineer",source_id: "chunk_1"
# Chunk 2 描述:"Lives in NY",source_id: "chunk_2"
# 合并结果:
# {"description": "An engineer<SEP>Lives in NY",
# "source_id": "chunk_1<SEP>chunk_2"}边(关系)合并使用智能摘要(Map-Reduce)策略:
- 收集(Collect):将所有从不同 Chunk 中提取的关于该关系的描述收集起来
- 去重(Deduplicate):通过
unique_edges字典对完全相同的描述文本去重 - 判断(Decision):
- 若描述总长度未超阈值:用
<SEP>分隔符拼接 - 若超出 Token 限制:调用 LLM 的
summarize_entity_descriptionsPrompt 进行语义压缩
- 若描述总长度未超阈值:用
- 边的权重(weight)累加,反映关系在语料中出现的频率
实体命名一致性完全依赖 LLM 在抽取阶段的归一化。System Prompt 中明确要求:
entity_name: If the entity name is case-insensitive, capitalize the first letter
of each significant word (title case). Ensure consistent naming across the
entire extraction process.这意味着 "Apple Inc." 和 "Apple" 在 LightRAG 眼里是两个不同的节点,除非 LLM 在提取时明确将它们归一化。
18.9.7 核心 Prompt 设计
LightRAG 的 Prompt 体系(lightrag/prompt.py):
| Prompt | 功能 |
|---|---|
entity_extraction_system_prompt | 定义实体/关系抽取规则和输出格式 |
entity_extraction_examples | Few-shot 示例,提升抽取质量 |
entity_continue_extraction_user_prompt | 自我纠错(第二轮对话,补充遗漏) |
summarize_entity_descriptions | 实体/关系描述摘要(长描述合并时使用) |
keywords_extraction | 从用户查询中提取 low-level + high-level 关键词 |
rag_response | 最终答案生成 |
自我纠错 Prompt 的设计亮点在于两轮对话结构:第一轮执行正常抽取,第二轮要求模型检查遗漏:
Based on the last extraction task, identify and extract any
**missed or incorrectly formatted** entities and relationships
from the input text.18.9.8 分块策略
LightRAG 默认使用固定窗口分块:
默认参数:
chunk_token_size = 1200(每块约 1200 个 token)chunk_overlap_token_size = 100(相邻块重叠 100 个 token,保证上下文连贯性)
18.9.9 可观测性:Langfuse 集成
LightRAG 原生集成 Langfuse。只要检测到环境变量,它会自动替换 OpenAI Client 为 Langfuse 的 Client,在后台记录完整的 API 调用轨迹(Trace):
# .env
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST="http://localhost:3000"启动后日志会显示:INFO: Langfuse observability enabled for OpenAI client
通过 Langfuse 可以监控每个环节的 LLM API 输入/输出,分析整个 LightRAG pipeline 中各步骤的 Prompt、Token 消耗和延迟。
18.10 Memory-RAG:Agent 记忆系统
Agent 记忆系统的架构设计:从事件流到 Proposition 分块,三层 RAG 融合的完整方案。
本节目标:理解 Agent 记忆系统的架构设计,掌握 Memory-RAG 的实现思路和高级分块策略。
18.10.1 为什么 Agent 需要记忆
LLM 本身是无状态的,每次对话从零开始。但在 Agent 场景中,系统需要:
- 记住用户偏好:知道用户喜欢简洁回答还是详细解释
- 跨会话延续上下文:上周讨论的项目进展不需要重新解释
- 积累工作记录:Agent 执行过的操作、得到的结论
18.10.2 Memory 的基本实现
**事件流(Events Stream)**方案:最简单的记忆实现,维护一个带时间戳的事件序列:
events = [
{"time": "2025-01-10 09:00", "type": "user_preference",
"content": "用户偏好简洁的代码,不喜欢冗长注释"},
{"time": "2025-01-10 10:30", "type": "task_result",
"content": "完成了 API 文档生成任务"},
]这种方案的优势在于实现简单,但缺乏语义检索能力,且随着事件增长会面临上下文窗口限制。
18.10.3 Memory vs RAG 的设计区分
| 维度 | 传统 RAG | Memory-RAG |
|---|---|---|
| 数据来源 | 静态文档库 | 动态交互历史 |
| 更新方式 | 批量索引 | 实时写入 |
| 检索粒度 | 文档块 | 事件/记忆条目 |
| 时间属性 | 无时序概念 | 强时序,近期优先 |
18.10.4 三层 RAG 融合
在完整的 Agent 系统中,RAG 通常分三层:
- User Prompt RAG:从知识库中检索与用户当前输入相关的信息
- Tool RAG:根据任务需求动态选择工具(工具描述本身也可以通过检索获取)
- Memory RAG:从记忆库检索与当前上下文相关的历史事件和用户偏好
每层检索后通过 Rerank 进行二次筛选(当然也可以基于 LLM 进行 semantic double check),确保注入到 LLM 的上下文精准且不冗余。
18.10.5 高级分块策略
Proposition-based Chunking(Dense X Retrieval,arXiv:2312.06648):
传统分块按字数切分,信息粒度粗糙。该论文提出引入一个新的检索单元——"命题"(Proposition)。一个命题被定义为一个原子化的、自包含的事实陈述,以简洁的自然语言形式呈现。
示例:
原文:比萨斜塔在1990年至2001年间的修复工作之前倾斜5.5度,现在倾斜约3.99度
命题分解:
比萨斜塔曾以5.5度的角度倾斜。在1990年至2001年间,比萨斜塔进行了修复工作。比萨斜塔现在倾斜约3.99度。
每个命题封装了一个不可再分的、有上下文的独立事实。优势:检索粒度更精准,避免大块文本中无关内容的噪音干扰。该理念已被 LlamaIndex 实现为 DenseXRetrievalPack。
18.10.6 LangChain vs LlamaIndex 的分块哲学
LangChain 和 LlamaIndex 在分块功能上的差异反映了对"块"这一基本单元的不同哲学:
| 框架 | 返回类型 | 设计哲学 |
|---|---|---|
LangChain split_text | List[str] | 块是基础字符串,元数据在外部管理(工具箱模式) |
LlamaIndex get_nodes_from_documents | List[Node] | 块是富对象,内含元数据、Embedding 和 parent_node 指针 |
LlamaIndex 的 Node 设计天然支持层级检索(AutoMergingRetriever):先用小块检索,命中率高时自动合并为父块提供更完整上下文。如果块仅仅是独立的字符串,这种逻辑将难以实现。
18.11 Mem0 实践
Mem0 框架的记忆生命周期管理:智能提取、语义检索与冲突处理。
本节目标:了解 Mem0 框架的核心能力,掌握其在 Agent 记忆管理中的应用。
18.11.1 Mem0 简介
Mem0 是一个专为 AI Agent 设计的记忆管理框架,提供了记忆的完整生命周期管理:写入、检索、更新、遗忘。
核心论文:arXiv:2504.19413
18.11.2 Mem0 的核心能力
智能记忆提取:Mem0 不是简单地存储所有对话,而是通过 LLM 从对话中抽取有价值的"记忆条目":
from mem0 import Memory
m = Memory()
# 从对话中自动提取记忆
result = m.add(
messages=[
{"role": "user", "content": "我是 Python 开发者,不喜欢 JavaScript"},
{"role": "assistant", "content": "好的,我会优先给你 Python 示例"}
],
user_id="user_001"
)
# Mem0 自动提取并存储:
# {"memory": "用户是 Python 开发者,不喜欢 JavaScript"}语义检索:
# 按语义相关性检索记忆
memories = m.search(query="用户的技术栈", user_id="user_001")
# 返回按相关性排序的记忆列表记忆更新与冲突处理:当新信息与已有记忆冲突时(例如用户改变了偏好),Mem0 通过 LLM 判断是更新已有记忆还是新增记忆条目。
18.11.3 在 Agent 中集成 Mem0
from mem0 import Memory
from openai import OpenAI
memory = Memory()
client = OpenAI()
def chat_with_memory(user_message: str, user_id: str) -> str:
# 1. 检索相关记忆
relevant_memories = memory.search(query=user_message, user_id=user_id)
memory_context = "\n".join([m["memory"] for m in relevant_memories])
# 2. 注入记忆到 System Prompt
system_prompt = f"""你是一个个性化助手。
用户历史偏好和背景信息:
{memory_context}
请基于以上信息提供个性化回答。"""
# 3. LLM 生成
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
answer = response.choices[0].message.content
# 4. 更新记忆(从对话中提取新的记忆条目)
memory.add(
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": answer}
],
user_id=user_id
)
return answer工作流程:检索记忆 → 注入上下文 → LLM 生成 → 更新记忆。这形成了一个自增强的记忆循环。
18.11.4 Memory 与 RAG 的统一视角
从系统设计角度,Memory-RAG 可以看作是将 RAG 的"外部知识库"替换为"个人化交互历史库":
传统 RAG: Query → 检索文档库 → LLM 生成
Memory-RAG: Query → 检索记忆库 → LLM 生成
融合方案: Query → 检索(文档库 ∪ 记忆库) → Rerank → LLM 生成Mem0 的底层存储通常包含三层:
- 向量存储(Vector Store):记忆的语义检索
- 图存储(Graph Store):记忆间的关系(例如"用户 A 提到过 B 项目,B 项目与 C 技术相关")
- 键值存储(KV Store):原始记忆文本
这种多层存储设计使得 Mem0 既能做精确匹配,也能做语义检索,还能利用图结构进行关联推理。
本章小结
本章系统介绍了 RAG 与知识增强的完整技术栈:
RAG Pipeline(18.1):两阶段检索(Bi-Encoder 初排 → Cross-Encoder Rerank 精排)解决召回与精度的矛盾。Rerank 同时缓解"Lost in the Middle"问题,在高召回、高精度、低成本之间取得平衡。
评估体系(18.2):EM、F1 评估答案质量;Recall@K、MRR 评估检索质量;RAGAS 等框架从 Faithfulness、Relevancy、Precision、Recall 四个维度提供端到端评估。
文档解析(18.3):MinerU 将复杂 PDF 转换为结构化 JSON(
x_content_list.json),按 text/equation/table/image 分类输出,为多模态 RAG 提供基础。论文检索(18.4):ChromaDB 提供轻量级本地向量存储,适合构建中小规模学术检索系统。
多模态 RAG(18.5):RAGAnything 通过 VLM 为非文本内容生成语义描述,将所有模态统一到向量检索空间。
知识图谱(18.6):LLMGraphTransformer 自动从文本构建 KG。KG-based RAG 解决了向量 RAG 在多跳推理、计数聚合、关系推理上的不足。
Neo4j 实践(18.7):Cypher 查询语言、LangChain 集成、Text2Cypher 自然语言查询、图可视化的完整工作流。
GraphRAG(18.8):微软方案通过社区发现 + 层次摘要支持全局性问题。LangGraph 提供 Text2Cypher 集成。Temporal KG 通过关系时间戳或版本化节点处理时序信息。
LightRAG(18.9):图索引 + 双层检索,支持 naive/local/global/hybrid/mix 五种查询模式。增量更新通过集合并集实现,无需重建图。实体合并通过
<SEP>拼接 + LLM 摘要。对关系做 Embedding 是其独特设计。Memory-RAG(18.10):从事件流到 Proposition 分块,Agent 记忆系统的核心设计。三层 RAG 融合(User Prompt + Tool + Memory)实现完整的 Agent 知识架构。
Mem0(18.11):提供记忆的完整生命周期管理(写入、检索、更新、遗忘),底层整合向量/图/KV 三层存储。
体系化认知:Chunk-based RAG 处理"点",KG-based RAG 处理"网",Memory-RAG 处理"流"——三者各有适用场景,可在同一系统中融合使用。
延伸阅读
- RAG 最佳实践综述:arXiv:2407.01219(Searching for Best Practices in RAG)
- Dense X Retrieval(命题分块):arXiv:2312.06648
- LightRAG 论文与项目:
https://github.com/HKUDS/LightRAG - Mem0 论文:arXiv:2504.19413
- Neo4j GraphRAG 生态:
https://neo4j.com/blog/news/graphrag-ecosystem-tools/ - LangChain 知识图谱文档:
https://python.langchain.com/docs/how_to/graph_constructing/ - LangChain GraphRAG 集成:
https://python.langchain.com/docs/integrations/retrievers/graph_rag/ - Temporal Knowledge Graph 实践:OpenAI Cookbook temporal_agents_with_knowledge_graphs
- RAGAS 评估框架:
https://docs.ragas.io/ - Memory vs RAG 深度对比:
https://supermemory.ai/docs/memory-vs-rag - LangExtract 介绍:
https://towardsdatascience.com/using-googles-langextract-and-gemma-for-structured-data-extraction/