L'API Doubao propose une interface compatible OpenAI, mais « compatible » ne signifie pas « copier-coller ». Trois points bloquent le plus souvent : une URL de base écrite avec /v1, un champ model rempli avec le nom d'affichage au lieu de l'ID de modèle ou de l'ID d'endpoint, et l'activation de la réflexion approfondie dès le premier appel, qui tronque la sortie. Cet article réalise dans l'ordre les appels curl et Python, puis détaille le streaming, les appels d'outils, les erreurs courantes et la checklist de migration.

Remarque : Doubao, Volcengine Ark, Volcengine et ByteDance sont des marques ou noms de produits de ByteDance et de ses sociétés affiliées ; OpenAI est une marque de son détenteur. Cet article n'a qu'une vocation d'explication technique et n'implique aucun lien d'affiliation, de représentation ou de caution avec ces sociétés. Les interfaces, modèles et quotas peuvent évoluer ; référez-vous à la documentation du fournisseur et à la page Tarifs des modèles.

1. Trois points à vérifier avant l'intégration

Élément Accès direct à Volcengine Ark Passerelle unifiée
URL de base https://ark.cn-beijing.volces.com/api/v3 https://ai.zhongshangqihang.com/v1
Authentification Authorization: Bearer <ARK_API_KEY> Authorization: Bearer <clé de passerelle>
model ID de modèle ou ID d'endpoint ep- Selon la liste de modèles de la plateforme
Convient à Doubao uniquement, vérification d'identité et facturation en autonomie Multi-modèles, facture et quotas unifiés

La clé et l'URL de base doivent être utilisées par paire. Une clé du fournisseur avec l'adresse de la passerelle, ou une clé de passerelle avec l'adresse du fournisseur, renvoie généralement une 401. Lors d'un changement d'entrée, remplacez les deux ensemble.

2. URL de base et chemin : /api/v3, pas /v1

L'adresse du plan de données fournie par l'éditeur est https://ark.cn-beijing.volces.com/api/v3, le chemin de complétion de chat est /chat/completions, et l'adresse complète est https://ark.cn-beijing.volces.com/api/v3/chat/completions. Elle n'a pas le préfixe /v1 habituel d'OpenAI ; ajouter un /v1 supplémentaire produit une 404.

La région figure dans le domaine (cn-beijing). Si les ressources se trouvent dans une autre région, utilisez l'adresse indiquée par la console et la documentation. La documentation signale aussi que certains cas comme le Coding Plan utilisent une URL de base différente ; une erreur de saisie peut entraîner des frais supplémentaires, vérifiez donc avant de changer d'offre.

3. Que mettre dans model : ID de modèle ou ID d'endpoint

La documentation propose deux modes d'appel :

  • Inférence préconfigurée : indiquez directement l'ID de modèle, par exemple doubao-seed-2-1-pro-260915, sans créer d'endpoint au préalable.
  • Endpoint d'inférence personnalisé : indiquez l'ID d'endpoint, généralement préfixé par ep-, adapté aux déploiements fixes, à une gestion fine ou aux modèles affinés par LoRA.

L'ID de modèle contient une date de version : n'écrivez pas un nom d'affichage comme « Doubao 2.1 Pro » et n'ajoutez pas de suffixe de mémoire. Avec une signature Access Key (AK/SK), model doit contenir l'ID d'endpoint ; avec une clé API, la documentation permet d'indiquer l'ID de modèle ou l'ID d'endpoint. Si la réponse est InvalidEndpointOrModel.ModelIDAccessDisabled, cela signifie que le compte ne permet pas l'appel direct par ID de modèle et qu'il faut créer un endpoint.

4. Requête minimale avec curl

Désactivez d'abord la réflexion approfondie pour réduire les variables. Exemple avec l'entrée du fournisseur :

export ARK_API_KEY="votre clé API Volcengine Ark"
export ARK_MODEL_ID="doubao-seed-2-1-pro-260915"

