事情本身不复杂,线索都在日志里。gpt-4o 一次并行调 get_price 三次,返回的 tool_calls 里三个 id 与参数顺序被打乱,代码按列表下标回填结果,最终答案把两个城市的报价说反了。
第一反应是「用 asyncio.gather 并发执行后结果顺序与提交顺序不一致」,验证下来不成立。
真正麻烦的地方在于,几个原因的现象几乎一样。tool_calls 数组顺序不代表执行顺序,id 才是唯一凭据。少数情况下是回填必须一条 tool 对应一个 tool_call_id,不能合并成一条。
还有一种情况,部分框架只回填了最后一个结果,前两个缺消息。
动手时先把返回的 id 与参数逐个配对打印
python -c "import json;d=json.load(open('tool_resp.json'));print([(t['id'],t['function']['arguments']) for t in d['choices'][0]['message']['tool_calls']])"
并发执行后按 id 建映射再回填
python -c "import json;d=json.load(open('tool_resp.json'));print({t['id']:t['function']['name'] for t in d['choices'][0]['message']['tool_calls']})"
随后按 id 逐条追加 tool 消息
python -c "print([{'role':'tool','tool_call_id':k,'content':v} for k,v in {'call_a':'128','call_b':'96'}.items()])"
等前面都确认过,再核对历史里 tool 条数与 tool_calls 条数
python -c "import json;m=json.load(open('messages.json'));print(sum(1 for x in m if x.get('role')=='tool'),sum(1 for x in m if x.get('tool_calls')))"
改完先别撤,确认这几项。回填的 tool 消息条数与 tool_calls 条数相等且 id 一一对应,模型复述城市与价格时不再错位。
并行工具调用不要用列表下标对齐结果,id 是服务端唯一认的关联键,下标只在单次单调用时偶然正确。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。
