8. RAG 与 Agent 系统整合
1. 从模块到系统:为什么需要架构收束
前几章我们逐一讨论了 RAG 流程中的各个组件:文档切分(chunking)、向量编码、检索、重排、记忆机制、长上下文处理、上下文压缩。这些组件单独来看都有成熟的实践方法,但当它们组合在一起时,系统层面的问题才开始浮现:
- 检索结果如何影响最终生成质量?
- 召回、精排、上下文窗口三者之间存在怎样的耦合关系?
- 外部知识、模型参数记忆、Agent 动作三者如何分工协作?
- 工具调用失败、检索噪声、上下文冲突如何统一处理?
本章将把这些模块统一到一个完整系统中,从数据流、接口设计、工程实践三个维度,给出 RAG/Agent 系统的全貌。
2. 完整系统架构
2.1 数据流总览
一个完整的 RAG/Agent 系统通常包含以下阶段:
原始文档 → 接入层 → 切分(Chunking) → 编码(Embedding) → 索引(Indexing)
↓
用户查询 → 查询理解 → 召回(Recall) → 精排(Rerank)
↓
压缩(Compression) → 上下文组装(Context Assembly)
↓
生成(Generation)
↓
记忆更新(Memory Update)
各阶段职责明确但相互耦合,不能独立调优。
2.2 组件分工与接口定义
| 组件 | 输入 | 输出 | 核心职责 |
|---|---|---|---|
| 文档接入层 | 原始文档(PDF/HTML/Markdown 等) | 结构化文本块 | 格式解析、噪声过滤、元数据提取 |
| 切分器 | 长文档 | 子块列表 | 保证语义完整性、控制块大小 |
| 编码器 | 文本块 | 向量嵌入 | 语义表示、跨语言对齐 |
| 索引 | 向量 + 元数据 | 可查询索引 | 高效检索、过滤条件支持 |
| 召回模块 | 查询向量 | Top-K 候选块 | 高召回、初步筛选 |
| 精排模型 | 查询 + 候选块 | 重排列表 | 语义精细化排序 |
| 压缩器 | 精排后上下文 | 精简上下文 | 降低噪声、适配窗口限制 |
| 上下文组装器 | 用户查询 + 记忆 + 检索块 | 组装后 prompt | 格式模板、角色设定、指令注入 |
| 生成模型 | 组装后 prompt | 自然语言回复 | 内容生成、质量控制 |
| 记忆模块 | 对话历史 + 重要信息 | 更新后的记忆向量/图 | 长期知识保持、上下文延续 |
接口设计的核心原则:
- 每个组件应该对上下游透明:召回模块不需要知道生成模型的能力上限,但它可以通过返回足够多的候选让压缩器有选择空间
- 信息流应该是双向的:不只是检索结果流向生成,生成阶段的置信度可以反馈给检索模块调整策略
- 元数据需要贯穿全流程:文档来源、块 ID、置信度分数、引用信息应在各组件间传递
3. 检索结果如何影响最终生成
3.1 检索质量到生成质量的映射
RAG 系统的生成质量并非简单由检索 quality 决定,而是由「检索结果对当前查询的覆盖度」决定。设生成模型为
生成质量的关键约束:
覆盖度约束:若相关文档
噪声约束:
上下文窗口约束:即使检索到了高质量候选,如果上下文组装阶段丢失了关键信息(由于长度限制或压缩不当),生成结果仍然会受损。
3.2 检索结果对生成的量化影响
设检索返回
生成质量指标(如 ROUGE、BLEU)与
其中
这解释了为什么:
- 只追求召回率不够:即使 100% 召回,如果噪声太多,生成质量依然差
- 只追求精确率不够:返回 1 个完美相关文档,但如果模型需要多个证据才能推理,依然不够
- 需要平衡 precision@K 和 recall@K:通常召回 20-50 个候选再做精排,是工程实践中的经验值
4. 召回、精排、上下文窗口的耦合关系
4.1 三者不是独立模块
这三个组件形成了一个耦合三角:
召回 (Recall)
/\
/ \
/ \
/ \
/ \
↓ ↓
精排(Rerank) ←→ 上下文窗口(Context Window)
召回 → 精排:召回阶段决定候选集的 recall 上限,精排只能在召回候选内排序。如果召回阶段遗漏了相关文档,精排无能为力。
精排 → 上下文窗口:精排结果直接决定了哪些文档进入上下文窗口。精排不仅要排 relevance,还要考虑文档之间的互补性、信息多样性、长度等因素。
上下文窗口 → 召回策略:如果上下文窗口较小(如 4K tokens),召回阶段可以只返回较高置信度的 top-10;如果窗口很大(如 128K tokens),召回可以更激进,返回 top-100 让精排有更多选择空间。
4.2 窗口大小对系统设计的约束
| 窗口级别 | 典型场景 | 召回策略 | 精排策略 | 压缩需求 |
|---|---|---|---|---|
| 短窗口(≤4K) | 实时问答、单一问题 | Top-10,高 precision | 必须高准确率 | 最小化压缩,几乎不压缩 |
| 中窗口(8-32K) | 文档分析、报告生成 | Top-20-50,平衡 | 需考虑多样性 | 轻度压缩,保留结构 |
| 长窗口(64K+) | 多文档综合、长时间对话 | Top-100,高 recall | 需分组/聚类 | 选择性压缩,重点突出 |
关键洞察:不要把窗口扩大看作简单的”塞更多内容”。更大的窗口要求更强的精排能力和更智能的压缩策略,否则只是把噪声放大了。
5. 外部知识、模型参数记忆、Agent 动作的分工
5.1 三种知识来源的定位
| 知识来源 | 特点 | 适用场景 | 局限性 |
|---|---|---|---|
| 外部知识(RAG) | 动态、可更新、需检索 | 实时数据、特定领域知识、长文档 | 有检索延迟、依赖检索质量 |
| 模型参数记忆 | 无延迟、整合深、抽象层次高 | 通用常识、推理能力、通用模式 | 静态、知识边界模糊、可能幻觉 |
| Agent 动作(工具调用) | 可执行、与外部系统交互 | 查天气、订票、数据库查询、API调用 | 需设计工具、可能失败、需要决策 |
为什么需要三者共存:
模型参数记忆无法覆盖所有场景(如 2024 年的新闻、特定公司的内部数据),RAG 解决了知识的时效性和覆盖度问题。但 RAG 本身没有执行能力,需要 Agent 动作来完成”做事情”而非仅”回答问题”。而模型参数记忆提供的推理能力和常识,是 RAG 和 Agent 动作无法替代的——它让系统能够真正理解和推理,而非只是检索和拼接。
5.2 决策框架:何时检索、何时查记忆、何时调用工具
用户输入
↓
┌─────────────────────────────────────────────┐
│ 判断依据: │
│ 1. 是否需要最新信息?(检索) │
│ 2. 是否需要跨对话上下文?(查记忆) │
│ 3. 是否需要执行操作?(工具调用) │
│ 4. 模型本身能回答吗?(依赖参数记忆) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 组合策略: │
│ - 常识问题 → 直接依赖模型 │
│ - 需要特定文档 → RAG │
│ - 需要历史上下文 → 查记忆 + RAG │
│ - 需要执行 → 工具调用 │
│ - 复杂任务 → 以上组合 │
└─────────────────────────────────────────────┘
5.3 World Model、Memory Agent、RAG 的协作
在复杂 Agent 系统中,三者的协作模式:
World Model(世界模型):负责理解当前环境状态和任务目标。它调用记忆来了解历史上下文,调用 RAG 来获取相关文档,调用工具来执行动作。世界模型本身不做具体的信息检索或工具调用,而是决策何时用何种知识源。
Memory Agent(记忆代理):管理短期记忆(当前对话)和长期记忆(历史积累)。当 World Model 需要历史信息时,Memory Agent 检索相关记忆返回。它还负责将当前对话中的重要信息写入长期记忆。
RAG 系统:作为 World Model 的外部知识库。当 World Model 判断需要特定领域的专业知识时,触发 RAG 检索。RAG 的结果再传回给 World Model 用于决策或生成。
World Model(决策中心)
├── 状态理解 → 调用 Memory Agent
├── 知识查询 → 调用 RAG
└── 动作执行 → 调用工具
Memory Agent(记忆管理)
└── 记忆读写
RAG(知识检索)
└── 文档检索
6. 异常处理:工具失败、检索噪声、上下文冲突
6.1 工具调用失败的处理
工具调用可能失败的原因:超时、无权限、网络错误、参数错误、返回格式不符合预期。
处理策略:
重试机制:对于临时性故障(网络超时),设置指数退避重试(最多 3 次)。
降级策略:对于持续性失败,切换到备用方案:
- 搜索 API 失败 → 尝试数据库直接查询
- 所有工具失败 → 回退到纯模型参数记忆
置信度传递:工具返回结果需要携带置信度标记。当置信度低于阈值时,生成模型应被提示”这个结果可能不准确”。
def tool_call_with_fallback(tool_name, args, max_retries=3):
for attempt in range(max_retries):
try:
result = call_tool(tool_name, args)
if result.confidence > threshold:
return result
except ToolError as e:
if attempt == max_retries - 1:
# 降级:尝试备用工具或回退到模型知识
return fallback(tool_name, args)
wait(exponential_backoff(attempt))6.2 检索噪声的处理
检索噪声包括:语义相关但不直接回答问题的文档、部分匹配的片段、多文档中的冲突信息。
多阶段过滤:
- 召回阶段:用 ANN/BM25 快速召回候选,允许低精度
- 精排阶段:用重排模型进一步筛选,提升 precision
- 压缩阶段:选择与查询最相关的句子/段落
多样性约束:在精排时加入 MMR(Maximal Marginal Relevance)约束,避免返回高度相似的文档,确保候选多样性。
冲突检测:当多个检索文档对同一问题给出不同答案时,应:
- 在上下文中标注冲突
- 让生成模型根据置信度判断
- 或者返回”根据不同来源存在分歧,以下是各方观点…“
6.3 上下文冲突的处理
上下文冲突指:检索结果与模型参数记忆矛盾、多个检索结果相互矛盾、用户历史偏好与检索结果不一致。
冲突检测与标注:在上下文组装阶段,检测并标注冲突。让生成模型知道哪些是有争议的信息。
优先级机制:
- 用户明确提供的上下文 > 检索结果 > 模型知识
- 最近的对话历史 > 早期对话历史
- 权威来源(如官方文档)> 非权威来源
置信度加权生成:当存在冲突时,生成模型应被提示不同来源的置信度,生成结果应反映这种不确定性。
7. 工程实践:数据流、接口、缓存、日志、追踪
7.1 端到端数据流
用户输入 → 查询预处理 → 查询理解
↓
意图分类(闲聊/知识/动作)
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
闲聊处理 知识检索 动作执行
(模型直接答) (RAG流程) (工具调用)
↓ ↓ ↓
└──────────────┼──────────────┘
↓
上下文组装 + 记忆整合
↓
生成模型
↓
记忆更新 + 响应返回
7.2 组件接口设计原则
接口标准化:每个组件应通过标准接口通信,推荐使用 JSON/Protobuf 格式:
// 检索接口示例
{
"query": "用户查询文本",
"top_k": 20,
"filters": {"source": "official_docs", "date_range": "2024-01-01~"},
"return_metadata": ["source", "title", "url", "relevance_score"]
}
// 返回格式
{
"results": [
{
"content": "文档片段...",
"metadata": {"source": "api_docs", "title": "..."},
"relevance_score": 0.92,
"chunk_id": "doc_123_chunk_5"
}
],
"total_hits": 150,
"query_time_ms": 23
}无状态设计:每个组件应尽量无状态,状态统一交给记忆模块管理,便于扩展和调试。
熔断机制:当下游组件连续失败时,触发熔断,避免雪崩。
7.3 缓存策略
| 缓存内容 | 缓存位置 | TTL | 适用场景 |
|---|---|---|---|
| Embedding 结果 | Redis/内存 | 7天 | 相同文档的重复检索 |
| 检索结果 | Redis | 1小时 | 相同查询的重复检索 |
| 生成结果 | Redis | 按业务定 | 重复问题的标准回答 |
| 用户会话 | 数据库 | 30天 | 对话历史恢复 |
缓存失效策略:使用文档版本号 + 时间戳双重校验。当文档更新时,版本号变化,缓存自动失效。
7.4 日志与追踪
结构化日志:每个请求应生成唯一 trace_id,贯穿所有组件:
{
"trace_id": "uuid-v4",
"timestamp": "2024-01-15T10:23:45Z",
"stage": "retrieval",
"component": "vector_search",
"query": "用户查询",
"top_k": 20,
"latency_ms": 23,
"results_count": 20,
"cache_hit": false
}关键指标:
- 检索延迟(p50/p95/p99)
- 缓存命中率
- 检索 recall@K(需要标注数据)
- 生成质量评分(BLEU/ROUGE/人工评估)
- 工具调用成功率
分布式追踪:使用 OpenTelemetry 或 Jaeger 追踪请求在各个组件间的流转,便于定位慢请求和错误。
7.5 端到端调试清单
当系统输出不符合预期时,按以下顺序排查:
1. 查询理解阶段
- 查询是否被正确解析?(意图分类是否正确)
- 查询扩展/改写是否引入了噪声?
- 查询向量是否正确生成?
2. 检索阶段
- 索引是否是最新的?(新增文档是否已索引)
- 召回结果是否包含相关文档?(recall 检查)
- 检索参数(top_k、filters)是否合理?
- Embedding 模型是否适合该领域?
3. 精排与压缩阶段
- 精排模型是否考虑了查询意图?
- 压缩是否丢失了关键信息?
- 是否有冲突的检索结果未被处理?
4. 生成阶段
- 上下文是否被正确组装?
- Prompt 模板是否有遗漏的指令?
- 生成模型的温度/Top-P 设置是否合适?
5. 记忆阶段
- 历史上下文是否被正确检索?
- 记忆更新是否引入了噪声?
- 多轮对话中记忆检索是否准确?
6. 工具调用阶段
- 工具参数是否正确传递?
- 工具返回是否被正确解析?
- 工具失败是否被正确降级?
8. 常见失败模式与防御策略
8.1 检索不准
症状:模型回答与文档内容不符,或者明显幻觉。
根因:
- Embedding 模型与文档领域不匹配
- 切分策略破坏了语义完整性
- 索引构建时元数据丢失
防御:
- 针对领域微调 Embedding 模型
- 使用语义切分而非固定长度切分
- 保留并传递元数据用于过滤
8.2 上下文污染
症状:生成内容中出现了检索文档的片段,但与问题无关,或者多个文档的信息被错误混合。
根因:
- 精排阶段没有有效筛选
- 压缩阶段丢失了结构信息(哪些内容来自哪篇文档)
- 组装阶段没有正确标记来源
防御:
- 精排阶段加入相关性阈值过滤
- 使用 citation 标记每个检索片段的来源
- 组装时保持”查询-检索块-生成”的引用链
8.3 幻觉
症状:模型生成了文档中不存在的内容,且语气置信度很高。
根因:
- 检索结果不完整,模型尝试”补全”
- 模型过度依赖参数记忆,忽略检索结果
- 检索结果与问题表面相关但实际不匹配
防御:
- 在 Prompt 中明确指令:“仅基于提供的上下文回答”
- 引入”不知道”选项,当检索结果不足时主动拒答
- 使用 contrastive RAG,对比正负样本训练检索器
8.4 过度依赖外部文档
症状:模型对所有问题都检索,即使是很常识的问题也不调用参数记忆。
根因:
- 意图分类不准确,把常识查询误判为知识查询
- 检索的收益被高估(因为总是能返回结果)
- 系统没有对”直接回答”给出正反馈
防御:
- 优化意图分类模型,提高常识判断准确率
- 在检索后评估相关性,若检索结果不相关则回退到模型回答
- 建立”直接回答 vs 检索回答”的评估反馈循环
9. 离线评测与在线观测
9.1 离线评测
检索系统评测:
| 指标 | 定义 | 评测方法 |
|---|---|---|
| Recall@K | Top-K 结果中相关文档的比例 | 人工标注相关性,构建测试集 |
| MRR | 第一个相关文档的排名倒数均值 | 标准化评测集 |
| NDCG@K | 考虑位置权重的归一化折扣收益 | 需多级相关性标注 |
| HitRate@K | Top-K 中至少有一个相关文档的比例 | 快速评估 |
端到端 RAG 评测:
| 指标 | 定义 | 评测方法 |
|---|---|---|
| Answer Accuracy | 回答正确率 | 人工评估或基于标注答案 |
| Citation Precision | 引用精确率 | 引用的文档是否真的支撑回答 |
| Citation Recall | 引用召回率 | 回答所需的关键信息是否都被引用 |
| Context Utilization | 上下文利用率 | 提供的上下文中有多少被实际使用 |
评测数据集构建:
- 从生产日志中采样真实查询
- 人工标注每个查询的正确答案和对应文档
- 按领域/难度/类型分层,确保覆盖多样性
- 定期更新,反映用户实际 query 分布
9.2 在线观测
核心指标监控:
# 关键指标定义
metrics = {
"retrieval_latency_p99": "检索延迟 p99",
"retrieval_recall_estimate": "基于点击的召回率估算",
"cache_hit_rate": "缓存命中率",
"tool_call_success_rate": "工具调用成功率",
"generation_quality_score": "生成质量评分(基于反馈)",
"context_length_avg": "平均上下文长度",
"retrieval_noise_ratio": "检索噪声比例(点击率估算)"
}Golden Set 监控:在生产环境中维护一个小型的 golden set(已知答案的标准 query),每次部署后运行回归测试,确保系统质量不退步。
用户反馈闭环: -点赞/点踩:快速收集用户对回答质量的反馈
- 追问率:如果用户频繁追问同一问题,说明上轮回答质量差
- 引用点击率:用户点击引用链接的比例,反映引用准确性
10. RAG/Agent 系统设计总图
┌─────────────────────────────────────────────────────────────────────┐
│ 用户输入层 │
│ 查询预处理/意图分类 │
└─────────────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 决策层(World Model) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ 闲聊处理 │ │ 知识检索 │ │ 记忆查询 │ │ 动作执行 │ │
│ │ (模型直接) │ │ (RAG) │ │(Memory Agent)│ │ (工具调用)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
↓ ↓ ↓
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 文档接入层 │ │ 记忆管理层 │ │ 工具执行层 │
│ - 格式解析 │ │ - 短期记忆 │ │ - 工具注册 │
│ - 切分策略 │ │ - 长期记忆 │ │ - 错误处理 │
│ - 编码索引 │ │ - 记忆检索 │ │ - 降级策略 │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ RAG 核心管道 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 召回 │ → │ 精排 │ → │ 压缩 │ → │ 上下文 │ │
│ │(ANN/BM25)│ │(Reranker)│ │(Selective)│ │ 组装 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 生成层 │
│ ┌─────────────┐ │
│ │ 生成模型 │ │
│ │ (Gemma/LLaMA│ │
│ └─────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ ↓ ↓ │
│ 答案输出 记忆更新 │
└─────────────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 工程支撑层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 缓存层 │ │ 日志 │ │ 追踪 │ │ 评估 │ │
│ │ (Redis) │ │(结构化) │ │(Otel) │ │(Golden) │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────────┘
11. 端到端调试清单
当遇到问题时,按以下清单逐项排查:
基础连接检查
- 各组件日志是否正常输出
- 组件间通信是否超时
- 缓存是否正常命中/失效
检索管道检查
- 查询向量是否正确生成(对比已知 embedding)
- 索引是否包含目标文档(用已知相关文档 ID 反查)
- 精排模型的排序是否合理(输出 top-5 人肉检查)
- 压缩后的上下文是否保留了关键信息
生成管道检查
- 组装后的 prompt 是否包含所有必要信息
- Prompt 中的指令是否被模型遵守
- 模型输出是否引用了正确的文档片段
- 幻觉检测:输出内容是否有文档未覆盖的内容
记忆系统检查
- 历史对话是否被正确检索
- 记忆写入是否完整
- 记忆检索的 query 是否准确
工具调用检查
- 工具参数是否正确序列化
- 工具返回是否被正确解析
- 失败场景是否触发了降级策略
系统级检查
- 缓存是否导致旧结果被返回(强制刷新)
- 模型版本是否与评估时一致
- 配置参数是否在预期范围内
- 是否有上游服务(数据库、向量库)性能下降
本章小结
RAG/Agent 系统不是各组件的简单拼接,而是一个需要全局优化的复杂架构。关键认知:
- 检索质量决定生成上限:召回率是硬约束,但噪声控制同样重要
- 三阶段耦合:召回、精排、上下文窗口需要联合优化,而非各自为政
- 知识三元组:外部知识(RAG)、参数记忆、Agent 动作各有分工,需要协调配合
- 异常处理是系统设计的一部分:工具失败、检索噪声、上下文冲突必须有统一的处理框架
- 工程支撑决定系统能否上线:缓存、日志、追踪、评估缺一不可
下一章将讨论如何在实际项目中落地这套系统,包括技术选型、成本控制、迭代优化策略等实践内容。