Bỏ qua để đến nội dung

Giao thức Claude Messages

Claude Messages là giao thức hội thoại gốc của Anthropic. Nếu client của bạn đã được phát triển theo thông số kỹ thuật Anthropic, chỉ cần thay Base URL và API Key bằng RouteAPI để sử dụng trực tiếp mà không cần sửa đổi cấu trúc yêu cầu.

Claude Messages sử dụng mảng messages để biểu diễn cuộc hội thoại nhiều lượt, trường system độc lập cho system prompt và yêu cầu khai báo rõ ràng max_tokens. So với định dạng tương thích OpenAI, cấu trúc content block của nó thống nhất hơn: văn bản, hình ảnh, lời gọi công cụ và kết quả công cụ đều là các giá trị type khác nhau trong cùng một mảng.

Các tình huống áp dụng:

Tình huốngMô tả
Claude CodeAgent code chính thức của Anthropic, chỉ nhận diện /v1/messages
Anthropic SDKSDK Python / TypeScript anthropic, chỉ cần thay đổi base_url
Client định dạng message gốcCác ứng dụng đã tổ chức với cấu trúc content block
Extended thinking & prompt cachingPhụ thuộc vào khả năng đặc trưng của Claude như thinking, cache_control

Nếu client của bạn chỉ hỗ trợ giao thức OpenAI, vui lòng sử dụng Chat Completions. RouteAPI sẽ hoàn thành chuyển đổi định dạng cần thiết bên trong, nhưng ưu tiên chọn giao thức được client hỗ trợ gốc để có khả năng tương thích tốt nhất.

POST /v1/messages

Địa chỉ đầy đủ:

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

Header yêu cầu hỗ trợ hai phương thức xác thực, cả hai đều sử dụng cùng một 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 là phương thức mặc định cho Anthropic SDK. RouteAPI tự động nhận diện nó là Token trên đường dẫn /v1/messages, do đó SDK chính thức không yêu cầu cấu hình bổ sung. anthropic-version được truyền nguyên vẹn lên upstream và SDK chính thức sẽ tự động thêm vào.

TrườngKiểuBắt buộcMô tả
modelstringCóID model, phải là model khả dụng cho tài khoản hiện tại
messagesarrayCóDanh sách message hội thoại, ít nhất một, role phải xen kẽ
max_tokensintegerCóSố token đầu ra tối đa, bắt buộc theo giao thức Claude
systemstring/arrayKhôngSystem prompt, trường độc lập, không trong messages
temperaturenumberKhôngNhiệt độ lấy mẫu, phạm vi từ 0 đến 1
top_pnumberKhôngTham số nucleus sampling
top_kintegerKhôngChỉ lấy mẫu từ K token có xác suất cao nhất
streambooleanKhôngCó sử dụng đầu ra streaming SSE không
stop_sequencesarrayKhôngChuỗi dừng tùy chỉnh
toolsarrayKhôngDanh sách định nghĩa công cụ
tool_choiceobjectKhôngChiến lược lựa chọn công cụ
thinkingobjectKhôngCấu hình extended thinking, tùy thuộc vào hỗ trợ của model
metadataobjectKhôngMetadata yêu cầu, đặc trưng của Claude

Đây là cái bẫy phổ biến nhất khi di chuyển từ OpenAI. max_tokens của OpenAI sử dụng giới hạn mặc định của model khi bỏ qua, giao thức Claude không có giá trị mặc định và upstream sẽ trả về invalid_request_error khi thiếu.

{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{ "role": "user", "content": "Xin chào" }]
}

max_tokens là giới hạn đầu ra, không bao gồm token đầu vào và không phải là cam kết độ dài chính xác: model có thể kết thúc sớm (stop_reason: "end_turn") hoặc bị cắt bỏ đúng tại giới hạn (stop_reason: "max_tokens"). Đối với môi trường production, đặt dựa trên độ dài phản hồi dự kiến với một số dư và kiểm tra stop_reason để xác định có bị cắt bỏ không.

Giao thức Claude không chấp nhận message với role: "system". System prompt phải được đặt trong trường system cấp cao nhất và mảng messages chỉ có thể chứa user và assistant.

Cách dùng đúng:

{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "Bạn là một trợ lý kỹ thuật nghiêm ngặt, giữ câu trả lời ngắn gọn.",
"messages": [{ "role": "user", "content": "Giải thích API gateway là gì" }]
}

Cách dùng sai (giao thức Claude sẽ từ chối):

{
"messages": [
{ "role": "system", "content": "Bạn là một trợ lý kỹ thuật nghiêm ngặt." },
{ "role": "user", "content": "Giải thích API gateway là gì" }
]
}

