上下文工程:决定 Agent 跑到第 50 轮还能不能干活

同一个模型,同一个工具列表,两个 Agent 在第十轮时看起来差不多,到第五十轮却可能完全不同。

一个还能准确说出"刚才修改了哪些文件、测试为什么没过、下一步要做什么";另一个开始重复读文件、忘记用户约束,甚至修改已经确认过的代码。

很多时候,这不是模型智商突然掉了,而是上下文管理出了问题。

模型的能力由模型提供商决定,开发者真正能控制的,是每一轮到底把什么信息放进上下文,以及哪些信息应该留在上下文之外。上下文工程不是写一段更长、更复杂的 Prompt,它更像 Agent 的供给系统:什么时候把信息送进去,送多少,旧的怎么清理,需要时又从哪里取回来。

一、先别急着微调,Agent 的问题往往不在模型

看到 Agent 表现不好,常见反应有两个:训练自己的模型,或者等产品稳定后用强化学习优化。

这两件事都不便宜,也不应该成为默认选项。

如果 Agent 记不住上一轮读过哪个文件,可能需要的是状态管理;如果它找不到项目里的某个实现,可能需要的是更好的检索工具;如果它在第 30 轮开始无视早期约束,可能需要的是上下文压缩和重注入。

这些问题里,只有一部分和模型参数有关。大部分是上下文供给问题。

可以用五个动作来整理:

  1. Offload:信息太挤,先搬到文件或数据库;
  2. Reduce:上下文太长,压成摘要、占位符或更短的表示;
  3. Retrieve:需要的信息不在窗口里,按需检索回来;
  4. Isolate:一个窗口装不下,就拆成多个独立上下文;
  5. Cache:重复计算很贵,就复用已有的计算结果。

它们不是五选一,而是一套组合拳。

二、Offload:文件系统比上下文窗口便宜

上下文窗口很贵,而且满了之后会影响模型对其他信息的关注。文件系统、数据库、对象存储便宜得多。

所以第一优先级不是"怎么把更多东西塞进去",而是"有什么东西根本不需要一直待在里面"。

工具执行结果就是典型例子。一次 Grep 可能返回几百行日志,一次网页读取可能有几万字符。Agent 当前只需要知道"在哪些文件里命中了什么",完整内容可以写到磁盘,等需要时再通过 Read 读取。

这类操作叫 Context Offloading:把信息从模型窗口中移走,在外部保存,需要时再恢复。

Offload 做得好,Agent 的上下文会保持干净。它不需要记住几万行日志,只需要记住日志文件在哪里,以及下一步为什么需要它。

三、JIT 和全量预填充是两种完全不同的思路

把所有可能有用的文档、代码和工具结果全部放进上下文,叫全量预填充。它简单,但成本高,而且噪声很大。

另一种方式是 Just-in-time:不提前塞,等 Agent 真正需要时再加载。

比如排查"登录后没有跳回原页面"的 Bug,最省钱的过程不是一上来读取整个项目,而是:

  1. 用 Glob 按文件名找和登录、路由、中间件相关的文件,只返回路径;
  2. 用 Grep 搜索 returnToredirect 等关键词,返回命中的行;
  3. 最后用 Read 读取最可疑的文件。

这是按成本递增来使用工具:先做便宜、范围大的搜索,再做昂贵、范围小的读取。

RAG 和 Agentic Search 都是 JIT 的一种。区别在于,RAG 通常由系统在一次生成前检索相关片段;Agentic Search 则把"要不要搜、搜什么、结果够不够"交给 Agent,在循环中动态决定。

当上下文里只有真正相关的信息时,模型的注意力不会浪费在大量无关内容上。

四、Reduce:先轻量清理,实在不行再摘要

无论前置管理做得多好,长任务跑几十轮之后,工具结果和对话历史仍然会堆积。压缩不是一次操作,而应该分成几层。

Microcompact

Microcompact 不改变消息结构,也不删除整段对话,只处理旧的工具结果。

比如第三轮读取文件得到 3000 token,到了第 30 轮,这段内容已经没人再用。可以把原文替换成:

text 复制代码
[Old tool result content cleared]

消息还在,对话轮次仍然可追踪,但昂贵的工具输出被释放了。

Snip

Snip 更激进,直接从历史对话头部删除老消息。被删的内容永久丢失,后面无法恢复。

它适合确实已经不重要、而且不会影响后续决策的历史。代价是容易把某条关键约束一起剪掉,所以需要状态系统标记哪些消息不能删。

Auto-compact

Auto-compact 用 LLM 生成摘要,把一大段历史替换成结构化总结。

有些实现不会等上下文真正爆满才触发,而是预留一段安全空间。例如:

