长任务越聊越贵、越聊越笨?用三层记忆把上下文成本降下来

你以为把全部聊天记录塞给模型最保险,实际可能是在花更多的钱,让模型从一堆过期要求里猜哪条才有效。

这种问题在连续修改代码、撰写研究报告、运营数据分析等长任务中尤其明显:前几轮模型反应很快,到了几十轮以后,输入越来越长、响应逐渐变慢,甚至开始遗忘刚刚更新的要求。

假设每轮都重复发送 30,000 Token,连续调用 50 轮,仅输入侧就会累计:

方案 A:每轮携带 30,000 Token × 50 轮 = 1,500,000 输入 Token

方案 B:每轮携带 3,000 Token × 50 轮 = 150,000 输入 Token

完整计算可以写成:

总输入 Token ≈ Σ(第 i 轮携带的历史上下文 + 当前输入)

这只是为了便于理解的示例。实际费用还取决于模型定价、缓存策略、检索回填长度以及输出量。

问题的关键也不只是“Token 太多”。

上下文越长,不等于记忆越可靠。无关信息会稀释注意力,旧要求可能与新要求冲突,模型还要在大量历史中判断“现在到底听谁的”。

真正可用的方案,不是粗暴删除历史,而是把记忆拆成三层:

1. 滚动摘要

2. 结构化状态表

3. 可追溯原文索引

目标不是把 Prompt 压到最短,而是做到三个词:低成本、可恢复、可审计。

为什么只做摘要,仍然可能把任务做坏

先看一个贯穿全文的可复现案例。

我们让 AI 在 30 轮内迭代一个 Python 数据分析工具,要求:

  • 使用 Python 3.11
  • 运行内存低于 512 MB
  • 不得修改原始 CSV
  • 输出字段必须兼容旧版本
  • 所有关键决策可以追溯

任务中又出现了三次需求变化:

  • 第 3 轮:输出 CSV
  • 第 12 轮:改为同时输出 CSV 和 JSON
  • 第 19 轮:JSON 字段名必须兼容 v2 接口

如果只让模型不断改写一段自然语言摘要,它很可能最终得到这样的内容:

工具需要输出 CSV 和 JSON,并注意兼容性与数据安全。

看起来没有错,实际上最关键的内容已经被磨平了:

  • “数据安全”不等于“不得修改原始 CSV”
  • “注意兼容性”没有说明必须兼容 v2
  • 没有记录第 19 轮覆盖了哪条旧要求
  • 无法找到原始指令进行核对

一旦后续代码直接覆盖源文件,或者继续使用旧版 JSON 字段,任务就会验收失败。

摘要擅长帮助模型理解大局,却不适合充当事实数据库。

这正是三层记忆存在的原因:不是简单删掉历史,而是给历史分级。

三层记忆分别保存什么

第一层:滚动摘要,回答“我们一路做了什么”

滚动摘要用于快速恢复任务语境,通常包括:

  • 任务背景
  • 当前阶段
  • 已完成工作
  • 阶段性结论
  • 下一步计划
  • 尚未解决的问题

它应该是一份不断重写的“项目简报”,而不是在末尾无限追加的新日记。

摘要内容还应明确区分:

  • 事实:已经确认的信息
  • 决定:已经生效的选择
  • 推测:尚未验证的判断

否则,一句“可能是内存占用导致失败”,经过多轮压缩后,很容易变成“失败原因是内存不足”。这就是典型的摘要漂移

第二层:结构化状态表,回答“现在准确处于什么状态”

状态表是当前任务的权威状态,适合使用 JSON、YAML 或数据库字段保存。

{

"task_id": "analytics_tool_001",

"goal": "完成可运行的数据分析工具",

"status": "implementation",

"constraints": {

"python_version": "3.11",

"memory_limit_mb": 512,

"modify_source_csv": false

},

"deliverables": ["CSV", "JSON"],

"interface_version": "v2",

"completed": [

"确定输入格式",

"完成数据清洗模块"

],

"open_issues": [

{

"id": "issue_07",

"description": "JSON 时间字段格式尚未确认",

"status": "pending",

"source_message_id": "msg_019"

}

],

"latest_instruction_id": "msg_019",

"state_version": 8

}

这里有三个字段尤其重要。

  • source_message_id:指向要求的原始出处,避免模型凭印象解释。
  • state_version:标记状态版本,便于检测并发覆盖与错误更新。
  • 明确的数据类型:false512["CSV", "JSON"] 比自然语言更不容易产生歧义。

为了保存需求覆盖关系,还可以单独记录:

