三种 LLM 接口协议:Chat Completions、Messages、Responses

2026年08月03日1 次阅读0 人喜欢
LLMAPIChatCompletionsAnthropicResponses APILangChain流式
所属合集

最近 DeepSeek V4 Flash 正式版发布了,除了模型本身更新,有一点让我挺意外——它原生支持了 Responses API。再一查,小米的 MiMo 也支持了。

也就是说,现在市面上同时跑着三种不同的 LLM 接口格式。这篇文章我把三种协议放在一起,聊聊它们到底有什么区别,为什么会出现不同的格式,以及 Responses API 到底在解决什么问题。

Chat Completions:事实标准

OpenAI 的 /v1/chat/completions,应该是现在被用得最多的 LLM 接口。

json 复制代码
{
  "model": "gpt-4o",
  "messages": [
    {"role": "system", "content": "你是一个助手"},
    {"role": "user", "content": "你好"}
  ]
}

这个格式的核心设计就一个字:无状态。每次请求你必须把完整的对话历史塞进去,模型不记住上一次聊了什么。请求发出去,拿到回复,完事。

响应格式也很直接:

json 复制代码
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "你好!有什么可以帮你的?"
    }
  }]
}

这种设计的好处是简单、好理解。因为所有东西都是你传进去的,出了问题很容易排查。而且因为几乎所有 LLM 厂商都兼容这个格式,你的代码换模型基本不用改。

坏处也很明显——每次都要重传整个对话历史。聊了 20 轮以后,每次请求都要带上前面所有消息,token 用量线性增长。想让模型帮你调用工具?返回的 tool_callstool 消息混在同一个 messages 数组里,你得自己管理这个来回。

Anthropic Messages API:Claude 的路子

Anthropic 做了自己的格式:/v1/messages

json 复制代码
{
  "model": "claude-sonnet-4-20250514",
  "max_tokens": 1024,
  "system": "你是一个助手",
  "messages": [
    {"role": "user", "content": "你好"}
  ]
}

乍一看和 Chat Completions 差不多,但有几个关键区别:

system 拿出来了。 Chat Completions 里 system prompt 是 messages 数组里的第一条,Anthropic 直接放到顶层参数里。这其实更合理——system prompt 是请求配置的一部分,不应该混在对话内容里。

响应是 content 数组。 不是 choices,而是一个类型化的内容块数组:

json 复制代码
{
  "content": [
    {"type": "text", "text": "你好!有什么可以帮你的?"}
  ],
  "stop_reason": "end_turn"
}

每个块有自己的 type——texttool_usethinking。一个响应里可能同时返回思考过程和最终答案。这比 Chat Completions 的 choices[0].message.content 灵活得多。

Extended Thinking。 Claude 的思考过程是响应的一部分,你可以看到模型推理的中间步骤。Chat Completions 到现在也没把这个做进原生格式里。

Prompt Caching。 通过 cache_control 标记可以缓存特定的内容块,对于长文档问答这种场景,能省不少重复计算的钱。

Anthropic 的 Messages API 做的事其实比 Chat Completions 更多,但代价是——它是 Claude 专用的。你想用同样的代码调 Gemini 或者 MiMo?做不到,得换一套。

Responses API:为 Agent 设计

OpenAI 在 2025 年推出的 Responses API,端点是 /v1/responses

这个格式的核心变化是:从"消息"变成了"Item"

Chat Completions 里所有东西都是 Message——文本、工具调用、工具结果,全是 role + content。Responses API 把这些拆成了类型化的 Item:

json 复制代码
{
  "model": "gpt-5.6",
  "input": "你好",
  "instructions": "你是一个助手"
}

注意输入不再是 messages 数组,而是一个 input 字段。它可以是字符串,也可以是 Item 列表。响应也不再是 choices,而是 output

json 复制代码
{
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [{"type": "output_text", "text": "你好!"}]
    }
  ]
}

output 数组里可能有不同类型的 Item:messagereasoningfunction_callfunction_call_output。每种类型各管各的事,不混在一起。

