你让 Codex Agent 加一个函数。Transcript 显示它写第一行代码之前调了 47 次工具:Read package.json 四次、同一个 pattern 的 Glob "**/*.ts" 三次、Read src/types.ts 八次——其中两次还是连着。等它终于开始写代码时,turn 预算已经用了一半、相关上下文也被挤出了窗口。最后产出形状错了——它从没真正落到问题上。
冗余 tool 调用不是模型缺陷,是 agent 在搜不在建的信号。它没地图、没计划、没”最近读过什么”的缓存,每一次 tool 调用都是猜。修法是提前给地图、写代码前强制写 plan、prompt 结构上不让 agent 重新发现你已经知道的事。
常见原因
按命中率排序。
1. 没结构图——agent 重新发现结构
你说”加个新 endpoint”。Agent 不知道 endpoint 在哪。它读 package.json、tsconfig.json,扫 src/,读 index.ts、app.ts、server.ts。一句话本来能告诉它的事,它读了 5 次。
如何判断:前 5+ 次 tool 调用是探索不是行动,read 之后没跟着 edit。
2. 动手前没写 plan
Agent 直接开编辑、撞到未知、再读、再编辑、再撞到未知。每个未知触发新 read。没写 plan,每个决定就变成临时查阅。
如何判断:Tool 调用穿插 Read → Write → Read → Write,而不是集中 Read 阶段 → 集中 Write 阶段。
3. Search 结果没做摘要
Agent 跑 grep -rn "useAuth" src/ 得到 80 个 match。不是摘要 / 过滤,而是一个个把 match 的文件读完。5 分钟后忘了摘要又跑一次同一个 query。
如何判断:grep N 个 match 之后跟着 N+ 次 file read——agent 没在读之前收敛。
4. 上下文里没有”我读过什么”的缓存
Agent 忘了它读过某个文件。10 turn 之后再提到同一路径 → 重读。没有结构性指针”文件 X 我们已经覆盖过”。
如何判断:同一个 Read <path> 在 transcript 里出现 3+ 次。
5. Tool 调用被当作思考辅助
某些 prompt 下 agent 更愿意跑 ls、cat、wc 来代替推理。像在”检查”而不是 commit 到决策。turn 数翻倍但没进展。
如何判断:大量便宜的、单文件 head / ls / wc 调用,中间没 edit。
6. 冗长 stdout 引发重读
某次 tool 调用返回 5000 行。Agent 在工作记忆里端不住,部分信息被 summarize 掉,它又重读同一文件的某些部分来”验证”。
如何判断:吵的工具输出之后紧跟着重读刚刚同一路径。
动手前先确认
- 拿一份典型任务的 tool 调用计数基线——
grep -c "tool_use" agent.log之类。 - 识别 agent 模板的探索阶段长度——长探索 = 症状。
- 判断这次是”我不熟这个仓库”还是”我熟但让 Codex 自己重推”——两个修法不同。
需要收集的信息
- 出问题那次跑的完整 transcript + 各类 tool 调用计数。
- 前 10 次 tool 调用——通常能看出 agent 是有图还是在搜。
- 任务 prompt 的原文——含糊的 prompt 引发更多搜索。
- 任何本来可以喂给 agent 但没喂的 AGENTS.md / repo-map.md / 约定文档。
最短修复路径
按收益从高到低。
Step 1:预喂地图和关键文件指针
每个任务 prompt 顶端:
仓库结构:
- API 路由:src/api/routes/*.ts(一个 resource 一个文件)
- Services:src/services/*.ts(业务逻辑)
- 类型:src/types/index.ts(共享)+ src/types/<feature>.ts(feature 专属)
- 测试:和源代码并列 *.test.ts
本次任务需要编辑:
- src/api/routes/orders.ts(新建)
- src/services/orderService.ts(扩展)
- src/types/order.ts(扩展)
- src/api/routes/orders.test.ts(新建)
Agent 不用读就拿到结构图,省 5-15 次冗余探索调用。
Step 2:动手前强制写 plan
先写 plan。输出:
PLAN:
1. <step>
2. <step>
...
每步列出涉及的文件。确认 plan 在 working scope 内,然后才能开始编辑。
执行中需要改 plan 就输出 REVISED PLAN 再继续。
强制 agent 在结构化阶段完成探索、再 commit 到动作。重读会大幅下降。
Step 3:多 match search 之后强制摘要
任何 grep / glob / search 返回 > 5 个结果,输出 SUMMARY:
SUMMARY of "<query>":
- M 个文件 N 个 match
- 相关文件:<列 1-5>
- 跳过:<列>
然后只读相关文件,不要每个 match 都读。
一份 grep 摘要 + 3 次 read 胜过 80 次原始 match 读。
Step 4:在 prompt 里维护显式 read tracker
每次 Read 完追加到 scratchpad:
READ_TRACKER:
- src/types/order.ts(step 2 时读过)
- src/services/orderService.ts(step 3 时读过)
Read 之前先查 tracker。文件已列出就凭记忆回忆,内容没变就不重读。
把”读过什么”的缓存外置化,agent 必须显式承认重读。
Step 5:按阶段限制工具
如果你的 runner 支持 per-phase tool 限制:
Phase 1(探索):允许 Read、Grep、Glob。禁止 Write / Edit。
Phase 2(规划):禁所有工具,只输出文本。
Phase 3(执行):允许 Edit、Write、Bash。禁止 Grep(已做过)。
阶段中拿掉工具能阻止”我再查最后一件事”的死循环。
Step 6:吵的工具输出加帽
爱灌水的命令包一层:
pnpm test 2>&1 | tee /tmp/test.log | grep -E "FAIL|PASS|Tests:" | head -50
输出少 → 为验证而重读也少。很多冗余 read 是在追被一墙 stdout 淹没的信号。
Step 7:换 tool-use 效率更好的模型
结构性修法之后还是浪费调用,可以试一下 tool-use 专门调优的变体(agent 流程上 gpt-5.5 > gpt-5.4)。同任务上常见 30-50% 的冗余调用降幅。
怎么确认已经修好
- 下一次跑数一下 tool 调用——光 Step 1-3 通常掉 40-70%。
- 看 transcript:清晰的 Read 阶段 → Plan 输出 → Write 阶段,而不是混在一起。
- 整跑里没有重复
Read <同一 path>。 - 完成所需 turn 数下降、有余量地在 turn 预算内结束。
长期预防
- 每个 agent 任务模板开头都有”repo 图 + 相关文件”块。
- Plan-first 强制;没有 PLAN section 的任务被 verifier 拒收。
- AGENTS.md 列”X 住在哪”的标准约定——agent 读它而不是每个 session 重新发现。
- 默认给吵的工具加输出帽,agent 永远见不到 5000 行 stdout。
- 长任务按 phase 限制工具。
- 留一份 agent-runs.log,每周复盘:tool call > 60 次的跑都要找根因。
常见坑
- 把”tool 调用更多 = 更彻底”当好事。每次调用都耗上下文和 turn,冗余是纯成本。
- prompt 里写”不要重读文件”但没给 agent tracker 机制——指令落不了地。
- 让 agent 在大仓库跑
find . -type f——光是输出就把预算炸光。 - 把 turn 预算调到 200 来”吸收”冗余——冗余在能装下时仍然拉低输出质量。
- 忘了 prompt cache 让冗余 dollar 上更便宜但 window 上依然贵。
常见 FAQ
Q:Agent 在相邻两次 turn 重读同一文件,为什么?
大概率你的 runner 没把”前序 tool 调用”surface 出来,agent 的 plan 也没引用那次 read。加显式 READ_TRACKER scratchpad 就好了。
Q:怎么自动数 tool 调用次数?
大多数 agent runner 会发 JSON event stream。数 tool_use 类型的记录。文本日志的话:grep -c "function_calls" transcript.txt。
Q:“read / edit 比例”有健康值吗?
粗略:熟悉代码 2-4 读 1 编辑,不熟悉 5-8。如果看到 15+ 读 1 编辑,prompt 缺结构图。
Q:直接把 max-turns 调低能强制提高效率吗?
调低 max-turns 也会惩罚正经工作。不如修原因(没图、没 plan)——效率上来的同时余量也变大。