跳到內容

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": {...}} 結構,詳見 錯誤處理。