AI API 协议教学:Anthropic Messages API 与 OpenAI Chat Completions API
文章目录
面向想要对接大模型 API 的开发者,系统讲解两大主流 AI API 的协议设计、请求/响应格式、工具调用机制及核心差异。
1. 概述:两套协议的定位
当前 AI 应用开发中,最主流的两套 API 协议是:
- OpenAI Chat Completions API:
POST /v1/chat/completions,GPT 系列模型的标准接口,也是行业事实标准(大量第三方模型兼容此格式) - Anthropic Messages API:
POST /v1/messages,Claude 系列模型的标准接口,设计更严格、结构更清晰
用 Java 类比:OpenAI 的协议像是 Servlet 规范——宽松、灵活、兼容性强;Anthropic 的协议像是 Spring WebFlux 的严格类型约束——更规范但限制更多。
2. 认证方式
OpenAI
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer sk-xxxxxxxx" \
-H "Content-Type: application/json"
使用标准的 Bearer Token 认证,API Key 以 sk- 开头。
Anthropic
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: sk-ant-xxxxxxxx" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json"
使用自定义 Header x-api-key 认证,且必须指定 anthropic-version 版本号。
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 认证方式 | Authorization: Bearer <key> | x-api-key: <key> |
| 版本控制 | URL 路径(/v1/) | Header(anthropic-version) |
| Key 前缀 | sk- | sk-ant- |
3. 请求体格式
3.1 OpenAI 请求
{
"model": "gpt-4o",
"messages": [
{ "role": "system", "content": "你是一个有帮助的助手" },
{ "role": "user", "content": "你好,请介绍一下自己" }
],
"temperature": 0.7,
"max_tokens": 1000
}
3.2 Anthropic 请求
{
"model": "claude-sonnet-4-20250514",
"system": "你是一个有帮助的助手",
"messages": [
{
"role": "user",
"content": [{ "type": "text", "text": "你好,请介绍一下自己" }]
}
],
"temperature": 0.7,
"max_tokens": 1024
}
3.3 关键差异
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 系统提示(System Prompt) | 放在 messages 数组中,role: "system" | 独立的顶层 system 字段 |
| 消息角色 | system、user、assistant、tool | 仅 user、assistant |
content 格式 | 字符串(简单场景)或数组(多模态) | 始终是数组,每个元素有 type 字段 |
max_tokens | 可选,有默认值 | 必填,不传会报错 |
| 消息顺序约束 | 无强制交替 | 严格交替,且第一条必须是 user |
| 停止词 | stop | stop_sequences |
| 特有参数 | presence_penalty、frequency_penalty | top_k |
Anthropic 的严格约束是初学者最容易踩的坑:
// ❌ 错误:content 不能是字符串
{ "role": "user", "content": "你好" }
// ✅ 正确:content 必须是数组
{ "role": "user", "content": [{ "type": "text", "text": "你好" }] }
// ❌ 错误:第一条消息不能是 assistant
"messages": [
{ "role": "assistant", "content": [...] },
{ "role": "user", "content": [...] }
]
// ✅ 正确:必须以 user 开头,严格交替
"messages": [
{ "role": "user", "content": [...] },
{ "role": "assistant", "content": [...] },
{ "role": "user", "content": [...] }
]
注:Anthropic Python SDK 在传入字符串时会自动包装为数组,但直接调用 HTTP API 时必须手动处理。
4. 响应体格式
4.1 OpenAI 响应
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!我是一个AI助手,很高兴为你服务。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 15,
"total_tokens": 35
}
}
4.2 Anthropic 响应
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-20250514",
"content": [
{
"type": "text",
"text": "你好!我是一个AI助手,很高兴为你服务。"
}
],
"stop_reason": "end_turn",
"usage": {
"input_tokens": 20,
"output_tokens": 15
}
}
4.3 响应结构对比
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 回复内容位置 | choices[0].message.content | content[0].text |
| 嵌套层级 | 深(choices → message → content) | 浅(顶层 content 数组) |
| content 类型 | 字符串 | 数组(每个元素有 type) |
| 结束原因 | finish_reason(值:stop、length、tool_calls) | stop_reason(值:end_turn、max_tokens、tool_use) |
| token 统计 | prompt_tokens + completion_tokens | input_tokens + output_tokens |
| 支持多选项 | 是(n 参数,多个 choices) | 否(始终返回一个结果) |
| 判断依据 | 存在 choices 字段 | 存在 "type": "message" |
用 Java 类比:OpenAI 的响应像是 List<Result> 包装(即使只有一个结果也用数组),Anthropic 的响应更像直接返回单个对象。
4.4 为什么响应体要设计成数组?
初学者常有疑问:既然大模型通常只返回一条回复,为什么 OpenAI 用 choices[] 数组、Anthropic 用 content[] 数组?这两个数组的设计动机其实完全不同。
OpenAI 的 choices[]:多候选方案
OpenAI 的请求参数中有一个 n 参数(默认为 1),表示"让模型一次生成 n 个独立的回复候选"。当 n > 1 时,choices 数组中就会有多个元素:
// 请求:n=3,让模型生成 3 个候选回复
{
"model": "gpt-4o",
"n": 3,
"messages": [{"role": "user", "content": "给我的咖啡店起个名字"}]
}
// 响应:choices 中有 3 个独立的完整回复
{
"choices": [
{ "index": 0, "message": { "content": "「晨光咖啡」" }, "finish_reason": "stop" },
{ "index": 1, "message": { "content": "「慢时光」" }, "finish_reason": "stop" },
{ "index": 2, "message": { "content": "「豆语」" }, "finish_reason": "stop" }
]
}
每个 choice 是一个完整且独立的回复,它们之间互不关联。这个设计适用于需要多样性的场景,比如让模型提供多个创意方案供用户挑选。即使 n=1(绝大多数情况),响应仍然是数组格式,只是数组中只有一个元素。
Anthropic 的 content[]:多内容块组合
Anthropic 的设计思路完全不同。它不支持 n 参数(永远只返回一条回复),但一条回复内部可以包含多个不同类型的内容块:
// 当模型同时返回文本 + 工具调用时
{
"content": [
{ "type": "text", "text": "我来帮你查一下天气。" },
{ "type": "tool_use", "id": "toolu_01A", "name": "get_weather", "input": {"city": "北京"} }
]
}
// 当模型返回多段文本(如思考过程 + 最终回答)时
{
"content": [
{ "type": "thinking", "thinking": "用户问的是..." },
{ "type": "text", "text": "答案是42。" }
]
}
这些内容块共同组成一条回复的不同部分,它们是有序的、互相关联的。用 Java 类比:OpenAI 的 choices 像 List<Response>(多个独立回复),Anthropic 的 content 像 List<ContentBlock>(一个回复内的多个组成部分)。
对比总结
| 维度 | OpenAI choices[] | Anthropic content[] |
|---|---|---|
| 设计目的 | 支持一次请求返回多个候选回复 | 支持一条回复包含多种内容类型 |
| 触发条件 | 设置 n > 1 参数 | 模型决定混合输出(文本+工具调用等) |
| 元素关系 | 互相独立,可任选其一 | 有序组合,共同构成完整回复 |
| 常见场景 | 创意生成、A/B 测试 | 工具调用、思维链、多模态输出 |
| 默认情况 | n=1,数组中只有 1 个元素 | 纯文本时,数组中只有 1 个 text 块 |
实际开发中的处理方式
# OpenAI:通常只取第一个 choice
reply = response.choices[0].message.content
# 如果用了 n>1,可以遍历所有候选
for choice in response.choices:
print(f"候选 {choice.index}: {choice.message.content}")
# Anthropic:需要遍历 content 块,按类型分别处理
for block in response.content:
if block.type == "text":
print(block.text)
elif block.type == "tool_use":
# 执行工具调用
result = call_tool(block.name, block.input)
一句话总结:OpenAI 的数组是"多个平行世界的回答",Anthropic 的数组是"一个回答的多个组成零件"。
5. 流式响应(Streaming)
两者都支持 Server-Sent Events(SSE)流式输出,但事件格式不同。
5.1 OpenAI 流式
请求时加 "stream": true,响应为 SSE 事件流:
data: {"id":"chatcmpl-abc","choices":[{"delta":{"role":"assistant"},"index":0}]}
data: {"id":"chatcmpl-abc","choices":[{"delta":{"content":"你"},"index":0}]}
data: {"id":"chatcmpl-abc","choices":[{"delta":{"content":"好"},"index":0}]}
data: [DONE]
关键点:增量内容在 choices[0].delta.content 中,流结束标志是 data: [DONE]。
5.2 Anthropic 流式
请求时加 "stream": true,响应为带类型的 SSE 事件流:
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[],"usage":{"input_tokens":20,"output_tokens":0}}}
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_stop
data: {"type":"message_stop"}
关键点:Anthropic 的流式事件有明确的生命周期(message_start → content_block_start → delta… → content_block_stop → message_stop),结构更清晰但解析更复杂。
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 增量字段 | choices[0].delta.content | delta.text(在 content_block_delta 事件中) |
| 结束标志 | data: [DONE] | event: message_stop |
| 事件类型 | 无(全是 data:) | 有明确的 event: 类型标签 |
| 生命周期 | 扁平(一连串 delta) | 分层(message → content_block → delta) |
6. 工具调用(Tool Use / Function Calling)
工具调用是构建 AI Agent 的核心能力。两者的设计思路差异最大。
6.1 工具定义
OpenAI:
{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" }
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}
Anthropic:
{
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的天气",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" }
},
"required": ["city"]
}
}
],
"tool_choice": { "type": "auto" }
}
差异:OpenAI 多一层 function 包装和 type: "function" 声明;Anthropic 更扁平,参数 schema 字段名为 input_schema(而非 parameters)。
6.2 模型返回工具调用
OpenAI 模型决定调用工具时:
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}]
},
"finish_reason": "tool_calls"
}]
}
注意:arguments 是 JSON 字符串(不是对象),需要自己 JSON.parse()。
Anthropic 模型决定调用工具时:
{
"content": [
{ "type": "text", "text": "好的,我来查询北京的天气。" },
{
"type": "tool_use",
"id": "toolu_01AbC",
"name": "get_weather",
"input": { "city": "北京" }
}
],
"stop_reason": "tool_use"
}
注意:input 直接是 JSON 对象(不需要额外解析),且工具调用和文本回复可以共存在同一个 content 数组中。
6.3 回传工具执行结果
这是两者差异最大的地方。
OpenAI:使用独立的 tool 角色:
{
"messages": [
{ "role": "user", "content": "北京天气怎么样?" },
{
"role": "assistant",
"content": null,
"tool_calls": [{ "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"北京\"}" } }]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temperature\": \"22°C\", \"condition\": \"晴\"}"
}
]
}
Anthropic:工具结果放在 user 消息中,类型为 tool_result:
{
"messages": [
{ "role": "user", "content": [{ "type": "text", "text": "北京天气怎么样?" }] },
{
"role": "assistant",
"content": [
{ "type": "text", "text": "好的,我来查询北京的天气。" },
{ "type": "tool_use", "id": "toolu_01AbC", "name": "get_weather", "input": { "city": "北京" } }
]
},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_01AbC",
"content": "{\"temperature\": \"22°C\", \"condition\": \"晴\"}"
}]
}
]
}
6.4 工具调用对比总结
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 工具调用位置 | message.tool_calls 数组 | content 数组中的 tool_use 块 |
| 参数格式 | JSON 字符串(需 parse) | JSON 对象(直接可用) |
| 结果回传角色 | 独立的 role: "tool" | 放在 role: "user" 中,类型为 tool_result |
| 关联 ID 字段 | tool_call_id | tool_use_id |
| 结束标志 | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| 文本与工具共存 | 不能(content 为 null) | 可以(同一 content 数组中) |
Anthropic 的设计哲学是:一切都是 content block。文本是 content block,工具调用是 content block,工具结果也是 content block。这种统一的结构在处理复杂的多工具调用场景时更一致,但初学者需要适应"工具结果伪装成 user 消息"这个设计。
7. 多模态(图片输入)
7.1 OpenAI
{
"model": "gpt-4o",
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "这张图片里有什么?" },
{
"type": "image_url",
"image_url": {
"url": "https://example.com/image.jpg"
}
}
]
}]
}
支持 URL 和 base64 两种方式,base64 格式为 data:image/jpeg;base64,/9j/4AAQ...。
7.2 Anthropic
{
"model": "claude-sonnet-4-20250514",
"messages": [{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQ..."
}
},
{ "type": "text", "text": "这张图片里有什么?" }
]
}],
"max_tokens": 1024
}
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 图片类型标识 | "type": "image_url" | "type": "image" |
| URL 支持 | 直接传 URL | 需要通过 "type": "url" 的 source |
| Base64 格式 | data URI 字符串 | 结构化对象(media_type + data 分开) |
| 支持格式 | JPEG、PNG、GIF、WebP | JPEG、PNG、GIF、WebP |
8. Python SDK 使用示例
8.1 OpenAI SDK
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是一个有帮助的助手"},
{"role": "user", "content": "你好"}
],
temperature=0.7,
max_tokens=1000
)
# 获取回复
print(response.choices[0].message.content)
8.2 Anthropic SDK
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-xxx")
response = client.messages.create(
model="claude-sonnet-4-20250514",
system="你是一个有帮助的助手",
messages=[
{"role": "user", "content": "你好"}
],
temperature=0.7,
max_tokens=1024
)
# 获取回复
print(response.content[0].text)
注意:使用 SDK 时,Anthropic SDK 会自动处理 content 的数组包装,你可以直接传字符串。但理解底层协议格式对于调试和理解 Agent 框架源码至关重要。
9. Agent Loop 中的协议应用
理解这两套协议对于构建 AI Agent 至关重要。Agent 的核心循环是:
用户输入 → 调用 LLM → 模型决定调用工具 → 执行工具 → 把结果回传给 LLM → 模型生成最终回复
用代码表示(以 Anthropic 为例):
import anthropic
client = anthropic.Anthropic()
messages = []
tools = [...] # 工具定义
# Agent Loop
def agent_loop(user_input: str) -> str:
messages.append({"role": "user", "content": user_input})
while True:
# 1. 调用模型
response = client.messages.create(
model="claude-sonnet-4-20250514",
system="你是一个有帮助的助手,可以使用工具完成任务。",
messages=messages,
tools=tools,
max_tokens=4096
)
# 2. 把模型回复加入历史
messages.append({"role": "assistant", "content": response.content})
# 3. 判断是否需要调用工具
if response.stop_reason == "tool_use":
# 找到所有工具调用
tool_results = []
for block in response.content:
if block.type == "tool_use":
# 4. 执行工具
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# 5. 把工具结果回传(作为 user 消息)
messages.append({"role": "user", "content": tool_results})
# 继续循环,让模型处理工具结果
else:
# 6. 模型生成了最终回复,退出循环
final_text = ""
for block in response.content:
if hasattr(block, "text"):
final_text += block.text
return final_text
这就是你正在学习的 s01_agent_loop/code.py 的核心逻辑。理解了协议格式,就能理解为什么 Agent 代码要这样组织消息。
10. 设计哲学对比总结
| 维度 | OpenAI | Anthropic |
|---|---|---|
| 设计风格 | 宽松灵活,向后兼容 | 严格规范,结构清晰 |
| 消息模型 | messages 是统一的时序容器,角色丰富 | 分离系统指令与对话,角色精简 |
| content 设计 | 简单场景用字符串,复杂场景用数组 | 统一用数组,一切皆 content block |
| 工具调用 | 独立角色 + 独立字段 | 嵌入 content 数组,保持消息交替 |
| 行业地位 | 事实标准,大量第三方兼容 | 独立协议,生态较小但设计更现代 |
| 适合场景 | 快速接入、多模型切换 | 深度集成、Agent 开发 |
对于 Agent 开发者的建议:两套协议都要熟悉。OpenAI 格式是行业通用语言,很多开源框架(LangChain、LlamaIndex)默认使用;Anthropic 格式在 Agent 场景下的 tool_use 设计更优雅,Claude 模型在长上下文和工具调用方面表现出色。实际项目中,可以使用 LiteLLM 或 Portkey 等代理层统一两套协议。
参考来源
更多推荐


所有评论(0)