text 复制代码
触发阈值 = effectiveContextWindow - 13k

这里的 13k 是给下一轮回答、工具调用和恢复过程留下的余量。等到窗口 100% 再压缩,通常已经来不及。

摘要也有格式。一个可用的压缩模板会覆盖这些信息:

  • 用户真正想要什么;
  • 涉及哪些技术概念;
  • 修改了哪些文件;
  • 修过哪些错误;
  • 已经解决了什么问题;
  • 用户提出过哪些约束;
  • 还有哪些待办;
  • 当前正在做什么;
  • 下一步准备做什么。

压缩之后,还要恢复关键文件。比如附上最近读过的五个文件,避免摘要生成完,Agent 马上又把它们读一遍。

最终的消息结构可以简化成:

text 复制代码
[压缩边界]
[结构化摘要]
[恢复的关键文件]
[最近几轮对话]

这里有个取舍:Microcompact 信息损失小,但释放的 token 有限;Snip 释放得多,却不可恢复;Auto-compact 释放最多,但摘要器可能漏掉关键细节。顺序应该是先做轻量处理,再考虑摘要,而不是每轮都直接重写全部历史。

五、Prompt 也要模块化,不是一大段文字

Agent 的 System Prompt 和 Chatbot 的 Prompt 目标不同。

Chatbot 更在意一次回答的质量。Agent 可能连续跑 50 轮,每一轮都要作出正确决策,所以它更需要稳定性和可维护性。

把所有规则写成一个巨大字符串,很快就会遇到问题:改输出风格时碰坏身份设定,加一个工具说明时让整个前缀缓存失效,用户自定义规则又不知道该插在哪里。

更合理的做法是按职责拆模块:

ts 复制代码
const systemPrompt = [
  identitySection(),
  systemRulesSection(),
  taskGuidelinesSection(),
  riskGuidelinesSection(),
  toolUsageGuide(tools),
  outputStyle(),

  // 以下是会话级动态信息
  envInfo(cwd, gitStatus),
  userConfig(claudeMd),
  languagePreference(lang),
  memoryContext(memories),
];

静态部分尽量稳定,可以复用来提升前缀缓存命中率。动态部分每会话甚至每轮都会变化,应该放在后面,避免影响前面的缓存。

用户自定义规则通常遵循两个原则:

  • 越通用的配置优先级越低;
  • 相同冲突下,更具体的规则后加载。

模型对 Prompt 末尾的信息通常更敏感。全局规范放前面,项目规则和当前任务约束放后面,实际效果会更稳定。

至于当前时间、IDE 打开的文件、相关 Skill 这类高频变化信息,不一定要改 System Prompt,可以直接注入到用户消息附近:

text 复制代码
<system-context>
当前打开文件:src/auth.ts(第 42 行)
相关 Skill:security-review
当前日期:2026-09-12
</system-context>

帮我看看 auth.ts 有什么问题

这样既保留了上下文,又不破坏静态前缀。

六、Cache:KV Cache、Prompt Cache 和 Context Collapse 不是一回事

缓存有三个层次,很容易混在一起。

KV Cache

KV Cache 在模型推理层,缓存的是 attention 计算过程中的 Key 和 Value。生成长文本时,前面的 token 不需要重复计算。开发者通常无法直接控制它,但模型服务商会利用它降低推理成本。

Prompt Cache

Prompt Cache 在 API 层,开发者可以通过缓存标记控制。它适合缓存稳定的系统 Prompt、工具说明和公共上下文前缀。

需要澄清一点:Prompt Cache 通常缓存的是前缀对应的计算结果,不等于"同一个问题问两次,直接返回上次答案"。后者是语义缓存,属于另一个系统,不能用 Prompt Cache 来代替。

Context Collapse

Context Collapse 处理的是历史消息。

与其把旧消息直接删除,不如把它们折叠进外部存储,上下文里只保留一个标记。后续真需要时,可以再展开或检索回来。

它和 Auto-compact 的差别在于:Auto-compact 用自然语言摘要替代历史,Context Collapse 更像可恢复的引用。

七、Isolate:上下文不够用,未必要继续压缩

有些任务不适合把所有信息放进同一个上下文,比如主 Agent 正在改代码,同时需要分析一个上万行的模块。

继续压缩可能丢细节,这时可以把任务拆给子 Agent。

多 Agent 的上下文隔离通常有两种方式:

  • 消息传递:主 Agent 给子 Agent 发任务,只拿回结果,不共享内部过程;
  • 上下文共享:子 Agent 复制主 Agent 的上下文,在副本上继续处理。

前者 token 成本低,隔离彻底,但子 Agent 缺少背景;后者背景完整,但复制上下文很贵,而且容易让两边状态分叉。

