spicyapiDocumentation
Contenu principal

Texte et streaming

Appelez les modèles texte au format officiel OpenAI, Anthropic ou Google Gemini, en maîtrisant conversations, outils, flux et coûts.

Choisir un modèle texte disponible

Consultez /api/v1/models?modality=text&includeSchema=1 avec authentification et ne soumettez de requête que si enabled et available valent tous deux true. Outils, messages multimodaux, champs de raisonnement et sorties structurées doivent tous être pris en charge par l’inputSchema du modèle. La compatibilité du protocole ne garantit pas toutes les fonctions pour chaque modèle.

Un même identifiant de modèle peut être appelé avec n’importe lequel des protocoles ci-dessous, quel que soit son éditeur : vous pouvez tout à fait appeler le modèle d’un autre éditeur au format Anthropic, ou un modèle qui n’est pas de Google au format Gemini.

Protocoles pris en charge

Les quatre protocoles texte partagent la même clé API, le même catalogue de modèles, ainsi que les mêmes limites de débit, la même validation des paramètres et la même facturation : seuls les formats de requête et de réponse diffèrent. Choisissez simplement celui que votre code ou votre SDK utilise déjà.

ProtocolePoint d’accèsEn-tête de la clé
OpenAI Chat CompletionsPOST /v1/chat/completionsAuthorization: Bearer
OpenAI ResponsesPOST /v1/responsesAuthorization: Bearer
Anthropic MessagesPOST /v1/messagesx-api-key ou Authorization: Bearer
Google GeminiPOST /v1beta/models/{model}:generateContentx-goog-api-key ou Authorization: Bearer
Google Gemini en streamingPOST /v1beta/models/{model}:streamGenerateContentx-goog-api-key ou Authorization: Bearer
Flux natifPOST /api/v1/jobs/streamAuthorization: Bearer

/v1 et /v1beta renvoient les réponses et les erreurs du protocole concerné, sans l’enveloppe native {code,msg,data,request_id} : un client qui lit uniquement body.data ne convient pas pour les analyser. Au format Gemini, l’identifiant du modèle figure dans le chemin de l’URL ; les barres obliques qu’il contient restent telles quelles, sans encodage.

La clé se transmet uniquement dans un en-tête de requête. x-api-key et x-goog-api-key ne valent que sur /v1 et /v1beta ; l’API native /api/v1 n’accepte que Authorization: Bearer. Le paramètre de requête ?key= est toujours refusé : une clé placée dans une URL reste dans l’historique du navigateur, sur les serveurs proxy et dans les journaux d’accès. Les SDK officiels transmettent tous la clé par en-tête et ne sont pas concernés.

Intégrer avec les SDK officiels

Seules l’URL de base et la clé changent ; pour le reste, utilisez chaque SDK comme d’habitude.

SDKURL de base
OpenAI (openai)https://api.spicyapi.ai/v1
Anthropic (anthropic)https://api.spicyapi.ai
Google GenAI (google-genai)https://api.spicyapi.ai

Les SDK Anthropic et Google ajoutent eux-mêmes /v1 ou /v1beta : leur URL de base ne comporte donc pas de chemin de version. Celle du SDK OpenAI doit, elle, inclure /v1. Les trois exemples Python ci-dessous lisent dans les variables d’environnement SPICY_API_KEY ainsi que SPICY_MODEL, choisi dans le catalogue actuel.

OpenAI
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
    model=os.environ["SPICY_MODEL"],
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)
Anthropic
import os
import anthropic

client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
    model=os.environ["SPICY_MODEL"],
    max_tokens=256,
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)
Google GenAI
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["SPICY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
    model=os.environ["SPICY_MODEL"],
    contents="Explain a rainbow in one sentence.",
)
print(response.text)

Pour éviter qu’une nouvelle tentative réseau ne soit facturée deux fois, générez une valeur Idempotency-Key par action utilisateur, conservez-la de façon persistante et transmettez-la via l’option d’en-têtes personnalisés du SDK (extra_headers pour les SDK OpenAI et Anthropic).

