记录一下那天晚上的排查过程。从 claude-3-5-sonnet 换到 claude-sonnet-4 后,代码只判断 stop_reason 等于 end_turn,长回答被截断时返回 max_tokens,业务当成正常结束直接入库,内容缺了结尾。

刚开始盯着「代码写死字符串比较,没覆盖 refusal 等新取值」,折腾了半个多小时,排掉了。

真正麻烦的地方在于,几个原因的现象几乎一样。新版本达到输出上限时返回 max_tokens 而不是 end_turn。少数情况下是流式下 stop_reason 在 message_delta 事件里,代码没读。

还有一种情况,回答长度本身超过请求里给的 max_tokens。

上来先把所有可能的 stop_reason 打出来

print(resp.stop_reason)

再往下在流式事件里取最终值

python claude_stream.py --show-stop-reason

随后对 max_tokens 显式续写

python claude_resume.py --when max_tokens

等前面都确认过,再用长输出请求验证分支

python claude_long.py --max-tokens 8192

怎么算真的好了,看的是同一批现象。长回答被截断时日志打出 max_tokens 并触发续写,入库内容长度与预期一致,不再出现缺尾。

容易忽略的是,换模型版本时 stop_reason 的取值集合要重新对一遍,比较写成白名单,别用不等于 end_turn 来兜底。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189