system cũng hỗ trợ dạng mảng để thiết lập prompt caching riêng trên các đoạn khác nhau:

{
"system": [
{ "type": "text", "text": "Bạn là một trợ lý review code." },
{
"type": "text",
"text": "Dưới đây là toàn bộ chuẩn coding của dự án...",
"cache_control": { "type": "ephemeral" }
}
]
}

metadata mang thông tin meta của yêu cầu, hiện tại chỉ có một trường user_id để phát hiện lạm dụng ở upstream. Không đặt thông tin cá nhân nhận dạng như email hoặc số điện thoại ở đây, khuyến nghị truyền giá trị hash hoặc ID nội bộ.

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

Mỗi message trong mảng messages chứa các trường role và content. role chỉ có thể là user hoặc assistant, phải xen kẽ và cái đầu tiên phải là user.

content hỗ trợ hai dạng. Chuỗi là viết tắt cho văn bản đơn:

{ "role": "user", "content": "Vui lòng giới thiệu RouteAPI trong một câu" }

Dạng mảng bao gồm các content block, mỗi block được phân biệt bởi type:

typeVị tríMô tả
textuser / assistantNội dung văn bản thuần
imageuserĐầu vào hình ảnh, hỗ trợ base64 và URL
documentuserĐầu vào tài liệu, tùy thuộc vào hỗ trợ của model
tool_useassistantModel yêu cầu gọi công cụ
tool_resultuserKết quả thực thi công cụ được client trả về
thinkingassistantContent block extended thinking

Hình ảnh được truyền qua trường source. Phương thức base64 cũng yêu cầu cung cấp media_type:

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQAAAQ..."
}
},
{ "type": "text", "text": "Có những control nào trong hình này?" }
]
}

Phương thức URL ngắn gọn hơn nhưng yêu cầu địa chỉ hình ảnh có thể truy cập được từ upstream:

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/screenshot.png"
}
},
{ "type": "text", "text": "Mô tả layout của giao diện này" }
]
}

Đặt text block sau image block thường cho hiệu quả tốt hơn. Nhiều hình ảnh có thể được bao gồm trong một yêu cầu nhưng sẽ tăng đáng kể token đầu vào, khuyến nghị nén kích thước trước.

Định nghĩa công cụ của Claude là cấu trúc phẳng với trường schema tham số được gọi là input_schema:

{
"tools": [
{
"name": "get_weather",
"description": "Truy vấn thời tiết hiện tại cho thành phố được chỉ định. Sử dụng tên đầy đủ của thành phố bằng tiếng Trung.",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "Tên thành phố, ví dụ Bắc Kinh" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
]
}

So với cấu trúc lồng nhau của OpenAI, sự khác biệt là Claude không có wrapper type: "function" bên ngoài, không có lớp lồng function và parameters được đổi tên thành input_schema:

{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Truy vấn thời tiết hiện tại cho thành phố được chỉ định.",
"parameters": { "type": "object", "properties": {} }
}
}
]
}

Chất lượng của description trực tiếp quyết định liệu model có chọn đúng công cụ không, khuyến nghị nêu rõ mục đích, định dạng tham số và điều kiện biên.

Định dạngHành vi
{ "type": "auto" }Model tự quyết định có gọi công cụ không, giá trị mặc định
{ "type": "any" }Phải gọi công cụ nhưng model chọn cái nào
{ "type": "tool", "name": "get_weather" }Bắt buộc gọi công cụ được chỉ định
{ "type": "none" }Cấm gọi công cụ

Thêm "disable_parallel_tool_use": true có thể giới hạn model chỉ khởi tạo một lời gọi công cụ tại một thời điểm.

Gọi công cụ là một vòng hội thoại hoàn chỉnh. Sau khi model trả về block tool_use, bạn cần truyền lại message assistant gốc cùng với kết quả thực thi.

Bước một, model trả về yêu cầu gọi công cụ:

{
"role": "assistant",
"content": [
{ "type": "text", "text": "Để tôi kiểm tra thời tiết ở Bắc Kinh." },
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "Bắc Kinh", "unit": "celsius" }
}
]
}

Bước hai, thêm message assistant này nguyên vẹn vào messages, sau đó thêm một message user mang kết quả. tool_use_id phải khớp chính xác với id từ bước trước:

{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Bắc Kinh, nắng, nhiệt độ 23 độ C, độ ẩm 45%."
}
]
}

Khi thực thi công cụ thất bại, sử dụng marker is_error để cho model biết cần thay đổi chiến lược thay vì thử lại:

{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Dịch vụ thời tiết timeout, không lấy được dữ liệu.",
"is_error": true
}

