Skip to content

第 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):预测答案与标准答案完全匹配的比例。

EM=1Ni=1N1[a^i=ai]

直觉:最严格的指标,一个字不差才算对。适合评估事实类问答("X 的首都是哪里?"),但对开放式问题过于苛刻。

F1 Score:在词元(token)级别计算预测答案与标准答案的重叠度。

F1=2PrecisionRecallPrecision+Recall

其中:

  • Precision=|a^a||a^|(预测词元中正确的比例)
  • Recall=|a^a||a|(标准答案词元被覆盖的比例)

直觉:比 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 基础用法

bash
# 命令行解析
mineru -p input_file.pdf -o output_dir -m auto

-m 参数控制解析模式:

  • auto:自动选择(推荐)
  • txt:纯文本模式,速度快但不处理图像
  • ocr:OCR 模式,适合扫描件

18.3.3 核心输出格式

MinerU 的核心输出包含两个部分:

1. Markdown 文件(图文交错格式)

markdown
# 论文标题

## 摘要
这是摘要文本...

![图1说明](images/ch18-lightrag.png)

## 1 引言
...

这个 Markdown 文件保留了文档的层级结构,图片被提取到 images/ 子目录。

2. x_content_list.json(结构化内容列表)

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)中:

python
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 流程:

  1. 数据准备:批量收集会议论文的标题、摘要、关键词等元数据
  2. Embedding 索引:将论文摘要编码为向量并存入 ChromaDB
  3. 语义检索:用户输入自然语言查询,系统返回语义最相关的论文列表
  4. 过滤增强:支持按研究方向、评分等元数据进行二次筛选

这种方案的优势在于部署简单(纯 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 知识图谱的核心抽象

KG=(V,E)
  • V:节点集合(Nodes),代表实体——Person、Organization、Location、Concept 等
  • E:边集合(Edges),代表关系——WORKS_ATSPOUSEFIELD_OF_RESEARCH

每条边是一个三元组 (source,predicate,target),例如 (Marie Curie, AWARDED, Nobel Prize)

知识图谱的构建流程:

非结构化文本
    ↓ LLM 信息抽取(Entity & Relation Extraction)
(实体, 关系, 实体) 三元组
    ↓ 去重 + 合并
图数据库存储(Neo4j 等)
    ↓ 查询接口
GraphRAG / Text2Cypher

18.6.3 LLM Graph Transformer

LangChain 提供了 LLMGraphTransformer,通过 LLM 自动从非结构化文本中抽取图结构。它的核心设计是将"图抽取"的输出 Schema(节点、关系及其属性的结构)作为一个 Tool 绑定到模型,让模型按 Schema 返回结构化结果。

python
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),避免具体类型(MathematicianScientist)。这确保了不同上下文中同一类型的实体使用统一的标签。

节点 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 的主流存储后端。

本地部署

  1. 安装 neo4j-desktop
  2. 配置 neo4j.conf,设置 server.bolt.listen_address=0.0.0.0:7687
  3. 通过 http://localhost:7474/browser/ 访问 Web 管理界面
  4. 安装 APOC 插件(Advanced Procedures on Cypher),支持图合并等高级操作

18.7.2 Cypher 查询语言

Cypher 是 Neo4j 的声明式图查询语言。基础语法:

cypher
-- 查看所有节点(限制数量)
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 n

18.7.3 LangChain + Neo4j 集成

python
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 语句的技术,让用户可以用自然语言查询知识图谱:

python
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 库进行交互式可视化:

python
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 的核心步骤:

  1. 实体/关系抽取:从原始文档提取三元组
  2. 图构建:建立实体关系网络
  3. 社区发现(Community Detection):对图进行 bottom-up 聚类,识别语义相关的实体群组。图被用来 "Create a bottom-up clustering that organizes the data hierarchically into semantic clusters"
  4. 社区摘要生成:为每个社区生成结构化摘要报告(Community Report),实现对数据的全局理解(holistic understanding)
  5. 查询
    • Local Search:从查询相关实体出发,在局部子图中检索
    • Global Search:遍历所有社区摘要,回答全局性问题("文档的主要主题是什么?")

18.8.2 GraphRAG vs KG-based RAG 对比

特性KG-based RAGGraphRAG(微软)
图结构用途实体-关系检索社区发现 + 摘要生成
全局理解有限强(bottom-up 层次摘要)
增量更新支持需重新社区发现(成本高)
时间语义需手动添加不内建

18.8.3 LangChain 集成实践

完整的 LangChain + Neo4j GraphRAG 集成流程:

python
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)中有没有把时间编码进去。

两种主流方案:

方案一:关系时间戳

直接在实体之间的关系(边)上附加时间戳,明确该关系在何时有效:

cypher
CREATE (a:Person {name: "Alice"})
  -[:CEO_OF {start: "2020", end: "2023"}]->
  (c:Company {name: "Acme"})

方案二:版本化节点 + PREVIOUS 关系

当实体属性变化时,创建新节点代表更新后的状态,用 PREVIOUS 关系连接新旧节点,形成可追溯的版本链:

cypher
(CEO_2023:Role {holder: "Bob"})-[:PREVIOUS]->(CEO_2020:Role {holder: "Alice"})

