Aller au contenu

Protocole Claude Messages

Claude Messages est le protocole de conversation natif d’Anthropic. Si votre client est déjà développé selon les spécifications Anthropic, il suffit de remplacer l’URL de base et la clé API par RouteAPI pour l’utiliser directement sans modifier la structure des requêtes.

Claude Messages utilise un tableau messages pour représenter les conversations multi-tours, un champ system indépendant pour les prompts système, et nécessite une déclaration explicite de max_tokens. Comparé au format compatible OpenAI, sa structure de bloc de contenu est plus unifiée : texte, images, appels d’outils et résultats d’outils sont tous des valeurs type différentes dans le même tableau.

Scénarios applicables :

ScénarioDescription
Claude CodeAgent de codage officiel d’Anthropic, reconnaît uniquement /v1/messages
Anthropic SDKSDK Python / TypeScript anthropic, il suffit de changer base_url
Clients au format de message natifApplications déjà organisées avec la structure de bloc de contenu
Pensée étendue & cache de promptDépend des capacités spécifiques à Claude comme thinking, cache_control

Si votre client ne prend en charge que le protocole OpenAI, veuillez utiliser Chat Completions. RouteAPI effectuera l’adaptation de format nécessaire en interne, mais privilégiez le protocole nativement pris en charge par le client pour une meilleure compatibilité.

POST /v1/messages

Adresse complète :

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

Les en-têtes de requête prennent en charge deux méthodes d’authentification, toutes deux utilisant le même jeton RouteAPI :

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 est la méthode par défaut pour le SDK Anthropic. RouteAPI le reconnaît automatiquement comme jeton sur le chemin /v1/messages, donc le SDK officiel ne nécessite aucune configuration supplémentaire. anthropic-version est transmis tel quel en amont, et le SDK officiel l’inclura automatiquement.

ChampTypeRequisDescription
modelstringOuiID du modèle, doit être un modèle disponible pour le compte actuel
messagesarrayOuiListe des messages de conversation, au moins un, les role doivent alterner
max_tokensintegerOuiNombre maximum de tokens de sortie, requis par le protocole Claude
systemstring/arrayNonPrompt système, champ indépendant, pas dans messages
temperaturenumberNonTempérature d’échantillonnage, plage 0 à 1
top_pnumberNonParamètre nucleus sampling
top_kintegerNonÉchantillonner uniquement à partir des K tokens les plus probables
streambooleanNonUtiliser ou non la sortie en streaming SSE
stop_sequencesarrayNonSéquences d’arrêt personnalisées
toolsarrayNonListe de définitions d’outils
tool_choiceobjectNonStratégie de sélection d’outil
thinkingobjectNonConfiguration de pensée étendue, dépend du support du modèle
metadataobjectNonMétadonnées de requête, spécifique à Claude

C’est le piège le plus courant lors de la migration depuis OpenAI. Le max_tokens d’OpenAI utilise la limite par défaut du modèle lorsqu’il est omis, le protocole Claude n’a pas de valeur par défaut, et l’amont retournera invalid_request_error s’il manque.

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

max_tokens est une limite de sortie, n’inclut pas les tokens d’entrée, et n’est pas un engagement de longueur exacte : le modèle peut finir plus tôt (stop_reason: "end_turn") ou être tronqué exactement à la limite (stop_reason: "max_tokens"). Pour la production, définissez en fonction de la longueur de réponse attendue avec une certaine marge, et vérifiez stop_reason pour déterminer si tronqué.

Le protocole Claude n’accepte pas les messages avec role: "system". Les prompts système doivent être placés dans le champ system de niveau supérieur, et le tableau messages ne peut contenir que user et assistant.

Utilisation correcte :

{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "Vous êtes un assistant technique rigoureux, gardez les réponses concises.",
"messages": [{ "role": "user", "content": "Expliquez ce qu'est une passerelle API" }]
}

Utilisation incorrecte (le protocole Claude rejettera) :

{
"messages": [
{ "role": "system", "content": "Vous êtes un assistant technique rigoureux." },
{ "role": "user", "content": "Expliquez ce qu'est une passerelle API" }
]
}

system prend également en charge la forme de tableau pour définir le cache de prompt sur différents paragraphes individuellement :

{
"system": [
{ "type": "text", "text": "Vous êtes un assistant de révision de code." },
{
"type": "text",
"text": "Voici la norme de codage complète du projet...",
"cache_control": { "type": "ephemeral" }
}
]
}