curl "https://ark.cn-beijing.volces.com/api/v3/chat/completions" \
  -H "Authorization: Bearer $ARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$ARK_MODEL_ID"'",
    "messages": [{"role": "user", "content": "Réponds seulement OK"}],
    "thinking": {"type": "disabled"},
    "max_tokens": 32
  }'

Un code 200 avec du contenu dans choices[0].message.content indique que la clé, l'adresse et le modèle sont corrects. En cas de 404, vérifiez d'abord si model contient bien le suffixe de version et s'il faut utiliser un endpoint ep- ; en cas de 401, vérifiez la clé et l'en-tête avant de soupçonner le solde.

5. Appel Python avec le SDK OpenAI

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ARK_API_KEY"],
    base_url="https://ark.cn-beijing.volces.com/api/v3",
)

resp = client.chat.completions.create(
    model=os.environ.get("ARK_MODEL_ID", "doubao-seed-2-1-pro-260915"),
    messages=[
        {"role": "system", "content": "Tu es un assistant technique francophone. Réponds de manière concise."},
        {"role": "user", "content": "Explique en trois phrases ce qu'est un endpoint d'inférence."},
    ],
    max_tokens=512,
    extra_body={"thinking": {"type": "disabled"}},
)

print(resp.choices[0].message.content)
print(resp.usage)

Le SDK OpenAI ne reconnaît pas le champ thinking, il est donc passé via extra_body ; si la passerelle ou le modèle ne le prend pas en charge, retirez simplement ce champ. Une fois l'appel réussi, examinez les tokens d'entrée et de sortie dans usage, puis fixez les timeouts, la concurrence et le budget.

6. Streaming : delta, reasoning_content et [DONE]

Avec stream=True, le serveur renvoie la réponse par blocs via SSE. L'objet de réponse est chat.completion.chunk, le texte se trouve dans choices[0].delta.content, le contenu de raisonnement dans delta.reasoning_content, et finish_reason n'apparaît que dans le dernier chunk ; la fin est signalée par data: [DONE].