但这还不是最重要的。Responses API 最大的变化是服务端状态管理

python 复制代码
# Chat Completions:每次都要带完整历史
response = client.chat.completions.create(
    model="gpt-5.6",
    messages=[msg1, msg2, msg3, msg4, msg5, msg6]  # 越来越长
)

# Responses API:只发新的,服务端帮你记住
response = client.responses.create(
    model="gpt-5.6",
    input="最新的一条消息",
    previous_response_id="resp_abc123"  # 引用上一次的响应
)

previous_response_id 让你不用重传历史。或者用 Conversations API 维护一个持久化的对话对象,客户端只管发新消息就行。

这对 Agent 场景特别有用。Agent 需要反复调用工具、推理、再调用,如果每次都重传完整历史,token 成本会非常高。Responses API 的 previous_response_id + 内置工具(web search、code interpreter、computer use)让这些可以在一个请求里自动完成,不需要你自己编排每一步。

Open Responses:社区的回应

Responses API 只能调 OpenAI 的模型。社区觉得这个设计不错,但不想被绑死,于是搞了一个开源规范叫 Open Responses

它把 Responses API 的核心设计——Item 类型化、状态管理、流式协议——抽出来做成了一个跨厂商标准。Hugging Face、OpenRouter、Vercel、Ollama、vLLM 都参与了。

这也是为什么 DeepSeek V4 Flash 和 MiMo 能支持 Responses API 格式——它们不是直接接入了 OpenAI 的服务,而是实现了这个社区规范。DeepSeek 的文档说得很直接:Responses API 格式是为了适配 Codex。

三种格式放一起看

Chat Completions Messages Responses
端点 /v1/chat/completions /v1/messages /v1/responses
设计目标 通用文本生成 Claude 原生能力 Agent 工作流
输入 messages[] 数组 messages[] 数组 input 字符串或 Item 列表
系统提示 混在 messages 里 独立的 system 参数 独立的 instructions 参数
响应结构 choices[].message content[] 类型块 output[] 类型化 Item
状态管理 客户端管理 客户端管理 可选服务端管理
内置工具 Web Search 等 Web Search、Code Interpreter、Computer Use、MCP
生态兼容 最广 Claude 专用 在扩大(通过 Open Responses)

LangChain 怎么用

三种协议 LangChain 都支持,而且接口设计得比较统一。这也是 LangChain 的价值所在——你不需要关心底层是哪种格式,用统一的 invoke / stream 就行。

Chat Completions 对应 ChatOpenAI

python 复制代码
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o")
response = model.invoke("你好")
print(response.text)

默认就是 Chat Completions 格式。如果要切到 Responses API,加一个参数:

python 复制代码
model = ChatOpenAI(
    model="gpt-5.6",
    use_responses_api=True,  # 切换到 Responses API
)

LangChain 会在底层自动处理格式转换,你写的代码几乎不用改。

Anthropic Messages API 对应 ChatAnthropic

python 复制代码
from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-sonnet-4-20250514")
response = model.invoke("你好")
print(response.text)

Anthropic 的 content 数组、thinking 块这些,LangChain 会统一抽象成 AIMessage 对象。你可以通过 response.content 拿到文本,response.tool_calls 拿到工具调用,不用关心底层是哪种格式。

Extended Thinking 也能用:

python 复制代码
model = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    thinking={"type": "enabled", "budget_tokens": 10000},
)

init_chat_model:统一入口

LangChain 还提供了一个 init_chat_model 函数,可以用字符串指定模型和提供商:

python 复制代码
from langchain.chat_models import init_chat_model

# Chat Completions
model = init_chat_model("openai:gpt-4o")

# Anthropic Messages
model = init_chat_model("anthropic:claude-sonnet-4-20250514")

# DeepSeek (兼容 Chat Completions)
model = init_chat_model("deepseek:deepseek-v4-flash")

写 Agent 的时候特别方便,换模型只需要改一个字符串。

流式返回

流式返回是现在几乎所有 LLM 应用的标配了。三种协议的流式返回方式不太一样,但 LangChain 把它们统一了。

