Skip to content

Claude Messages 协议

Claude Messages 是 Anthropic 的原生对话协议。如果你的客户端已经按 Anthropic 规范开发,把 Base URL 和 API Key 换成 RouteAPI 即可直接使用,不需要改写请求结构。

Claude Messages 用一个 messages 数组表达多轮对话,用独立的 system 字段表达系统提示词,并要求显式声明 max_tokens。相比 OpenAI 兼容格式,它的内容块(content block)结构更统一:文本、图像、工具调用、工具结果都是同一个数组里的不同 type。

适用场景:

场景说明
Claude CodeAnthropic 官方编码代理,只认 /v1/messages
Anthropic SDKanthropic Python / TypeScript SDK,改 base_url 即可
原生消息格式客户端已经按 content block 结构组织提示词的应用
扩展思考与提示缓存依赖 thinking、cache_control 等 Claude 特有能力

如果你的客户端只支持 OpenAI 协议,请改用 Chat Completions。RouteAPI 会在内部完成必要的格式适配,但优先选择客户端原生支持的协议,兼容性最好。

POST /v1/messages

完整地址:

https://api.routeapi.ai/v1/messages

请求头支持两种鉴权写法,都使用同一个 RouteAPI Token:

Authorization: Bearer sk-your-routeapi-token
Content-Type: application/json
x-api-key: sk-your-routeapi-token
anthropic-version: 2023-06-01
Content-Type: application/json

x-api-key 是 Anthropic SDK 的默认写法,RouteAPI 在 /v1/messages 路径上会自动把它识别为 Token,所以官方 SDK 无需额外配置。anthropic-version 会原样透传给上游,官方 SDK 会自动带上。

字段类型必填说明
modelstring是模型 ID,必须是当前账户可用模型
messagesarray是对话消息列表,至少一条,role 需交替出现
max_tokensinteger是最大输出 token 数,Claude 协议强制要求
systemstring/array否系统提示词,独立字段,不放在 messages 里
temperaturenumber否采样温度,取值 0 到 1
top_pnumber否nucleus sampling 参数
top_kinteger否只从概率最高的 K 个 token 中采样
streamboolean否是否使用 SSE 流式输出
stop_sequencesarray否自定义停止序列
toolsarray否工具定义列表
tool_choiceobject否工具选择策略
thinkingobject否扩展思考配置,取决于模型是否支持
metadataobject否请求元信息,Claude 特有

这是从 OpenAI 迁移过来最容易踩的坑。OpenAI 的 max_tokens 省略时会使用模型默认上限,Claude 协议没有默认值,缺失时上游会直接返回 invalid_request_error。

{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{ "role": "user", "content": "你好" }]
}

max_tokens 是输出上限,不包含输入 token,也不是精确长度承诺:模型可能提前结束(stop_reason: "end_turn"),也可能正好被截断(stop_reason: "max_tokens")。生产环境建议按业务预期的回复长度设置,留一定余量,同时检查 stop_reason 判断是否被截断。

Claude 协议不接受 role: "system" 的消息。系统提示词必须放在请求顶层的 system 字段里,messages 数组只能包含 user 和 assistant。

正确写法:

{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "你是一个严谨的技术助手,回答保持简洁。",
"messages": [{ "role": "user", "content": "解释什么是 API 网关" }]
}

错误写法(Claude 协议会拒绝):

{
"messages": [
{ "role": "system", "content": "你是一个严谨的技术助手。" },
{ "role": "user", "content": "解释什么是 API 网关" }
]
}

system 也支持数组形式,用于给不同段落单独设置提示缓存:

{
"system": [
{ "type": "text", "text": "你是一个代码审查助手。" },
{
"type": "text",
"text": "以下是项目编码规范全文……",
"cache_control": { "type": "ephemeral" }
}
]
}

metadata 用于携带请求元信息,目前只有 user_id 一个字段,用于上游的滥用检测。不要在这里放邮箱、手机号等可识别个人身份的信息,建议传哈希值或内部 ID。

{
"metadata": {
"user_id": "a3f1c2d4e5b6"
}
}

messages 数组中每条消息包含 role 和 content 两个字段。role 只能是 user 或 assistant,且必须交替出现,第一条必须是 user。

content 支持两种形式。字符串是单文本的简写:

{ "role": "user", "content": "请用一句话介绍 RouteAPI" }

数组形式由内容块组成,每个块用 type 区分:

type出现位置说明
textuser / assistant纯文本内容
imageuser图像输入,支持 base64 和 URL
documentuser文档输入,取决于模型是否支持
tool_useassistant模型请求调用工具
tool_resultuser客户端回传的工具执行结果
thinkingassistant扩展思考内容块

图像通过 source 字段传入。base64 方式需要同时给出 media_type:

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQAAAQ..."
}
},
{ "type": "text", "text": "这张图里有哪些控件?" }
]
}

URL 方式更简洁,但要求图片地址可被上游访问:

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/screenshot.png"
}
},
{ "type": "text", "text": "描述这个界面的布局" }
]
}