这里要注意,子 Agent 不是越多越好。每增加一个 Agent,就增加一组交互路径、共享状态和排查成本。只有任务边界清楚、结果可以独立验收时,拆分才划算。

八、Memory 不等于 Context

Context 是本次会话的工作台,Memory 是跨会话留下来的长期信息。

Claude Code 一类编码 Agent 常用文件式记忆:

  • MEMORY.md 作为索引,记录用户偏好、项目规范等简短条目;
  • 每条记忆单独存文件,用 Frontmatter 描述类型和用途;
  • 每轮只把索引放进 System Prompt,详细内容按需读取。

一条好的记忆不只是一句结论,还应该说明原因和适用方式:

md 复制代码
用 pnpm,不要用 npm。

Why: 团队统一规范,锁文件和 CI 都基于 pnpm。
How to apply: 所有 install/add 命令使用 pnpm。

记忆少、单人使用时,文件方案足够。记忆多、多人协作时,数据库更容易做权限、版本和冲突处理。

但记忆系统会失效:

  • 把推测当事实记录,污染记忆;
  • 只写不删,低质量内容越来越多;
  • 项目结构变化后,旧记忆过期;
  • 新旧规则冲突,没有明确覆盖关系;
  • 新项目没有项目级记忆,Agent 只能靠猜;
  • 外部内容进入记忆,导致记忆投毒。

所以 Memory 不是"记得越多越好"。它同样需要写入标准、过期策略和清理机制。

九、RAG 的瓶颈通常是数据,不是模型

RAG 适用于模型没训练过的企业私有数据,也适用于 Agent 运行时需要查的外部资料。

一条完整管线包括:文档加载、分块、Embedding、向量存储、检索、注入上下文。

其中最容易被低估的是前两步。

Markdown 对 RAG 很友好。PDF 很麻烦,纯文本提取容易丢表格和标题结构,需要更强的版面解析。代码也不适合按照固定 token 硬切,因为一个函数从中间切断,两个 chunk 都可能失去意义。代码检索有时更适合 Glob、Grep、Read 这种 Agentic Search,而不是全部向量化。

分块策略也有差别:

  • 固定大小最简单,但经常切断语义;
  • 递归分块按段落、句子、token 逐级拆,通常更稳;
  • 语义分块用 Embedding 判断相邻段落是否相关,成本更高。

检索也不是"语义相似就一定能回答"。向量召回和关键词召回各有盲区,混合检索往往更可靠。用户问题不适合直接搜索时,还可以做 Query 改写;返回一堆重复内容时,可以用 MMR 去重。

最后,RAG 可以做成一个循环:Agent 先判断要不要搜,搜完判断信息够不够,不够就换 Query 或调用其他工具,直到足够再生成答案。

十、上下文工程的检查顺序

遇到长任务效果下降,我通常会按这个顺序排查:

  1. 有没有把大结果 Offload,而不是一直塞进上下文;
  2. Prompt 是否拆成了稳定前缀和动态信息;
  3. 是否使用 JIT,只在需要时加载文件和文档;
  4. 压缩是否分层,是否保留了边界、摘要和关键文件;
  5. 一个上下文是否装得下任务,需不需要拆给子 Agent;
  6. 跨会话信息是否进入 Memory,而不是指望模型自然记得;
  7. 检索数据是否干净,分块是否保留了结构。

上下文工程没有一种万能模板。模型、任务长度、工具数量和数据形态都会改变答案。

但目标始终一样:让模型在需要做决定的那一刻,看到最相关的信息,同时不被历史噪声拖住。

相关推荐
parksben1 小时前
OpenSider:让浏览器驱动 Agent
开源·github·agent
桃西西呀1 小时前
文件监控 Agent 为什么总在关键时刻掉链子
人工智能·llm·agent
半糖程序员1 小时前
从零构建 Agent(6):让 Agent 完成一次问答
agent
无限压榨切图仔1 小时前
向量库能搜到内容,为什么还不算学会 RAG
前端·agent
夫子3961 小时前
【第三部分:第一个 Agent 应用】13. 为 Agent 增加记忆能力:不是记住一切,而是在需要时想起正确的信息
llm·agent
PPPCODE2 小时前
从零手写一个MCP Agent服务:stdio与SSE两种连接模式的踩坑实录
llm·agent·mcp
Java的搬运工2 小时前
Agent 记忆系统难在取舍
agent
YDS8292 小时前
AI Agent 脚手架 —— Service层和Trigger层接口实现
ai·agent·spring ai
能不能静下心来看2 小时前
手搓三种 Agent 范式后,一次翻车让我看穿了它的本质
agent