Correspondance des paramètres entre protocoles

Les champs de chaque protocole sont d’abord convertis vers les noms de paramètres unifiés de la plateforme, puis validés selon l’inputSchema du modèle choisi. Un paramètre que le modèle ne prend pas en charge renvoie 400 : il n’est jamais ignoré en silence pour être ensuite facturé comme si de rien n’était. Le tableau ci-dessous décrit uniquement les correspondances, « — » signifiant que le protocole n’a pas de champ équivalent ; pour savoir si un modèle accepte un paramètre et dans quelle plage de valeurs, son inputSchema fait foi.

Paramètre unifiéChat CompletionsResponsesMessagesGemini
messagesmessagesinstructions, inputsystem, messagessystemInstruction, contents
max_tokensmax_tokens, max_completion_tokensmax_output_tokensmax_tokensgenerationConfig.maxOutputTokens
temperaturetemperaturetemperaturetemperaturegenerationConfig.temperature
top_ptop_ptop_ptop_pgenerationConfig.topP
toolstoolstools (type function)tools (outils personnalisés)tools[].functionDeclarations
tool_choicetool_choicetool_choicetool_choicetoolConfig.functionCallingConfig
response_formatresponse_formattext.formatgenerationConfig.responseMimeType avec responseSchema ou responseJsonSchema
reasoning_effortreasoning_effortreasoning.effortgenerationConfig.thinkingConfig

Les champs absents du tableau sont eux aussi convertis en paramètres unifiés, puis soumis à la validation du modèle : ceux que l’inputSchema ne contient pas renvoient 400. Par exemple, si le modèle n’accepte pas stop, le stop de Chat, le stop_sequences de Messages et le generationConfig.stopSequences de Gemini renvoient tous 400. Aujourd’hui, aucun modèle de texte ne déclare seed dans son inputSchema : le seed de Chat et le generationConfig.seed de Gemini renvoient donc eux aussi 400.

Quelques exceptions :

  • Les champs qui n’ont de sens que pour la plateforme d’origine du protocole sont ignorés et n’affectent pas la génération : user, metadata et store pour Chat ; user, metadata, store et include pour Responses ; metadata pour Messages.
  • Responses ne prend pas en charge previous_response_id : envoyez l’historique complet dans input.
  • Pour Gemini, safetySettings est accepté mais reste sans effet ; cachedContent et les outils exécutés côté serveur, comme googleSearch ou codeExecution, ne sont pas pris en charge et renvoient 400. Les images peuvent être transmises par inlineData (base64) ou fileData.fileUri (URL https) ; l’inputSchema indique si le modèle accepte les images.

Envoyer un flux Chat

Définissez sur votre serveur SPICY_API_KEY, SPICY_MODEL obtenu du catalogue et un SPICY_IDEMPOTENCY_KEY conservé pour cette opération. L’exemple nécessite jq. Adaptez le prompt, mais validez toujours les champs contre le schéma courant.

jq -n --arg model "$SPICY_MODEL" '{
  model: $model,
  messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
  stream: true
}' > chat-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  https://api.spicyapi.ai/v1/chat/completions \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @chat-request.json

Envoyer une requête Gemini

Les variables d’environnement sont les mêmes qu’à la section précédente. L’identifiant du modèle figure dans l’URL et le corps de la requête ne contient pas model. Mettez l’URL entre guillemets doubles et écrivez ${SPICY_MODEL}, pour que le shell ne prenne pas le deux-points qui suit pour un modificateur de variable.

jq -n '{
  systemInstruction: {parts: [{text: "Answer in one sentence."}]},
  contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
  generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  "https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @gemini-request.json

Sans streaming, remplacez :streamGenerateContent?alt=sse par :generateContent, puis lisez la réponse dans candidates[0].content.parts[].text et la consommation dans usageMetadata. Une requête en streaming sans alt=sse renvoie un tableau JSON écrit au fil de l’eau ; les SDK officiels ajoutent alt=sse d’eux-mêmes.