把文本块放在图像块之后通常效果更好。一次请求可以放多张图,但会显著增加输入 token,建议先压缩尺寸。

Claude 的工具定义是平铺结构,参数 schema 字段叫 input_schema:

{
"tools": [
{
"name": "get_weather",
"description": "查询指定城市的当前天气。城市名使用中文全称。",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名,如 北京" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
]
}

对比 OpenAI 的嵌套结构,差异在于 Claude 没有外层 type: "function" 包装,也没有 function 嵌套层,且 parameters 改名为 input_schema:

{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气。",
"parameters": { "type": "object", "properties": {} }
}
}
]
}

description 的质量直接决定模型是否会正确选用工具,建议写清用途、参数格式和边界条件。

写法行为
{ "type": "auto" }模型自行决定是否调用工具,默认值
{ "type": "any" }必须调用工具,但由模型选择调用哪个
{ "type": "tool", "name": "get_weather" }强制调用指定工具
{ "type": "none" }禁止调用工具

追加 "disable_parallel_tool_use": true 可以限制模型单次只发起一个工具调用。

工具调用是一次完整的对话往返。模型返回 tool_use 块后,你需要把原始 assistant 消息和执行结果一起回传。

第一步,模型返回工具调用请求:

{
"role": "assistant",
"content": [
{ "type": "text", "text": "我来查一下北京的天气。" },
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "北京", "unit": "celsius" }
}
]
}

第二步,把这条 assistant 消息原样加入 messages,再追加一条 user 消息携带结果。tool_use_id 必须与上一步的 id 完全一致:

{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "北京,晴,气温 23 摄氏度,湿度 45%。"
}
]
}

工具执行失败时用 is_error 标记,让模型知道需要换策略而不是重试:

{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "天气服务超时,未获取到数据。",
"is_error": true
}

注意 tool_result 属于 user 角色,Claude 协议里没有 OpenAI 那样独立的 role: "tool"。如果模型一次返回了多个 tool_use 块,所有对应的 tool_result 必须放在同一条 user 消息的 content 数组里。

{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-5",
"content": [
{
"type": "text",
"text": "RouteAPI 是一个统一管理多家 AI 模型供应商的 API 网关。"
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 24,
"output_tokens": 18
}
}

content 始终是数组,即使只有一段文本。客户端不要假设 content[0] 就是文本块,模型开启扩展思考或发起工具调用时,第一个块可能是 thinking 或 tool_use。

stop_reason 的取值:

值含义
end_turn模型自然结束回复
max_tokens达到 max_tokens 上限被截断
stop_sequence命中 stop_sequences 中的序列
tool_use模型请求调用工具,等待结果回传

设置 stream: true 后返回 SSE。Claude 的流式格式与 OpenAI 差异较大:每个事件都有明确的 event: 类型名,结束标志是 message_stop 事件,而不是 data: [DONE]。

event: message_start
data: {"type":"message_start","message":{"id":"msg_01XFD","type":"message","role":"assistant","content":[],"model":"claude-sonnet-4-5","usage":{"input_tokens":24,"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":"RouteAPI"}}
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":18}}
event: message_stop
data: {"type":"message_stop"}

事件类型说明:

事件说明
message_start消息开始,携带初始 usage(此时 output_tokens 不准)
content_block_start一个内容块开始,index 标识位置
content_block_delta增量内容,文本用 text_delta,工具参数用 input_json_delta
content_block_stop当前内容块结束
message_delta消息级增量,携带最终 stop_reason 和累计 output_tokens
message_stop整个响应结束
ping心跳事件,可忽略
error流中途出错

工具调用的参数是逐片返回的 JSON 字符串,需要把所有 input_json_delta 的 partial_json 拼接完整后再解析:

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"city\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"北京\"}"}}

按 index 分组累积,不要在拼接过程中尝试解析中间态。

字段说明
input_tokens输入 token 数,不含缓存命中部分
output_tokens输出 token 数
cache_creation_input_tokens写入提示缓存的 token 数
cache_read_input_tokens从提示缓存读取的 token 数
server_tool_use服务端工具用量,如 web_search_requests

流式响应中 output_tokens 要以 message_delta 事件里的值为准,message_start 里的是初始占位。计费口径以控制台日志为准,实际支持的字段取决于所选模型。

Claude MessagesOpenAI Chat Completions差异说明
modelmodel一致
system(顶层字段)messages[0] 中 role: "system"位置不同,Claude 不接受 system 消息
messagesmessagesClaude 只允许 user / assistant 交替
max_tokensmax_tokens / max_completion_tokensClaude 必填,OpenAI 可选
stop_sequencesstop名称不同
temperaturetemperatureClaude 上限 1,OpenAI 上限 2
top_k无对应OpenAI 不支持
tools[].input_schematools[].function.parameters层级和字段名都不同
tool_choice: {"type":"any"}tool_choice: "required"写法不同
metadata.user_iduser位置不同
thinkingreasoning_effort控制方式不同
无对应nClaude 不支持一次生成多个候选
无对应frequency_penalty / presence_penaltyClaude 不支持
无对应response_formatClaude 用工具或提示词约束输出结构

