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自然语言回复内容生成、质量控制
记忆模块对话历史 + 重要信息更新后的记忆向量/图长期知识保持、上下文延续

接口设计的核心原则

  1. 每个组件应该对上下游透明:召回模块不需要知道生成模型的能力上限,但它可以通过返回足够多的候选让压缩器有选择空间
  2. 信息流应该是双向的:不只是检索结果流向生成,生成阶段的置信度可以反馈给检索模块调整策略
  3. 元数据需要贯穿全流程:文档来源、块 ID、置信度分数、引用信息应在各组件间传递

3. 检索结果如何影响最终生成

3.1 检索质量到生成质量的映射

RAG 系统的生成质量并非简单由检索 quality 决定,而是由「检索结果对当前查询的覆盖度」决定。设生成模型为 ,检索系统返回的上下文为 ,用户查询为 ,理想生成结果为

生成质量的关键约束:

覆盖度约束:若相关文档 未被召回,即 ,则即使 再强大,也无法生成正确答案。召回率是生成质量的硬上限。

噪声约束 中混入无关文档会干扰 。实验表明,当噪声文档占比超过 30% 时,生成质量显著下降,即使检索到的相关文档质量很高。

上下文窗口约束:即使检索到了高质量候选,如果上下文组装阶段丢失了关键信息(由于长度限制或压缩不当),生成结果仍然会受损。

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 检索噪声的处理

检索噪声包括:语义相关但不直接回答问题的文档、部分匹配的片段、多文档中的冲突信息。

多阶段过滤

  1. 召回阶段:用 ANN/BM25 快速召回候选,允许低精度
  2. 精排阶段:用重排模型进一步筛选,提升 precision
  3. 压缩阶段:选择与查询最相关的句子/段落

多样性约束:在精排时加入 MMR(Maximal Marginal Relevance)约束,避免返回高度相似的文档,确保候选多样性。

冲突检测:当多个检索文档对同一问题给出不同答案时,应:

  1. 在上下文中标注冲突
  2. 让生成模型根据置信度判断
  3. 或者返回”根据不同来源存在分歧,以下是各方观点…“

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天相同文档的重复检索
检索结果Redis1小时相同查询的重复检索
生成结果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@KTop-K 结果中相关文档的比例人工标注相关性,构建测试集
MRR第一个相关文档的排名倒数均值标准化评测集
NDCG@K考虑位置权重的归一化折扣收益需多级相关性标注
HitRate@KTop-K 中至少有一个相关文档的比例快速评估

端到端 RAG 评测

指标定义评测方法
Answer Accuracy回答正确率人工评估或基于标注答案
Citation Precision引用精确率引用的文档是否真的支撑回答
Citation Recall引用召回率回答所需的关键信息是否都被引用
Context Utilization上下文利用率提供的上下文中有多少被实际使用

评测数据集构建

  1. 从生产日志中采样真实查询
  2. 人工标注每个查询的正确答案和对应文档
  3. 按领域/难度/类型分层,确保覆盖多样性
  4. 定期更新,反映用户实际 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 系统不是各组件的简单拼接,而是一个需要全局优化的复杂架构。关键认知:

  1. 检索质量决定生成上限:召回率是硬约束,但噪声控制同样重要
  2. 三阶段耦合:召回、精排、上下文窗口需要联合优化,而非各自为政
  3. 知识三元组:外部知识(RAG)、参数记忆、Agent 动作各有分工,需要协调配合
  4. 异常处理是系统设计的一部分:工具失败、检索噪声、上下文冲突必须有统一的处理框架
  5. 工程支撑决定系统能否上线:缓存、日志、追踪、评估缺一不可

下一章将讨论如何在实际项目中落地这套系统,包括技术选型、成本控制、迭代优化策略等实践内容。