OpenAI 兼容的 /chat/completions 接口拆解:哪些保持不变,哪些在聚合站上有所变化
本文逐字段拆解通过聚合站发出的 OpenAI 兼容 /chat/completions 调用,说明哪些 HTTP 与 JSON 结构保持不变,哪些字段在聚合站上可能受路由影响——包括 model 解析、错误体、流式行为、计费与重试差异。读者可据此在写客户端代码前明确哪些可以信任,哪些需做防御性处理。
为什么需要逐字段梳理
聚合站对外提供的是 OpenAI 兼容的 /chat/completions 接口,这意味着大多数现有 SDK 和示例代码可以几乎不改地跑起来。但「兼容」指的是请求与响应的结构兼容,而不是行为完全等价。聚合站要在多个上游厂商之间路由请求,这一层会带来一些可观察的差异。写客户端前先分清哪些字段可以照抄直连写法、哪些需要加防御,能省掉不少调试时间。
保持不变的:HTTP 层与消息结构
以下部分与直连厂商 API 基本一致,可以放心沿用现有代码:
- HTTP 方法、路径与鉴权头:POST 到
/chat/completions(具体前缀以文档为准),使用Authorization: Bearer <key>。 - 请求体的消息数组:
messages中role与content的组合方式不变,system/user/assistant的语义保持一致。 - 采样参数:
temperature、topp、maxtokens、stop、presencepenalty、frequencypenalty等字段名与类型不变。 - 响应外层结构:
id、object、created、choices、usage这些顶层字段仍在,choices[].message与choices[].delta的结构不变。 - 流式传输形态:
stream: true时返回data:前缀的 SSE 行,以data: [DONE]结束,与 OpenAI 的流式约定一致。
换句话说,如果你的客户端只依赖「发消息、拿回复文本」这条主路径,迁移成本接近于零。差异集中在下面这些地方。
有变化的:model 字段的解析语义
直连时 model 是一个厂商内的确定标识;在聚合站上,它同时承担选择上游的作用。
- 同一个
model字符串可能对应多个可用的上游通道,聚合站按可用性与路由策略选择其中一个。 - 不同厂商对模型名的命名习惯不同,聚合站通常会做名称映射,因此你写下的名称未必与上游厂商文档逐字相同。
- 不要在客户端硬编码「这个 model 一定来自某一家厂商」的假设。如果业务逻辑依赖特定厂商的独有特性,需要在文档里确认该名称的映射关系。
建议把 model 名称当作配置项而非常量,方便后续调整。
有变化的:错误体与状态码
直连时,错误格式由单一厂商定义;聚合站上,错误可能来自三个层次:
- 聚合站自身(鉴权失败、余额不足、参数校验失败)。
- 路由层(无可用通道、上游超时)。
- 上游厂商透传的错误。
外层通常仍是 OpenAI 风格的 error 对象,但 error.type、error.code 的取值集合会比单一厂商更杂。客户端不要把错误处理写成只识别某几个固定字符串,而应:
- 以 HTTP 状态码做第一层分支(如 4xx 与 5xx 的区别)。
- 对
error.message只做展示,不做逻辑判断。 - 对「可重试」与「不可重试」按状态码归类,而不是按错误文案。
有变化的:流式响应的细节
SSE 的整体形态不变,但聚合站可能在上游切换时影响流的行为:
- 首个 chunk 的到达时间取决于路由与上游排队,波动可能比直连更大,客户端超时要留余量。
- 某些字段(如
usage)在流式模式下是否返回、在哪个 chunk 返回,可能因上游而异。需要精确用量时,优先在非流式调用中读取usage。 finish_reason的取值以 OpenAI 约定为主,但遇到异常中断时应把它当作「可能缺失」来处理,而不是假定每条流都会正常收尾。
有变化的:usage 与计费口径
聚合站的 usage 字段仍反映 token 消耗,但它与账单之间的关系和直连不同:
- 扣费按「官方价 ×1.3」计算,因此
usage里的 token 数是计费依据,但金额换算系数不是 1。 - 不同上游的 token 计数方式可能存在细微差异(例如分词器不同),
usage的数值以实际返回为准。 - 如果对账要求精确,应以聚合站提供的账单/用量记录为准,而不是只靠客户端累加
usage。
不要根据 usage 自行推算「省了多少钱」——聚合站的定价模型是固定的倍率关系,不是折扣关系。
有变化的:重试与幂等
聚合站内部可能对失败的上游请求做重试或切换通道,这对客户端意味着:
- 同一请求在极少数情况下可能被上游执行多次,若你的业务对重复执行敏感(例如把模型输出直接写入外部系统),应在应用层加幂等键或去重逻辑。
- 客户端自身的重试策略应避免与聚合站的重试叠加,建议对 5xx 采用有限次、带退避的重试;对 4xx(尤其是鉴权与参数错误)不重试。
可以信任的部分与需要防御的部分
可以信任:消息结构、采样参数名、响应外层结构、SSE 基本形态、Bearer 鉴权方式。这些足以支撑一个标准聊天客户端。
需要防御:model 名的厂商归属假设、错误码字符串的固定匹配、流式 usage 的必然出现、finish_reason 的必然存在、以及基于 usage 的金额推算。
把这些点在设计阶段标出来,客户端代码就能在聚合站与直连之间平滑切换,而不必等到线上报错才回头改解析逻辑。