API Responses Chat Completions API OpenAI Conception d'API

API Responses ou Chat Completions : sur laquelle bâtir

Un guide de terrain sur l'API Responses d'OpenAI et Chat Completions : forme de la requête, éléments de sortie, gestion d'état, raisonnement, outils, et quand chaque endpoint est le bon choix.

AveMujica API 10 min de lecture

Si vous démarrez une nouvelle intégration aujourd'hui, bâtissez sur l'API Responses (POST /v1/responses) et réservez Chat Completions (POST /v1/chat/completions) au code qui existe déjà et fonctionne. Les deux endpoints résolvent le même problème central — envoyer un prompt, obtenir une réponse du modèle — mais ils modélisent la conversation différemment. Chat Completions traite un tour comme une liste plate de messages et renvoie un tableau choices. Responses traite un tour comme une liste d'éléments input typés et renvoie un tableau output typé qui peut contenir côte à côte des messages, du raisonnement et des appels d'outils. Cette différence structurelle est toute l'histoire, et c'est elle qui décide quel endpoint convient à votre charge de travail.

Cet article compare les deux sur la forme de la requête, la structure de sortie, l'état, le raisonnement et les outils, puis vous donne une règle de décision applicable sans deviner. Il ne déprécie rien : Chat Completions reste pris en charge et demeure la forme la plus portable entre fournisseurs.

La forme de la requête : messages ou input

Chat Completions attend un tableau messages où chaque élément possède un role (system, user, assistant, tool) et un content. Tout ce qui concerne un tour — instructions, historique, résultats d'outils — est collé dans cette unique liste.

from openai import OpenAI
client = OpenAI(base_url="https://your-gateway.example.com/v1", api_key="AVE_GATEWAY_API_KEY")

completion = client.chat.completions.create(
    model="gpt-5.6",
    messages=[
        {"role": "system", "content": "You are a terse assistant."},
        {"role": "user", "content": "Name three uses for an API gateway."},
    ],
)
print(completion.choices[0].message.content)

Responses sépare ces préoccupations. Les consignes de haut niveau passent dans instructions, et le contenu du tour passe dans input, qui accepte soit une simple chaîne, soit un tableau d'éléments typés.

response = client.responses.create(
    model="gpt-5.6",
    instructions="You are a terse assistant.",
    input="Name three uses for an API gateway.",
)
print(response.output_text)

Deux choses méritent d'être remarquées immédiatement. Premièrement, response.output_text est un accesseur pratique qui concatène la sortie textuelle pour vous, remplaçant le chemin completion.choices[0].message.content. Deuxièmement, instructions n'est pas persistant à travers les tours chaînés — si vous poursuivez une conversation avec previous_response_id, les instructions de la réponse précédente ne sont pas reportées, vous les renvoyez donc à chaque tour.

La forme de la sortie : choices ou éléments output

Chat Completions renvoie un tableau choices. Avec le paramètre n, vous pouviez demander plusieurs complétions candidates ; chacune était un choix distinct. Responses supprime n et renvoie une seule génération, mais cette génération est un tableau output d'éléments, chacun avec son propre type. Les éléments que le modèle renvoie sont :

  • message — texte de l'assistant (et tout refus éventuel)
  • reasoning — résumés de raisonnement ou jetons de raisonnement chiffrés
  • function_call — un appel d'outil que le modèle souhaite vous voir exécuter

Notez ce qui est absent : function_call_output n'est pas un élément renvoyé par le modèle. C'est un élément d'entrée que vous construisez et renvoyez pour transmettre un résultat d'outil au modèle, indexé par call_id. Garder ces deux listes distinctes — les éléments de sortie que vous lisez, les éléments d'entrée que vous écrivez — évite une source fréquente de confusion, car l'appel et son résultat vivent sur les côtés opposés de l'échange.

Traiter chaque entrée output comme un message est l'erreur de migration la plus courante. Un modèle de raisonnement peut émettre un élément reasoning avant un message, et un tour utilisant un outil peut émettre un function_call sans aucun texte visible par l'utilisateur. Parcourez output par type au lieu de supposer que l'indice 0 est votre réponse :

for item in response.output:
    if item.type == "message":
        print(item.content[0].text)
    elif item.type == "function_call":
        print("tool wanted:", item.name, item.arguments)

La comptabilité de l'usage change aussi de vocabulaire. Chat Completions rapporte prompt_tokens et completion_tokens ; Responses rapporte input_tokens et output_tokens sur l'objet usage. Tout tableau de bord qui analyse le décompte de jetons doit gérer les deux formes, si bien qu'une passerelle qui les normalise en une seule vue comptable épargne à chaque client de porter deux chemins de code.

