Agent 的“记忆管理”和“自我审视”:上下文压缩、意图规划与自验证

系列博客第六篇 · 2026-07-06
今天解决两个核心问题:记不住那么多、干活不验证


一、先说两个困扰我很久的问题

用了一段时间后,我的 Agent Harness 暴露了两个非常实际的问题。

问题一:聊多了它就“失忆”

你有没有遇到过这种情况——跟 AI 聊了十几轮之后,它开始忘记最开始聊了什么?

这不是模型变笨了,而是 上下文太长,超过了模型的窗口限制

每个大模型都有一个“上下文窗口”(比如 DeepSeek 是 64K tokens)。超过这个长度,最早的消息就会被“挤出去”。就像一个人同时只能记住最近半小时的对话,再往前就模糊了。

在 Agent 场景里这个问题更严重——因为每次工具调用都会产生大量结果(比如 list_files 一下子返回 75 个文件名),这些全塞进上下文,很快就满了。

问题二:它“干活”但不“检查”

还有一个让我不太舒服的问题:Agent 写完文件、生成 Excel 之后,直接就告诉你“完成了”

它不会去验证文件真的写对了吗?Excel 真的打开正常吗?脚本真的跑通了吗?

想象一下,你让一个实习生帮你写一份报告,他写完直接交给你,自己都没检查一遍——你会放心吗?Agent 也是一样。

而且,TaskPolicy 判断任务类型的方式一直很“粗暴”——全靠正则表达式匹配关键词。如果用户说“帮我看看这个文件”,它能正确识别;但如果用户说“这个文档里有没有提到 XXX”,它就可能判断失误。


二、问题一怎么解决:三层上下文压缩

先解释一下“上下文压缩”是什么。

概念解释:上下文压缩就是把“对当前任务不那么重要的历史信息”压缩成更短的摘要,腾出空间给当前正在做的事。

就像你开会的时候,前面几十分钟讨论的内容被秘书记成了“会议纪要”——不用记住每一句话,但关键结论都在。

我设计了三层压缩,逐级递进,越往后压缩越狠,但代价也越大。

第一层:整形(ToolResultShaper)—— 零成本,最常用

工具返回的数据往往有大量冗余。list_files 返回 75 个文件名,但模型可能只需要前 20 个就能判断下一步。

所以我在工具结果写入上下文之前,先做一次“瘦身”:

工具 瘦身规则
list_files 最多保留 25 条,超出部分只显示“还有 N 个文件”
read_file 超过 10KB 只保留开头和结尾各一部分
grep 最多保留 15 条匹配结果
run_shell stdout/stderr 超过 4KB 截断
search_web 最多保留 8 条搜索结果

成本:零(纯代码规则,不调用模型)。
效果:大部分工具调用产生的上下文,体积直接砍掉 60%-80%。

第二层:规则摘要(ContextCompressor)—— 零成本,对历史轮次生效

即使第一层整形做完了,跑了很多轮之后,上下文还是会慢慢涨上去。

这时候第二层出手:当上下文超过阈值(默认 70% 满)时,把较早轮次中 role=tool 的消息压缩成一条 JSON 摘要

比如:

Tool result (turn 1): list_files returned 75 files
Tool result (turn 2): read_file returned 8180 bytes
Tool result (turn 3): grep returned 3 matches

压缩成一条:

{
  "summary": "list_files: 75 files; read_file: README.md 8180 bytes; grep: 3 matches"
}

保留:system prompt、最近 2 轮的工具结果(完整保留),其余全压缩。
成本:零(纯规则拼接)。
效果:历史轮次的上下文体积从几千字变成一两百字。

第三层:LLM 摘要(LlmContextSummarizer)—— 有成本,只在必要时触发

前两层都是“规则压缩”——靠代码逻辑截断或拼接。但有些复杂对话,规则压缩会丢失关键信息。

比如一个多轮对话里,用户反复修改需求:“改成红色”→“不对,改成蓝色”→“还是回到红色吧”。规则压缩可能会丢掉这些转折,LLM 摘要能理解语义,完整保留“最终结论是红色”。

所以第三层用 DeepSeek 自己来压缩:

  • 环境变量 CONTEXT_LLM_SUMMARY=1 开启(默认开)
  • 当前两层压缩后仍然超阈值时,调用 LLM 把中间轮次的消息块压缩成自然语言摘要
  • 成本:每次触发多一次模型调用,但比让主流程因超长上下文失败要划算得多

配置示例

CONTEXT_LLM_SUMMARY=1              # 开启 LLM 摘要(默认开)
SESSION_LLM_SUMMARY=1              # Session 历史也用 LLM 摘要
LLM_SUMMARY_INPUT_MAX_CHARS=28000  # 单次摘要最多处理 28000 字符

三层总结

层级 名称 成本 触发条件 效果
第一层 ToolResultShaper 每次工具调用后 单次工具结果瘦身 60-80%
第二层 ContextCompressor 上下文 > 70% 阈值 历史 tool 消息→JSON 摘要
第三层 LlmContextSummarizer 1次 LLM 调用 前两层仍不够 LLM 理解语义后压缩

