这类问题我又碰上了一次。自建网关把 Claude 的 tool_use 转成 OpenAI 的 tool_calls,block.input 本来是对象,序列化时被 json.dumps 成字符串,下游报 400 arguments 必须是 JSON 字符串,改回对象后 Claude 回填又报 input 必须是对象。

第一反应是「回填历史时把 OpenAI 的字符串原样交给了 Claude」,验证下来不成立。

排查方向其实有限,先把可能性排一排。两家协议不同,一边要对象一边要字符串;转换层对 input 做了两次 dumps;没有针对转换层写契约测试。

上来先打印转换前后的类型与内容

python -c "import json;b={'input':{'city':'杭州'}};print(type(b['input']), json.dumps(b['input'],ensure_ascii=False))"

出站时转成字符串,入站时再 loads

python -c "import json;s=json.dumps({'city':'杭州'},ensure_ascii=False);print(type(json.loads(s)))"

然后用一段固定样例跑通双向转换

python gateway_contract_test.py --case tool_roundtrip

等前面都确认过,再在网关日志里记录原始 block 结构,便于回溯

python -c "print('log raw block before transform')"

怎么算真的好了,看的是同一批现象。同一条工具调用在两家协议之间往返后结构一致,网关不再返回 400,回填后模型能正常继续。

协议转换最容易坏在类型上,出站和入站各写一条断言,比上线后靠日志猜省事得多。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189