État : historique géré par le client ou fils stockés côté serveur

Avec Chat Completions, vous possédez la conversation : vous renvoyez l'intégralité de l'historique messages à chaque appel, et la requête transporte tout ce dont le modèle a besoin. Cette forme gérée par le client est simple à raisonner et facile à router à travers n'importe quel proxy, ce qui explique en grande partie pourquoi elle est devenue la lingua franca de l'industrie. Que OpenAI conserve ou non une copie de l'échange est une question distincte de l'endpoint — cela est régi par le réglage store et les valeurs par défaut de votre compte (qui peuvent activer le stockage à des fins de journalisation), et non par le fait que Chat Completions soit intrinsèquement « sans état ». Si vous devez garantir que rien n'est conservé en amont, envoyez store: false explicitement plutôt que de supposer que l'endpoint ne garde rien.

Les réponses sont stockées par défaut. Chaque appel renvoie un id, et vous pouvez poursuivre le fil en passant previous_response_id sur la requête suivante au lieu de renvoyer l'historique. Vous disposez de trois façons de gérer l'état :

  1. previous_response_id pour chaîner vers une réponse stockée,
  2. le renvoi manuel des éléments output antérieurs dans input,
  3. l'API Conversations pour des fils à plus longue durée de vie.

Le stockage est une commodité, pas une obligation. Définissez store: false pour vous en désengager, ce qui est souhaitable pour les workflows sans conservation de données. Un détail de facturation compte pour la planification : même lorsque vous chaînez avec previous_response_id, tous les jetons d'entrée antérieurs de la chaîne sont facturés comme des jetons d'entrée. Le chaînage vous évite de renvoyer des octets sur le réseau ; il ne rend pas le contexte antérieur gratuit.

Raisonnement : là où Responses prend l'avantage

La raison la plus claire de préférer Responses, ce sont les modèles de raisonnement. Parce que le raisonnement est un type d'élément output de première classe, l'API peut préserver l'état de raisonnement d'un tour à l'autre au lieu de le rejeter entre les appels. Pour les configurations sans état, vous pouvez demander un raisonnement chiffré en envoyant store: false en même temps que include: ["reasoning.encrypted_content"] ; l'API renvoie des jetons de raisonnement chiffrés que vous repassez au tour suivant, et le contenu est déchiffré en mémoire puis jeté. Chat Completions n'a aucun emplacement typé équivalent pour le raisonnement, si bien qu'un modèle de raisonnement perd cette continuité entre les tours.

Si votre produit dépend d'un raisonnement en plusieurs étapes, de boucles d'outils pilotées par un modèle de raisonnement, ou de longues exécutions agentiques, Responses est l'endpoint conçu pour cela. Pour un examen plus approfondi des paramètres en jeu, voir les paramètres d'API des modèles de raisonnement.

Outils et sortie structurée

Les deux endpoints prennent en charge l'appel de fonctions et la sortie structurée, mais les noms de champs diffèrent :

PréoccupationChat CompletionsResponses
Endpoint/v1/chat/completions/v1/responses
Contenu du tourmessages[]input (chaîne ou éléments)
Prompt systèmemessage systeminstructions
Texte de résultatchoices[0].message.contentoutput_text
Sortie structuréeresponse_formattext.format
Liaison du résultat d'outiltool_call_id sur un message toolfunction_call_output avec call_id
Candidats multiplesnsupprimé (génération unique)
Outils natifscâblage manueltools: [{ "type": "web_search" }]

Le passage de response_format à text.format est facile à manquer et produit des erreurs déroutantes quand vous envoyez le mauvais. Pour les Structured Outputs, définissez strict: true explicitement sur le schéma afin que le modèle y soit lié. (Un repli distinct par omission de strict — tenter l'application stricte, se dégrader en non-strict sur un schéma incompatible — s'applique aux définitions de fonctions/outils de Responses, pas aux sorties structurées text.format ; ne confondez pas les deux.) Si vous vous appuyez sur des sorties conformes à JSON Schema, lisez les sorties structurées avec JSON Schema avant de porter votre code de validation.

Comment une boucle d'outils diffère en pratique

L'aller-retour d'appel d'outil est là où les deux formes se ressentent le plus différemment, il vaut donc la peine de les voir côte à côte. Dans Chat Completions, le modèle renvoie un message d'assistant dont vous exécutez les tool_calls, puis vous ajoutez un message avec role: "tool" et un tool_call_id correspondant :

# Chat Completions: append a tool result as a message
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})

Dans Responses, le modèle émet un élément function_call, et vous renvoyez le résultat sous la forme d'un élément function_call_output lié par call_id — et non comme un message :

