这类问题我又碰上了一次。curl 调同一个地址返回 200 和正常 JSON,python 客户端却抛 JSONDecodeError,日志里响应体开头是空行加 ,出现在接入自建网关之后。
第一反应是「SDK 版本较新,遇到非 JSON 会直接抛而不是返回原文」,验证下来不成立。
排查方向其实有限,先把可能性排一排。网关在响应前多输出了一段空白或调试信息;响应头 content-type 写成了 text/html,客户端按错误分支处理;开启流式时返回了压缩编码,客户端未解压。
第一件事是用 -i 看响应头和响应体前几个字节
curl -sS -i https://gw.example.com/v1/chat/completions -H 'content-type: application/json' -d @p.json | head -c 400
然后确认是否被 gzip 压缩但未解压
curl -sS --compressed -o /dev/null -w '%{size_download}\n' https://gw.example.com/v1/models
随后对比直连与走网关的响应头差异
curl -sS -D - -o /dev/null https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"
等前面都确认过,再在客户端打印原始响应文本
python -c "import httpx;r=httpx.post('https://gw.example.com/v1/chat/completions',json={'model':'gpt-4o-mini','messages':[{'role':'user','content':'hi'}]});print(repr(r.text[:120]))"
恢复的判定其实很直接。SDK 能正常反序列化并返回对象,响应体首个字符是左花括号,content-type 为 application/json。
提醒一点。遇到解析失败先看原始字节,SDK 的异常信息往往把网关插入的 HTML 错误页藏起来,直连对比最快。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。
