更好的模型,更差的工具:Flask 作者揭示 LLM 工具调用退化现象

Flask 作者 Armin Ronacher 发现,Anthropic 最新的 Opus 4.8 和 Sonnet 5 在调用非标准工具接口时,会"发明"不存在的参数字段,而旧模型没有此问题。根因是 RL 后训练在 Claude Code 宽容 harness 中进行,模型缺乏不要发明字段的梯度信号。
Armin Ronacher——Flask 框架的创建者——在排查 Pi 编辑器的工具调用 bug 时,意外发现了一个反直觉的现象:Opus 4.8 和 Sonnet 5 在工具调用中比旧模型表现更差。
问题表现
Pi 的编辑工具接受一个嵌套的 edits[] 数组参数。新模型在调用时,会在这个数组的对象中发明大量不存在的字段:
{
"oldText": "正确的文本",
"newText": "正确的替换",
"requireUnique": true
}

图1:正确的工具调用 schema vs 模型发明的畸形 schema
发明的字段多达数十种:type、id、kind、unique、requireUnique、matchCase、in_file、forceMatchCount、oldText2、newText2 等等。最令人沮丧的是,实际的 oldText 和 newText 值是完全正确的——模型知道要做什么,只是在输出参数格式时添加了多余的"尾巴"。
为什么更强大的模型反而更差
Ronacher 的核心假设是:这不是随机衰退,而是 RL 后训练的副产物。
现代 Anthropic 模型的后训练很可能包含了 Claude Code 或类似 harness 的训练环境。Claude Code 的工具调用处理极其宽容——它有参数别名(old_str → old_string)、Unicode 修复、未知键过滤、类型强制转换。在这种环境中,稍微畸形的工具调用仍然能完成任务并获得 RL 奖励。

图2:RL 后训练在宽容 harness 中进行——畸形调用仍获奖励
更深层的问题是:模型可能对 Claude Code 的扁平化编辑工具格式形成了强先验。Claude Code 的编辑工具是 file_path + old_string + new_string + 可选 replace_all。当模型遇到 Pi 的嵌套 oldText/newText 格式时,它的先验告诉它"编辑操作应该有一个额外可选字段",但在新 schema 下它不知道该字段叫什么名字,于是每次随机猜测一个。
高熵区域的问题
发明的字段总是出现在同一个位置:在一个数百 token 的转义 newText 字符串结束后。这是模型必须决定 } 还是 , "..." 的时刻——整个任务中熵最高的决策点。
如果 Anthropic 在这个位置没有启用约束解码(constrained decoding),模型就会自由采样下一个 token,而它的训练先验倾向于添加一个额外字段。这就是为什么 strict 模式能完全消除这个问题——服务端约束解码会直接屏蔽不在 schema 中的键名。
对工具开发者的启示
Ronacher 的发现对整个 AI Agent 生态有深远影响:工具 schema 不是中性的。某些形状更接近模型的后训练分布,某些则更远。模型可能足够聪明来理解 schema,但在采样精确格式时会因训练先验而失败。
这意味着:如果你在开发自己的 AI Agent 工具链,不能假设模型会忠实遵循你的工具定义。你需要考虑模型的"训练偏好",或者直接启用 strict 模式来强制约束。
关联推荐
- Flask 作者 Armin Ronacher 发出 AI 编程循环警告 — Ronacher 对 AI 编程的更早反思
- 构建可靠 AI Agent 系统:Martin Fowler 工程实践 — AI Agent 可靠性工程指南
- Cloudflare 为 AI Agent 推出临时账户 — AI Agent 安全部署实践
评论 (0)
加载评论中…