核心理念:Trace 存全量(用于复盘),Context 送摘要(给模型用)。原始数据全部保留在 runs/ 的 JSON 里,但送给模型的是精简版。


三、问题二怎么解决:意图规划 + 自验证

3.1 意图规划(IntentPlanner)—— 让 Agent 先想清楚再动手

概念解释:意图规划就是让 Agent 在动手之前,先花一点时间分析“用户到底想让我做什么”。

之前我用的是 TaskPolicy,纯正则匹配:

if (/读取|查看|打开/.test(prompt)) → tool 模式
if (/聊天|你好/.test(prompt)) → direct 模式

但正则太脆弱了。用户说“这个文档里有没有提到 XXX”,没出现“读取”或“查看”关键词,正则就可能判断失误。

所以现在在 TaskPolicy 不确定的场景下,会调用 LLM 做一个意图分析:

{
  "needsTools": true,
  "subtasks": ["读取目标文件", "搜索关键词 XXX", "汇总结果"],
  "suggestedTools": ["read_file", "grep"],
  "verificationSteps": ["确认文件是否读取成功"],
  "reasoning": "用户想检查文档内容,需要先读取文件再搜索"
}

然后把 subtaskssuggestedTools 注入到 ContextBuilder 的 system prompt 里,相当于提前给模型“指了一条路”。

成本:每次不确定场景多一次 LLM 调用,但换来的是更准确的任务判断和工具选择。

环境变量 INTENT_PLANNER=auto|always|off 控制行为。

3.2 自验证(VerificationGate)—— 写完必须自己检查一遍

概念解释:自验证就是 Agent 在执行了“修改性操作”之后,必须自己验证一下操作是否成功,才能给出最终答案。

这是我最想要的一个功能。

当 Agent 调用了以下工具时:

  • write_file / edit_file(写或修改文件)
  • create_excel / write_csv(导出数据)
  • run_script(运行脚本)

系统会 拦截本轮“结束”的意图,自动注入一条系统消息:

[harness] 你刚才修改了文件/导出了数据。请先验证操作是否成功,再回答用户。

然后强制模型进入下一轮 Turn,去执行验证操作(比如 read_file 确认写入内容、list_files 确认文件已生成、再跑一次 run_script 验证脚本可执行)。

效果:Agent 不再“写完就交差”,而是自己检查一遍再交工

3.3 一个实际场景

用户说:“帮我写一个 hello.py,然后运行它。”

没有自验证时:

Agent: 写了 hello.py → “已生成并运行”  (其实可能运行失败了)

有自验证时:

Agent: 写了 hello.py
[harness] 请先验证再作答
Agent: 调用 run_script hello.py → 输出 "Hello World"
Agent: “已生成并运行,输出为 Hello World”

虽然多了一轮 Turn,但可靠性大大提升。


四、架构变成了这样

现在完整的调用链:

用户输入
  → TaskPolicy(正则粗筛)
  → IntentPlanner(LLM 精判,不确定时触发)
  → ContextBuilder(注入 TaskBrief + 压缩后的上下文)
  → DeepSeekToolEngine
       → 每轮调用前检查 TokenBudget
       → 超阈值则 ToolResultShaper + ContextCompressor
       → 必要时 LlmContextSummarizer
       → 工具循环
       → 检测到修改操作时,VerificationGate 强制验证
  → 输出答案

五、代价与取舍

新增的这些能力不是免费的:

组件 额外代价 说明
IntentPlanner 1 次 LLM 调用/轮 仅在不确定场景触发,可用 off 关闭
LlmContextSummarizer 1 次 LLM 调用/触发 仅在前两层压缩后仍超阈值才触发
VerificationGate 多 1 个 Turn 仅在检测到修改操作时触发

你可以通过环境变量灵活控制:

INTENT_PLANNER=off   # 关掉意图规划,回到纯正则
CONTEXT_LLM_SUMMARY=0 # 关掉 LLM 摘要,只用规则压缩
AUTO_VERIFY=0        # 关掉自验证

我目前全部开着,因为每次额外调用的成本在几分钱级别,但换来的正确率提升非常值得。


六、总结

1. 上下文管理不是“要不要做”,而是“怎么做”

随着项目变复杂,上下文一定会超。与其等它崩了再救,不如主动设计压缩策略。三层压缩的设计让我有了“从免费到付费”的梯度选择——大部分情况零成本解决,极端情况才调用 LLM。

2. 让 Agent 学会“先想再做”和“做完检查”

IntentPlanner 和 VerificationGate 其实模拟了人类的工作习惯:拿到任务先拆解,干完之后自己检查一遍再交工。这不是模型自己能学会的,需要 Harness 在流程层面“硬性”介入。

3. 多花一次模型调用,比任务失败要好

之前我总担心“多调一次 LLM 会增加成本”。但后来算了一下,一次失败的任务浪费的时间、情绪和 debug 成本,远大于几分钱的 API 费用。能花钱解决的,就别花时间(当然也别浪费)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