Conversations et appels d’outils

Pour Chat sans flux, lisez choices[0].message ; en streaming, choices[].delta par événement SSE complet. Un fragment TCP n’est pas un objet JSON. Les arguments d’outil peuvent arriver en plusieurs morceaux : assemblez les tool_calls complets par index, puis validez les arguments avant exécution. Conservez le message assistant et renvoyez chaque résultat avec le tool_call_id correspondant. Responses exige tout l’historique dans input et refuse previous_response_id ; retournez function_call_output avec call_id. Messages utilise des blocs text, tool_use et tool_result dans content ; le format de message de Chat ne s’y applique pas tel quel.

Pour Gemini, contents alterne deux rôles, user et model. Lorsque le modèle veut appeler un outil, il renvoie une partie functionCall ; après exécution, placez le résultat dans une partie functionResponse, au sein du contenu user suivant, avec le même name que lors de l’appel.

Fin de flux, erreurs et déconnexions

ProtocoleFin normaleConsommation
Chat Completionsdata: [DONE]usage du dernier chunk
Responsesévénement response.completedresponse.usage dans l’événement
Messagesmessage_stop après message_deltausage de message_delta
Geminidernier événement portant finishReason, sans [DONE]usageMetadata

Avant de lire le flux, vérifiez le statut HTTP et Content-Type ; les erreurs arrivent dans le JSON propre à chaque protocole :

ProtocoleCorps de l’erreur
Chat Completions, Responses{"error": {"message", "type", "param", "code"}}
Messages{"type": "error", "error": {"type", "message"}}
Gemini{"error": {"code", "message", "status"}}

Les codes de statut HTTP sont les mêmes pour les quatre protocoles. Le champ status de Gemini reprend les noms d’état de Google : 400 correspond à INVALID_ARGUMENT, 401 à UNAUTHENTICATED, 402 à FAILED_PRECONDITION, 403 à PERMISSION_DENIED, 404 à NOT_FOUND, 429 à RESOURCE_EXHAUSTED et 503 à UNAVAILABLE. Dans votre code, appuyez-vous sur le code de statut et le type d’erreur, jamais sur le texte de message.

Si une erreur survient après le début du flux, Chat et Gemini envoient un événement portant un objet error, Responses un événement error et Messages event: error ; le client doit gérer ces événements d’erreur, les timeouts et les déconnexions prématurées. Désactivez la mise en tampon des réponses au niveau du proxy. Fermer le navigateur ou déclencher un AbortSignal arrête seulement la réception, sans valoir annulation ni promesse de remboursement. Si le serveur dispose d’une consommation fiable, une déconnexion volontaire du client reste facturée selon la consommation réellement produite ; sans consommation mesurée, ne considérez pas une connexion fermée comme une exécution gratuite.

Devis et facturation

Pour faire confirmer à l’utilisateur le devis exact et son plafond, vous pouvez passer par le flux natif jobs/quote → jobs/stream avec le même model/input et quoteId, expectedCost. /v1 et /v1beta ne reçoivent pas ces justificatifs de devis natifs et appliquent le tarif à l’acceptation. Le coût final vient des tâches et du relevé, pas du marqueur de fin de texte. reasoning_tokens est inclus dans completion_tokens : ne l’additionnez pas deux fois.

Autres formats de requête

Ces exemples décrivent les enveloppes. Remplacez MODEL_ID_FROM_CATALOG par un modèle actif prenant en charge les champs ; n’envoyez pas ce texte tel quel.

POST /v1/responses
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": "Explain a rainbow in one sentence.",
  "stream": false
}
POST /v1/messages
{
  "model": "MODEL_ID_FROM_CATALOG",
  "max_tokens": 256,
  "messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
  "stream": false
}

Pour poursuivre

Devis et compatibilité des protocoles · Facturation · Intégration en production · Diagnostic et reprise des requêtes

Sur cette page