openai-compatible-endpoint-basics · ZH · 2026-11-05

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 名称当作配置项而非常量,方便后续调整。

有变化的:错误体与状态码

直连时,错误格式由单一厂商定义;聚合站上,错误可能来自三个层次:

  1. 聚合站自身(鉴权失败、余额不足、参数校验失败)。
  2. 路由层(无可用通道、上游超时)。
  3. 上游厂商透传的错误。

外层通常仍是 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 的金额推算。

把这些点在设计阶段标出来,客户端代码就能在聚合站与直连之间平滑切换,而不必等到线上报错才回头改解析逻辑。