RAG 与 Agent
RAG 全流程
Retrieval-Augmented Generation 把「知识」从模型参数里搬到外部检索系统。两条链路:
离线索引:文档 → 解析 → 切片 → 向量化 → 存入向量库 在线查询:问题 → 向量化 → 检索 → 重排 → 拼进 prompt → 生成
1. 切片(chunking)
最影响效果且最被低估的一步。切太小丢上下文,切太大引入噪声且挤占 context。
| 策略 | 说明 |
|---|---|
| 固定长度 + 重叠 | 最简单。典型 512 token + 10~20% overlap,重叠是为了避免答案正好被切断 |
| 按语义边界 | 优先在段落、标题、句子处切,别切断句子 |
| 结构感知 | Markdown 按标题层级切,代码按函数切,表格整体保留 |
| 父子切片 | 检索用小块(精准匹配),送给模型用所属大块(上下文完整)。效果通常最好 |
实务要点:给每个 chunk 附加元数据(来源文档、标题路径、更新时间),既用于过滤也用于生成引用。
2. 向量化(embedding)
把文本映射成稠密向量,语义相近的距离近。注意:
- 查询和文档必须用同一个模型编码
- 很多模型区分 query 和 passage 的前缀指令,用错会掉点
- 多语言场景要选多语言模型(bge-m3、multilingual-e5)
- 向量维度与检索精度和存储成本正相关
3. 向量检索
精确的 KNN 在百万级以上太慢,所以用 ANN(近似最近邻):
| 索引 | 原理 | 特点 |
|---|---|---|
| HNSW | 多层邻近图,上层稀疏做粗导航,下层稠密做精定位 | 召回高、查询快,内存占用大,主流选择 |
| IVF | 先聚类成若干桶,查询只搜最近的 n 个桶 | 内存友好,需要训练,召回略低 |
| PQ / IVF-PQ | 向量压缩成乘积量化码 | 极省内存,精度有损,适合超大规模 |
距离度量:余弦相似度(最常用,只看方向)、内积、欧氏距离。embedding 归一化后三者等价。
4. 混合检索与重排
纯向量检索的短板:对专有名词、产品型号、错误码这类需要精确匹配的查询效果差 —— 语义相近但字面不同的内容会被排前面。
所以生产方案是混合检索:向量检索(语义)+ BM25 关键词检索(字面),用 RRF(Reciprocal Rank Fusion)融合两路结果。
重排(rerank) —— 用 cross-encoder 对召回的 top-50 重新打分取 top-5。区别在于:
- 双塔 embedding:query 和 doc 分别编码再算距离。能预计算、可索引,但两者没有交互
- cross-encoder:query 和 doc 拼在一起送进模型算相关性。精度高得多,但无法预计算,只能对少量候选做
所以标准架构是「召回要广(向量+BM25 取 50~100 条),重排要准(cross-encoder 筛到 3~5 条)」。
5. 生成
prompt 里要明确三件事:只能基于给定材料回答、材料里没有就说不知道、回答要标注引用的材料编号。
RAG 的常见失效模式
| 症状 | 原因 | 解法 |
|---|---|---|
| 检索不到明显存在的内容 | 问题与文档表述差异大 | 查询改写 / HyDE(先让 LLM 生成一个假设答案再用它检索)/ 多查询扩展 |
| 检索到了但模型没用 | 相关内容埋在中间(Lost in the Middle) | 重排后把最相关的放首位,减少送入的块数 |
| 答案需要跨多个文档综合 | 单轮检索拿不全 | 多跳检索、GraphRAG |
| 答案需要统计汇总(「一共有几个」) | 向量检索本质是语义相似,不会做聚合 | 这类问题应走 Text2SQL 而非 RAG |
| 引用了错误来源 | 生成阶段编造引用 | 后处理校验引用编号是否真实存在 |
:::tip 长上下文能替代 RAG 吗 不能完全替代。四个理由:成本(每次都塞 100 万 token 极贵,RAG 只送几千)、延迟(prefill 时间随长度增长)、Lost in the Middle(长上下文中间信息召回率下降)、可审计性(RAG 能明确给出引用来源)。
但长上下文改变了 RAG 的参数 —— 可以送更多、更大的 chunk,对切片精度的要求下降了。两者是互补关系。 :::
Agent
Agent 的本质是让 LLM 具备调用外部工具和多步执行的能力,突破「只能输出文本」的限制。
ReAct 范式
交替进行 Thought(推理)→ Action(调用工具)→ Observation(观察结果),循环直到得出答案。
Thought: 需要先查用户的订单状态
Action: query_order(user_id="123")
Observation: {"status": "shipped", "tracking": "..."}
Thought: 已拿到物流单号,再查物流详情
Action: query_logistics(tracking="...")
...
Thought: 信息齐了,可以回答
Answer: 您的订单已发货,当前在...
关键价值是把推理过程显式化 —— 模型能根据工具返回的真实结果修正方向,而不是一次性凭想象给答案。
Function Calling
模型被训练成能输出结构化的工具调用请求。流程:
- 把工具的 JSON Schema(名称、描述、参数类型)随请求发给模型
- 模型判断需要调用时,返回
tool_call(函数名 + JSON 参数)而不是普通文本 - 应用代码实际执行该函数 —— 注意模型本身不执行任何东西
- 把结果作为
tool角色的消息回传,模型据此继续
工程要点:工具的 description 写得好不好直接决定模型选不选对 —— 这是 prompt 工程的一部分;参数要有严格 schema 并在执行前校验;工具数量多了(>20)准确率下降,应分层或先做工具检索。
MCP(Model Context Protocol) —— Anthropic 提出的开放协议,把「模型如何连接外部工具和数据源」标准化。价值在于工具提供方实现一次 MCP server,所有支持 MCP 的客户端都能用,不必为每个应用重复适配。
Agent 的工程难点
- 错误累积 —— 多步执行中任一步出错会传导到后续。需要每步校验和失败重试
- 循环与不终止 —— 必须设最大步数和超时
- 成本不可控 —— 每一步都是一次完整推理,10 步就是 10 倍成本和延迟
- 权限与安全 —— 工具能改数据就意味着模型能改数据。写操作必须要人确认或有严格的权限边界,且要防 prompt injection(检索到的内容里藏指令)
- 可观测性 —— 必须记录完整的 trace(每步的思考、调用、返回)才能调试
记忆
- 短期 —— 对话历史。超出 context 时做滚动摘要
- 长期 —— 把用户偏好、历史结论写入向量库或结构化存储,需要时检索回来。本质还是 RAG
提示工程要点
零样本 / 少样本 —— few-shot 给 2~5 个例子,对格式约束效果显著。例子的多样性比数量重要。
思维链(CoT) —— 「让我们一步步思考」。对数学和逻辑任务提升明显,原理是给模型更多「计算步数」来展开推理。注意对现代推理模型(o 系列、R1)不要加 CoT 提示 —— 它们已经内化了推理过程,额外的 CoT 指令反而干扰。
自我一致性 —— 采样多条推理路径取多数答案,用算力换准确率。
结构化输出 —— 要求 JSON 时用 schema 约束 + 服务端的 structured output 功能,比在 prompt 里写「请返回 JSON」可靠得多。