stream = client.chat.completions.create(
    model=os.environ["ARK_MODEL_ID"],
    messages=[{"role": "user", "content": "Écris un exemple Python de lecture de fichier CSV"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

Les blocs de raisonnement peuvent avoir un content vide ; ne supposez pas que chaque chunk contient du texte. Un max_tokens trop faible tronque la sortie. En cas de coupure réseau, le contenu déjà reçu reste disponible : le client doit pouvoir enregistrer les résultats partiels et décider s'il faut réessayer.

7. Appels d'outils : tools, tool_calls et ordre de retour

La syntaxe de tools est proche d'OpenAI : chaque outil contient type: "function" et function, avec name, description et parameters obligatoires ; parameters utilise JSON Schema. Lorsque le modèle décide d'appeler un outil, finish_reason vaut tool_calls, et message.tool_calls[i].function.arguments est une chaîne JSON à analyser.

L'ordre de retour ne doit pas être erroné : ajoutez d'abord au dialogue le message assistant contenant tool_calls, puis ajoutez pour chaque appel un message role: "tool" avec le tool_call_id et le résultat correspondants, puis interrogez à nouveau le modèle jusqu'à ce que finish_reason ne soit plus tool_calls. parallel_tool_calls vaut true par défaut ; mettez-le à false pour limiter à un outil à la fois. En streaming, les appels d'outils incrémentaux portent un index : accumulez les arguments selon cet index.

8. Erreurs fréquentes : localiser 401, 404 et 429

HTTP Code d'erreur typique Signification Traitement
401 AuthenticationError Clé absente, invalide, ou en-tête sans Bearer Recopier la clé, vérifier l'en-tête Authorization
404 InvalidEndpointOrModel.NotFound Modèle ou endpoint inexistant, ou espace sans droit Copier l'ID complet depuis la console, vérifier région et espace
404 ModelNotOpen Modèle non activé sur le compte Activer le modèle dans la console Ark
404 InvalidEndpointOrModel.ModelIDAccessDisabled Appel direct par ID de modèle non permis Créer un endpoint personnalisé et utiliser l'ID ep-
400 InvalidParameter, MissingParameter Paramètre manquant, type erroné ou non pris en charge Supprimer ou corriger les champs selon la documentation, repartir de la requête minimale
403 AccessDenied, AccountOverdueError Droits insuffisants ou solde négatif Vérifier la clé et les droits de l'espace, régulariser l'impayé
429 RateLimitExceeded.* RPM/TPM dépassé ou pic de trafic Réduire la concurrence, backoff exponentiel, demander une hausse si nécessaire
500/503 InternalServiceError, ServerOverloaded Anomalie ou surcharge amont Attendre brièvement puis réessayer, préparer un canal de secours

9. Checklist de migration

Pour passer de l'accès direct à une passerelle, ou d'une passerelle à une autre, vérifiez dans cet ordre :

  1. Figez un lot d'invites réelles et consignez les sorties, durées et usage actuels comme référence.
  2. Remplacez en même temps base_url et api_key, jamais l'un sans l'autre.
  3. Reconfirmez la correspondance des noms de modèles sur la page Tarifs des modèles ; ne conservez pas les anciens noms.
  4. Validez le streaming : délai du premier token, delta.content, finish_reason et [DONE].
  5. Validez les appels d'outils : tools, tool_calls, retour de tool_call_id et ordre multi-tours.
  6. Vérifiez la prise en charge de thinking, reasoning_effort et autres paramètres d'extension, ainsi que leurs valeurs par défaut.
  7. Réglez les timeouts de connexion et de lecture ; ne réessayez que sur 429, 5xx et timeout, et rendez les écritures idempotentes.
  8. Basculez d'abord 5 % à 10 % du trafic, observez 3 à 7 jours et conservez l'accès direct comme canal de retour arrière.

Si vous avez besoin d'un point d'entrée unifié, créez une clé de test sur la page d'inscription ; la couverture des modèles et les règles de facturation sont indiquées sur la page Tarifs des modèles. ZSQH AI Gateway est un service tiers indépendant, sans lien d'affiliation avec ByteDance.

Questions fréquentes

Faut-il ajouter /v1 à l'URL de base ?

Non. L'entrée compatible OpenAI de Volcengine Ark est https://ark.cn-beijing.volces.com/api/v3, avec le chemin de chat /chat/completions ; ajouter /v1 produit une 404. L'adresse d'une passerelle se termine généralement par /v1 ; ne mélangez pas les deux.

Faut-il mettre l'ID de modèle ou l'ID d'endpoint commençant par ep- dans model ?

Pour l'inférence préconfigurée, mettez l'ID de modèle ; pour un endpoint personnalisé, mettez l'ID ep- ; avec une signature Access Key, l'ID d'endpoint est obligatoire. Si la réponse est ModelIDAccessDisabled, il faut créer un endpoint, et non changer la clé.

Faut-il activer la réflexion approfondie au premier appel ?

Je conseille de la désactiver d'abord et de confirmer la chaîne avec la requête la plus courte. Certains modèles Doubao activent la réflexion approfondie par défaut ; elle produit d'abord du raisonnement, et un max_tokens trop faible tronque facilement le texte final. Activez-la ensuite selon la tâche.

Pourquoi le streaming ne renvoie-t-il aucun contenu ?

Vérifiez d'abord que stream=True est bien défini, puis si vous ne lisez que delta.content en ignorant les blocs vides ; le raisonnement se trouve dans delta.reasoning_content. Vérifiez aussi max_tokens, la mémoire tampon réseau et si un proxy regroupe les événements SSE en une seule réponse.

Pourquoi faut-il renvoyer les tool_calls de l'assistant ?

Le protocole exige que le message assistant porte les tool_calls, puis que chaque message role: "tool" associe le résultat via tool_call_id. Si cette étape est sautée, le modèle peut répéter l'appel d'outil ou renvoyer une 400. En streaming, les paramètres doivent aussi être accumulés selon index.

À lire également :