这个坑值得记一笔。把函数返回的 dict 直接塞进 role 为 tool 的 content,接口返回 400,原文 Invalid type for 'messages[3].content',同样的 dict 放在 user 消息里却能通过。
刚开始盯着「函数返回值里有 datetime,序列化后仍不是字符串」,折腾了半个多小时,排掉了。
真正麻烦的地方在于,几个原因的现象几乎一样。tool 消息的 content 只接受字符串,不接受对象;SDK 的类型标注没拦住,运行时才由服务端报错;并发拼装时把整个 result 对象塞了进去。
第一件事是用 json.dumps 转成字符串再回填
python -c "import json;r={'temp':23};print(json.dumps(r,ensure_ascii=False))"
然后打印消息数组确认每个 content 的类型
python -c "import json;m=[{'role':'tool','content':{'a':1}}];print([type(x['content']).__name__ for x in m])"
接着在封装层统一做一次序列化
python -c "print('def to_tool_msg(r): return {\"role\":\"tool\",\"content\":json.dumps(r,ensure_ascii=False)}')"
等前面都确认过,再用 curl 发一条最小 tool 消息验证
curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $OPENAI_API_KEY" -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"},{"role":"assistant","content":null,"tool_calls":[{"id":"c1","type":"function","function":{"name":"f","arguments":"{}"}}]},{"role":"tool","tool_call_id":"c1","content":"{\"temp\":23}"}]}'
中途换过一次思路,把范围缩到最小的那一层,反而快了。
改完先别撤,确认这几项。带工具结果的请求返回 200,content 类型为 str,模型能基于工具结果继续回答。
补一句。工具结果最好序列化后原样回填,不要顺手包一层再交给模型,格式错一次后面全乱。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。
