拆.
协议与适配——AI 怎么对接不同 LLM · 第 03

OpenAI 的协议

reasoning、tool_calls——最小公分母

4500读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:OpenAI Chat API 的设计

OpenAI 是 LLM API 的事实起点。2020 年的 GPT-3 API、2022 年的 ChatGPT API、2023 年的 Chat Completions API——OpenAI 一路定义了"LLM API 长什么样"。

后来的 Anthropic、Google、Meta 都参考过 OpenAI 的设计。OpenAI Chat Completions API 因此成为业界最小公分母——所有第三方"OpenAI 兼容 API"(Llama、Mistral、各种开源模型)都模仿这个格式。

我们看 OpenAI Chat API 的核心结构。

Request body

{
  "model": "gpt-4",
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Write a Python function..."}
  ],
  "max_tokens": 1024,
  "temperature": 0.7,
  "stream": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get weather for a city",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {"type": "string"}
          }
        }
      }
    }
  ]
}

跟 Anthropic 对比几个差别:

差别 1:system 是 message 不是字段

OpenAI 把 system prompt 作为 messages 数组的第一条 message(role: "system"),而不是独立字段。

Anthropic 是独立 system 字段。Google 是 systemInstruction 字段。

OpenAI 的做法更简单——messages 数组统一处理。但缺点是 system 不能独立做 cache_control。

差别 2:tools 嵌套在 function 字段

OpenAI 的 tool 描述结构:

{ type: "function", function: { name, description, parameters } }

外层 type: "function"——多此一举。Anthropic 直接 { name, description, input_schema } 更简洁。

OpenAI 这种"type + 嵌套"是为了未来扩展——理论上未来可以加 type: "code_interpreter" 等。但实际上没怎么扩展——徒增复杂度。

差别 3:tool_calls 是数组

OpenAI 的 response 把 tool calls 放在专门的 tool_calls 字段:

{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc",
      "type": "function",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\":\"Shanghai\"}"
      }
    }
  ]
}

注意 arguments字符串而不是对象——里面是 JSON 字符串(双重 encode)。客户端要 JSON.parse() 一次才能用。

这是个 OpenAI 的 quirk——其他 provider 直接给 JSON 对象。

差别 4:tool result 是独立 role

OpenAI 的 tool result 用 role: "tool"

{
  "role": "tool",
  "tool_call_id": "call_abc",
  "content": "Weather in Shanghai: sunny, 25°C"
}

Anthropic 是 role: "user" 里嵌 tool_result content block。

OpenAI 的做法语义更清晰——tool 是独立 role。

差别 5:缺 prompt caching

OpenAI Chat Completions API 没有类似 Anthropic 的 cache_control 机制。

OpenAI 在 2024 年底加了"automatic caching"——LLM 服务自动识别重复 prompt 部分给折扣。但用户没法显式标记——全靠 OpenAI 服务端识别。

这导致缓存命中率不可预测——你不知道你的 prompt 哪部分被缓存了、能省多少。

这是 OpenAI Chat API 的主要缺陷——caching 不可控。

差别 6:缺 thinking

OpenAI 的 GPT-4 / GPT-4o 没有显式的 thinking 字段。LLM 的推理过程不暴露。

只有 o1 系列模型有 "reasoning"——但 API 层面reasoning content 不返回给用户,只在内部消耗 token。

这导致两个问题:

  • 用户看不到 o1 的推理过程——可解释性差
  • reasoning 消耗的 token 用户付钱但看不到——成本不透明

这是 OpenAI 的产品判断——他们认为 "reasoning 是模型内部的事,用户不需要看"。Anthropic 的判断相反——thinking 是用户体验的一部分。

这两种判断都有道理。但对 AI Agent 产品来说——能看到 reasoning 更可控、更可调试

把这些差别合起来——OpenAI Chat API 是 LLM API 的最小公分母

  • 结构简单——messages 数组统一处理
  • 功能基础——core 能力都有但没有 advanced 特性
  • 跨实现广泛——所有 OpenAI 兼容 API 都按这个格式

功能受限

  • caching 不可控
  • thinking 不可见
  • tools 结构 verbose
  • arguments 双重 encode

我们看 opencode 怎么处理。

二、案例:opencode openai-chat.ts(最小公分母)

R6-02 阶段的研究告诉我们——opencode 的 openai-chat.ts adapter 比 anthropic-messages.ts 短得多——493 行 vs 845 行。

这是因为 OpenAI Chat API 功能少——adapter 不需要那么多代码处理 advanced 特性。

我们看几个 opencode 在 OpenAI 上的实现细节。

细节 1:system 作为第一条 message

opencode 内部的 system 数组(5 张脸 + AGENTS.md + 环境)在发给 OpenAI 时拼成一个字符串,作为 messages[0]:

function adaptForOpenAI(internalRequest) {
  const systemContent = internalRequest.system
    .map(s => s.text)
    .join("\n\n")
  
  return {
    messages: [
      { role: "system", content: systemContent },
      ...internalRequest.messages
    ],
    tools: internalRequest.tools.map(adaptTool),
    // ...
  }
}

这种"3 段拼成一段"丢了细粒度——不能做 cache_control(OpenAI 也不支持)。

但简单——OpenAI 协议就这么设计的。

细节 2:tools schema 适配

OpenAI 的 tool 结构需要 type: "function" 嵌套:

