从 Chat 到 Responses:OpenAI API 抽象为什么变了?

1. 背景

早期 LLM API 的核心任务是"给一段上下文,生成一段回复"。因此 Chat Completions 采用了非常直接的抽象:客户端传入 messages[],模型返回一条 assistant message。这一设计适合聊天、问答、摘要、改写、简单结构化输出等场景,也成为大量 OpenAI-compatible API 的事实标准。

但模型能力已经从"文本生成器"演进为"可调用工具的执行单元"。一次模型请求可能包含多轮推理、工具调用、文件检索、网页搜索、代码执行、图片生成、后台任务、结构化输出和流式事件。此时,把所有能力继续塞进 assistant message 会使接口语义变得混乱:message 既是文本回复,又承载工具调用、工具结果、运行状态和执行轨迹。

OpenAI Responses API 的出现,代表抽象从 chat completion 转向 model run / response。Claude Messages API 则走了另一条路线:仍保留 messages[] -> assistant message 的顶层模型,但通过 typed content blocks 表达工具调用、多模态和结构化内容。

2. Chat Completions 的 message-centric 抽象

Chat Completions 的核心模型是:

text 复制代码
messages[] -> assistant message

典型请求:

json 复制代码
{
  "model": "gpt-4.1",
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "解释一下 TCP 三次握手"}
  ]
}

典型响应:

json 复制代码
{
  "id": "chatcmpl_xxx",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "TCP 三次握手是..."
      }
    }
  ],
  "usage": {
    "prompt_tokens": 32,
    "completion_tokens": 300,
    "total_tokens": 332
  }
}

这个模型的优点是简单、稳定、容易代理。后端只需要维护一个 messages[] 数组,每轮把历史消息拼进去,再把 assistant message 追加到历史中。对兼容层、简单业务和多模型路由来说,这仍然是最小公分母。

但 Chat Completions 的主要限制也来自这个抽象本身:它默认一次调用的目标是生成一条 assistant message。后续的 function calling、tool calling、structured output、多模态输入,都是在这个模型上逐步扩展出来的。

工具调用时,assistant message 可能不包含最终文本,而是包含 tool_calls

json 复制代码
{
  "role": "assistant",
  "tool_calls": [
    {
      "id": "call_1",
      "type": "function",
      "function": {
        "name": "get_order",
        "arguments": "{\"order_id\":\"A001\"}"
      }
    }
  ]
}

后端执行工具后,再把结果作为 tool role message 追加回去:

json 复制代码
{
  "role": "tool",
  "tool_call_id": "call_1",
  "content": "{\"status\":\"shipped\"}"
}

这套机制可用,但本质上是用 chat message 模拟执行流。模型的动作、工具结果、最终回复都被编码进同一个 messages timeline。随着工具和多模态能力增加,这种编码方式会越来越重。

3. Chat 抽象的工程边界

从后端角度看,Chat Completions 有四个典型边界。

第一,状态完全由客户端维护。多轮对话需要客户端保存历史,并在下一轮请求中重放:

json 复制代码
{
  "messages": [
    {"role": "user", "content": "解释 TCP 三次握手"},
    {"role": "assistant", "content": "..."},
    {"role": "user", "content": "那 TIME_WAIT 呢?"}
  ]
}

这使 Gateway 必须负责上下文裁剪、摘要、token budget、历史重建和审计持久化。

第二,streaming 主要是 token delta。很多系统把接口抽象成 onToken(token),这对纯文本输出足够,但无法自然表达工具调用开始、工具参数增量、后台状态变化、reasoning summary 等事件。

第三,工具调用是 message 的扩展字段,而不是一等执行事件。后端需要从 assistant message 中解析 tool_calls,再构造 tool message 继续对话。

第四,多模态和结构化输出会增加 message schema 的复杂度。content 从 string 变成 array,工具调用、JSON schema、图片、文件等能力不断扩展,message-centric 模型会变得越来越像一个通用运行时协议。

这就是 OpenAI 需要 Responses API 的主要原因:不是 Chat Completions 不可用,而是它已经不是表达 agentic workflow 的最佳抽象。

4. Responses 的 model-run 抽象

