三种 LLM 接口协议:Chat Completions、Messages、Responses
最近 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_calls 和 tool 消息混在同一个 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——text、tool_use、thinking。一个响应里可能同时返回思考过程和最终答案。这比 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:message、reasoning、function_call、function_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.created、response.output_item.added、response.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_call 和 function_call_output 是独立的 Item 类型,不用再跟文本消息挤在同一个数组里。这不只是格式好看不好看的问题,而是处理起来确实更不容易出错。
至于 Anthropic 的 Messages API,它在自己的地盘上做得很好,但跨厂商兼容性这个问题短期内看不到解决办法。Open Responses 格式如果能普及,可能以后会出现一个"Messages API 适配层"——但那是以后的事了。
而 LangChain 这类框架的价值也在这里——它帮你屏蔽了底层格式的差异。不管底下是哪种协议,你写的代码都是 model.invoke() 和 model.stream()。代价是你会损失一些对底层细节的控制,但对于大多数应用场景来说,这个 tradeoff 是值得的。