Lưu ý tool_result thuộc về role user, giao thức Claude không có role: "tool" độc lập như OpenAI. Nếu model trả về nhiều block tool_use cùng lúc, tất cả tool_result tương ứng phải được đặt trong mảng content của cùng một message user.

{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-5",
"content": [
{
"type": "text",
"text": "RouteAPI là một API gateway quản lý thống nhất nhiều nhà cung cấp model AI."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 24,
"output_tokens": 18
}
}

content luôn là mảng ngay cả khi chỉ có một đoạn văn bản. Client không nên giả định content[0] là text block; khi model bật extended thinking hoặc khởi tạo lời gọi công cụ, block đầu tiên có thể là thinking hoặc tool_use.

Các giá trị stop_reason:

Giá trịÝ nghĩa
end_turnModel tự nhiên kết thúc phản hồi
max_tokensĐạt giới hạn max_tokens và bị cắt bỏ
stop_sequenceTrúng một chuỗi trong stop_sequences
tool_useModel yêu cầu gọi công cụ, đợi kết quả

Đặt stream: true trả về SSE. Định dạng streaming của Claude khác biệt đáng kể so với OpenAI: mỗi sự kiện có tên kiểu event: rõ ràng và marker kết thúc là sự kiện message_stop, không phải 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":" là một"}}
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"}

Mô tả loại sự kiện:

Sự kiệnMô tả
message_startBắt đầu message, mang usage ban đầu (output_tokens chưa chính xác)
content_block_startMột content block bắt đầu, index xác định vị trí
content_block_deltaNội dung tăng dần, văn bản dùng text_delta, tham số công cụ dùng input_json_delta
content_block_stopContent block hiện tại kết thúc
message_deltaTăng dần cấp message, mang stop_reason cuối cùng và output_tokens tích lũy
message_stopToàn bộ phản hồi kết thúc
pingSự kiện heartbeat, có thể bỏ qua
errorLỗi xảy ra giữa stream

Tham số gọi công cụ được trả về dưới dạng chuỗi JSON từng mảnh, cần nối tất cả partial_json của input_json_delta trước khi parse:

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":"\"Bắc Kinh\"}"}}

Nhóm và tích lũy theo index, không cố parse trạng thái trung gian trong quá trình nối.

TrườngMô tả
input_tokensSố lượng token đầu vào, loại trừ phần trúng cache
output_tokensSố lượng token đầu ra
cache_creation_input_tokensToken được ghi vào prompt cache
cache_read_input_tokensToken được đọc từ prompt cache
server_tool_useSử dụng công cụ phía server, ví dụ web_search_requests

Trong phản hồi streaming, output_tokens nên dùng giá trị trong sự kiện message_delta, giá trị trong message_start là placeholder ban đầu. Tính phí dựa trên log console, các trường thực sự được hỗ trợ tùy thuộc vào model được chọn.

Claude MessagesOpenAI Chat CompletionsSự khác biệt
modelmodelGiống nhau
system (trường cấp cao nhất)messages[0] với role: "system"Vị trí khác, Claude từ chối system message
messagesmessagesClaude chỉ cho phép xen kẽ user / assistant
max_tokensmax_tokens / max_completion_tokensClaude bắt buộc, OpenAI tùy chọn
stop_sequencesstopTên khác
temperaturetemperatureGiới hạn Claude 1, giới hạn OpenAI 2
top_kKhông có tương đươngOpenAI không hỗ trợ
tools[].input_schematools[].function.parametersThứ bậc và tên trường khác
tool_choice: {"type":"any"}tool_choice: "required"Định dạng khác
metadata.user_iduserVị trí khác
thinkingreasoning_effortPhương thức điều khiển khác
Không có tương đươngnClaude không hỗ trợ tạo nhiều ứng viên
Không có tương đươngfrequency_penalty / presence_penaltyClaude không hỗ trợ
Không có tương đươngresponse_formatClaude dùng công cụ hoặc prompt để ràng buộc cấu trúc đầu ra

Sự khác biệt về cấu trúc phản hồi:

MụcClaude MessagesOpenAI Chat Completions
Nội dung cấp cao nhấtMảng contentchoices[0].message
Vị trí văn bảncontent[0].textchoices[0].message.content
Lý do dừngstop_reasonfinish_reason
Gọi công cụBlock tool_use trong contentmessage.tool_calls
Role kết quả công cụBlock tool_result trong message userrole: "tool" độc lập
Sử dụng đầu vàousage.input_tokensusage.prompt_tokens
Sử dụng đầu rausage.output_tokensusage.completion_tokens
Trường tổngKhông có, phải tính thủ côngusage.total_tokens
Kết thúc streamingSự kiện message_stopdata: [DONE]

