$catMANUAL||~17 分钟

模型越强,工具越弱?Armin Ronacher 这篇新文章,戳中了 AI 编程最别扭的现状

advertisement

昨晚刷 Hacker News,被一篇文章吸引了——Armin Ronacher(就是 Flask 和 Jinja2 的作者,Python 圈应该没人不认识他)发了篇新文章,标题就叫"Better Models: Worse Tools"。

这标题够扎心的。但我读完发现,他指的跟我第一反应想的不是一回事。

他不是在抱怨"AI 生成的代码质量差"或者"新模型出来也写不好业务逻辑"。他说的是一个更底层、更隐蔽的问题:新的模型正在变差,不是在推理能力上,而是在遵循工具规范的能力上。

翻车了。而且翻得很微妙。

事情的起因:Pi 编辑器里的一次奇怪报错

Armin 在维护一个叫 Pi 的编辑器(一个基于终端的 AI 编程工具,我之前折腾过一阵,还挺好用的)。Pi 有一个文件编辑工具,接受 edits 数组参数——就是一次调用可以传多个替换操作进去。

结构大概是这样:

json
1
{
2
  "path": "some/file.py",
3
  "edits": [
4
    {
5
      "oldText": "要替换的代码",
6
      "newText": "替换后的代码"
7
    }
8
  ]
9
}

看起来挺直接的,对吧?但问题来了。新版本的 Claude(Opus 4.8 和 Sonnet 5)开始在 edits 数组里的每个对象后面加一些稀奇古怪的额外字段:

json
1
{
2
  "oldText": "...",
3
  "newText": "...",
4
  "requireUnique": true
5
}

或者这样:

json
1
{
2
  "oldText": "...",
3
  "newText": "...",
4
  "oldText2": "",
5
  "newText2": ""
6
}

Armin 说他看到的离谱字段包括:typeidkinduniquerequireUniquematchCasein_fileforceMatchCountchildrennotescost,甚至还有 event.0.additionalProperties

最气人的是啥?oldTextnewText 的值本身就是对的。 模型其实知道要改什么,就是在最后顺手加了一堆没用的东西。然后 Pi 一校验,格式不对,工具调用被拒绝,整个流程卡住了。

这也太尴尬了吧。

为什么老模型反而更靠谱?

这还不是最诡异的。最诡异的是——老版本的 Claude(Opus 4.5 和更早的模型)没这个问题。 Armin 说 Opus 4.5 刚出那会儿,它适配其他工具 schema 的能力特别好。不管你的工具结构长啥样,只要指令给清楚,它基本都能正确执行。

但 Opus 4.8 和 Sonnet 5 不行了。它们更强了,反而在某些场景下不会用工具了。

这不是退化,这叫过度适应(overfitting)

Armin 的分析很有意思。他猜测原因出在 Anthropic 的后训练(post-training)环节。Claude Code 是 Anthropic 自己的产品,它的编辑工具长什么样呢?很平:

python
1
Edit(
2
    file_path="some/file.py",
3
    old_string="要替换的代码",
4
    new_string="替换后的代码",
5
    replace_all=False  # 可选
6
)

没有嵌套数组,没有复杂的 JSON 结构。就是三个字段,一个可选的 flag。模型在 RL 训练中,不断地跟这个工具交互,学会了一套"编辑工具的调用模式"。它知道工具调用偶尔写错几个字段也没事——因为 Claude Code 的客户端非常宽容,会自动修复很多问题。

Armin 扒了一下 Claude Code 的客户端代码(虽然是闭源的,但 minified 代码可以看),发现它做了这些事情:

  • 检查模型输出的文本中是否有泄露的 <invoke> 标记
  • 修复损坏的 Unicode 转义序列
  • 支持参数别名——old_strold_stringnew_strnew_string 都能用
  • 自动过滤不在 schema 里的字段
  • 不支持 strict 模式(因为 Anthropic 对 strict 模式的工具定义复杂度有限制)

换句话说,Claude Code 的客户端是一个"宽容模式"的接收器,它接受各种轻微变形的调用,把脏活累活都自己扛了。

问题来了。模型在 RL 阶段学到的是:这套"松散"的调用模式是 OK 的。它不知道外面的工具可能不这么宽容。

这跟 Prompt 有什么关系?

很多人遇到这类问题,第一反应是:是不是 prompt 写得不够好?

我也一度这么想。但读了 Armin 的分析之后,我觉得问题出在更深的地方。

他说了一个很重要的点:工具 schema 不是中立的。 我们写工具的时候,习惯性地觉得"我定义了一套 JSON schema,模型是通用推理器,只要指令清晰它就能按规矩来"。但现实不是这样的——特别是对于 Anthropic 的模型。

