Claude Messages 協議
Claude Messages 是 Anthropic 的原生對話協議。如果你的客戶端已經按 Anthropic 規範開發,把 Base URL 和 API Key 換成 RouteAPI 即可直接使用,不需要改寫請求結構。
Claude Messages 用一個 messages 陣列表達多輪對話,用獨立的 system 欄位表達系統提示詞,並要求顯式宣告 max_tokens。相比 OpenAI 相容格式,它的內容塊(content block)結構更統一:文字、圖像、工具呼叫、工具結果都是同一個陣列裡的不同 type。
適用場景:
| 場景 | 說明 |
|---|---|
| Claude Code | Anthropic 官方編碼代理,只認 /v1/messages |
| Anthropic SDK | anthropic 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-tokenContent-Type: application/jsonx-api-key: sk-your-routeapi-tokenanthropic-version: 2023-06-01Content-Type: application/jsonx-api-key 是 Anthropic SDK 的預設寫法,RouteAPI 在 /v1/messages 路徑上會自動把它識別為 Token,所以官方 SDK 無需額外配置。anthropic-version 會原樣透傳給上游,官方 SDK 會自動帶上。
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
model | string | 是 | 模型 ID,必須是目前帳戶可用模型 |
messages | array | 是 | 對話訊息列表,至少一條,role 需交替出現 |
max_tokens | integer | 是 | 最大輸出 token 數,Claude 協議強制要求 |
system | string/array | 否 | 系統提示詞,獨立欄位,不放在 messages 裡 |
temperature | number | 否 | 取樣溫度,取值 0 到 1 |
top_p | number | 否 | nucleus sampling 參數 |
top_k | integer | 否 | 只從機率最高的 K 個 token 中取樣 |
stream | boolean | 否 | 是否使用 SSE 串流輸出 |
stop_sequences | array | 否 | 自訂停止序列 |
tools | array | 否 | 工具定義列表 |
tool_choice | object | 否 | 工具選擇策略 |
thinking | object | 否 | 擴展思考配置,取決於模型是否支援 |
metadata | object | 否 | 請求元資訊,Claude 特有 |
max_tokens 是必填參數
Section titled “max_tokens 是必填參數”這是從 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 判斷是否被截斷。
system 是獨立欄位
Section titled “system 是獨立欄位”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
Section titled “metadata”metadata 用於攜帶請求元資訊,目前只有 user_id 一個欄位,用於上游的濫用檢測。不要在這裡放電子郵件、手機號碼等可識別個人身分的資訊,建議傳雜湊值或內部 ID。
{ "metadata": { "user_id": "a3f1c2d4e5b6" }}messages 陣列中每條訊息包含 role 和 content 兩個欄位。role 只能是 user 或 assistant,且必須交替出現,第一條必須是 user。
content 支援兩種形式。字串是單文字的簡寫:
{ "role": "user", "content": "請用一句話介紹 RouteAPI" }陣列形式由內容塊組成,每個塊用 type 區分:
| type | 出現位置 | 說明 |
|---|---|---|
text | user / assistant | 純文字內容 |
image | user | 圖像輸入,支援 base64 和 URL |
document | user | 文件輸入,取決於模型是否支援 |
tool_use | assistant | 模型請求呼叫工具 |
tool_result | user | 客戶端回傳的工具執行結果 |
thinking | assistant | 擴展思考內容塊 |
圖像透過 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,建議先壓縮尺寸。
tools 定義格式
Section titled “tools 定義格式”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 的品質直接決定模型是否會正確選用工具,建議寫清用途、參數格式和邊界條件。
tool_choice 選項
Section titled “tool_choice 選項”| 寫法 | 行為 |
|---|---|
{ "type": "auto" } | 模型自行決定是否呼叫工具,預設值 |
{ "type": "any" } | 必須呼叫工具,但由模型選擇呼叫哪個 |
{ "type": "tool", "name": "get_weather" } | 強制呼叫指定工具 |
{ "type": "none" } | 禁止呼叫工具 |
追加 "disable_parallel_tool_use": true 可以限制模型單次只發起一個工具呼叫。
工具結果傳遞
Section titled “工具結果傳遞”工具呼叫是一次完整的對話往返。模型返回 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_startdata: {"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_startdata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_deltadata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"RouteAPI"}}
event: content_block_deltadata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" 是一個"}}
event: content_block_stopdata: {"type":"content_block_stop","index":0}
event: message_deltadata: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":18}}
event: message_stopdata: {"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_deltadata: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"city\":"}}
event: content_block_deltadata: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"台北\"}"}}按 index 分組累積,不要在拼接過程中嘗試解析中間態。
usage 欄位
Section titled “usage 欄位”| 欄位 | 說明 |
|---|---|
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 裡的是初始佔位。計費口徑以控制台日誌為準,實際支援的欄位取決於所選模型。
與 OpenAI 格式對比
Section titled “與 OpenAI 格式對比”| Claude Messages | OpenAI Chat Completions | 差異說明 |
|---|---|---|
model | model | 一致 |
system(頂層欄位) | messages[0] 中 role: "system" | 位置不同,Claude 不接受 system 訊息 |
messages | messages | Claude 只允許 user / assistant 交替 |
max_tokens | max_tokens / max_completion_tokens | Claude 必填,OpenAI 可選 |
stop_sequences | stop | 名稱不同 |
temperature | temperature | Claude 上限 1,OpenAI 上限 2 |
top_k | 無對應 | OpenAI 不支援 |
tools[].input_schema | tools[].function.parameters | 層級和欄位名都不同 |
tool_choice: {"type":"any"} | tool_choice: "required" | 寫法不同 |
metadata.user_id | user | 位置不同 |
thinking | reasoning_effort | 控制方式不同 |
| 無對應 | n | Claude 不支援一次產生多個候選 |
| 無對應 | frequency_penalty / presence_penalty | Claude 不支援 |
| 無對應 | response_format | Claude 用工具或提示詞約束輸出結構 |
回應結構差異:
| 項目 | Claude Messages | OpenAI Chat Completions |
|---|---|---|
| 頂層內容 | content 陣列 | choices[0].message |
| 文字位置 | content[0].text | choices[0].message.content |
| 結束原因 | stop_reason | finish_reason |
| 工具呼叫 | content 中的 tool_use 塊 | message.tool_calls |
| 工具結果角色 | user 訊息中的 tool_result 塊 | 獨立的 role: "tool" |
| 輸入用量 | usage.input_tokens | usage.prompt_tokens |
| 輸出用量 | usage.output_tokens | usage.completion_tokens |
| 總量欄位 | 無,需自行相加 | usage.total_tokens |
| 串流結束 | message_stop 事件 | data: [DONE] |
遷移注意事項
Section titled “遷移注意事項”從 OpenAI 遷移到 Claude Messages 時,按以下順序檢查:
- 把 system 訊息從
messages陣列移到頂層system欄位。 - 補上
max_tokens,這是必填項。 - 確認
messages首條是user,且角色嚴格交替,沒有連續兩條同角色訊息。 - 工具定義去掉
type和function包裝層,parameters改名input_schema。 - 工具結果從
role: "tool"改為user訊息裡的tool_result塊,並對齊tool_use_id。 temperature如果原來大於1,需要下調到 Claude 的取值範圍內。- 回應解析改為遍歷
content陣列按type分發,不要假設固定下標。 - 串流解析改為按
event:類型分發,結束條件換成message_stop。
如果改造成本較高,也可以繼續用 OpenAI 協議呼叫 Claude 系列模型,由 RouteAPI 完成格式轉換。代價是部分 Claude 特有能力(如擴展思考的完整控制、細粒度提示快取)在 OpenAI 格式下無法完整表達。
curl:
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 osfrom 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:
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 base64import osfrom 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 jsonimport osfrom 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 第二輪請求:
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": {...}}結構,詳見 錯誤處理。