问题出现得很突然,日志倒是留得挺全。同一份参数用 openai 1.51 的客户端调用正常,改成手写 curl 立刻 400,错误说 messages 字段不合法,抓包对比发现两边请求体差别不小。

刚开始盯着「SDK 把 extra_body 合并进顶层,手写时层级写错」,折腾了半个多小时,排掉了。

把日志和配置对完,能解释这个现象的基本就这几处。SDK 会把参数补默认值,手写时漏了必填字段。另一种情况是SDK 自动带上组织与项目头,缺失时被判定为另一个上下文。

不容易想到的是,字符集与编码头不同,中文请求体被解析成乱码。

先抓一次 SDK 实际发出的请求体

python -c "import httpx,openai;c=openai.OpenAI(http_client=httpx.Client(event_hooks={'request':[lambda r:print(r.content[:500])]}));c.chat.completions.create(model='gpt-4o-mini',messages=[{'role':'user','content':'hi'}],max_tokens=8)"

接着把请求体落盘后直接回放

curl -sS https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $OPENAI_API_KEY" -H 'content-type: application/json' --data-binary @body.json

再往下逐字段比对两边差异

python -c "import json;a=json.load(open('sdk.json'));b=json.load(open('curl.json'));print(set(a)-set(b),set(b)-set(a))"

等前面都确认过,再确认请求头里的编码声明

curl -sS -v https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY" 2>&1 | grep -i content-type

别只看报错消失,验证要多做一步。回放 SDK 的请求体得到 200,手写请求补齐缺失字段后结果一致,两边返回的响应结构相同。

提醒一点。SDK 不是简单的序列化器,它带默认值、重试和头部处理,排查兼容问题以抓包为准而不是读源码猜。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189