OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 都能完成模型调用,但状态模型并不相同:Chat Completions 以聊天消息为中心;Responses 把消息、推理和工具调用统一建模为 Item;Anthropic Messages 则在消息内部使用不同类型的 content block。这些差异会直接影响应用如何保存多轮状态、压缩上下文、执行工具和处理流式事件。
下面分别从最小请求讲起,再讨论多轮消息、上下文管理、工具调用、结构化输出、流式响应和常见边界,最后对比三套 API 的状态协议。
本文讨论的是直接调用 HTTP API 的行为,不是 ChatGPT 或 Claude 网页产品中的聊天记录和记忆功能。示例中的 OPENAI_MODEL_ID 和 CLAUDE_MODEL_ID 需要替换为账号当前可用、并且支持对应能力的模型。
Chat Completions 的入口是 POST /v1/chat/completions。请求的核心是 messages,响应的核心是 choices。如果应用已经把一段对话保存成按时间排列的消息列表,这套接口最容易理解。
1
2
3
4
5
6
7
8
9
10
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "developer", "content": "回答要准确、简洁。"},
{"role": "user", "content": "解释什么是幂等性。"}
]
}'
|
一个普通响应大致如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{
"id": "chatcmpl_...",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "幂等性是指同一个操作执行一次或多次,结果保持一致。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 22,
"total_tokens": 50
}
}
|
最常读取的字段是:
choices[0].message:模型生成的完整 assistant 消息。
choices[0].message.content:普通文本内容。
choices[0].message.tool_calls:模型要求应用执行的工具调用。
choices[0].finish_reason:本次生成结束的原因。
usage:输入和输出 token 用量。
不要只保存 message.content。一条 assistant message 还可能包含工具调用、拒绝信息或其他结构化内容,后续多轮对话可能需要原样带回。
常用角色包括:
developer:应用开发者给模型的稳定规则。新模型通常优先使用该角色。
system:系统级指令,旧模型和部分兼容实现仍常用。
user:用户输入。
assistant:模型以前的回复。
tool:应用执行工具后的结果。
简单文本可以直接写进 content。需要传入图片或其他多模态内容时,content 可以改为由多个 content part 组成的数组。具体支持的内容类型取决于模型,不能只根据接口名称判断模型是否支持图片、音频或文件。
下面的请求同时发送文字和图片 URL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "描述这张图片。"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/image.jpg"}
}
]
}]
}'
|
响应仍然是普通 assistant message,图片不会改变 choices[].message.content 的读取方式。
Chat Completions 本身不保存对话状态。下一轮请求必须再次发送需要保留的历史消息。
1
2
3
4
5
6
7
8
9
10
11
12
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "developer", "content": "回答要准确、简洁。"},
{"role": "user", "content": "法国首都是哪里?"},
{"role": "assistant", "content": "法国首都是巴黎。"},
{"role": "user", "content": "它位于哪条河沿岸?"}
]
}'
|
第二次请求中的 assistant 消息来自第一次响应。示例直接写出该消息,是为了展示最终发送给 API 的完整请求体。
生产环境一般不会把 messages 只放在进程内存中,而是会保存到数据库。建议同时保存:
- 原始角色和完整内容。
- assistant 的完整消息对象。
- 工具调用 ID 及其执行结果。
- 使用的模型、请求时间和 token 用量。
- 应用自己的会话 ID、用户 ID 和租户 ID。
这样既能继续对话,也能审计模型实际看到了什么。
每次请求中的 developer/system 指令、历史消息、工具定义、工具结果和当前问题都会占用上下文窗口。随着轮数增加,请求会越来越大,成本和延迟也会上升,最终还可能超过模型的上下文限制。
Chat Completions 没有对应 Responses API 服务端 compaction 的内置机制。应用需要自行选择一种策略:
- 滑动窗口:只保留最近若干轮。实现简单,但较早的约束和事实会丢失。
- 摘要旧消息:让模型把较早对话压缩成一条摘要,再保留最近几轮原文。
- 提取结构化状态:把用户偏好、任务状态和关键事实保存为 JSON,需要时重新注入。
- 检索历史记录:完整记录放在外部存储中,只检索当前问题需要的片段。
一个常见的消息布局是:
1
2
3
4
5
|
developer 指令
较早对话的摘要
必须长期保留的结构化事实
最近几轮原始消息
当前用户消息
|
例如,应用已经把较早对话压缩成摘要,只保留最近一轮原文时,可以发送:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "developer", "content": "回答要准确、简洁。"},
{
"role": "developer",
"content": "历史摘要:用户正在排查数据库连接超时;已经确认连接池上限为 20,下一步检查慢查询。"
},
{"role": "user", "content": "刚才的慢查询日志里出现了锁等待。"},
{"role": "assistant", "content": "请先确认锁等待涉及的事务和持续时间。"},
{"role": "user", "content": "事务持续了 45 秒,应该先查什么?"}
]
}'
|
这里第二条 developer message 是应用生成并信任的摘要,不应直接塞入未经处理的用户文本。
摘要是一种有损压缩,不能直接替代所有历史。压缩时至少要注意:
- 不要把稳定的 developer/system 指令混进可随意改写的摘要。
- 不要拆散一组 assistant
tool_calls 和对应的 tool 结果。
- 订单号、资源 ID、金额等精确数据更适合结构化保存,而不是只写进自然语言摘要。
- 摘要生成失败时,不要覆盖最后一份可用上下文。
- 在发送请求前估算 token,并为模型输出预留空间。
如果应用需要服务端管理对话状态或自动压缩上下文,Responses API 通常更合适。
Chat Completions 使用 tools 声明函数。模型不会真正执行函数,只会生成调用请求;应用负责验证参数、执行函数,再把结果作为 tool 消息发回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "user", "content": "台北今天天气如何?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": false
},
"strict": true
}
}]
}'
|
模型决定调用工具时,响应中的关键部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"台北\"}"
}
}]
},
"finish_reason": "tool_calls"
}]
}
|
应用读取 call_123 和参数,执行 get_weather 后,第二次请求需要同时带回原始 assistant message 和工具结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "user", "content": "台北今天天气如何?"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"台北\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_123",
"content": "{\"temperature\":28,\"condition\":\"多云\"}"
}
]
}'
|
第二次响应回到普通 assistant 文本:
1
2
3
4
5
6
7
8
9
|
{
"choices": [{
"message": {
"role": "assistant",
"content": "台北目前 28°C,多云。"
},
"finish_reason": "stop"
}]
}
|
一次响应可能包含多个 tool_calls。应用应逐个按调用 ID 返回结果,并把完整的 assistant 工具调用消息保留在历史中。工具参数来自模型,必须像外部输入一样做 schema 校验、权限检查和超时限制。
如果结果要交给程序消费,可以使用 response_format 约束输出,而不是提示模型“只返回 JSON”。JSON Schema 模式的基本形态如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [{
"role": "user",
"content": "把下面的问题整理成工单:数据库连接经常超时。"
}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
},
"required": ["title", "priority"],
"additionalProperties": false
}
}
}
}'
|
响应中的结构化结果仍位于 assistant message 的 content 字符串中:
1
2
3
4
5
6
7
8
9
|
{
"choices": [{
"message": {
"role": "assistant",
"content": "{\"title\":\"数据库连接经常超时\",\"priority\":\"high\"}"
},
"finish_reason": "stop"
}]
}
|
应用需要对 content 再做一次 JSON 反序列化,得到工单对象。
结构化输出保证的是模型输出符合受支持的 schema,不代表字段内容一定符合业务事实。应用仍然要检查拒答、截断和业务约束。
设置 stream=true 后,服务端通过 Server-Sent Events 返回 chat.completion.chunk。文本不是一次返回,而是出现在 choices[].delta.content 中。
1
2
3
4
5
6
7
8
9
10
|
curl -N https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"messages": [
{"role": "user", "content": "写一段关于台北雨夜的短文。"}
],
"stream": true
}'
|
curl -N 会关闭输出缓冲。下面省略了 created、model 等重复字段;同一次请求中的 chunk 使用相同的 id:
1
2
3
4
5
6
7
8
9
|
data: {"id":"chatcmpl_123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl_123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"台北"},"finish_reason":null}]}
data: {"id":"chatcmpl_123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"的雨夜。"},"finish_reason":null}]}
data: {"id":"chatcmpl_123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
|
把每个 delta.content 按顺序拼接,最终得到「台北的雨夜。」。finish_reason 出现在结束 chunk,而不是每个文本 chunk 中。
原始 SSE 流最终通常以 data: [DONE] 结束。最后一个 chunk 还可能给出 finish_reason;如果请求启用了流式 usage 选项,usage 可能出现在单独的尾部 chunk 中。
工具调用也可以流式返回,但 delta.tool_calls[].function.arguments 只是参数字符串的一部分。应用必须按 choice、tool call 的 index 和 ID 累积片段,等调用结束后再解析完整 JSON,不能对每个 delta 单独执行 json.loads,更不能在参数尚未完整时执行工具。
常见 finish_reason 包括正常结束、达到输出上限、工具调用和内容过滤等情况。应用不应把所有非空文本都当作完整答案:
- 达到长度上限时,JSON 或代码可能只生成了一半。
- 存在
tool_calls 时,应进入工具执行循环。
- 请求超时后重试可能产生重复工具操作,工具应尽量幂等。
- HTTP 429 和 5xx 可以退避重试;参数错误和权限错误不应盲目重试。
完整字段以 Chat Completions API Reference 为准。
Responses API 的入口是 POST /v1/responses。OpenAI 建议新项目优先考虑它。它不再假设一次模型运行只产生一条 assistant message,而是用 output[] 保存多个有类型的 Item,例如消息、推理和工具调用。
1
2
3
4
5
6
7
8
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"instructions": "回答要准确、简洁。",
"input": "解释什么是幂等性。"
}'
|
典型响应可能同时包含 reasoning Item 和 message Item:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"id": "resp_...",
"object": "response",
"status": "completed",
"output": [
{"type": "reasoning", "id": "rs_...", "summary": []},
{
"type": "message",
"id": "msg_...",
"role": "assistant",
"status": "completed",
"content": [
{"type": "output_text", "text": "幂等性是指……", "annotations": []}
]
}
],
"usage": {}
}
|
使用原始 HTTP 响应时,需要在 output 中找到 type 为 message 的 Item,再读取其中的 output_text。
需要实现 Agent、审计工具调用或保存多轮上下文时,应遍历完整 response.output,并根据每个 Item 的 type 处理。不能假设 output[0] 一定是文本消息。
Responses 提供三种常见的对话状态管理方式。
应用把上一轮的所有 response.output 追加到下一轮 input,再加入新的 user message。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"instructions": "回答要准确、简洁。",
"store": false,
"input": [
{
"role": "user",
"content": [{"type": "input_text", "text": "法国首都是哪里?"}]
},
{
"type": "message",
"id": "msg_123",
"role": "assistant",
"status": "completed",
"content": [{
"type": "output_text",
"text": "法国首都是巴黎。",
"annotations": []
}]
},
{
"role": "user",
"content": [{"type": "input_text", "text": "它位于哪条河沿岸?"}]
}
]
}'
|
示例为了便于阅读,只展示了一条 assistant message。实际应用必须把第一次响应的全部 output Item 原样插入第二次请求,而不只是最终文本。推理模型可能需要上一轮的 reasoning Item 或加密推理内容来保持推理连续性,工具调用 Item 也必须和结果配对。
1
2
3
4
5
6
7
8
9
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"instructions": "回答要准确、简洁。",
"previous_response_id": "resp_123",
"input": "它位于哪条河沿岸?"
}'
|
其中 resp_123 来自上一轮响应的 id。
OpenAI 会关联前一条 Response。需要注意:
- 前一轮顶层
instructions 不会因为设置了 previous_response_id 而自动继承,稳定指令应在后续请求中重新发送。
- 关联历史并不意味着历史输入免费,链中的历史 token 仍会计入输入用量。
- 应用仍应保存 response ID,并明确处理响应保留、删除和数据治理要求。
需要让一段长期对话跨会话、设备或任务持续存在时,可以创建 conversation,再让多个 Response 关联同一个 conversation。它适合服务端托管持久对话状态;如果应用必须精确控制每个历史 Item 的裁剪和跨供应商迁移,手动管理通常更透明。
先创建 conversation:
1
2
3
4
5
6
|
curl https://api.openai.com/v1/conversations \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {"topic": "地理问答"}
}'
|
响应会返回一个持久 conversation ID:
1
2
3
4
5
|
{
"id": "conv_123",
"object": "conversation",
"metadata": {"topic": "地理问答"}
}
|
后续调用把 conversation 指向同一个 ID:
1
2
3
4
5
6
7
8
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"conversation": "conv_123",
"input": "法国首都是哪里?"
}'
|
下一轮继续使用 conversation: "conv_123",只把 input 换成「它位于哪条河沿岸?」。此前写入 conversation 的 Item 会由服务端参与后续请求。
三种方式不应在没有明确状态边界的情况下混用。应用最好为一段会话固定一种主策略,并把 provider conversation ID、response ID 和本地会话 ID 分开保存。
Responses 支持服务端 compaction。它会在上下文达到阈值时,把较早内容压缩成一个可继续传回 API 的 compaction Item,以减少后续请求实际携带的上下文。
在请求中配置 context_management:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"store": false,
"input": [
{"role": "user", "content": "继续完成当前任务。"}
],
"context_management": [{
"type": "compaction",
"compact_threshold": 100000
}]
}'
|
达到阈值时,普通 Response 的 output 中会多出一个不透明的 compaction Item:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
"id": "resp_123",
"object": "response",
"output": [
{
"id": "cmp_123",
"type": "compaction",
"encrypted_content": "gAAAAAB...="
},
{
"id": "msg_123",
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{
"type": "output_text",
"text": "继续处理当前任务。",
"annotations": []
}]
}
]
}
|
encrypted_content 不是给人阅读的摘要。应用应保存整个 Item,并在下一轮原样传回。
达到阈值后,本次响应的 output 会包含 compaction Item。手动维护 input_items 时,应把完整 response.output 追加回上下文。最新 compaction Item 已经代表它之前的压缩状态,因此应用可以丢弃该 Item 之前的旧 Item,再继续追加新的输入。
如果使用 previous_response_id 链接状态,不要再自行删除链中的 Item;让服务端按链和 compaction 配置管理上下文即可。
应用也可以显式调用 POST /v1/responses/compact,把当前上下文压缩为下一轮可以直接使用的规范 Item 列表:
1
2
3
4
5
6
7
8
9
10
11
|
curl https://api.openai.com/v1/responses/compact \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"input": [
{"role": "user", "content": "分析这份较长的会话记录。"},
{"role": "assistant", "content": "这里是前一轮输出。"},
{"role": "user", "content": "保留关键约束并压缩上下文。"}
]
}'
|
独立压缩接口返回 response.compaction 对象。下面的响应节选保留了一个 user Item 和一个加密 compaction Item:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
{
"id": "resp_123",
"object": "response.compaction",
"output": [
{
"id": "msg_001",
"type": "message",
"status": "completed",
"content": [{
"type": "input_text",
"text": "分析这份较长的会话记录。"
}],
"role": "user"
},
{
"id": "cmp_001",
"type": "compaction",
"encrypted_content": "gAAAAAB...="
}
]
}
|
响应中的 output 就是下一轮的规范上下文。下一次调用 /v1/responses 时,应先原样放入这组 Item,再追加新的 user Item。
不要自行拆解、改写或只摘取 compaction Item 中看得懂的部分。官方文档将压缩结果定义为后续请求的规范输入。自动 compaction 与 store=false 也可以组合,用于由客户端维护状态的场景。
压缩仍然是有损处理。业务关键数据最好保存在应用自己的结构化状态中,不要只依赖模型生成的上下文摘要。详细行为见 OpenAI Compaction 指南。
Responses 的函数定义比 Chat Completions 更扁平:函数名、描述和参数直接放在 function tool 上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"input": "台北今天天气如何?",
"tools": [{
"type": "function",
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": false
},
"strict": true
}]
}'
|
模型决定调用函数时,会在 output 中返回独立的 function_call Item:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"id": "resp_123",
"status": "completed",
"output": [{
"type": "function_call",
"id": "fc_123",
"call_id": "call_123",
"name": "get_weather",
"arguments": "{\"city\":\"台北\"}",
"status": "completed"
}]
}
|
应用解析参数并执行函数后,可通过 previous_response_id 返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"previous_response_id": "resp_123",
"input": [{
"type": "function_call_output",
"call_id": "call_123",
"output": "{\"temperature\":28,\"condition\":\"多云\"}"
}]
}'
|
模型拿到 function_call_output 后,会产生新的 message Item:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"id": "resp_124",
"status": "completed",
"output": [{
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{
"type": "output_text",
"text": "台北目前 28°C,多云。",
"annotations": []
}]
}]
}
|
function_call 和 function_call_output 通过 call_id 关联。应用要支持一次返回多个调用,并限制工具可访问的资源范围。
Responses 还支持 web search、file search、code interpreter、computer use、remote MCP 等 OpenAI 托管工具。托管工具由服务端执行,具体可用性、参数和费用取决于模型与工具。客户端函数则始终由应用自己执行。
Responses 把结构化输出配置放在 text.format 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"input": "把下面的问题整理成工单:数据库连接经常超时。",
"text": {
"format": {
"type": "json_schema",
"name": "ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
},
"required": ["title", "priority"],
"additionalProperties": false
}
}
}
}'
|
响应中的 JSON 仍然作为 output_text 的文本返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"status": "completed",
"output": [{
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{
"type": "output_text",
"text": "{\"title\":\"数据库连接经常超时\",\"priority\":\"high\"}",
"annotations": []
}]
}]
}
|
应用找到 output_text 后,再对其中的 text 做 JSON 反序列化。
这和 Chat Completions 的 response_format 不是同一个字段路径。迁移时如果只替换 endpoint 而保留原请求体,会得到参数错误。
设置 stream=true 后,Responses 返回带语义类型的事件,而不是只有一个通用 delta 结构。
1
2
3
4
5
6
7
8
|
curl -N https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"input": "写一段关于台北雨夜的短文。",
"stream": true
}'
|
Responses 的 SSE 同时包含 event 行和 JSON data 行。下面省略了 Response 对象中与流式过程无关的字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
event: response.created
data: {"type":"response.created","response":{"id":"resp_123","status":"in_progress","output":[]}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_123","output_index":0,"content_index":0,"delta":"台北"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_123","output_index":0,"content_index":0,"delta":"的雨夜。"}
event: response.output_text.done
data: {"type":"response.output_text.done","item_id":"msg_123","output_index":0,"content_index":0,"text":"台北的雨夜。"}
event: response.completed
data: {"type":"response.completed","response":{"id":"resp_123","status":"completed"}}
|
文本界面通常只拼接 response.output_text.delta 的 delta。response.output_text.done 给出完整文本,response.completed 则表示整个 Response 已完成;工具和其他 Item 可能在文本完成前后继续产生事件。
常见事件包括:
response.created:Response 已创建。
response.output_item.added:新增一个 output Item。
response.content_part.added:新增一个内容部分。
response.output_text.delta:文本增量。
response.function_call_arguments.delta:函数参数字符串增量。
response.output_item.done:一个 Item 完成。
response.completed:整个 Response 完成。
error:流内错误。
事件集合会随启用的工具和输出类型变化。实现时应按事件 type 分派,并忽略暂时不认识但不影响当前功能的事件,而不是假定每个事件都有 delta。
函数参数和结构化输出在流式阶段同样可能是不完整 JSON。应在对应 Item 完成后解析;如果只是把文本实时展示给用户,可以只消费 response.output_text.delta,同时在结束时保存完整 Response。
Responses 的 input 可以是字符串,也可以是包含 input_text、input_image、文件等内容的 Item 数组。输出也可能包含文本之外的 Item。是否支持某种模态仍由模型和工具决定。
下面的请求同时发送文字和图片 URL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "OPENAI_MODEL_ID",
"input": [{
"role": "user",
"content": [
{"type": "input_text", "text": "描述这张图片。"},
{
"type": "input_image",
"image_url": "https://example.com/image.jpg"
}
]
}]
}'
|
文字结果仍然出现在 message Item 的 output_text 中。传文件时使用 input_file,并按来源提供 file_id 或 file_url。
生产代码至少要检查:
response.status 是否为 completed。
- 是否存在
error 或 incomplete_details。
output 中是否有函数或托管工具调用。
usage 是否符合预算。
store 设置是否符合数据保留要求。
Responses 的完整状态模型和字段见 Responses API Reference,多轮状态见 Conversation state 指南。
Anthropic Messages API 的入口是 POST /v1/messages。一次请求生成一条 assistant message;message 的 content 不是固定字符串,而是由 text、tool_use 等 content block 组成的数组。
1
2
3
4
5
6
7
8
9
10
11
12
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"system": "回答要准确、简洁。",
"messages": [
{"role": "user", "content": "解释什么是幂等性。"}
]
}'
|
Claude API 使用 x-api-key,并要求 anthropic-version header。max_tokens 是请求中的显式输出上限,不代表模型一定生成到这个长度。
典型响应如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"content": [
{"type": "text", "text": "幂等性是指……"}
],
"model": "CLAUDE_MODEL_ID",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 25,
"output_tokens": 20
}
}
|
即使只请求文本,也应遍历 content 并根据 block 的 type 处理,不要长期依赖 content[0].text。启用工具或其他能力后,第一块不一定是文本。
Anthropic 的 system 是顶层字段,不是 messages 中的一条 system role message。Messages 中主要使用 user 和 assistant 两种角色。
Messages API 默认是无状态的。每次请求都要重新发送希望 Claude 看到的历史消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"system": "回答要准确、简洁。",
"messages": [
{"role": "user", "content": "法国首都是哪里?"},
{
"role": "assistant",
"content": [{"type": "text", "text": "法国首都是巴黎。"}]
},
{"role": "user", "content": "它位于哪条河沿岸?"}
]
}'
|
第二次请求中的 assistant content 来自第一次响应。示例把 content block 完整写出,是为了强调下一轮应保存并带回整个 content 数组。
API 允许把连续同角色消息组合起来,但应用最好保持清晰的 user/assistant 轮次,并完整保存 content blocks。只把 Claude 的文本拼成一个字符串,会丢失 tool_use、compaction 等后续需要的结构。
顶层 system 指令、所有历史消息、工具定义、工具结果、图片和文档都会占用上下文窗口。max_tokens 又会占用输出预算,因此应用不能等到请求已经超限才处理历史。
Anthropic 提供 token counting API,可以在真正生成之前估算同一组 system、messages 和 tools 的输入 token。适合在以下时机调用:
- 长文档首次进入上下文时。
- 工具定义很多或工具结果很大时。
- 接近应用自己设置的压缩阈值时。
- 需要在调用前估算成本时。
1
2
3
4
5
6
7
8
9
10
11
|
curl https://api.anthropic.com/v1/messages/count_tokens \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"system": "回答要准确、简洁。",
"messages": [
{"role": "user", "content": "分析这段较长的对话。"}
]
}'
|
token counting 响应只返回估算后的输入 token 数,不会生成 assistant message:
1
2
3
|
{
"input_tokens": 18
}
|
响应中的 usage.input_tokens 和 usage.output_tokens 用于记录实际计费量。缓存读写等能力还可能增加更细的 usage 字段,不能假定 usage 永远只有两个数字。
Anthropic 当前提供服务端 compaction。该能力使用 beta header,把较早的对话压缩成 compaction content block,并在下一轮用这个 block 替代它之前的历史内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: compact-2026-01-12" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{"role": "user", "content": "继续完成这段较长会话中的任务。"}
],
"context_management": {
"edits": [{
"type": "compact_20260112",
"trigger": {"type": "input_tokens", "value": 100000}
}]
}
}'
|
触发压缩后,assistant content 的开头会出现 compaction block,后面仍可继续包含普通文本:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"content": [
{
"type": "compaction",
"content": "<summary>用户正在实现一个天气服务,已经确定接口和错误处理策略……</summary>"
},
{
"type": "text",
"text": "接下来可以实现缓存层。"
}
],
"stop_reason": "end_turn"
}
|
下一轮请求必须把包括该 block 在内的完整 assistant content 放回 messages,然后再追加新的 user message。如果启用了 pause_after_compaction,响应只包含 compaction block,且 stop_reason 为 compaction。
按当前官方文档,该版本默认在 150,000 个输入 token 时触发,可配置的最低阈值是 50,000。默认值、beta 名称和支持模型都可能变化,使用前应再次确认 Anthropic Compaction 文档。
compaction 的关键行为是:
- 触发后,响应
content 中会出现 compaction block。
- 应用要像保存 text 和 tool block 一样,保存完整 assistant content。
- 下一轮再次提交该 block 时,API 会丢弃它之前已经被压缩的 content blocks。
- 可以提供自定义压缩指令,但它会替换默认摘要提示,不是简单追加一句要求。
- 可以设置
pause_after_compaction,让请求在完成压缩后暂停,应用检查或持久化状态后再继续。
服务端 compaction 之外,应用仍可自行摘要、滑动窗口或提取结构化状态。和 Chat Completions 一样,精确业务数据不应只存在于自然语言摘要中。
Anthropic 使用 tools 声明工具,参数 schema 字段名为 input_schema。Claude 请求执行工具时,会返回 tool_use block;应用执行后,用 tool_result block 作为下一条 user message 的内容返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "台北今天天气如何?"}
],
"tools": [{
"name": "get_weather",
"description": "查询指定城市的天气",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": false
}
}]
}'
|
模型决定调用工具时,会返回 tool_use block,并把 stop_reason 设为 tool_use:
1
2
3
4
5
6
7
8
9
10
|
{
"role": "assistant",
"content": [{
"type": "tool_use",
"id": "toolu_123",
"name": "get_weather",
"input": {"city": "台北"}
}],
"stop_reason": "tool_use"
}
|
应用执行函数后,需要把原始 assistant content 和 tool_result 一起带回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "台北今天天气如何?"},
{
"role": "assistant",
"content": [{
"type": "tool_use",
"id": "toolu_123",
"name": "get_weather",
"input": {"city": "台北"}
}]
},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_123",
"content": "{\"temperature\":28,\"condition\":\"多云\"}"
}]
}
],
"tools": [{
"name": "get_weather",
"description": "查询指定城市的天气",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]
}'
|
下一条响应会重新回到 text block:
1
2
3
4
5
6
7
8
|
{
"role": "assistant",
"content": [{
"type": "text",
"text": "台北目前 28°C,多云。"
}],
"stop_reason": "end_turn"
}
|
tool_use.id 和 tool_result.tool_use_id 必须匹配。一次消息可能要求多个工具调用,应该在同一条 user message 中返回对应的多个结果。工具失败时可以在 result 中标记错误,而不是伪造一个正常结果。
Anthropic 当前使用 output_config.format 指定 JSON Schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": "把下面的问题整理成工单:数据库连接经常超时。"
}],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
},
"required": ["title", "priority"],
"additionalProperties": false
}
}
}
}'
|
JSON 结果位于普通 text block 的 text 字符串中:
1
2
3
4
5
6
7
8
|
{
"role": "assistant",
"content": [{
"type": "text",
"text": "{\"title\":\"数据库连接经常超时\",\"priority\":\"high\"}"
}],
"stop_reason": "end_turn"
}
|
符合 schema 的 JSON 会出现在 text block 中,应用仍要取出字符串并反序列化。工具输入如果也需要严格符合 schema,可以在工具定义中启用 strict: true。字段形态可能随 API 版本变化,升级前应对照 Structured outputs 文档。
设置 stream=true 后,Anthropic 通过 SSE 返回 message 和 content block 的生命周期事件:
1
2
3
4
5
6
7
8
9
10
11
12
|
curl -N https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "写一段关于台北雨夜的短文。"}
],
"stream": true
}'
|
一次文本响应的 SSE 形态如下。为了突出事件顺序,示例省略了 model 等不影响拼接的字段。Anthropic 会在 event 行中写事件名,并在 data.type 中重复同一个类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
event: message_start
data: {"type":"message_start","message":{"id":"msg_123","type":"message","role":"assistant","content":[],"stop_reason":null,"usage":{"input_tokens":25,"output_tokens":1}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"台北"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"的雨夜。"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":15}}
event: message_stop
data: {"type":"message_stop"}
|
按 index 把同一个 content block 的 text_delta.text 依次拼接,就能得到最终文本。最后的 message_delta 提供停止原因和累计 usage,message_stop 表示整条 Message 完成。
如果直接处理原始事件,常见顺序是:
message_start
- 一个或多个
content_block_start
- 每个 block 的若干
content_block_delta
content_block_stop
message_delta
message_stop
文本增量的 delta 类型是 text_delta。工具参数使用 input_json_delta,其中的 partial_json 只是 JSON 字符串片段,应该按 content block 的 index 累积,等 content_block_stop 后再解析。
流中还可能出现 ping、error 和新增的未知事件类型。客户端应该容忍 ping 和暂时不关心的事件,同时对流内 error 做明确处理。网络连接关闭不等于模型正常完成;需要看到 message_stop,并保存由各个事件汇总出的最终 Message。
Messages 的 user content 可以由多个 block 组成,例如文本、图片和 PDF 文档。内容可以直接用 base64、URL,或使用 Files API 支持的文件引用;具体方式和限制取决于内容类型及模型。
下面的请求同时发送文字和图片 URL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "CLAUDE_MODEL_ID",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.jpg"
}
},
{"type": "text", "text": "描述这张图片。"}
]
}]
}'
|
响应中的图片描述仍然位于 text block。使用 base64 时,把 source.type 改为 base64,并提供 media_type 和 data。
常见 stop_reason 包括:
end_turn:模型正常结束当前轮次。
max_tokens:达到请求的输出上限,结果可能不完整。
stop_sequence:命中了自定义停止序列。
tool_use:模型要求应用执行工具。
pause_turn:服务端工具或长任务暂时暂停,应按文档继续该轮。
refusal:模型拒绝请求。
model_context_window_exceeded:生成到模型上下文允许的边界。
应用要根据停止原因进入不同分支,而不是只判断 content 是否非空。完整列表和处理建议见 Anthropic Stop reasons 文档。
接口不同,但可靠的生产实现通常都需要同一个控制循环:
flowchart TD
A[接收用户输入] --> B[加载指令和会话状态]
B --> C[调用模型 API]
C --> D{模型是否请求工具}
D -- 是 --> E[校验权限和参数]
E --> F[幂等执行工具]
F --> G[追加工具结果]
G --> C
D -- 否 --> H{上下文是否需要压缩}
H -- 是 --> I[压缩历史并保留关键状态]
H -- 否 --> J[保存完整响应]
I --> J
J --> K[向用户返回结果]
生产实现还需要处理以下共同问题:
- 保存供应商返回的完整结构,不只保存最终文本。
- 把模型输出和工具参数都当作不可信输入。
- 为工具设置权限、超时、并发限制和幂等键。
- 对 429、连接中断和 5xx 使用带抖动的指数退避;不要无条件重试业务错误。
- 流式展示和最终持久化分开处理,结束后保存完整响应,而不是只保存已经打印的 delta。
- 记录模型、请求 ID、停止原因、token 用量和工具执行结果。
- 把关键业务状态放在数据库中,模型上下文只作为工作记忆。
如果要同时支持多个供应商,不要强行把所有原始对象压成一个只有 role 和 content 的最小结构。更稳妥的做法是保留原始 provider payload,再在应用控制层定义少量统一动作,例如“追加用户输入”“执行工具”“读取最终文本”和“记录用量”。
| 维度 |
OpenAI Chat Completions |
OpenAI Responses |
Anthropic Messages |
| Endpoint |
POST /v1/chat/completions |
POST /v1/responses |
POST /v1/messages |
| 核心输出 |
choices[].message |
output[] Item |
content[] block |
| 多轮状态 |
应用重发 messages |
手动 Item、previous_response_id 或 Conversations |
应用重发 messages |
| 上下文压缩 |
应用自行摘要、裁剪或检索 |
自动 compaction 或 /responses/compact |
beta 服务端 compaction,也可应用自行处理 |
| 工具请求 |
tool_calls |
function_call Item |
tool_use block |
| 工具结果 |
tool message |
function_call_output Item |
tool_result block |
| 结构化输出 |
response_format |
text.format |
output_config.format |
| 流式文本 |
choices[].delta.content |
response.output_text.delta |
content_block_delta / text_delta |
选择时可以按现有系统的状态模型来判断:
- 已经稳定使用
messages、choices 和 tool_calls 的 OpenAI 应用,可以继续使用 Chat Completions。
- 新的 OpenAI 应用,尤其需要服务端对话状态、自动压缩、推理 Item 或托管工具时,优先使用 Responses。
- 直接调用 Claude 时使用 Anthropic Messages,并围绕 content block、无状态多轮和
stop_reason 设计控制循环。
多供应商适配的边界应放在状态协议,而不是几个文本字段。先完整保存各自的消息、Item 或 content block,再在应用层统一工具执行、重试、预算和持久化;这能避免过早的统一抽象丢失供应商特有的状态。