报错是这么冒出来的。用 gpt-4o 从商品图里抽属性,prompt 要求返回 JSON,实际拿到的字符串以 ```json 开头、以 ``` 结尾,json.loads 直接抛 Expecting value,灰度到新版本模型后这种情况从 2% 涨到 30%。

中间走过弯路,把「max_tokens 设得过小,JSON 在中间被截断」当成主因,改完现象没变。

这类报错背后常见的原因有几处,逐个排除就行。模型把结构化结果包进 markdown 围栏。另一种情况是response_format 没开,约束只停留在提示词层。

少数情况下是提示词里混了示例文本,模型照抄了围栏写法。

上来先打印原始返回内容看前缀

python -c "import json;d=json.load(open('resp.json'));print(repr(d['choices'][0]['message']['content'][:60]))"

解析前剥掉围栏并定位首个花括号

python -c "import re,json;s=open('raw.txt',encoding='utf-8').read();s=re.sub(r'^```(?:json)?|```$','',s.strip());print(json.loads(s[s.find('{'):s.rfind('}')+1]))"

再往下改走 response_format 的 json_schema 强约束

curl https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"model":"gpt-4o","response_format":{"type":"json_object"},"messages":[{"role":"system","content":"只输出 json 对象"},{"role":"user","content":"抽取图中的品牌与型号"}]}'

等前面都确认过,再把解析失败样本落盘做回归样本

python collect_fail.py --raw raw.txt --out fails.jsonl

恢复的判定其实很直接。连续 500 次请求里解析成功率为 100%,失败样本目录不再新增,前端不再因为 Unexpected token 报错白屏。

容易忽略的是,多模态输出的格式漂移比纯文本更常见,提示词约束只是兜底,真正稳的是结构化输出参数加一层容错解析。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。

作者 9823189