18.9 LightRAG 框架

LightRAG 的图索引与双层检索范式:Local/Global 查询模式及增量更新优势。

本节目标:深入理解 LightRAG 的图索引机制和双层检索范式,掌握其核心实现细节。

18.9.1 LightRAG 核心思想

LightRAG(香港大学 HKUDS)提出了两大核心创新:基于图的文本索引双层检索范式

LightRAG 架构图

1. 图索引(Graph-based Text Indexing)

Graph Index=Dedupe(Prof(Extract(Text)))
  • 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 最显著的工程优势:

对于新文档 D,LightRAG 无需重建整个图。它对新文档执行相同的提取步骤,然后通过集合并集操作快速合并:

V^V^E^E^

而 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?") 时,系统的工作流程:

  1. 关键词匹配/向量搜索:在 vdb_entities.json 中找 "FooBar" 和 "Acquired" 相关的实体
  2. 图遍历:在 graph_chunk_entity_relation.graphml 中找到这些实体节点,获取其邻居关系(发现 "ExampleCorp")
  3. 补充细节:在 kv_store_text_chunks.json 中拿出相关的原始文本块作为上下文
  4. 生成答案:把所有信息组装后交给 LLM 生成最终回复

18.9.5 四种查询模式

python
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)策略合并:

节点(实体)合并

python
# 若实体 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)策略:

  1. 收集(Collect):将所有从不同 Chunk 中提取的关于该关系的描述收集起来
  2. 去重(Deduplicate):通过 unique_edges 字典对完全相同的描述文本去重
  3. 判断(Decision)
    • 若描述总长度未超阈值:用 <SEP> 分隔符拼接
    • 若超出 Token 限制:调用 LLM 的 summarize_entity_descriptions Prompt 进行语义压缩
  4. 边的权重(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_examplesFew-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 默认使用固定窗口分块:

startk=k×(chunk_token_sizechunk_overlap_token_size)

默认参数:

  • 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):

bash
# .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)**方案:最简单的记忆实现,维护一个带时间戳的事件序列:

python
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 的设计区分

维度传统 RAGMemory-RAG
数据来源静态文档库动态交互历史
更新方式批量索引实时写入
检索粒度文档块事件/记忆条目
时间属性无时序概念强时序,近期优先

18.10.4 三层 RAG 融合

在完整的 Agent 系统中,RAG 通常分三层:

  1. User Prompt RAG:从知识库中检索与用户当前输入相关的信息
  2. Tool RAG:根据任务需求动态选择工具(工具描述本身也可以通过检索获取)
  3. 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_textList[str]块是基础字符串,元数据在外部管理(工具箱模式)
LlamaIndex get_nodes_from_documentsList[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 从对话中抽取有价值的"记忆条目":

python
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"}

语义检索

python
# 按语义相关性检索记忆
memories = m.search(query="用户的技术栈", user_id="user_001")
# 返回按相关性排序的记忆列表

记忆更新与冲突处理:当新信息与已有记忆冲突时(例如用户改变了偏好),Mem0 通过 LLM 判断是更新已有记忆还是新增记忆条目。

18.11.3 在 Agent 中集成 Mem0

python
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 与知识增强的完整技术栈:

  1. RAG Pipeline(18.1):两阶段检索(Bi-Encoder 初排 → Cross-Encoder Rerank 精排)解决召回与精度的矛盾。Rerank 同时缓解"Lost in the Middle"问题,在高召回、高精度、低成本之间取得平衡。

  2. 评估体系(18.2):EM、F1 评估答案质量;Recall@K、MRR 评估检索质量;RAGAS 等框架从 Faithfulness、Relevancy、Precision、Recall 四个维度提供端到端评估。

  3. 文档解析(18.3):MinerU 将复杂 PDF 转换为结构化 JSON(x_content_list.json),按 text/equation/table/image 分类输出,为多模态 RAG 提供基础。

  4. 论文检索(18.4):ChromaDB 提供轻量级本地向量存储,适合构建中小规模学术检索系统。

  5. 多模态 RAG(18.5):RAGAnything 通过 VLM 为非文本内容生成语义描述,将所有模态统一到向量检索空间。

  6. 知识图谱(18.6):LLMGraphTransformer 自动从文本构建 KG。KG-based RAG 解决了向量 RAG 在多跳推理、计数聚合、关系推理上的不足。

  7. Neo4j 实践(18.7):Cypher 查询语言、LangChain 集成、Text2Cypher 自然语言查询、图可视化的完整工作流。

  8. GraphRAG(18.8):微软方案通过社区发现 + 层次摘要支持全局性问题。LangGraph 提供 Text2Cypher 集成。Temporal KG 通过关系时间戳或版本化节点处理时序信息。

  9. LightRAG(18.9):图索引 + 双层检索,支持 naive/local/global/hybrid/mix 五种查询模式。增量更新通过集合并集实现,无需重建图。实体合并通过 <SEP> 拼接 + LLM 摘要。对关系做 Embedding 是其独特设计。

  10. Memory-RAG(18.10):从事件流到 Proposition 分块,Agent 记忆系统的核心设计。三层 RAG 融合(User Prompt + Tool + Memory)实现完整的 Agent 知识架构。

  11. 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/