{

"current_requirement": "JSON 字段必须兼容 v2 接口",

"supersedes": "msg_012",

"source_message_id": "msg_019"

}

这样系统知道第 19 轮是当前有效要求,而不是把第 3、12、19 轮的指令混在一起。

配图 1:三层记忆架构图
当前请求 → 读取滚动摘要与状态表 → 检索相关原文 → 组装 Prompt → 调用模型 → 更新状态、原文与摘要。

第三层:原文索引,回答“这项结论从哪里来”

原文索引不必把全部内容重复放进 Prompt,只需要保存定位信息与检索标签。

{

"document_id": "conversation_001",

"message_id": "msg_019",

"timestamp": "2025-03-08T10:30:00+08:00",

"type": "requirement_change",

"tags": ["json", "interface-v2", "output-format"],

"summary": "用户要求 JSON 字段兼容 v2 接口",

"storage_path": "messages/conversation_001/msg_019.json",

"content_hash": "sha256:..."

}

其中的 summary 只负责帮助检索。真正引用或执行前,系统应回到 storage_path 对应的原始内容进行核对。

content_hash 则可以帮助判断原文是否被意外修改。

三层记忆的关系可以概括为:

  • 摘要回答:我们一路做了什么?
  • 状态表回答:现在准确处于什么状态?
  • 原文索引回答:结论从哪里来,能否找回原话?

哪些内容绝对不能做有损压缩

“不能压缩”不代表每轮都要发送完整原文,而是必须在权威存储中逐字、逐字段保留,需要时精确回填。

以下七类内容尤其危险。

1. 硬约束与验收标准

预算上限、截止日期、输出格式、禁止事项、性能指标和合规要求。

2. 关键参数与唯一标识

金额、日期、版本号、订单号、文件路径、接口字段、数据库主键和模型参数。

3. 最新有效指令与覆盖关系

用户修改了什么要求,哪条新指令替代了哪条旧指令。

4. 代码、配置与错误现场

完整报错、堆栈、日志、配置文件和复现步骤,不能只写成“程序运行失败”。

5. 证据与引用原文

合同条款、政策内容、论文结论、客户原话及出处,不能依赖模型二次转述。

6. 不可逆操作信息

付款、删除、发布、生产环境变更的对象、范围、权限和确认记录。

7. 尚未解决的分歧与不确定性

不能把“可能”“待确认”“存在争议”压缩成确定事实。

一个非常实用的判断方法是:

如果某个细节被改错后,会导致任务无法验收、无法追责、无法复现,或者造成不可逆损失,就不能只保留自然语言摘要。

例如,把“不得修改原始 CSV”压缩为“注意数据安全”,看起来只少了几个字,实际上丢失的是一个可以直接判断真假的硬约束。

配图 2:错误案例截图
左侧展示原始要求“不得修改原始 CSV”,右侧展示摘要“注意数据安全”,下方标出模型随后使用覆盖写入模式。截图需隐藏 API Key、用户隐私和真实业务数据。

从零搭建一套可运行的分层记忆流程

每轮请求开始前,系统只需要完成四件事:

1. 读取当前状态表

2. 附上短摘要

3. 根据本轮目标检索相关原文

4. 将三者与当前请求组装成 Prompt

伪代码如下:

def build_context(task, user_message):

state = load_current_state(task.id)

summary = load_rolling_summary(task.id)

query = create_retrieval_query(

current_goal=state["goal"],

open_issues=state["open_issues"],

user_message=user_message

)

source_chunks = retrieve_source_text(

task_id=task.id,

query=query,

top_k=5

)

return {

"summary": summary,

"authoritative_state": state,

"retrieved_sources": source_chunks,

"current_user_message": user_message

}

模型返回结果后,不要让它自行把所有历史重新揉成一段话,而应分别更新三层记忆:

response = call_model(build_context(task, user_message))

append_raw_message(user_message, response)

state_patch = extract_state_patch(response)

validate_state_patch(state_patch)

update_state(state_patch)

if should_refresh_summary():

rewrite_summary_from_state_and_recent_messages()

这里最重要的一条工程原则是:

不要允许模型自由改写整个状态表,最好只让它提交 Patch,再由程序验证并更新。

例如,模型可以提交:

{

"deliverables": ["CSV", "JSON"],

"interface_version": "v2",

"latest_instruction_id": "msg_019"

}

程序随后检查:

  • deliverables 是否为数组
  • interface_version 是否属于允许的枚举
  • latest_instruction_id 对应的消息是否存在
  • 当前 state_version 是否仍是预期版本
  • 修改是否触碰不可变字段