function adaptTool(internalTool) {
  return {
    type: "function",
    function: {
      name: internalTool.name,
      description: internalTool.description,
      parameters: internalTool.input_schema  // 字段名不同
    }
  }
}

注意——内部用 input_schema,OpenAI 用 parameters——字段名要改。

细节 3:tool_calls 解析

response 里的 tool_calls 数组要解析:

for (const toolCall of response.tool_calls) {
  const args = JSON.parse(toolCall.function.arguments)  // 双重 encode
  await executeToolCall({
    id: toolCall.id,
    name: toolCall.function.name,
    input: args
  })
}

注意 JSON.parse() 那一行——OpenAI 的 arguments 是字符串,要 parse 才能用。

如果忘了 parse——下游代码会以为参数是字符串、出错。

这种小 quirk 是 adapter 层要处理的。

细节 4:tool result 用 tool role

function adaptToolResult(internal) {
  return {
    role: "tool",
    tool_call_id: internal.tool_use_id,  // 字段名转换
    content: internal.content
  }
}

字段名又不一样——tool_use_id 内部用,tool_call_id OpenAI 用。

每个 provider 字段名都有自己习惯——adapter 层做翻译。

细节 5:stream event 解析

OpenAI 的 stream 跟 Anthropic 不一样——更简单但表达力弱:

data: {"choices":[{"delta":{"content":"def "}}]}
data: {"choices":[{"delta":{"content":"reverse"}}]}
data: {"choices":[{"delta":{"tool_calls":[{"id":"call_x","function":{"name":"read","arguments":"{\""}}]}}]}

每个 chunk 是 choices[0].delta——可能是 content 增量、tool_calls 增量。

stream 解析逻辑:

  • 累积 delta.content——text 部分
  • 累积 delta.tool_calls[i].function.arguments——tool 参数(同样是字符串,需要 parse)
  • 看到 finish_reason 知道结束

opencode 的 openai-chat.ts 里这部分逻辑大概 100 行。

细节 6:缺失的能力 fallback

OpenAI 没有 cache_control——opencode 不发这个字段、不期待缓存收益。

OpenAI 没有 thinking——opencode 在 UI 上不显示 thinking section(OpenAI 模型的 session 没这个)。

这些"能力缺失"在 adapter 层 graceful degradation——不报错、不假装能用。

三、设计启示:"最小公分母"是优势还是限制

这一章的核心论点:OpenAI Chat API 是 LLM 协议的最小公分母——简洁但能力受限

如果你做 AI 产品考虑 OpenAI API,下面几条原则有用:

1. OpenAI 适合"广覆盖"场景

如果你的产品要支持很多 LLM——OpenAI 兼容格式覆盖最广(GPT、Llama、Mistral、各种开源模型)。

用 OpenAI 协议作为内部标准——adapter 简单。

2. OpenAI 不适合"精细控制"场景

如果你的产品要 prompt caching、要 thinking、要细粒度 tool 行为——OpenAI Chat API 力不从心。

考虑用 Anthropic 协议作为参考、其他 provider 做 adapter——能力更强。

3. 注意 arguments 双重 encode

OpenAI 的 tool arguments 是字符串——JSON 内部 escape。

不要忘了 JSON.parse()——常见 bug 来源。

4. system 拼接丢细粒度

OpenAI 的 system 是单一字符串——你内部的多层 system prompt 拼接时会丢结构。

如果你需要细粒度(比如 caching)——OpenAI 上没法做。考虑跨 provider 时的 trade-off。

5. stream parsing 跟 Anthropic 完全不同

不要假设"stream 解析逻辑通用"——每个 provider 不一样。

OpenAI 的 stream 简单但 verbose,Anthropic 的 stream 丰富但复杂。adapter 层各写各的。

6. 善用 OpenAI 兼容性

很多服务(Azure OpenAI、各种代理、各种第三方)提供"OpenAI 兼容 API"——格式相同、endpoint 不同。

你的 OpenAI adapter 几乎能直接用——只换 endpoint。

这是 OpenAI 协议最大的实际价值——生态广。

7. 跟 GPT-4 比 Claude 不只是模型差异

很多团队觉得"GPT-4 vs Claude 是模型能力对比"——但API 协议差异同样影响产品质量

caching 差异 → 成本差几倍 tool calling 差异 → 错误率不一样 thinking 差异 → 可解释性不一样

整体 ROI 是模型 + API 综合。

8. 监控 OpenAI 的协议变化

OpenAI 偶尔会出新 API(Responses API、Assistants API 等)——比 Chat Completions 功能更多。

绝大多数生产产品仍在 Chat Completions——稳定、跨实现广、文档全。

不要盲目跟新——评估新 API 的成熟度。

最后一个观察。OpenAI Chat API 作为"最小公分母"——是个双刃剑

优势:

  • 简单——好 implement
  • 跨实现广——所有 LLM 都"兼容"
  • 文档丰富——开发者熟悉

限制:

  • 功能少——advanced 能力 limit
  • 控制粗——细粒度优化做不了

你的产品选 OpenAI 协议作为内部标准——还是选 Anthropic 协议作为内部标准——是个根本架构判断。

opencode 的选择是——内部用统一抽象——既不绑 OpenAI 也不绑 Anthropic,每个 provider 都有自己的 adapter。这种"双向适配"代码量大,但灵活性最高。

下一章 6.4 我们看 Gemini 的协议——它有完全不同的设计思路,跟 Anthropic / OpenAI 都不像。