记忆与 RAG 工程化
第 24 课:记忆与 RAG 工程化
上一课我们把"工具"这条通道打通了——模型能通过 MCP 去调用外部世界。但工具解决的是"能做什么",没解决"知道什么":模型的知识停在训练截止日,你的私域文档它从没见过。这一课从一个反问开始:上下文窗口都到百万 token 了,塞进上下文就等于用得上吗?我们从检索链路(切分 / 嵌入 / 重排)、中间遗忘、记忆分层、写权重 vs 放检索,一路算到"怎么评估一条 RAG(先去资料库检索出相关片段,再把这些片段塞进上下文让模型照着回答)链路"。
② 讲清 RAG 检索链路的每一环,用 recall / precision 量化召回质量,并解释"中间遗忘"为什么让塞进去的内容用不上
③ 用 CoALA 的四层记忆组织 Agent 状态,在"写进权重 vs 放检索"之间做取舍,并用 RAGAS 分层评估定位"是检索坏还是生成坏"
上一课我们把"工具"这条通道打通了:模型能通过 MCP 去调用外部世界。但工具解决的是"能做什么",没解决"知道什么"——模型的内部知识停在训练截止日,你的私域文档它从没见过。这一张先回答一个最容易被忽略的反问:窗口都这么大(标称百万 token),为什么还要费劲检索?
因为"装得下"和"用得上"是两回事。装得下是显存/工程问题(窗口够大即可),用得上是能力问题(模型在训练时见过多少长依赖)。就像开卷考试:你不会把整间图书馆搬进考场,而是按题目去书架上取那几页——取哪几页、取多少页,才是真正决定成绩的一步。所以下面用两笔账,把"全塞"与"检索"的差距算出来。
更自然的做法是「窗口既然到了百万 token,那就把知识库整个塞进去」。但输入 token 是线性计费的:100 万 token 的知识库每次请求都送,10,000 次/天就是 1×10¹⁰ token、约 $1,500/天;按需检索 top-5 只要约 $3.84/天,相差约 391 倍。「装得下」也不等于「用得上」——这由第 3 张卡片来算。
| 方案 | 每次输入 token | 说明 |
|---|---|---|
| 全量塞入 | 1,000,000 | 把 100 万 token 的知识库整个放进 context |
| 检索 top-5 | 2,560 | 5 块 × 512 token |
| 比值 | 1,000,000 ÷ 2,560 ≈ 391 倍 | |
| 口径 | 每天 10,000 次请求的累计输入 | 按 $0.15/百万 token 计 |
|---|---|---|
| 全量塞入 | 10,000 × 1,000,000 = 1×10¹⁰ token | ≈ $1,500 / 天 |
| 检索 top-5 | 10,000 × 2,560 = 2.56×10⁷ token | ≈ $3.84 / 天 |
| 项 | 数值 |
|---|---|
| 标称窗口(百万级窗口的一个示例取值) | 500,000 token |
| 假设的实测有效区间(举例,非某模型实测) | 约 125,000 token |
| 有效利用率(示意) | 125,000 ÷ 500,000 = 25% |
RAG 的第一个理由不是"窗口不够大",而是成本与效果都不支持全塞:输入 token 线性计费,而模型对超长上下文的利用在远低于标称上限时就已退化。RAG 把"把整个库读一遍"换成"只读相关的那几页"——这是在成本与效果上同时更优的选择。
上一张卡片说明知识要运行时取,而不是全塞。这一张盯住"取"这条链路:它这么长,怎么保证拿回来的是对的?
因为入库与查询是两条独立的路,任何一环出错都会沿着链路传到最终答案——切分把一句话劈成两半、嵌入模型两端用得不一样、粗排召回了却没排进前面,都会让生成拿到错的材料。就像一条水管:只要有一个接口漏水,末端就出不了水;你没法在末端"补回来"。所以下面先用一笔账看切分,再用两个指标量化召回质量。
更笨的做法是「让粗排的结果直接进生成」。但粗排靠向量相似度打分,只判「语义像不像」,分不清「字面很像、其实在讲别的」;重排用更强的交叉编码器把候选重新打分,实测能把 Context Precision 从 0.80 抬到 0.85、Context Recall 从 0.78 抬到 0.86——代价是运行时间约上升至原来的 5 倍。
| 项 | 数值 |
|---|---|
| 文档长度 | 10,000 token |
| 块大小 chunk | 512 token |
| 重叠 overlap | 50 token |
| 步长 | 512 − 50 = 462 |
| 块数 | ⌈(10,000 − 512) ÷ 462⌉ + 1 = 21 + 1 = 22 块 |
| 项 | 数值 |
|---|---|
| 文档里真正相关的片段 | 3 个 |
| 检索 top-5 命中的相关片段 | 2 个 |
| 召回率 recall | 2 ÷ 3 ≈ 0.667 |
| 指标 | 重排前 | 重排后 | 变化 |
|---|---|---|---|
| Context Precision(相关块是否排在前面) | 0.80 | 0.85 | +0.05 |
| Context Recall(相关块是否被召回) | 0.78 | 0.86 | +0.08 |
检索链路的产出上限由最弱的一环决定:切分错了,后面的重排与生成都在错的材料上工作,修生成模型救不回来。所以调优顺序应该是——先把切分与嵌入对齐,再上混合检索与重排,而不是一上来就换更大的生成模型。
上一张卡片把检索质量量化成了 recall 与 precision。但还有半步没走完:就算检索到的片段是对的,把它们一股脑塞进上下文,模型就一定会用吗?
因为这不是"有没有材料"的问题,而是"材料放在哪"的注意力问题——同一段正确材料,放在上下文开头/结尾和放在中间,模型的利用率差别很大。就像念一长串名单:开头和结尾的名字记得最清楚,中间那一大段常被一带而过。所以下面看这个"中间遗忘"有多大,以及怎么把材料放对位置。
| 相关信息位置 | 相对准确率(示意) | 说明 |
|---|---|---|
| 第 1 位(最前) | 100%(基准) | primacy bias |
| 第 10 位(中间) | ≈ 70% 基准 | 明显下降 |
| 第 20 位(最后) | ≈ 95% 基准 | recency bias,回升 |
| 方案 | 塞入片段数 | 注意力竞争面 |
|---|---|---|
| 粗排放进全部候选 | 20 块 | 20 个片段竞争 |
| 精排后只放 top-5 | 5 块 | 5 个片段竞争 |
| 做法 | 具体操作 |
|---|---|
| 最相关的放首尾 | 重排后把 top-1 放开头、top-2 放结尾 |
| 减少总片段数 | 精排取 top-5,而不是 top-20 |
| 结构化标注 | 给每块加来源标题,帮模型定位 |
RAG 的失效常被误判成"没检索到",其实更常见的是"检索到了但没用上"。对策有三:把最相关的放首尾、减少塞入的片段数、给材料加来源标记。记住这条顺序:先保证取到(recall),再保证用好(位置)。
上一张卡片说明取回来还要放对位置。可"放"在哪里、放多久,本身就需要一个结构——这一张问:Agent 的记忆为什么要分这么多层?
因为不同的信息有完全不同的生命周期与取用方式:当前这一步的推理必须常驻 context(快、但贵、会话结束就没),而"三天前用户说过什么"要能跨会话留存、"公司报销规则"要能长期复用、"先校验再下单"这类步骤要能指导行为。就像办公桌、日记本、百科全书、操作手册——信息不同,放的抽屉和取的方式也不同,全堆桌面上很快就乱。所以下面看四层各存什么,再算一笔上下文预算。
更省事的做法是「所有信息都放进同一段上下文,谁需要谁自己看」。但上下文的位置是稀缺资源:单次会话约 5,000 token 的轨迹,30 次会话就是 150,000 token,是 8,000 预算的 18.75 倍——不分层、不外置,历史很快就把窗口占满。
| 层 | 存什么 | 典型载体 | 存取方式 |
|---|---|---|---|
| 工作记忆 working | 当前任务的状态与中间结果 | context 窗口 | 直接读写 |
| 情节记忆 episodic | 过去的事件、会话记录、工具调用轨迹 | 日志 | 向量检索 |
| 语义记忆 semantic | 事实、偏好、领域知识 | 知识库 | 向量 + 图检索 |
| 程序记忆 procedural | 做事的步骤、技能、决策规则 | prompt 规则或代码 | 直接注入 |
| 用途 | 预算 | 占比 |
|---|---|---|
| system prompt | 500 token | 6.25% |
| 工作记忆(本轮历史 + 中间结果) | 3,000 token | 37.5% |
| 检索片段 | 4,000 token | 50% |
| 输出预留 | 500 token | 6.25% |
| 合计 | 500 + 3,000 + 4,000 + 500 = 8,000 | 100% |
| 项 | 数值 |
|---|---|
| 每次会话产生的轨迹 | 5,000 token |
| 30 次会话累计 | 30 × 5,000 = 150,000 token |
| 相对 8,000 预算 | 150,000 ÷ 8,000 = 18.75 倍 |
记忆分层回答的问题是"什么该常驻、什么该外置":工作记忆常驻 context(快但贵、易失),情节/语义/程序记忆外置到存储(便宜、可持久、需检索)。把这条想清楚,RAG 就不只是"文档问答",而是 Agent 记忆体系里的语义记忆层。
上一张卡片把记忆分了层,其中"语义记忆"这一层既可以写进权重、也可以放进 context 里的检索。这一张问最上层的决策:新知识到底该放哪?为什么不能全用微调?
因为两条路的成本结构完全不同:改权重要跑训练/编辑,慢、贵且会遗忘旧知识;放检索只需更新索引,快、可回滚、可溯源。就像改一个人的习惯要重新训练很久,而给他贴一张"今天改走这条路"的便签当场就生效——短期、易变的信息用便签,长期、稳定的能力才值得改习惯。所以下面用一笔账把两条路的成本摆出来。
更自然的做法是「有新知识就微调一遍模型」。但改权重的边际成本高、还会遗忘旧知识:按每月 100 条更新算,一年要微调 12 次,每次都是数 GPU·小时级的训练;同样的知识放进索引只要写 1,200 次、每次秒级。模型编辑只在很小的编辑量下还可用——一旦持续顺序编辑,ROME/MEMIT 就会先"渐进遗忘"、后"灾难性遗忘"(Gupta 等,arXiv:2401.07453)。
| 维度 | 写进权重 | 放进检索 |
|---|---|---|
| 更新成本 | 高(训练/编辑一轮) | 低(更新索引) |
| 更新速度 | 小时 ~ 天 | 秒级 |
| 可溯源 | 难 | 易(可给出处) |
| 适合规模 | 少量、定向 | 大量、频繁 |
| 项 | 写进权重(月度微调) | 放进检索(实时索引) |
|---|---|---|
| 更新频率 | 100 条/月 → 12 次/年 | 1,200 条/年 |
| 单次代价 | 一次微调(数 GPU·小时) | 一次索引写(毫秒 ~ 秒) |
| 编辑规模 | ROME / MEMIT 的表现 | 来源 |
|---|---|---|
| 少量、定向的更正 | 尚可 | Yao 等,arXiv:2305.13172 |
| 持续顺序编辑(规模增大) | 先"渐进遗忘"、后"灾难性遗忘" | Gupta 等,arXiv:2401.07453 |
选路看四个问题:① 更新多频繁 ② 要溯源吗 ③ 规模多大 ④ 要保旧知识吗。频繁 / 要溯源 / 规模大 → 放检索;稳定 / 通用 / 少量更正 → 可考虑写权重;两者都要 → 检索为主、对稳定的部分做微调补齐。判据一句话:先问"把正确资料给它,它就能答对吗"——能就是检索问题,不能才动权重。
前面五张卡片解决了"为什么要检索、怎么取、怎么放、放哪层、放权重还是放检索"。最后一张要解决一个工程问题:怎么证明这条链路真的对,且出问题能定位?
因为"答案错了"是一个笼统的现象,可能错在检索(没找到材料),也可能错在生成(材料在手却写歪);不把两段分开测,就会一直修错地方。就像体检要分科室:肝指标高不能说明心脏没问题,必须分开看才能定位。所以下面先给五个指标各测什么,再用算例把"分段定位"跑一遍。
| 指标 | 测哪段 | 一句话定义 |
|---|---|---|
| Context Precision | 检索 | 相关片段是否被排到前面(average precision@k,排名敏感) |
| Context Recall | 检索 | 相关片段被召回的比例 |
| Faithfulness | 生成 | 答案被上下文支持的程度 |
| Answer Relevancy | 生成 | 答案是否切题 |
| Answer Correctness | 生成 | 与标准答案的一致度 |
| 指标 | 算例 | 结果 |
|---|---|---|
| Context Precision | 取回 5 条、相关 3 条(在第 1、3、4 位) | (1.000 + 0.667 + 0.750) ÷ 3 ≈ 0.806 |
| Context Recall | 相关 3 条,召回 2 条 | 2 ÷ 3 ≈ 0.667 |
| Faithfulness | 答案 10 条断言,8 条有上下文支持 | 8 ÷ 10 = 0.80 |
| 指标 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| Context Precision | 0.80 | 0.85 | +0.05 |
| Context Recall | 0.78 | 0.86 | +0.08 |
| 生成侧(Faithfulness / Correctness) | —— | +0.6% / +4%(相对提升) | 仅小幅提升(原文自述「muted downstream response」) |
本课收口:RAG 是把"知道什么"从权重里拆出来、放进可检索的外部记忆。它的工程要点是一条链:为什么要检索(成本 + 有效窗口)→ 检索质量(切分 / 嵌入 / 重排 + recall / precision)→ 用得上的关键(位置)→ 记忆分层(工作 / 情节 / 语义 / 程序)→ 放权重还是放检索(按场景)→ 分层评估(检索段 vs 生成段)。下一课我们把这条链继续往前推:当任务长到一步做不完,Agent 怎么规划、怎么和别的 Agent 协作。