从 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

相关推荐
MariaH1 小时前
Koa和Express的区别
后端
MariaH2 小时前
Koa框架的使用
后端
luckdewei3 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某4 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom4 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
用户1474853079748 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端
Melody1239 小时前
用 abort 中断 AI 流式请求,我之前做错了
后端
onething3659 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 5 —— SSE 流式输出 + 打字机效果
人工智能·后端·全栈