做 Agent 写到第三个小时,我开始给它算账

11 月 11 号凌晨两点,我把一个跑了四十分钟的会话关了。不是因为它报错,是因为它第三次问我同一个问题:这个接口的鉴权到底放在网关还是放在服务里。这个问题我在第一轮就答过,答了两次,第二次还贴了代码。

关掉之后我没接着调提示词,而是先去翻那四十分钟的 token 记录。翻完有点意外:总消耗里,真正的"新信息"不到一成,剩下的全是重复搬运------把我早就看过的文件内容一遍遍重新塞进窗口,把我上一轮已经得出的结论重新推理一遍。

那天我想明白一件事:上下文窗口不是内存,是预算。

窗口大小这件事,一直在骗人

厂商把 200K、1M 写进标题,给人的直觉是"能装",而"能装"很容易被理解成"该装满"。装不坏,大不了慢一点。真实情况是两头都付钱。

一头是钱。缓存写和缓存读的价差很大,同一个字节,第一次进窗口按写计费,后面命中缓存按读计费。这个价差意味着真正贵的不是你贴进去的那份文件,而是你每次改动都让它失效。会话里最贵的操作,是让前缀重算。

另一头是注意力。模型的检索能力在长上下文里并不是均匀下降的,它对"中间那段"特别不敏感。我遇到过同一个文件里的两处约束,一处写在开头一处写在第 380 行,模型稳定地只看开头那一处------这不是幻觉,是它确实没读到。

所以我给自己定了一条不成文的规矩:凡是进窗口的东西,得能说清"它在这一步会被用到吗"。答不上来就别放。

我后来分三层来放东西

最外面一层是工作记忆,就是当前这一轮的窗口。它只该装三样:要改的那段代码、判断标准(验收条件)、和上一步的结论。结论要压成一两句话,不要把整段推理过程带过去。带过程是浪费,带结论是纪律。

中间一层是外部状态,写进文件或者写进任务清单。比如接口约定、命名规则、某个坑的复现步骤。这些东西不该靠"我们之前聊过"来传递,应该有个地方能读,而且读出来是短的。我现在的做法是让每个阶段收尾时产出一份交接笔记,只写下一步需要的字段,前因后果一律不写。

最里面一层是索引,不进窗口,只用来定位。整个代码库塞不进去,但文件路径列表和符号名塞得进去。先花几千 token 拿到目录形状,再精准打开两三个文件,比一上来就贴十个文件便宜得多,而且准。

分层这事,难的不是画三层,是每次决定"这条信息该沉到哪一层"。我现在的判断标准很粗:这轮用完就丢的放窗口;下轮还要用的写文件;只有查的时候才需要的留在索引。

大厂那些工具的做法,其实是一个思路

用 Cursor 的同学大概注意到它不把你打开的文件原文全量塞进去,而是走一套 embedding 检索,先召回再补全。本质是把"装"变成"查"。

TRAE 这类工具做得更明显一层:子代理跑完只回一条聚合结果给主会话,中间那几百步的搜索和试错全部留在子代理自己的上下文里,不进主窗口。这一条我一开始觉得是限制,后来意识到这就是分账。真正难的问题不是能不能传回来,而是传回来的那条摘要丢了什么------丢了没人知道。

Anthropic 那套 subagent 的说明里也是同样的口径:并行、独立、只回结论。三家的措辞不同,算的是同一笔账。

有一点我还没搞明白,也不打算装懂:压缩的时机。会话快满的时候会自动摘要一次,但摘要挑重点的口味不稳定。有时它保住了一整段错误堆栈却丢了我明确给过的命名约束,有时反过来。这事目前没有可靠规则,只能在每次压缩后抽查一遍关键约束还在不在。我见过有人把关键约束重复写三遍来提高存活率,有点土,但有效,而且说明大家心里都清楚压缩是有损的。

一份具体的算法

我把它写成能照着做的步骤,不掺观点。

第一步,先测一轮成本。别优化,先测。跑一个真实任务,记下每轮的输入 token、缓存命中 token、输出 token。我第一份数据是这样的:一次前端改动,单轮输入 6 万到 18 万之间波动,缓存命中占了一半上下,输出只有几百。看到这个分布,优化方向就不言自明了------省输入,不是省输出。

第二步,把工具输出口径改小。翻整个目录树返回的几万字符,是窗口里的常客。加一条限制,输出超长的结果先落盘再看,不要原样进窗口。

第三步,长会话按阶段断开。一个阶段只干一件事:设计、实现、验证。阶段结束产出一份交接笔记,然后开新会话。这一步收益最大,也最反直觉------大家本能地觉得同一会话继续聊更"连贯",实际上带着 15 万 token 历史去改一个小按钮,只会让模型在旧信息和当前任务之间摇摆。

第四步,给上下文设预算。我现在的做法很粗:设计阶段允许 8 万,实现阶段允许 6 万,验证阶段最省,只留代码和判据。超了就压,压不下去就说明这个任务该拆。

一句结论

