报错是这么冒出来的。通义 qwen-plus 走 OpenAI 兼容接口,多轮工具调用时 assistant 消息里的 tool_calls id 是空字符串,下一轮带 tool_call_id 回填,接口返回 400,原文 tool_call_id is required,DashScope 原生接口却正常。

中间走过弯路,把「历史里两条 tool 消息顺序与调用顺序不一致」当成主因,改完现象没变。

真正麻烦的地方在于,几个原因的现象几乎一样。兼容层没有为工具调用生成 id。也可能是模型在并行调用时只回一个 id,另一个为空。

另外,SDK 序列化时把空字符串字段也发了出去。

动手时先把流式 chunk 里的 tool_calls 原样落盘

python -c "import json;[print(json.loads(l)['choices'][0]['delta'].get('tool_calls')) for l in open('chunks.jsonl')]"

本地补一个稳定 id,并在回填时复用

python -c "import uuid;tid='call_'+uuid.uuid4().hex[:8];print(tid)"

接着按 index 排序后再回填,保证顺序一致

python -c "import json;d=json.load(open('msg.json'));d['tool_calls'].sort(key=lambda x:x.get('index',0));print(d)"

末了用一次两工具调用的用例回归

python tool_regression.py --case two_tools --model qwen-plus

别只看报错消失,验证要多做一步。回填后返回 200,两条 tool 消息都能对上各自的调用,模型在下一轮正确引用两个结果。

兼容层和原生接口的字段完整度经常不一样,空 id 要在客户端补,不能指望服务端每次都返回。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189