metadata transporte les méta-informations de requête, n’a actuellement qu’un champ user_id pour la détection d’abus en amont. Ne placez pas d’informations personnellement identifiables comme l’e-mail ou le numéro de téléphone ici, il est recommandé de transmettre des valeurs de hachage ou des ID internes.

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

Chaque message dans le tableau messages contient les champs role et content. role ne peut être que user ou assistant, doit alterner, et le premier doit être user.

content prend en charge deux formes. La chaîne est une abréviation pour un texte unique :

{ "role": "user", "content": "Veuillez présenter RouteAPI en une phrase" }

La forme de tableau est composée de blocs de contenu, chacun distingué par type :

typeEmplacementDescription
textuser / assistantContenu texte brut
imageuserEntrée d’image, prend en charge base64 et URL
documentuserEntrée de document, dépend du support du modèle
tool_useassistantLe modèle demande à appeler un outil
tool_resultuserRésultat d’exécution d’outil retourné par le client
thinkingassistantBloc de contenu de pensée étendue

Les images sont transmises via le champ source. La méthode base64 nécessite également de fournir media_type :

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQAAAQ..."
}
},
{ "type": "text", "text": "Quels contrôles sont dans cette image ?" }
]
}

La méthode URL est plus concise mais nécessite que l’adresse de l’image soit accessible par l’amont :

{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/screenshot.png"
}
},
{ "type": "text", "text": "Décrivez la mise en page de cette interface" }
]
}

Placer les blocs de texte après les blocs d’image fonctionne généralement mieux. Plusieurs images peuvent être incluses dans une requête, mais cela augmentera considérablement les tokens d’entrée, il est recommandé de compresser la taille d’abord.

La définition d’outil de Claude est une structure plate avec un champ de schéma de paramètre appelé input_schema :

{
"tools": [
{
"name": "get_weather",
"description": "Interroger la météo actuelle pour une ville spécifiée. Utilisez le nom complet de la ville en chinois.",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "Nom de la ville, par ex. Pékin" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
]
}

Comparé à la structure imbriquée d’OpenAI, la différence est que Claude n’a pas d’enveloppe externe type: "function", pas de couche d’imbrication function, et parameters est renommé en input_schema :

{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Interroger la météo actuelle pour une ville spécifiée.",
"parameters": { "type": "object", "properties": {} }
}
}
]
}

La qualité de description détermine directement si le modèle choisira correctement l’outil, il est recommandé d’indiquer clairement l’objectif, le format des paramètres et les conditions limites.

FormatComportement
{ "type": "auto" }Le modèle décide d’appeler ou non des outils, valeur par défaut
{ "type": "any" }Doit appeler un outil, mais le modèle choisit lequel
{ "type": "tool", "name": "get_weather" }Forcer l’appel de l’outil spécifié
{ "type": "none" }Interdire les appels d’outils

L’ajout de "disable_parallel_tool_use": true peut limiter le modèle à initier un seul appel d’outil à la fois.

L’appel d’outil est un aller-retour conversationnel complet. Après que le modèle retourne un bloc tool_use, vous devez retransmettre le message assistant original avec les résultats d’exécution.

Première étape, le modèle retourne une demande d’appel d’outil :

{
"role": "assistant",
"content": [
{ "type": "text", "text": "Laissez-moi vérifier la météo à Pékin." },
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "Pékin", "unit": "celsius" }
}
]
}

Deuxième étape, ajoutez ce message assistant tel quel à messages, puis ajoutez un message user portant le résultat. tool_use_id doit correspondre exactement à l’id de l’étape précédente :

{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Pékin, ensoleillé, température 23 celsius, humidité 45%."
}
]
}

Lorsque l’exécution de l’outil échoue, utilisez le marqueur is_error pour faire savoir au modèle qu’il doit changer de stratégie plutôt que de réessayer :

{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Délai d'attente du service météo, aucune donnée récupérée.",
"is_error": true
}

Notez que tool_result appartient au rôle user, le protocole Claude n’a pas de role: "tool" indépendant comme OpenAI. Si le modèle retourne plusieurs blocs tool_use en une fois, tous les tool_result correspondants doivent être placés dans le tableau content du même message user.

