博客

“OpenAI-compatible”到底覆盖了什么:一份集成清单

Compatible 不是一个开关。这份清单把这一声明拆成三个值得分别验证的层面:协议面、功能面和行为面,并给出八项可以对着任何 OpenAI-compatible 端点执行的检查。

“OpenAI-compatible”到底覆盖了什么:一份集成清单

“OpenAI-compatible”不是一个服务有或没有的开关。它是对一组行为的声明,而这些行为分布在三个独立的层面:协议面,请求与响应的结构以及认证必须对齐;功能面,streaming、tool calling、结构化输出和输入模态各自有各自的结论;行为面,错误语义、限流和按模型区分的参数支持,决定一个集成能否经受生产环境的考验。Compatible 也不等于 equivalent:两个端点都可以配得上这个标签,却在你的应用所依赖的细节上并不相同。

本文逐层给出验证方法,然后把所有内容压缩成一份八项清单,你可以在一个下午内对任何 OpenAI-compatible 端点跑完,包括 https://www.string.ink/v1,如果那正是你的目标。

第 1 层:OpenAI-compatible 的协议面

OpenAI-compatible API 的最小公共面很小:一个以 /v1 结尾的 base URL、Authorization 头里的 Bearer token 认证、用于对话的 POST /v1/chat/completions,以及用于发现的 GET /v1/models。如果你的客户端能说这四件事,它就能和这个端点开启对话,而这正是大多数“compatible”声明真正指的层面。

值得内化的一点是:Chat Completions 和 Responses API 是两种不同的协议形态,而不是同一件事的两个名字。Chat Completions 形态发送一个 messages 数组,并用 choices 数组作答,多数第三方端点把它作为公共面来实现。Responses 形态使用另一套请求与事件词汇。OpenAI 的迁移指南指出两者在 function calling 形态上不同,结构化输出在 Responses 形态里用的是 text.format 而不是 response_format,同时也明确写道:“While Chat Completions remains supported, Responses is recommended for all new projects.” 两种形态都是当前形态;它们只是在线上协议层面不能互换。

对集成工作来说,这产生一条规则:让客户端发送的形态与端点提供的形态相匹配。这个选择往往在客户端侧。例如 Codex CLI 在其 provider 配置里通过 wire_api 设置把它显式化。所以评估一个端点时,先写下你的客户端说的是哪种形态,然后再验证端点是否按那种形态作答。String AI 的平台文档直接描述了它的兼容面:base URL 固定为 https://www.string.ink/v1,认证遵循 OpenAI 的 Bearer 约定,接口列表覆盖 chat completions、文本与视觉输入、图像生成与编辑,以及模型列表。

第 2 层:功能面,逐项来看

协议一致只是入场券。差异是在功能上累积的,每一项都要单独测试,而不是一概假设。

  • 模型列表。调用 GET /v1/models,确认响应带有 object "list" 和一个 data 数组。你也在这里复制要用的 model ID;文档里的示例(例如 gpt-5.6-luna 和 gpt-6-astra)只是方向指引,实时列表才是事实来源。文档建议先用更轻的 gpt-5.6-luna 验证链路,再切换到其他模型。
  • 最小 chat completion。发送一条 user 消息,确认响应在 Chat Completions 形态该放的位置包含 assistant 的文本。这端到端验证了请求路径,仅此而已。
  • Streaming。打开 stream,读 Server-Sent Events 序列:每个 chunk 到达时 object 为 "chat.completion.chunk",增量文本出现在 choices[].delta.content 下,最后通常有一个统计 chunk 带 usage,流以 [DONE] 标记结束。绝不要因为非 streaming 能用就假设 streaming 也能用;事件序列是另一份实现。
  • Tool calling。传入 tools 和 tool_choice,确认模型以预期形态返回一个调用,然后把工具结果发回并拿到后续回答,完成整个往返。只发一次、从不回环的调用会掩盖最常见的 tool calling 缺陷。
  • 结构化输出。请求一个特定的 JSON 形态,并多跑几次。问题不是文本能不能解析一次;问题是形态在多次运行和不同输入下是否稳定。
  • 视觉输入。在消息里放一张图,确认模型真的处理了它。文本和视觉输入在这个端点上是一起走的,但这并不能说明它背后的每个模型都行;要按模型逐个验证。
  • 图像生成与编辑。这些是独立的端点,不是 chat 功能。如果你的方案涉及生成或编辑图像,就单独测这些路由;chat 端点做不了这件事。