响应结构差异:

项目Claude MessagesOpenAI Chat Completions
顶层内容content 数组choices[0].message
文本位置content[0].textchoices[0].message.content
结束原因stop_reasonfinish_reason
工具调用content 中的 tool_use 块message.tool_calls
工具结果角色user 消息中的 tool_result 块独立的 role: "tool"
输入用量usage.input_tokensusage.prompt_tokens
输出用量usage.output_tokensusage.completion_tokens
总量字段无,需自行相加usage.total_tokens
流式结束message_stop 事件data: [DONE]

从 OpenAI 迁移到 Claude Messages 时,按以下顺序检查:

  1. 把 system 消息从 messages 数组移到顶层 system 字段。
  2. 补上 max_tokens,这是必填项。
  3. 确认 messages 首条是 user,且角色严格交替,没有连续两条同角色消息。
  4. 工具定义去掉 type 和 function 包装层,parameters 改名 input_schema。
  5. 工具结果从 role: "tool" 改为 user 消息里的 tool_result 块,并对齐 tool_use_id。
  6. temperature 如果原来大于 1,需要下调到 Claude 的取值范围内。
  7. 响应解析改为遍历 content 数组按 type 分发,不要假设固定下标。
  8. 流式解析改为按 event: 类型分发,结束条件换成 message_stop。

如果改造成本较高,也可以继续用 OpenAI 协议调用 Claude 系列模型,由 RouteAPI 完成格式转换。代价是部分 Claude 特有能力(如扩展思考的完整控制、细粒度提示缓存)在 OpenAI 格式下无法完整表达。

curl:

Terminal window
curl https://api.routeapi.ai/v1/messages \
-H "x-api-key: $ROUTEAPI_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "你是一个严谨的技术助手,回答保持简洁。",
"messages": [
{ "role": "user", "content": "请用一句话介绍 RouteAPI" }
]
}'

Anthropic Python SDK,只需改 base_url:

import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["ROUTEAPI_KEY"],
base_url="https://api.routeapi.ai",
)
message = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="你是一个严谨的技术助手,回答保持简洁。",
messages=[
{"role": "user", "content": "请用一句话介绍 RouteAPI"},
],
)
print(message.content[0].text)
print(message.usage.input_tokens, message.usage.output_tokens)

base_url 填到域名即可,SDK 会自动拼接 /v1/messages。流式调用用 client.messages.stream():

with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "逐步解释什么是 API 网关"}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message()
print()
print(final.stop_reason, final.usage.output_tokens)

curl,使用 base64:

Terminal window
curl https://api.routeapi.ai/v1/messages \
-H "x-api-key: $ROUTEAPI_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "'"$(base64 -w 0 screenshot.jpg)"'"
}
},
{ "type": "text", "text": "这张图里有哪些界面控件?" }
]
}
]
}'

Python SDK:

import base64
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["ROUTEAPI_KEY"],
base_url="https://api.routeapi.ai",
)
with open("screenshot.jpg", "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
message = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": image_data,
},
},
{"type": "text", "text": "这张图里有哪些界面控件?"},
],
}
],
)
print(message.content[0].text)

完整两轮往返,包含结果回传:

import json
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["ROUTEAPI_KEY"],
base_url="https://api.routeapi.ai",
)
tools = [
{
"name": "get_weather",
"description": "查询指定城市的当前天气。城市名使用中文全称。",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如 北京"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
}
]
def get_weather(city: str, unit: str = "celsius") -> str:
# 这里替换为真实的天气服务调用
return f"{city},晴,气温 23 摄氏度,湿度 45%。"
messages = [{"role": "user", "content": "北京现在天气怎么样?"}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
# stop_reason 为 tool_use 时才需要执行工具并回传
if response.stop_reason == "tool_use":
# 原始 assistant 消息必须原样加回,否则 tool_use_id 无法对齐
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
try:
result = get_weather(**block.input)
is_error = False
except Exception as exc:
result = f"工具执行失败:{exc}"
is_error = True
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
"is_error": is_error,
}
)
# 同一轮的所有工具结果放在一条 user 消息里
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
print(response.content[0].text)

对应的 curl 第二轮请求:

Terminal window
curl https://api.routeapi.ai/v1/messages \
-H "x-api-key: $ROUTEAPI_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "查询指定城市的当前天气。",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"messages": [
{ "role": "user", "content": "北京现在天气怎么样?" },
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "北京" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "北京,晴,气温 23 摄氏度,湿度 45%。"
}
]
}
]
}'
  • 参数的实际支持程度取决于所选模型和上游服务能力,thinking、cache_control、mcp_servers 等可选能力建议先在测试环境验证。
  • 明确传入 0 或 false 的可选参数会被视为用户显式设置,不会当作缺省值丢弃。
  • 生产环境建议固定模型 ID,不要依赖临时别名或展示名称。
  • 记录每次请求的 request ID、模型 ID、状态码和 token 用量,便于排查延迟与成本异常。
  • 错误响应遵循 Claude 的 {"type": "error", "error": {...}} 结构,详见 错误处理。