model ID 换成 claude-fable-5-1,就改了一行,然后服务开始报 400。查了两天,三个原因,都不是我代码写错,是这个版本本身不支持了。写下来省得后面的人再踩。
一、tool_choice 不能再强制了
以前为了拿稳定的 JSON,我习惯把 tool_choice 设成 any,或者直接点名某个工具。5.1 上这两种写法直接 400,报错说 type tool 和 any 在这个模型上不支持。
现在只剩 auto 和 none 能用。官方给的理由是这版思考常开,强制调工具会跳过思考,模型就把推理过程写进参数里,参数质量反而更差。
我的改法:tool_choice 保持 auto,schema 那边开 strict,然后在提示词里明确写清楚什么情况下必须调哪个工具。实测它是听话的,几百次调用下来没出现该调不调的情况。要是只想要结构化输出,直接走 structured outputs 更干净。
补一句,token counting 那个接口也是同一套校验,别以为只有正式请求会拦,我就是在那儿先撞的。
二、思考块只能往上兼容
现在每个思考块都记着是哪个模型生成的,兼容方向是单向的:5.1 能读旧模型的块,旧模型读不了 5.1 的。
我这边有个路由,遇到限流会自动降级到别的模型。以前一直没事,换 5.1 之后降级那一段的推理全丢了,回答质量肉眼可见地掉。麻烦的是它不报错,API 会把读不了的块直接丢掉,既不计费也不提示。
想知道到底丢没丢,得带 thinking-binding-controls-2026-08-01 这个 beta 头,丢弃动作会出现在 input_transformations 里。不带就是无声的。我是加了这个头跑了一晚上日志才看出问题。
三、改历史等于作废后面所有思考块
这个坑最深。规则是只要动了思考块前面的任何东西,后面的块全失效。重建系统提示、重新拼 tools 数组、往早先的消息里塞一行提醒然后下一轮删掉,全算改。
我的代码正好干了第三件事:每轮往历史里注入一句当前时间和任务状态,下一轮再替换掉。以前完全没问题,5.1 上第二轮就 400,报的是 block is bound to a different conversation。
改法是官方那套:per-turn 的提醒改成 turn-scoped 系统消息,设 clear_at 为 next_user_message,消息留在数组里原样发回去,过了这一轮就不渲染,缓存也不断。tools 要改就用会话中工具变更,别去重拼数组。要裁上下文就用服务端的 context editing 或者 compaction,那两个不算改历史。
还有个细节挺阴的:这个校验只对 2026 年 8 月 31 号之后新建的账号强制生效。老账号默认只记录不拦截,除非你自己设了 prefix_mismatch_behavior。所以你在老账号上测没事,不代表上线没事。
排查建议
如果你的 messages 数组是自己拼的,别直接切过去。先把 prefix_mismatch_behavior 设成 drop_block 跑一轮,把 input_transformations 打进日志,看有没有块被丢。有就说明你的代码在改历史,先改干净再切。
用官方 SDK、Claude Code 或者托管那一套的可以跳过这一节,前缀是帮你保住的。