每一条都是一个勾选框,在你亲眼看到行为之前,框保持未勾选。每项两分钟,换来一份真正属于你的兼容性说明。

第 3 层:行为面

行为层在文档示例里看不见,在生产里却是决定性的。把它当成一组以症状为线索的探测来读。

错误形态。401、403、404、model-not-found 和 429 的含义各不相同,客户端常常需要区分它们才能正确反应。故意触发每一种:错误的 key、畸形的路径、错误的 model ID,以及足够的量来看是否出现限流响应。记录每一项的状态码和响应体形态。如果你的客户端的重试逻辑依赖错误类型,这份记录就是它运行的依据。

限流与并发语义。限流是实现行为,不是协议。弄清楚端点如何表示限流,以及它期望你的客户端接下来做什么,然后据此设置并发和退避。结果因厂商而异,也可能因 key 而异;把你自己的测量当作规范。

参数支持随模型而异。temperature 类控制、与 reasoning 相关的参数,以及模态选项,在一个端点服务的每个模型上并不统一。旗舰模型接受的参数,可能在同一 base URL 的其他模型上被拒绝。绝不要假设参数是整个端点通用的;按模型设门槛,并针对你实际调用的每个模型验证。

这一层的主线是:当声明与观察冲突时,观察优先。写下你看到了什么、在哪个模型上、在哪个请求下,因为下一个调试这个集成的人正需要这些上下文。

API 兼容性清单

用这八项去跑你正在评估的端点。每一项都产出可记录的产物,而不是一种感觉。

  1. 用 GET /v1/models 列出模型,并把当前的 model ID 复制进测试配置。如果这是你第一次调用,五步快速上手详细覆盖了这一步和下一步。
  2. 发送一个最小 chat completion,只带一条 user 消息,并保存原始响应。
  3. 打开 stream 重跑一次,从第一个 chunk 走到终止标记。
  4. 执行一次完整的 tool calling 往返:调用、结果、后续回答。
  5. 在请求里放一张图,确认模型读到了它。
  6. 请求严格的结构化输出,并确认形态在多次运行中保持成立。
  7. 故意注入三种错误(错误的 key、错误的路径、错误的 model ID),记录每一种返回什么。
  8. 记一条基线:每次检查的原始响应加时间戳,再加上观察到的响应时间,方便你以后重跑清单并对比。方法比这一次的数字更重要;留下产物,而不是印象。

任何端点或客户端升级之后,以及切换 model ID 之后,都重跑整份清单。

compatible 被误读的三种方式

1. “它返回了 200,所以它是兼容的。”一个最小 chat 请求返回 200,只证明了协议层,别的什么都没证明。streaming、tool calling、结构化输出和各类模态各自独立失败,各自需要单独通过。

2. “这个模型接受了这个参数,所以端点支持它。”参数支持是按模型而不是按端点。在你自己的检查说出别的结论之前,把每个参数都当成模型特有的。

3. “示例用的是一种形态,我的客户端用的是另一种。”文档和示例同时存在 Chat Completions 和 Responses 两种形态。把 text.format 片段复制进 Chat Completions 客户端,或者反过来,产生的错误看起来像不兼容,其实是形态错配。让示例与你客户端真正使用的协议对齐。

当兼容还不够时

一份诚实的兼容性说明比标签暗示的范围更窄,只要所有人都知道这个宽度就没问题。你记录的结论是某一个端点实现加某一个客户端版本的属性;任何一方升级都可能改变它,所以上面的清单是为了反复重跑而设计的,而不是用来信任的。当某项需求落在已验证子集之外时,有意识地决定怎么办:改造客户端、在同一端点上换一个模型,或者围绕这个限制重新设计。

让实时引用保持实时。任何平台的能力都在向前走,平台文档里的接口列表是当前的地图;而你的清单是你确认地形的方式。还有一个可以从运维借来的节奏:model ID 会变老,退役终会到来,所以也要规划在模型变化后重跑这套流程。弃用应对手册直接覆盖了这个生命周期。

在功能列表之上是线上形态本身:Chat Completions 与 Responses 在端点、输出和状态处理上都不同,API 形态指南展示了如何测试一个端点真正实现的是哪一种。

相关阅读