curl 层面

Chat Completions 流式

bash 复制代码
curl -X POST https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ***" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": true
  }'

SSE 事件格式是 data: {"choices":[{"delta":{"content":"你"}}]},每个 chunk 带一小段文本,最后发 data: [DONE]

Responses API 流式

bash 复制代码
curl -X POST https://api.openai.com/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ***" \
  -d '{
    "model": "gpt-5.6",
    "input": "你好",
    "stream": true
  }'

SSE 事件格式完全不同——是语义化的事件类型:response.createdresponse.output_item.addedresponse.content_part.delta。事件带有明确的类型和结构,不只是文本 chunk。

Anthropic Messages 流式

bash 复制代码
curl -X POST https://api.anthropic.com/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-4-20250514",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "你好"}],
    "stream": true
  }'

事件类型包括 content_block_delta(文本增量)、message_start(消息开始,带 model 和 usage 信息)、message_stop(消息结束)。比 Chat Completions 的纯文本 delta 多了一些元信息。

LangChain 层面

LangChain 把三种协议的流式都统一成了 .stream() 方法:

python 复制代码
# Chat Completions
model = ChatOpenAI(model="gpt-4o", streaming=True)
for chunk in model.stream("你好"):
    print(chunk.text, end="", flush=True)

# Anthropic Messages
model = ChatAnthropic(model="claude-sonnet-4-20250514")
for chunk in model.stream("你好"):
    print(chunk.text, end="", flush=True)

# Responses API
model = ChatOpenAI(
    model="gpt-5.6",
    use_responses_api=True,
    streaming=True,
)
for chunk in model.stream("你好"):
    print(chunk.text, end="", flush=True)

三个 for chunk in model.stream() 循环,底层协议完全不同,但你的代码几乎一模一样。每个 chunk 都是 AIMessageChunk 对象,.text 拿文本,.tool_calls 拿工具调用。

如果你想拿到更详细的流式事件(比如 token usage、reasoning 过程),可以用 stream_events

python 复制代码
model = ChatAnthropic(model="claude-sonnet-4-20250514")
async for event in model.astream_events("你好", version="v3"):
    if event["event"] == "on_chat_model_stream":
        chunk = event["data"]["chunk"]
        # 这里可以拿到 reasoning、text 等不同类型的内容块
        print(chunk)

stream_events 会把底层的 SSE 事件映射成统一的事件格式,你不需要关心到底是哪种协议。

怎么选

如果你在做通用应用,只是调用模型生成文本,Chat Completions 依然是最稳的选择。兼容性最好,出了问题社区资料最多。

如果你在 Claude 上做深度开发,特别是需要 Extended Thinking 和 Prompt Caching,用 Messages API 是正道。

如果你在做 Agent 类的应用——模型需要反复调工具、做多步推理——Responses API 的设计明显更适合。服务端状态管理和内置工具能省掉大量编排代码。而且随着 DeepSeek、MiMo 这些模型也开始支持,它不再是 OpenAI 独占的东西了。

OpenAI 官方的态度也很明确:Chat Completions 不会废弃,但他们推荐新项目用 Responses API。

我个人觉得,Responses API 的 Item 模型确实比 messages 更干净。特别是当你在做 Agent 的时候,function_callfunction_call_output 是独立的 Item 类型,不用再跟文本消息挤在同一个数组里。这不只是格式好看不好看的问题,而是处理起来确实更不容易出错。

至于 Anthropic 的 Messages API,它在自己的地盘上做得很好,但跨厂商兼容性这个问题短期内看不到解决办法。Open Responses 格式如果能普及,可能以后会出现一个"Messages API 适配层"——但那是以后的事了。

而 LangChain 这类框架的价值也在这里——它帮你屏蔽了底层格式的差异。不管底下是哪种协议,你写的代码都是 model.invoke()model.stream()。代价是你会损失一些对底层细节的控制,但对于大多数应用场景来说,这个 tradeoff 是值得的。

加载评论中...