{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-5",
"content": [
{
"type": "text",
"text": "RouteAPI est une passerelle API qui gère uniformément plusieurs fournisseurs de modèles AI."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 24,
"output_tokens": 18
}
}

content est toujours un tableau, même avec un seul segment de texte. Les clients ne doivent pas supposer que content[0] est un bloc de texte ; lorsque le modèle active la pensée étendue ou initie des appels d’outils, le premier bloc peut être thinking ou tool_use.

Valeurs de stop_reason :

ValeurSignification
end_turnLe modèle a terminé naturellement la réponse
max_tokensA atteint la limite max_tokens et a été tronqué
stop_sequenceA atteint une séquence dans stop_sequences
tool_useLe modèle demande à appeler un outil, en attente du résultat

La définition de stream: true retourne SSE. Le format de streaming de Claude diffère considérablement d’OpenAI : chaque événement a un nom de type event: explicite, et le marqueur de fin est un événement message_stop, pas 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":" est une"}}
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"}

Descriptions des types d’événements :

ÉvénementDescription
message_startDébut du message, porte l’usage initial (output_tokens pas encore précis)
content_block_startUn bloc de contenu commence, index identifie la position
content_block_deltaContenu incrémental, le texte utilise text_delta, les paramètres d’outil utilisent input_json_delta
content_block_stopLe bloc de contenu actuel se termine
message_deltaIncrément au niveau du message, porte le stop_reason final et output_tokens cumulatif
message_stopLa réponse entière se termine
pingÉvénement de pulsation, peut être ignoré
errorErreur survenue au milieu du flux

Les paramètres d’appel d’outil sont retournés sous forme de chaînes JSON pièce par pièce, il faut concaténer tous les partial_json d’input_json_delta avant l’analyse :

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":"\"Pékin\"}"}}

Regrouper et accumuler par index, ne pas essayer d’analyser les états intermédiaires pendant la concaténation.

ChampDescription
input_tokensNombre de tokens d’entrée, exclut la partie d’accès au cache
output_tokensNombre de tokens de sortie
cache_creation_input_tokensTokens écrits dans le cache de prompt
cache_read_input_tokensTokens lus depuis le cache de prompt
server_tool_useUtilisation d’outils côté serveur, par ex. web_search_requests

Dans la réponse en streaming, output_tokens doit utiliser la valeur dans l’événement message_delta, celle dans message_start est un espace réservé initial. La facturation est basée sur les journaux de la console, les champs réellement pris en charge dépendent du modèle sélectionné.

Claude MessagesOpenAI Chat CompletionsDifférence
modelmodelIdentique
system (champ de niveau supérieur)messages[0] avec role: "system"Emplacement différent, Claude rejette les messages système
messagesmessagesClaude n’autorise que l’alternance user / assistant
max_tokensmax_tokens / max_completion_tokensClaude obligatoire, OpenAI optionnel
stop_sequencesstopNom différent
temperaturetemperatureLimite Claude 1, limite OpenAI 2
top_kPas d’équivalentOpenAI ne prend pas en charge
tools[].input_schematools[].function.parametersHiérarchie et nom de champ différents
tool_choice: {"type":"any"}tool_choice: "required"Format différent
metadata.user_iduserEmplacement différent
thinkingreasoning_effortMéthode de contrôle différente
Pas d’équivalentnClaude ne prend pas en charge la génération de plusieurs candidats
Pas d’équivalentfrequency_penalty / presence_penaltyClaude ne prend pas en charge
Pas d’équivalentresponse_formatClaude utilise des outils ou des prompts pour contraindre la structure de sortie

Différences de structure de réponse :

ÉlémentClaude MessagesOpenAI Chat Completions
Contenu de niveau supérieurTableau contentchoices[0].message
Emplacement du textecontent[0].textchoices[0].message.content
Raison d’arrêtstop_reasonfinish_reason
Appels d’outilsBlocs tool_use dans contentmessage.tool_calls
Rôle du résultat de l’outilBloc tool_result dans message userrole: "tool" indépendant
Utilisation d’entréeusage.input_tokensusage.prompt_tokens
Utilisation de sortieusage.output_tokensusage.completion_tokens
Champ totalAucun, doit sommer manuellementusage.total_tokens
Fin du streamingÉvénement message_stopdata: [DONE]