# Responses: append a typed function_call_output item
client.responses.create(
    model="gpt-5.6",
    previous_response_id=response.id,
    input=[{
        "type": "function_call_output",
        "call_id": call.call_id,
        "output": result,
    }],
)

La clé de liaison se nomme call_id dans Responses et tool_call_id dans Chat Completions, et envoyer un résultat sans l'id correspondant est une source fréquente d'erreurs. Parce que Responses conserve l'appel et son résultat comme des éléments typés distincts, une boucle d'agent se lit plus proprement : vous n'avez jamais à extraire l'intention d'outil de l'intérieur d'un message d'assistant. Des définitions d'outils portables entre fournisseurs nécessitent tout de même leur propre couche de compatibilité.

Une règle de décision applicable

Vous n'avez pas besoin de vous tourmenter là-dessus. Servez-vous du tableau :

SituationEndpoint
Nouvelle intégration, surtout agents ou modèles de raisonnementResponses
Code Chat Completions existant et fonctionnel, sans nouvelle exigenceConserver Chat Completions
Vous avez besoin d'une portabilité maximale entre de nombreux fournisseurs aujourd'huiChat Completions (la forme largement répliquée)
Vous voulez un état de conversation géré par le serveur ou des outils natifsResponses
Vous voulez une continuité de raisonnement chiffré sans conservationResponses avec store: false + raisonnement chiffré

Une passerelle rend ce choix moins coûteux des deux côtés. Derrière une seule URL de base, vous pouvez faire transiter du trafic Chat Completions et du trafic Responses vers les mêmes fournisseurs en amont, enregistrer les fournisseurs une seule fois, et publier ce que chaque modèle prend en charge et coûte sur une page de tarification publique. Si vous migrez plus tard un endpoint, le changement côté client est réduit et le routage reste en place. Le parcours pas à pas est couvert dans migrer Chat Completions vers l'API Responses.

Le streaming fonctionne sur les deux, avec des événements différents

Les deux endpoints diffusent via Server-Sent Events, mais le vocabulaire d'événements diffère. Chat Completions diffuse des objets chat.completion.chunk avec choices[].delta et se termine par un data: [DONE] littéral. Responses diffuse des événements sémantiques tels que response.created, response.output_text.delta et response.completed, qui sont plus faciles à router parce que chaque événement nomme ce qu'il est. Si vous construisez une interface jeton par jeton, diffuser les réponses de LLM avec SSE détaille l'analyse des deux formats.

FAQ

Chat Completions est-il déprécié maintenant que Responses existe ?

Non. Chat Completions reste un endpoint pris en charge et demeure la forme de requête la plus largement répliquée par les fournisseurs non-OpenAI. C'est l'API Assistants qu'OpenAI a marquée pour retrait, pas Chat Completions. Bâtissez vos nouveaux travaux agentiques sur Responses en raison de ses éléments de sortie structurés et de sa prise en charge du raisonnement, non parce que Chat Completions disparaîtrait.

Puis-je obtenir plusieurs réponses candidates de l'API Responses comme n dans Chat Completions ?

Pas avec un seul paramètre n — Responses renvoie une génération par appel. Si vous avez besoin de plusieurs candidats, envoyez plusieurs requêtes. C'est un vrai changement de comportement à prendre en compte si votre code Chat Completions s'appuyait sur n > 1.

previous_response_id réduit-il ma facture de jetons ?

Il réduit les octets sur le réseau parce que vous cessez de renvoyer l'historique, mais il ne réduit pas les jetons d'entrée facturés : tous les jetons d'entrée antérieurs d'une chaîne sont facturés comme des jetons d'entrée. Traitez le chaînage comme une fonctionnalité d'ergonomie et de latence, non comme une optimisation de coût. Pour réellement réduire le coût du contexte, examinez le prompt caching entre OpenAI, Claude et Gemini.

Sur quel endpoint une configuration multi-fournisseurs devrait-elle se standardiser ?

Si vous routez vers de nombreux fournisseurs, Chat Completions reste aujourd'hui le plus petit dénominateur commun le plus portable, tandis que Responses est la surface native d'OpenAI la plus riche. Une passerelle vous permet de conserver les deux : exposez la forme que chaque client parle déjà et traduisez par canal.

Faites tourner les deux sur un même workflow

Choisissez un workflow à faible risque et faites-le passer deux fois à travers votre passerelle — une fois en appel Chat Completions et une fois en appel Responses — puis comparez la forme output/choices, la comptabilité des jetons et la latence dans les journaux d'usage de votre passerelle. Cette unique comparaison côte à côte vous en dit plus qu'aucun résumé de spécification sur l'endpoint qui convient à votre code.

Références