探测 /chat/completions 的 schema 一致性:六个后端响应的逐字段对照
通过向聚合站支持的六个后端发送同一个最小请求,逐字段对比 /chat/completions 响应结构,分析 id、usage 子字段、finish_reason 取值、system_fingerprint 等字段的共性、差异与稳定性,帮助开发者识别哪些字段可以安全依赖,并给出解析器设计建议。
调用多个模型时,开发者常假设所有 /chat/completions 响应结构一致。但不同厂商实现有细微差别,尤其经聚合站转发时。本文用同一个最小请求探测六个后端,逐字段对比,帮你判断解析器能安全依赖哪些字段。
探测方法
准备一个最小请求体,只含必需参数:
{
"model": "<目标模型>",
"messages": [
{"role": "user", "content": "hi"}
]
}
向聚合站的 /v1/chat/completions 端点分别发送六次,每次将 model 替换为对应后端标识(Claude、GPT、DeepSeek、Qwen、GLM、Kimi 的模型名)。记录原始 JSON 响应,按字段逐项比对。建议关闭流式,先看非流式响应。
id 字段
所有六个后端都返回顶层 id 字符串,但格式不同:有的带厂商前缀,有的是 UUID 风格,有的含请求类型标记。
结论:id 可用于日志追踪和请求去重,但不要解析其内部结构。把它当作不透明字符串处理。
usage 子字段
usage 对象是差异最大的部分。
- 共同字段:
prompttokens、completiontokens、total_tokens在六个响应中都存在,数值类型为整数。 - 差异字段:部分后端额外返回
prompttokensdetails、completiontokensdetails,内部包含cachedtokens、reasoningtokens等子字段;有的后端只返回部分细节。 - 缺失情况:某些后端在特定请求下可能省略
usage对象,但最小请求下均出现。
结论:三个基础 token 字段可安全依赖;细节子字段按需可选读取,解析代码应做空值检查。
finish_reason 取值
choices[].finish_reason 是枚举字符串。六个后端在正常结束时均返回 "stop",但非正常结束的取值不完全一致:
- 长度截断:多数返回
"length",个别返回其他标记,或通过finish_reason之外的字段提示。 - 内容过滤:部分后端返回
"content_filter",部分返回"stop"并在内容中体现。 - 工具调用:当模型触发工具调用时,部分后端返回
"toolcalls",旧版字段可能用"functioncall"。
结论:"stop" 最稳定;其他取值需按后端特性做兼容,或依赖更通用的判断(如检查 choices[].message 内容是否为空)。
system_fingerprint
system_fingerprint 用于标识后端配置版本,并非所有后端都返回。探测中部分后端有该字段,部分完全没有。
结论:将其视为可选字段。适合用于问题排查或缓存键的附加维度,但不能作为必选依赖。
其余字段的稳定性
object:均为"chat.completion",可依赖。created:均为 Unix 时间戳整数,可依赖。model:返回的模型名可能与请求名不完全一致(如带日期后缀),不要用于精确匹配。choices[].index:均为整数且从 0 开始,可依赖。choices[].message.role:均为"assistant",可依赖。choices[].message.content:均为字符串,但内容格式(是否含 markdown、是否为空)因模型而异。
解析器设计建议
基于以上对比,建议按以下优先级设计解析器:
- 强依赖:
object、created、choices[].index、choices[].message.role、choices[].message.content、usage.prompttokens、usage.completiontokens、usage.total_tokens。 - 弱依赖:
id、model、choices[].finish_reason(仅"stop"可靠)、usage细节子字段。 - 可选读取:
systemfingerprint、prompttokensdetails、completiontokens_details。 - 避免依赖:
id的内部格式、model的精确值、finish_reason的非stop取值。
实际编码时,使用带默认值的字段访问,对嵌套对象逐层判空,并对 finish_reason 做白名单或降级处理。这样即使某个后端返回结构有细微差异,解析器也不会崩溃。
测试与回归
聚合站可能随上游更新调整响应结构。建议将上述最小请求写成冒烟测试,定期运行,比对字段存在性和类型,及时发现破坏性变更。测试断言应聚焦于强依赖字段,弱依赖字段只记录不阻断。