摘要生成器只能读取状态,不能绕过校验直接覆盖权威数据。

小白版:先用三个文件跑起来

不要一开始就部署复杂 Agent 平台或向量数据库。最简单的目录结构已经足够验证方案:

memory/

├── state.json

├── summary.md

├── source_index.jsonl

└── messages/

├── msg_001.json

├── msg_002.json

└── msg_019.json

检索初期甚至可以只使用关键词、消息类型和标签,不必马上引入向量搜索。

进阶版:加入检索、版本与自动校验

任务规模扩大后,再逐步增加:

  • 数据库状态表
  • 关键词与向量混合检索
  • 乐观锁或版本号校验
  • 原文哈希验证
  • 状态变更日志
  • 自动化 Schema 校验
  • 状态回滚机制
配图 3:状态表前后对比截图
展示第 12 轮前后的 deliverables 变化,以及第 19 轮新增的 interface_versionsupersedessource_message_id
配图 4:调试日志截图
标出本轮实际发送的摘要长度、状态字段、检索到的消息 ID 和原文片段。所有内部地址与敏感字段应脱敏。

想验证分层记忆到底能节省多少 Token,可以把完整历史、仅摘要、三层记忆三套 Prompt 分别跑一遍,记录每轮输入量与约束遗漏情况。需要接入模型进行连续调用时,可前往 api.884819.xyz 查看 API 接入方式,并将示例中的模型调用部分替换为自己的接口配置。

怎么判断是真省钱,而不是“省 Token、丢任务”

只看输入长度,很容易得出错误结论。

如果一种方案减少了 Token,却导致版本号频繁出错、硬约束遗漏、最终任务无法验收,那么它不是优化,只是把成本从模型调用转移到了返工。

可以对同一项长任务运行三组测试:

  • A 组:每轮发送完整历史
  • B 组:只发送滚动摘要
  • C 组:摘要、状态表与原文索引

分别观察第 5、20、50 轮,或按实际任务记录第 10、30、50 轮的数据。

建议至少统计:

  • 累计输入 Token
  • 单轮平均延迟
  • 关键约束遗漏次数
  • 数字与版本号错误次数
  • 原文引用准确率
  • 最终任务验收通过率

四种常见方案的特点如下:

| 方案 | 输入成本 | 精确信息保留 | 可追溯性 | 实现难度 | |---|---:|---:|---:|---:| | 完整历史 | 高 | 中高 | 高 | 低 | | 仅滚动摘要 | 低 | 低 | 低 | 低 | | 摘要 + 状态表 | 中低 | 高 | 中 | 中 | | 摘要 + 状态表 + 原文索引 | 中低 | 高 | 高 | 中高 |
配图 5:上下文长度曲线图
横轴为对话轮数,纵轴为单轮输入 Token。完整历史随轮数近似线性增长,分层记忆保持在相对稳定区间,仅在需要核对证据时短暂上升。

注意,这张表描述的是方案特征,不代表任何任务都能得到相同幅度的节省。最终结果必须通过自己的任务、模型与检索策略实测。

今天就能执行的三步清单

如果你准备把分层记忆加入现有项目,正确顺序不是先搭向量数据库,而是:

1. 列出不可压缩字段

找出硬约束、数字、版本、唯一标识、证据与不可逆操作信息。

2. 建立权威状态表

用固定 Schema 保存当前有效状态,并记录版本与来源。

3. 最后增加摘要和原文检索

摘要负责恢复语境,索引负责按需找回证据。

最佳实践从来不是“永远不发送原文”,而是:

默认发送最小必要状态,需要时精确找回原文。

不要先相信任何“上下文可节省 90%”的结论,最可靠的方法是用自己的任务做 A/B 测试。复制本文的状态表与 Prompt 组装代码,通过 api.884819.xyz 接入模型,跑满 20~50 轮,再比较真实成本、召回率和最终验收结果。

8848AI 注册只需用户名和密码,无需邮箱验证;平台内置 AI 对话功能,注册后可直接使用。国产模型如 Deepseek、千问等完全免费,没有月租、没有订阅,其他模型按量付费。新用户注册即送体验token。

分层记忆解决了“该保存什么、该取回什么”,但还有一个更棘手的问题:检索出来的内容彼此冲突时,模型应该相信最新消息、状态表,还是原始文档?

下一篇,我们将拆解长任务中的记忆冲突处理与版本控制,并给出“新指令覆盖旧指令、事实不可被摘要覆盖、状态更新必须可回滚”的完整实现。

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。

#AI教程 #上下文管理 #Agent记忆 #Prompt工程 #人工智能 #Token优化 #8848AI