Responses API 的核心模型是:

text 复制代码
input items -> response/model run -> output items

它不再把"assistant message"作为唯一中心,而是把一次模型调用视为一次 response/run。这个 run 可以产生文本、工具调用、reasoning、文件检索结果、图片结果、后台状态等多种 output item。

简化请求:

json 复制代码
{
  "model": "gpt-4.1",
  "input": "查询订单 A001 的状态",
  "tools": [
    {
      "type": "function",
      "name": "get_order",
      "parameters": {
        "type": "object",
        "properties": {
          "order_id": {"type": "string"}
        },
        "required": ["order_id"]
      }
    }
  ]
}

简化响应:

json 复制代码
{
  "id": "resp_xxx",
  "status": "completed",
  "output": [
    {
      "type": "function_call",
      "call_id": "call_1",
      "name": "get_order",
      "arguments": "{\"order_id\":\"A001\"}"
    }
  ]
}

后端执行工具后,将结果作为 input item 传回:

json 复制代码
{
  "model": "gpt-4.1",
  "previous_response_id": "resp_xxx",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_1",
      "output": "{\"status\":\"shipped\"}"
    }
  ]
}

这套模型的关键变化是:工具调用不再是 message 的附属字段,而是 response 运行过程中产生的结构化 output item。后端可以把模型输出当作事件或动作列表来处理,而不是解析一条 assistant message。

5. Responses 的状态性

Responses API 是"可有状态"的。它不是让模型自动记住所有用户历史,而是提供显式的 provider-side continuation。

核心字段是 previous_response_id

json 复制代码
{
  "model": "gpt-4.1",
  "previous_response_id": "resp_abc123",
  "input": "继续上一个问题"
}

它表示本次请求接在上一个 response 后面。OpenAI 可以根据 resp_abc123 恢复上一次 response 的输入、输出、工具调用等上下文。

对比两种状态模型:

text 复制代码
Chat Completions:
  client stores messages[]
  client sends full history each turn
  provider processes one stateless request

Responses:
  provider stores response object
  client sends previous_response_id + new input
  provider continues from prior response chain

状态性主要体现在五点:

维度 体现
Response object 每次调用返回稳定 response.id
Continuation 下一轮可通过 previous_response_id 续接
Tool state function_callfunction_call_output 通过 call_id 关联
Background task response 可处于 in_progresscompletedfailed 等状态
Execution trace output[] 保存结构化执行轨迹

需要注意的是,previous_response_id 不是业务会话 ID,也不是用户记忆 ID。它更接近 provider-side execution state pointer。企业系统仍应保存自己的 conversation event log,用于审计、回放、跨模型迁移和故障恢复。

6. previous_response_id 与 prompt cache

previous_response_id 和 cache 有关系,但不是同一个概念。

text 复制代码
previous_response_id: 解决上下文从哪里续接
prompt cache: 解决相同或稳定上下文能否复用以降低成本/延迟

使用 previous_response_id 后,客户端不必重传完整历史。但服务端恢复出的历史上下文仍可能进入本轮模型上下文,因此仍可能计入 input tokens。区别在于,历史上下文已经在 provider 侧出现过,更容易命中 prompt cache 或内部上下文复用机制。

错误理解:

text 复制代码
previous_response_id = 历史上下文免费

正确理解:

text 复制代码
previous_response_id = 少传 HTTP payload
                     + provider-side continuation
                     + 更稳定的缓存复用机会
                     + usage 中仍可能包含历史 input tokens

因此成本统计应以 provider 返回的 usage 为准,而不是以请求体大小估算。

Gateway 里也不要把该字段命名为 cacheKey。更合适的命名是:

ts 复制代码
type ProviderState = {
  provider: "openai"
  previousResponseId?: string
}

它是状态续接 token,不是缓存 key。

7. Claude Messages 的 typed-block 抽象

Claude Messages API 与 OpenAI Responses 有相似点,但不是同一个抽象。

Claude 的核心模型仍然是:

text 复制代码
messages[] -> assistant message with content blocks

请求:

json 复制代码
{
  "model": "claude-sonnet-4-5",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": "解释一下 TCP 三次握手"
    }
  ]
}

