跳到正文

Agent 为什么连自己调了几次工具都数不清?

上下文窗口本质上是一台极强的检索引擎,但原生缺失提炼与聚合层。规则写得再严,模型也数不清自己调用了几次工具。解析 Agent 状态栏机制(Status Bar),用确定性代码接管状态投影,让模型思考开销从线性膨胀收敛为恒定。

2026年9月15日·1,940 字·5 分钟
Agent上下文工程工程实践
Agent 为什么连自己调了几次工具都数不清?

上周测试一个自动外呼与退款核销的 Agent。

为了防止骚扰用户,我们在 System Prompt 里写了明确的硬性规则:“对同一个商家的拨打尝试,不得超过 3 次”。

会话跑起来之后很顺利,前 3 次拨打均因对方占线而挂断。打开上下文日志,前 3 次通话的时间、返回码和失败原因记录得一清二楚。

但在第 4 轮迭代中,Agent 毫无迟疑地再次调用了拨打工具,一路打到了第 5 次,直接把测试号码打进了运营商的风控黑名单。

规则白纸黑字写在开头,事实清清楚楚记在眼前,调用的也是顶级的商用大模型。为什么它连“三”都数不清?

很多开发者遇到这种问题,第一反应是去改提示词:“务必严格数清调用次数”、“千万不要超过三次”。

但改完之后,它依然会偶发性地数错。

这不是提示词写得不够严厉,而是撞上了 Transformer 注意力机制的硬限制。

上下文窗口原生缺失状态聚合层

很多人习惯把上下文窗口当成模型的“工作内存”,以为放进去的内容模型都能随时计算。

但从底层机制看,上下文学习(In-Context Learning)主要负责检索,并不负责状态维护。

注意力机制擅长做的事,是从成千上万个 Token 里把相关的原始片段找出来。但它原生缺失了一层:状态聚合

上下文中记录的信息,模型不会自动计数,更不会就地累加。

一个简单的实验就能说明这种差异:

在上下文里塞入 100 篇巡检记录,其中 90 篇标明设备正常,10 篇标明异常。

如果问它:“第 37 号设备的巡检状态是什么?” 注意力机制可以精准定位,答案毫无偏差。

但如果问它:“正常和异常的设备各有多少台?” 在不开启思维链逐行排查时,模型极容易数错。

回答第一问只需要做模式匹配检索;回答第二问则需要遍历全量记录并维护累加状态。这是典型的带状态计算。

当会话进行到十几轮、上下文中塞满代码和工具输出时,让模型在单次前向传播里精准数出某个工具调了几次,无异于强求一个搜索引擎去心算账本。

检索第 37 号记录与聚合 90 比 10 的状态是两类任务

把隐式计算,变成确定性的状态栏

既然概率模型不擅长自发数数,工程上的解法就非常纯粹:绝不要把状态统计的工作留给模型。

最直接的工程解法,是由 Harness 在每次交互时用确定性代码提前算好高阶状态,在上下文末尾挂上 Agent 状态栏(Agent Status Bar)

在外呼场景里,当 Harness 在每次工具返回的末尾显式注入一句“本次是该商家第 3 次呼叫(已达最大上限 3/3)”,模型就能瞬间命中规则边界,终止拨打,错误率直接归零。

状态栏机制带来了两项工程收益:

第一,提高信噪比。原始的工具执行日志充斥着大量低价值 Token,状态栏用几十个 Token 的代价,直接向模型提供原本需要扫描数千 Token 才能提取的统计结论。

第二,缩短注意力跨度。在长会话中,模型的注意力极易被滚动的日志稀释。把算好的元状态放在上下文最末端,紧挨着模型即将生成的下一个 Token,能获得最确定的注意力权重。

思考开销从线性膨胀,收敛为基本恒定

状态栏机制带来的提升,在实验数据中体现得非常剧烈。

研究人员基于注意力分布和思考 Token 做了对照实验:

  • 无状态栏:模型的注意力高度分散在各个历史工具调用的片段中,思考过程充斥着反复扫描上下文、数数和对账的动作。每次查询所需的思考 Token 量,随着会话轮次和上下文长度线性持续增长
  • 有状态栏:注意力高度聚焦在状态栏的提炼信息上,思考过程直接消费现有结论。单次迭代的思考 Token、响应延迟和 API 花费降低了近一个数量级

更关键的是:加入状态栏后,模型单步思考的开销从随上下文变长而线性膨胀,收敛为基本恒定。

这种收敛是决定性的。它意味着一个长程任务不会因为跑到第 20 轮就由于思考膨胀而拖垮性能。甚至较小的开源模型在挂上状态栏后,任务完成率也能直接逼近前沿大模型。

状态栏里最该放的 3 样东西

在写 Harness 状态栏时,有三类信息被证明拥有最高的工程杠杆:

工具日志被聚合为调用次数、TODO 和结构化错误

1. 工具调用计数器

在工具返回结果中,由 Harness 维护一个计数,标明“这是该工具的第 N 次调用”。

当同一个工具在短时间内连续失败 2 到 3 次时,计数器能迅速触发模型的模式识别:第一次失败它会检查路径,第二次它会查看目录,第三次它会主动放弃死磕,转去寻找替代方案。

2. 显式的 TODO 列表

在上下文末尾维护结构化的任务清单,每轮刷新每一项的状态(pending / in_progress / completed)。

实测表明,启用显式 TODO 列表后,Agent 完成长任务所需的平均迭代轮次从 21 轮下降到 15 轮,且极少出现“做完第一件事就把第二件事忘了”的过早终止现象。

3. 结构化错误与修复建议

很多系统在工具报错时,只把原始的 stderr 扔回给模型。

高质量的状态栏会对错误做分层包装:错误类型、调用参数、调用栈,以及由 Harness 预设的针对性修复建议(比如“文件不存在,可尝试使用 fffind 搜索文件名”)。

在测试中,这一项改动把 Agent 找到正确替代方案的成功率直接从 60% 提升到了 95%

两条必须遵守的实现边界

在实现状态栏时,必须守住两条工程底线:

第一,注入位置必须挂在末尾。状态栏应当借用 user 消息的角色槽位追加在对话最后,或者放在最新工具返回的末尾。绝对不能为了“看起来整洁”去修改开头的 System Prompt,否则每一轮状态更新都会击穿前面的前缀缓存。

第二,模型对状态栏近乎盲目采信。实验表明,模型极度信任上下文末尾的状态投影。如果你的 Harness 计数器代码有 bug(比如实际上调了 2 次却写成了 3 次),模型就会毫不怀疑地依据错误计数做出判断。

写给模型的统计数据,必须由绝对可靠的确定性代码来保证。


大模型专精于语义关联与生成,无法替代精准的计数器。

不要指望把所有事情都丢给模型去“心算”。让确定性代码负责算术与状态投影,让大模型专注逻辑与语义推理,这才是生产级 Agent 系统最稳固的底座。

版权声明

作者
XingKaiXin
标题
Agent 为什么连自己调了几次工具都数不清?
发布时间
2026年9月15日

本作品采用CC BY-NC-ND 4.0 DEED许可。