如果只能带走一句话,我带走这句:2026 年做 Agent 工程,先做上下文经济学,再谈提示词。

提示词优化的是单次回答的质量上限,上下文管理决定的是这个上限你能付得起多少次。一个 40 分钟的会话烧掉两百万 token 然后开始原地打转,不是模型不行,是账没算。

那晚关掉的会话,我第二天用三个阶段重跑了一遍,输入总量少了七成,结论是一样的,只是这次它没再问我鉴权放哪儿。## 缓存前缀这件事,能省一半

前面说贵的是前缀重算,这里给个具体的做法。

模型计费对同一份前缀的第二次访问走缓存。想命中,前缀必须逐字节一致。听起来简单,做起来有几个稳定的坑。

第一个坑是时间戳和随机 ID。有些工具会把当前时间、请求 ID 拼进系统提示,每轮都变。这一下整条前缀全废,后面所有历史都按写计费。我试过把自己写的 Agent 提示里那个动态时间字段挪到最后一条消息里,同样的任务,输入成本下来一大截。这类字段往后放,不要往前放。

第二个坑是工具列表的顺序。序列化工具描述时,顺序按字典序固定下来,别按注册顺序。注册顺序会随代码改动漂移,一漂移前缀就断。

第三个坑是对话历史里的引用。如果你习惯把文件内容原样贴进消息,一旦那个文件被改过一个字符,后续所有轮次的缓存都会重算。改用"读文件"工具让模型自己取,或者把它放在靠近末尾的位置,损失面就小。

交接笔记该有哪些字段

三个会话串起来靠交接笔记,这个笔记不是聊天摘要,是给下一个会话的输入。我自己的字段固定六条,少一条都会让下一个会话反问。

一,当前状态:改完了什么,用什么命令能验证。二,判据:怎么算完成,越具体越好,"能跑"不算判据。三,约束:命名规则、不能动的文件、接口取舍。这四条最常被丢,一丢就重来。四,已知坑:试过的死路,写清楚为什么不行。五,下一步:只写一件事。六,不做的:这一轮明确搁置的内容。

第六条是我后来加的。不加的后果是下一个会话以为啥都能碰,回头把上一轮故意搁置的东西又捡起来做了一遍。

篇幅控制在一屏内。超过一屏说明你在写日志,不是写交接。

两个反例

反例一,把整个报错栈连同上下文文件全贴进去求诊断。八成的场景,真正有信息量的只有前三行和一句"改动只在某个函数"。全贴的结果是模型开始在无关代码里找因果,编出一条看起来通顺的解释。我现在只贴栈顶,再附一句"只改过 X",模型反而稳。

反例二,为了"效果"把系统提示越写越长。角色设定、语气要求、示例、免责声明,一层层叠上去,最后到四五千 token。这份提示每轮都在,是所有缓存计算里最扎实的一份成本。里面有一半内容是给永远不会遇到的场景写的。写完我会删一遍:把这条规则删掉,如果三次任务里它没被触发过,就永久删掉。

什么时候断会话

给个我自己的经验阈值,不讲道理。

同一目标改了三处还没通过验证,断。发现自己在同一条消息里重复解释同一个约束,断。输出开始引用已经被推翻的旧方案,断。工具连续两次返回同样长的冗余结果,断。

这四个信号本质是一件事:窗口里已经有太多互相矛盾的历史,模型在旧信息和新指令之间摇摆。这时补一句"你别再这样了"是没用的,加进去的那句只是又多一份历史。

断掉的成本是重写一份交接笔记,几十行。继续聊的成本是再烧一遍历史。我算过一次,继续聊十轮,平均每轮把前面的账重付一次。

相关推荐
西安小哥15 分钟前
让大模型操作浏览器跑了两周,我发现瓶颈根本不在模型
javascript
用户2986985301430 分钟前
用 Python 自动化把 JavaScript 代码转换为 Word 文档
javascript·python·api
学以智用34 分钟前
正则表达式完整参考手册(JavaScript 版,兼容绝大多数语言)
javascript
北城笑笑41 分钟前
Vue 109 ,从零搭建 Vue3 + Electron 工程:生态、安全桥接与打包选型( Vue + Electron )
前端·javascript·vue.js·安全·electron·vue
Highcharts.js1 小时前
Highcharts智能数据分析平台Orbit 多语言配置技术文档
java·前端·javascript·highcharts·orbit·智能数据分析·设置文档
非优秀程序员12 小时前
第07章 孙德良的阳谋|《互联网产品恩仇录》
javascript·后端
程序员大阳16 小时前
HTML+CSS+JS对话式实战(5)-活动管理页之查看报名名单的实现
javascript·css·html·查看报名名单
新*18 小时前
从一段 WebSocket 代码聊开:bytes 不是 zlib、binaryType 决定一切,15MB 大消息的零拷贝优化
javascript·python·websocket·性能优化·blob·zlib·arraybuffer
OpsEye19 小时前
别再挨个充会员了:多模型编码,最麻烦的从来不是选模型
javascript·ai编程