响应:

json 复制代码
{
  "id": "msg_xxx",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "TCP 三次握手是..."
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 20,
    "output_tokens": 300
  }
}

Claude 的关键设计是 typed content blocks。工具调用不是纯文本,而是 tool_use block:

json 复制代码
{
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "我需要查询订单状态。"
    },
    {
      "type": "tool_use",
      "id": "toolu_123",
      "name": "get_order",
      "input": {
        "order_id": "A001"
      }
    }
  ],
  "stop_reason": "tool_use"
}

工具结果通过下一轮 user message 的 tool_result block 传回:

json 复制代码
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_123",
      "content": "{\"status\":\"shipped\"}"
    }
  ]
}

因此 Claude Messages 不是老式 role + string 的简陋 chat API。它是 message-centric,但 message 内部使用 typed blocks 表达多模态、工具调用和结构化内容。

8. Claude Messages 与 OpenAI Responses 的关键差异

二者相似之处在于都支持结构化内容单元。OpenAI 称为 output/input items,Claude 称为 content blocks。二者都能表达文本、工具调用、多模态内容,都适合映射到内部统一事件模型。

但顶层语义不同。

维度 OpenAI Responses Claude Messages
顶层资源 response / model run message
输入模型 input / input items messages[]
输出模型 output[] items content[] blocks
状态续接 previous_response_id 默认重传 messages[]
工具调用 function_call output item tool_use content block
工具结果 function_call_output input item tool_result content block in user message
message id / response id resp_xxx 可用于 continuation msg_xxx 不等价于 continuation token

不要把 Claude 的 msg_xxx 当成 OpenAI 的 resp_xxx 使用。Claude message id 是返回对象标识,不是"下次只传 id 就能续接上下文"的状态指针。

Claude 也支持 prompt caching,但它与 OpenAI previous_response_id 的语义不同。Claude 的典型模式仍然是客户端维护 messages,并通过稳定前缀获得缓存收益;OpenAI Responses 可以通过 response id 显式引用 provider-side response chain。

9. 关键结论

Chat Completions 是 message-centric 的文本/聊天 RPC。它简单、兼容性强,但表达复杂 agent workflow 时会逐渐吃力。

OpenAI Responses 是 run-centric 的模型执行 API。它把文本、工具调用、reasoning、后台任务和多模态结果统一为 typed input/output items,并通过 previous_response_id 支持 provider-side continuation。

Claude Messages 是 message-centric with typed blocks 的 API。它保留 messages 顶层结构,但用 content blocks 表达工具调用和多模态。它与 Responses 在结构化内容上相似,但默认状态模型仍是 client-managed context。

Gateway 的核心设计不应绑定任一厂商的表层 API。更稳的主抽象是:

text 复制代码
conversation events -> model run -> typed output events/items

Chat Completions、OpenAI Responses 和 Claude Messages 都应作为该核心抽象的 adapter。

Sources

相关推荐
QX_hao12 分钟前
【Go】--Cobra-cli的用法
开发语言·后端·golang
大厂码农老A17 分钟前
177K star,扒开DeepSeek Harness的营销,我看到了什么
人工智能·后端·deepseek
别看我只是一直狼17 分钟前
补充:证书自动续约检查、故障诊断与重新签发
后端
Lear20 分钟前
Java 实现多步骤异步任务编排
后端
小强198835 分钟前
告别 "if-else 地狱":PHP 中的策略模式、管道模式与责任链模式实战
后端
面试鸭38 分钟前
Codex 跑进法务部,周活居然暴涨了 108 倍???
后端·面试·求职
Larcher1 小时前
从 SDD 到可交付:我如何用规范驱动 AI 做出一个 Chrome 翻译插件
前端·后端·架构
choumou_M1 小时前
SpringBoot_5:商品秒杀场景的并发实践(初步)
java·spring boot·后端
陈明勇2 小时前
Go 1.27 重磅发布:泛型方法落地、分配性能跃升,JSON v2 正式可用
后端
Zane19942 小时前
create_task 和直接 await 到底有什么不一样?asyncio 的 Task、gather 与并发数量控制
后端·python