Lors de la migration d’OpenAI vers Claude Messages, vérifiez dans cet ordre :

  1. Déplacez le message système du tableau messages vers le champ system de niveau supérieur.
  2. Ajoutez max_tokens, c’est obligatoire.
  3. Confirmez que le premier message dans messages est user, et que les rôles alternent strictement sans messages consécutifs de même rôle.
  4. Supprimez les couches d’enveloppe type et function des définitions d’outils, renommez parameters en input_schema.
  5. Changez les résultats d’outils de role: "tool" à bloc tool_result dans message user, et alignez tool_use_id.
  6. Si temperature était à l’origine supérieur à 1, ajustez-le à la plage de valeurs de Claude.
  7. Changez l’analyse de réponse pour itérer à travers le tableau content et répartir par type, ne supposez pas d’indices fixes.
  8. Changez l’analyse de streaming pour répartir par type event:, remplacez la condition de fin par message_stop.

Si le coût de refonte est élevé, vous pouvez continuer à utiliser le protocole OpenAI pour appeler les modèles de la série Claude, avec RouteAPI effectuant la conversion de format. Le compromis est que certaines capacités spécifiques à Claude (comme le contrôle complet de la pensée étendue, le cache de prompt à grain fin) ne peuvent pas être entièrement exprimées dans le format OpenAI.

curl :

Fenêtre de terminal
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": "Vous êtes un assistant technique rigoureux, gardez les réponses concises.",
"messages": [
{ "role": "user", "content": "Veuillez présenter RouteAPI en une phrase" }
]
}'

SDK Python Anthropic, changez simplement 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="Vous êtes un assistant technique rigoureux, gardez les réponses concises.",
messages=[
{"role": "user", "content": "Veuillez présenter RouteAPI en une phrase"},
],
)
print(message.content[0].text)
print(message.usage.input_tokens, message.usage.output_tokens)

base_url n’a besoin que du domaine, le SDK ajoutera automatiquement /v1/messages. Les appels en streaming utilisent client.messages.stream() :

with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Expliquez étape par étape ce qu'est une passerelle 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, en utilisant base64 :

Fenêtre de terminal
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": "Quels contrôles UI sont dans cette image ?" }
]
}
]
}'

SDK Python :

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": "Quels contrôles UI sont dans cette image ?"},
],
}
],
)
print(message.content[0].text)

Aller-retour complet sur deux tours, incluant la transmission du résultat :

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": "Interroger la météo actuelle pour une ville spécifiée. Utilisez le nom complet de la ville en chinois.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Nom de la ville, par ex. Pékin"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
}
]
def get_weather(city: str, unit: str = "celsius") -> str:
# Remplacez par un appel de service météo réel
return f"{city}, ensoleillé, température 23 celsius, humidité 45%."
messages = [{"role": "user", "content": "Quel temps fait-il à Pékin maintenant ?"}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
# N'a besoin d'exécuter l'outil et de retransmettre que lorsque stop_reason est tool_use
if response.stop_reason == "tool_use":
# Le message assistant original doit être rajouté tel quel, sinon tool_use_id ne peut pas s'aligner
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"Échec de l'exécution de l'outil : {exc}"
is_error = True
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
"is_error": is_error,
}
)
# Tous les résultats d'outils du même tour vont dans un 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)

Requête curl du deuxième tour correspondante :

Fenêtre de terminal
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": "Interroger la météo actuelle pour une ville spécifiée.",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"messages": [
{ "role": "user", "content": "Quel temps fait-il à Pékin maintenant ?" },
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "Pékin" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Pékin, ensoleillé, température 23 celsius, humidité 45%."
}
]
}
]
}'
  • Le support réel des paramètres dépend du modèle sélectionné et des capacités du service en amont, les capacités optionnelles comme thinking, cache_control, mcp_servers doivent être vérifiées dans l’environnement de test en premier.
  • Les paramètres optionnels explicitement passés comme 0 ou false seront traités comme des paramètres explicites de l’utilisateur, ne seront pas jetés comme valeurs par défaut.
  • L’environnement de production devrait fixer l’ID du modèle, ne pas compter sur des alias temporaires ou des noms d’affichage.
  • Enregistrez l’ID de requête, l’ID de modèle, le code d’état et l’utilisation de tokens pour chaque requête afin de faciliter le dépannage des anomalies de latence et de coût.
  • Les réponses d’erreur suivent la structure {"type": "error", "error": {...}} de Claude, voir Gestion des erreurs pour plus de détails.