OpenAI 的协议
reasoning、tool_calls——最小公分母
一、原理: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 都不像。