Function Calling : Responses API face à Chat Completions
Comparaison des définitions, Call IDs, résultats, arguments streaming, autorisation, idempotence et compatibilité des routes.
Responses et Chat Completions peuvent demander à une application d’exécuter une fonction, mais représentent le tool loop différemment. Chat Completions définit les fonctions sous tools[].function, retourne message.tool_calls et reçoit les résultats sous forme de messages role: "tool". Responses utilise des définitions plates, des output items function_call et des function_call_output liés par call_id.
Le modèle n’exécute pas la fonction. Le code de l’application doit valider les arguments, autoriser l’opération, l’exécuter, retourner le résultat et empêcher la répétition d’un effet de bord lors d’un retry.
Le tool loop comporte quatre étapes
1. Déclarer une fonction autorisée et son Schema d’arguments
2. Recevoir une ou plusieurs propositions du modèle
3. Valider, autoriser et exécuter chaque appel dans l’application
4. Retourner chaque résultat avec sa correlation ID
Le modèle ne peut produire une réponse fondée sur l’outil qu’après la quatrième étape. Un nouvel outil crée un nouveau Call ID. Le guide OpenAI Function Calling décrit cet échange en plusieurs étapes.
Comparer les wire contracts
| Sujet | Responses API | Chat Completions |
|---|---|---|
| Définition | Élément plat dans tools[] |
Champs sous tools[].function |
| Appel proposé | Output item function_call |
Assistant tool_calls[] |
| Corrélation | call_id |
id, renvoyé comme tool_call_id |
| Nom | function_call.name |
tool_calls[].function.name |
| Arguments | String function_call.arguments |
String tool_calls[].function.arguments |
| Résultat | function_call_output |
Message role: "tool" |
| Streaming | Events typés | Fragments delta.tool_calls[] |
| Texte final | Output items / helper | choices[0].message.content |
Ne corrélez jamais par position dans un tableau. Les appels parallèles et les chunks peuvent se terminer dans un autre ordre. L’ID explicite est la clé stable.
Définir un Function Schema strict
{
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^ORDER-[0-9]{4}$" }
},
"required": ["order_id"],
"additionalProperties": false
}
strict: true demande au modèle compatible de respecter le Schema, mais l’application doit toujours parser et valider. Le support de pattern varie ; le même gate que pour Structured Outputs s’applique.
Implémenter le loop avec Responses API
const tools = [{
type: "function",
name: "get_delivery_status",
description: "Return the current delivery status for one order.",
parameters: {
type: "object",
properties: { order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" } },
required: ["order_id"], additionalProperties: false,
},
strict: true,
}];
const first = await client.responses.create({
model, input: "Where is ORDER-1001?", tools, parallel_tool_calls: false,
});
const calls = first.output.filter(item => item.type === "function_call");
const outputs = calls.map(call => ({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(executeTool(call.name, call.arguments)),
}));
const final = await client.responses.create({
model, input: [...first.output, ...outputs], tools, parallel_tool_calls: false,
});
Le call.call_id du modèle doit correspondre exactement au call_id du résultat. Un échange stateless renvoie les output items précédents avec les résultats. Ne supposez pas que chaque route OpenAI-compatible conserve un état de Response.
Implémenter le loop avec Chat Completions
const tools = [{
type: "function",
function: {
name: "get_delivery_status",
description: "Return the current delivery status for one order.",
parameters: {
type: "object",
properties: { order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" } },
required: ["order_id"], additionalProperties: false,
},
strict: true,
},
}];
const first = await client.chat.completions.create({ model, messages, tools });
const assistant = first.choices[0].message;
messages.push(assistant);
for (const call of assistant.tool_calls ?? []) {
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(executeTool(call.function.name, call.function.arguments)),
});
}
Le message assistant contenant les calls doit précéder les résultats dans messages. Son absence ou un tool_call_id non apparié rend le transcript invalide.
Traiter les arguments streaming comme des fragments
{"order est un JSON incomplet, pas invalide. Maintenez un buffer par Call, ajoutez les deltas, attendez Arguments Done ou Completed, parsez une fois, puis validez et exécutez. Responses utilise des events typés ; Chat Completions emploie choices[].delta.tool_calls[]. N’exécutez jamais au premier fragment. Voir le guide AI API Streaming.
Rendre l’exécution sûre et idempotente
- autoriser uniquement les fonctions enregistrées ;
- borner la taille et valider le Schema complet ;
- autoriser User ou Workload pour la ressource ;
- séparer lecture et effets de bord ;
- retirer les secrets du résultat ;
- utiliser deadline et retries downstream bornés ;
- enregistrer une référence sûre.
Un outil à effet de bord doit utiliser une idempotency key dérivée de la requête stable et du Call ID. Persistez le résultat avant de le retourner. Un replay doit renvoyer ce résultat plutôt que déclencher un second remboursement, message, deployment ou changement de base.
Le nom et les arguments produits par le modèle sont des données non fiables. Un Schema valide n’accorde pas d’autorisation et ne remplace pas une transaction.
Décider du traitement de plusieurs appels
parallel_tool_calls: false simplifie la State Machine. En parallèle, corrélez par Call ID, limitez nombre et concurrence, définissez le comportement des échecs, retournez un résultat pour chaque appel accepté et sérialisez les effets dont l’ordre compte. Le parallélisme accélère les lectures indépendantes mais complique autorisation, retry et échec partiel.
Comprendre la limite de compatibilité Modelflare
Modelflare conserve les définitions strictes et mappe les formes supportées entre Responses et Chat Completions sur les routes OpenAI/Codex éligibles. Ce mapping est volontairement plus étroit que toute la surface Tools de Responses.
Les fonctions définies par l’application sont représentables dans les deux formats. Les Hosted Tools exécutés par le fournisseur — recherche ou exécution de code — ne sont pas équivalents. Les autres familles OpenAI-compatible restent Raw Chat Completions Pass-through jusqu’à vérification ; l’upstream décide du support de tools, strict, des calls parallèles, du streaming d’arguments et de tool_choice.
| Test | Preuve requise |
|---|---|
| Appel en lecture | Nom, arguments, Call ID, liaison du résultat, texte final |
| Arguments invalides | Échec explicite sans exécution |
| Fonction inconnue | Rejet par allowlist |
| Aucun outil | Texte normal sans appel inventé |
| Streaming | Arguments reconstruits et ID correct |
| Requête répétée | Un effet ou résultat mémorisé |
| Plusieurs appels | Corrélation indépendante de l’ordre |
| Route fallback | Même modèle, protocole, Schema et contrat |
Utilisez Reliable AI API Routing pour les changements de route et AI API Key Security pour séparer workloads et permissions.
Choisir le format selon le contrat applicatif
Responses convient aux Typed Output Items, à son event model, à State Continuation et aux capacités Responses vérifiées. Chat Completions convient à un transcript stable lorsque le contrat Tools du provider est testé. Voir Responses API vs Chat Completions.
La norme reste la même : allowlist explicite, argument Schema strict, validation applicative, autorisation avant exécution, corrélation par Call ID, idempotence des effets et résultat complet retourné au modèle. La fiabilité vient de ces contrôles, pas du nom de l’endpoint.