原因出在工具调用的底层机制上。

LLM 的 tool call 其实没啥神秘的。它不是像我们人类调用函数那样去执行,而是在输出文本里"模拟"一个调用。模型收到的是一大段拼接起来的上下文(system prompt + 对话历史 + 工具定义),然后它在适当的位置输出一段标记,被客户端解析为"调用这个工具,参数是这些"。

对 Anthropic 来说,这个标记长这样:

code
1
<antml:function_calls>
2
 <antml:invoke name="edit">
3
 <antml:parameter name="path">some/file.py</antml:parameter>
4
 <antml:parameter name="edits">
5
[
6
 {
7
 "oldText": "text to replace",
8
 "newText": "replacement text"
9
 }
10
]
11
 </antml:parameter>
12
 </antml:invoke>
13
</antml:function_calls>

注意这里的问题:顶层的 path 是直接在 XML 标签里的纯文本,但底层的 edits 数组是 JSON 序列化后再塞到 XML 里的。这就是坑的源头。当模型处在一个嵌套 JSON 结构内部,而且前面的 newText 可能是一大段几百个 token 的字符串,它输出完这个字符串之后,需要决定下一秒输出的是 } 还是 , "..."

这就是整个问题的高熵点。 模型前面写了太多 token,上下文压力大,它最容易出错的位置就是这个点。新模型因为对"编辑工具的格式"有更强的先验,所以在这个高熵点上倾向于发明一个新的字段名——而不是老老实实写 }

我自己的经历:类似的坑也踩过

看完这篇文章我回忆了一下,我之前用 Hermes Agent + Claude 做自动化的时候,也遇到过类似的奇怪问题。

有一次我写了一个自定义 MCP 工具,接受一个嵌套参数:

json
1
{
2
  "action": "review",
3
  "options": {
4
    "strict_mode": true,
5
    "focus_areas": ["security", "performance"]
6
  }
7
}

Claude 有时候会给我加上一些我 schema 里没有的字段。比如会在 options 里多一个 "severity": "high" 或者 "check_all": false。当时我以为是模型抽风了,重试一次或者换 prompt 就正常了。

看了 Armin 的分析我才反应过来,这可能不是抽风——是我的 tool schema 跟 Claude Code 内部工具的 schema "长得不够像",模型在 RL 训练中学到的"套话"被带到了我的工具调用里。

还有一次,我用 Cursor 写代码,发现它的 inline 编辑功能有时候会凭空多出一些配置字段。我一直以为是 Cursor 自己的 bug。但现在想想,Cursor 的编辑后端也是用 LLM 来生成的,可能也是同一个根因。

模型越强,它对"什么格式是对的"这件事的先验就越强。如果这个先验跟你的工具 schema 有偏差,那模型越强反而越容易出幺蛾子。

Codex 那边怎么样?

Armin 也提到了 OpenAI Codex 的情况。他测试了所有能访问的 Codex 模型(包括 5.5,但 5.6 还没权限),结论是 Codex 那边没有出现类似的回归问题。

原因可能跟 OpenAI 的 Harmony 框架有关。Harmony 在 prompt 格式里明确加了 <|constrain|>json 这样的标记,让模型在调用工具的时候切换到 JSON 约束采样模式。OpenAI 甚至还允许用户提供 LARK 文法来自定义工具输出格式——这个级别的约束,想要靠采样产生非法输出几乎是不可能的。

也就是说,OpenAI 在工具调用的健壮性上,走的是"框架约束"路线——用采样策略来保证输出合法性,而不是依赖模型自己学会"不犯错"。

Anthropic 的路线则偏"模型自己去理解 schema 然后生成正确输出"。在 strict 模式下他们也用约束采样,但这个 strict 模式有复杂度限制,不是所有工具都能用。

对 AI 编程工具开发者来说,有什么启示?

这个问题我觉得挺重要的,特别是对于像我这种折腾 AI 编程工具的开发者。

第一,别假设模型会严格按照你的 schema 来。不管你的工具定义写得多清楚,模型都可能给你加料。你的客户端代码要做好过滤和兜底。

第二,不要太相信"新模型更好用"这个叙事。 Opus 4.5 在某个工具上比 Opus 4.8 表现好,这不是 bug,是后训练数据分布差异导致的必然结果。升级模型之前,最好做一下自己的工具兼容性测试。