Khi di chuyển từ OpenAI sang Claude Messages, kiểm tra theo thứ tự sau:

  1. Chuyển system message từ mảng messages sang trường system cấp cao nhất.
  2. Thêm max_tokens, đây là bắt buộc.
  3. Xác nhận message đầu tiên trong messages là user và các role xen kẽ nghiêm ngặt không có message cùng role liên tiếp.
  4. Xóa các lớp wrapper type và function khỏi định nghĩa công cụ, đổi tên parameters thành input_schema.
  5. Thay đổi kết quả công cụ từ role: "tool" thành block tool_result trong message user và căn chỉnh tool_use_id.
  6. Nếu temperature ban đầu lớn hơn 1, điều chỉnh xuống phạm vi giá trị của Claude.
  7. Thay đổi parse phản hồi để duyệt mảng content và phân phối theo type, không giả định chỉ số cố định.
  8. Thay đổi parse streaming để phân phối theo kiểu event:, thay điều kiện kết thúc bằng message_stop.

Nếu chi phí tái cấu trúc cao, bạn có thể tiếp tục sử dụng giao thức OpenAI để gọi các model dòng Claude với RouteAPI thực hiện chuyển đổi định dạng. Đánh đổi là một số khả năng đặc trưng của Claude (như kiểm soát hoàn toàn extended thinking, prompt caching chi tiết) không thể được biểu đạt đầy đủ trong định dạng 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": "Bạn là một trợ lý kỹ thuật nghiêm ngặt, giữ câu trả lời ngắn gọn.",
"messages": [
{ "role": "user", "content": "Vui lòng giới thiệu RouteAPI trong một câu" }
]
}'

Anthropic Python SDK, chỉ cần thay đổi 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="Bạn là một trợ lý kỹ thuật nghiêm ngặt, giữ câu trả lời ngắn gọn.",
messages=[
{"role": "user", "content": "Vui lòng giới thiệu RouteAPI trong một câu"},
],
)
print(message.content[0].text)
print(message.usage.input_tokens, message.usage.output_tokens)

base_url chỉ cần domain, SDK sẽ tự động thêm /v1/messages. Gọi streaming dùng client.messages.stream():

with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Giải thích từng bước API gateway là gì"}],
) 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, sử dụng 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": "Có những control UI nào trong hình này?" }
]
}
]
}'

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": "Có những control UI nào trong hình này?"},
],
}
],
)
print(message.content[0].text)

Vòng hoàn chỉnh hai lượt, bao gồm truyền kết quả:

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": "Truy vấn thời tiết hiện tại cho thành phố được chỉ định. Sử dụng tên đầy đủ của thành phố bằng tiếng Trung.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Tên thành phố, ví dụ Bắc Kinh"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
}
]
def get_weather(city: str, unit: str = "celsius") -> str:
# Thay bằng lời gọi dịch vụ thời tiết thực
return f"{city}, nắng, nhiệt độ 23 độ C, độ ẩm 45%."
messages = [{"role": "user", "content": "Thời tiết ở Bắc Kinh bây giờ thế nào?"}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Chỉ cần thực thi công cụ và truyền lại khi stop_reason là tool_use
if response.stop_reason == "tool_use":
# Message assistant gốc phải được thêm lại nguyên vẹn, nếu không tool_use_id không thể căn chỉnh
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"Thực thi công cụ thất bại: {exc}"
is_error = True
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
"is_error": is_error,
}
)
# Tất cả kết quả công cụ từ cùng một lượt vào một message 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)

Yêu cầu curl lượt thứ hai tương ứng:

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": "Truy vấn thời tiết hiện tại cho thành phố được chỉ định.",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"messages": [
{ "role": "user", "content": "Thời tiết ở Bắc Kinh bây giờ thế nào?" },
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "Bắc Kinh" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Bắc Kinh, nắng, nhiệt độ 23 độ C, độ ẩm 45%."
}
]
}
]
}'
  • Hỗ trợ tham số thực tế phụ thuộc vào model được chọn và khả năng dịch vụ upstream, các khả năng tùy chọn như thinking, cache_control, mcp_servers nên được xác minh trong môi trường test trước.
  • Tham số tùy chọn được truyền rõ ràng là 0 hoặc false sẽ được coi là cài đặt rõ ràng của người dùng, không bị loại bỏ như giá trị mặc định.
  • Môi trường production nên cố định ID model, không dựa vào alias tạm thời hoặc tên hiển thị.
  • Ghi lại request ID, ID model, mã trạng thái và sử dụng token cho mỗi yêu cầu để tạo điều kiện khắc phục sự cố độ trễ và chi phí bất thường.
  • Phản hồi lỗi tuân theo cấu trúc {"type": "error", "error": {...}} của Claude, xem Xử lý lỗi để biết chi tiết.