AI Agent 的"上下文窗口"开始不够用了,我换了三种方法才把它的注意力稳住
我有个项目,最早用 Cursor 写的时候,五六个文件随便让它跑。现在这个项目迭代了大半年,主分支上几百个文件,三个微服务共享同一份数据库 migration,最近一次让 Claude Code 帮我改一个支付回调的字段,AI 直接给我生成了一个看起来对、跑起来也能编译、但放到生产环境会扣错钱的版本。
排查的时候我去翻它的推理过程,发现它在第 23 步就把”这个字段是 string 类型”这事儿给忘了。后面 14 步全在围绕一个错的假设展开,最后输出一个逻辑自洽的 bug。
这件事让我意识到一个问题:“上下文窗口 100 万 token”和”AI 真的读进去了 100 万 token”,根本不是一回事。
我以前以为,模型能塞下 100 万 token 的文件内容,那就等于我整个项目都”在它的脑子里”。实际操作下来才发现,AI 的注意力是有瓶颈的,给它越多材料,它反而越容易分心。
后来我花了大概一个多礼拜,把项目的工作流重新理了一遍。我不是模型研究者,下面的方法都是从代码里一次次试出来的,没有学术严格性,但管用。
先解释清楚我在说什么
如果你是用 AI 写代码比较早的那批用户,应该会有类似的体感:项目小的时候,Cursor Composer、Copilot Chat、Claude Code 怎么玩都顺手。但项目复杂度上来之后,AI 突然变得”笨了”——同一个需求,三周前它能一次写对,现在改三遍都不行。
这件事有个学术名字,叫 Lost in the Middle。大意是:模型对输入的开头和结尾记得最清楚,中间部分容易”看走眼”。原始论文(Stanford 和 Samaya AI 2023 年的研究)测出来,对于一段 2000 token 的输入,开头和结尾位置的信息召回率能到 70% 以上,正中间的位置掉到 40% 多。
100 万 token 的窗口里,这个效应不是消失了,是被放大了。因为材料越多,”中间”越长,越容易丢东西。
我自己的体感是:当我给 Agent 喂超过 30-40 个相关文件,它开始犯低级错(忘字段、混变量、引用不存在的文件)的概率会显著上升。40-100 个文件,它开始产出”看起来对但其实跑不通”的代码。100 个文件以上——基本就在赌运气。
这件事不是”模型不够聪明”造成的,是注意力分配的物理限制。所以解决思路也不是”换个更强的模型”,而是主动管理它看什么、看多少、按什么顺序看。
方法一:把项目”目录化”喂给 AI
我最早的做法是让 AI 自己 @ 文件或者 find 整个项目。后来发现这等于让一个注意力分散的人去图书馆自己找资料——它会去,但找回来的东西经常跑偏。
现在的做法是,每个项目我都维护一个 AGENTS.md(这个文件名很多 Agent 工具会优先读),里面不长,大概是这样:
1 | |
这个文件有什么作用?让 AI 在做任何事之前,先读到一份”项目地图”。它接下来去找文件、改代码的时候,会基于这份地图判断”我应该动哪里、不应该动哪里”。比让它自己摸索高效得多。
方法二:分阶段拆任务,而不是一次塞完整需求
第二种方法,是任务设计上的改变。
我以前写给 Cursor 的 prompt 大概是这样的:
帮我把支付回调改成支持支付宝退款回调。
这种 prompt 看上去很明确,实际上信息量很少。AI 拿到这个任务会去做什么?它会把所有 payment/ 目录下的文件全读一遍,然后试图一次性给你一个完整的方案。
现在的写法是这样的:
任务 1:先读
payment/callback.go和payment/types.go,告诉我现有的回调处理函数是怎么实现的,有哪些事件类型、哪些状态字段。看完之后,告诉我你打算怎么改,再开始动手。
这一步等 AI 跑完,我自己看一眼它的理解对不对,再让它开始写。
第二个 prompt 是:
任务 2:基于你刚才的总结,修改
payment/alipay_callback.go,新增一个退款回调处理函数。要求:
- 复用现有的 idempotent 中间件
- 退款金额校验逻辑写在
payment/validation/refund.go- 不要改
payment/types.go(如果需要改,告诉我为什么)改完先跑测试
go test ./payment/...,把测试结果给我看。
第三步:
任务 3:如果测试都过了,再读一遍你改的代码,写一个总结给我,列出你做了哪些改动、为什么、改了哪些文件之外的东西。
这种”分阶段”的方法,本质上是把一个大的上下文拆成 N 个小上下文。每个小任务里,AI 只需要关注”当前这一步”相关的文件,注意力就不会被稀释。
我后来用 GitHub Copilot 和 Claude Code 都有类似的体感:让 AI 一次只做一件事,比一次让它做一整套事情靠谱得多。
方法三:把”重点提示”放在文件开头和结尾
第三个方法,是我从 Lost in the Middle 那篇论文里直接抄来的。
既然模型对开头和结尾记得最清楚,那我就在它要读的文件前面和后面各塞一段”重点提醒”。
具体怎么做的?用一个 .cursor/rules 或者 CLAUDE.md 之类的规则文件,针对特定目录加规则。比如我的 services/payment/ 目录有个规则文件是这样的:
1 | |
第一段”必读”放在 AI 即将打开相关文件时最先看到的位置,第二段”重要约束”放在它写完代码、准备让我看的位置。
这样做的原理很简单:让 AI 在”开始工作”和”结束工作”这两个最容易记住的时刻,都看到最重要的约束。中间那段”它实际写代码的过程”,哪怕它真的注意力分散了,开头结尾两边的强化也能把关键信息顶住。
Claude Code 这边有个官方支持的 hooks 机制可以更精细地控制——比如在 AI 读完 payment/callback.go 之后,自动注入一段”金额校验”的提醒。我目前在测这个组合,但还没完全稳定,先不放出来。
这些方法不能解决什么
老实说,这三个方法都是”减缓症状”,不是”根治问题”。
根上的问题是:上下文窗口大了,注意力分配能力没跟上。AI 仍然会丢关键约束,会在长会话里自打嘴巴。
下面这些情况,这三个方法帮不上太多:
- 跨服务的事务一致性。比如”用户下单后通知库存系统 + 支付系统 + 物流系统”。三个服务分布在不同目录里,AI 还是容易搞混调用顺序。
- 跨 PR 的代码风格统一。如果两个 PR 由 AI 各自生成,它们很容易生成风格不一的代码(比如一个用 zap log,一个用 logrus)。
- 测试覆盖率。我试过让 AI 给一个 2000 行的服务补单元测试,它给我生成了 60% 行覆盖率,但关键的边界条件(空指针、nil slice、超时)一个都没覆盖。
这些事目前还是得靠人盯。AI 是个强辅助,不是替代品。
我现在的默认工作流
总结一下,我现在每天写代码的工作流是这样的:
- 进入项目第一件事:确认
AGENTS.md是最新的(同事改了什么新约束我会同步进去) - 任何任务开始之前:先把任务拆成 2-4 个小任务,每个步骤明确”读什么、改什么、跑什么测试”
- 每个步骤结束之后:让 AI 自己总结一下它做了哪些改动,我扫一眼 diff
- 关键代码改完之后:单独让 AI 读一遍相关文件,列出它看到的”重点约束”,看它有没有漏
这套流程不能让我跑得更快。但能让我少掉几次坑。尤其是那种”AI 改完之后没冒烟测试、直接合到主分支、线上出了事”的坑。
上次那个支付回调的 bug,按这套流程走了一遍,AI 还是在第三步的时候把字段类型搞混了——但因为我让它在任务 3 里”自己列一遍字段定义”,我自己看了一眼,发现它把 amount_int64 写成了 amount,提了一句让它改了。
如果当时我让它一次性写完,我可能也得排查半天才能发现这个 bug。
工具越来越强,但人的注意力还是比 AI 的注意力贵一点。这件事我觉得接下来几年都不会变。