第三,如果你的工具是通用的(不是专门为某个模型设计的),最好让工具 schema 尽量扁平。 避免嵌套太深的 JSON 结构,避免在数组里面套复杂对象。因为嵌套越深,模型在 JSON 内部犯错的概率就越大。

第四,如果你用 Anthropic 的模型,能开 strict 模式就开 strict。虽然 strict 对复杂 schema 有长度限制,但对大部分简单工具来说是够用的。开了之后模型就没办法输出不在 schema 里的字段。

第五,这问题不只是 Anthropic 的。虽然 Codex 目前没这个问题,但保不齐哪天 Google Gemini 或者其他模型也会出现类似的过度适应。毕竟所有 LLM 的后训练阶段都在朝着"特定生态适配"的方向走。

这事背后更大的趋势

往大里说,Armin 这篇文章点出了一个 AI 编程工具领域正在发生的变化。

几个月前,大家还在吹"通用推理能力"、"模型会自己适应各种工具"。2026 年上半年的主流叙事是——模型足够聪明了,你随便给它一个工具它都能用,就像给它一把螺丝刀它就知道怎么拧螺丝。

但现在回头看看,这个叙事可能过于乐观了。

实际情况是:主流模型正在被后训练过程"拉"向特定的工具生态。 Anthropic 的模型在优化 Claude Code 的使用体验,OpenAI 的模型在优化 Codex 的使用体验。双方的后训练数据都高度偏向自家的工具链。

这本身不是坏事。自家的产品配合自己的模型,体验肯定最好。但问题是,AI 编程工具的江湖不是一家独大——有 Cursor、Windsurf、Aider、Cline、Pi 等等一大批第三方工具。如果 Anthropic 的后训练让 Claude 越来越"理解" Claude Code 的编辑器格式,那 Pi 和其他工具的用户怎么办?

他们的选择只有两个:要么改工具 schema 去匹配 Claude Code 的习惯,要么接受模型越来越不好用的现实。

都不是什么好选项。

还值得一试的工具和思路

这问题看着无解,但也不是完全没辙。我整理了几个能缓解问题的策略:

1. 对 Anthropic 模型用结构化工具调用

Anthropic 提供了 tool_choice.type = "any" 加上 tool_choice.disable_parallel_tool_use = false 的模式,这个组合可以让模型更专注于正确调用工具。至少我自己试下来,加了之后非法字段的出现频率低了 60% 左右。

2. 偏平化你的工具设计

前面说了,嵌套越深越容易出问题。如果你的 MCP 工具或者 API 需要接收复杂参数,考虑拆成多个简单工具,而不是一个复杂工具。比如把 edit_code 拆成 find_text + replace_text 两个步骤,比一个 edit 带嵌套数组要靠谱。

3. 客户端做防御性编程

学学 Claude Code 的做法——每个工具的调用结果都做一次 schema 校验,不合规的自动过滤或标记。Python 里用 pydantic、TypeScript 里用 zod,都能很方便地做这个事情。

4. 试试 OpenAI 模型

如果你被 Anthropic 模型的"加字段"问题搞得很烦,又不想改工具结构,换 Codex 或者 GPT-5.5 可能会直接解决问题。至少 Armin 的测试结果显示 Codex 没有这个问题。

结语

我在建站这三个月里,写过不少工具评测和对比。但这篇文章跟那些不太一样——它不是在比"哪个工具更好",而是在说一个更深层的问题:AI 模型正在变得越来越"偏科"。

一个模型在某个生态里训练得越多,它在那个生态里就越强,但在其他地方可能就越弱。这是 RL 训练的必然结果,跟模型本身的智能没太大关系。

所以我的建议很简单:别把模型当成"通用智能体"。它是有偏好的。你的工具如果跟它的偏好匹配,它就很给力。如果不匹配,你怪模型不行也没用——它只是长成了它被训练成的样子。

Armin 原文里有一句话说得很好,我翻译过来大概意思就是:

我本来对严格约束采样的价值持怀疑态度,但这个问题让我的想法发生了明显变化。如果新模型在任务上变强了,但在遵循可替换的 schema 上变弱了,那工具链需要某个地方提供更强的保证。

我现在也是这个想法。

后训练把模型往某个方向推得越远,开放生态的工具要付出的代价就越大。

不过话说回来,这也不是什么世界末日。理解了问题出在哪,就知道该在客户端、schema 设计和采样策略上做哪方面的加固。有坑就填呗,搞技术的谁不是这样过来的。

有啥想法评论区聊。我的态度很开放——你觉得这文章太悲观也好,觉得我小题大做也好,都行。我自己反正是把 Armin 的这个分析记下来了,以后做工具设计的时候多留个心眼。

advertisement