Documentation vérifiée le 10 juillet 2026.
Un appel d’outil avec DeepSeek consiste à fournir au modèle une liste de fonctions disponibles, puis à le laisser décider s’il doit demander l’exécution de l’une d’elles. Le point essentiel est que DeepSeek ne lance pas réellement la fonction : il renvoie une demande structurée, votre application valide les arguments, exécute le code côté serveur, puis renvoie le résultat au modèle pour produire la réponse finale. La documentation officielle le précise dans son exemple de Tool Calls : la fonctionnalité de l’outil doit être fournie par l’utilisateur, et le modèle n’exécute pas lui-même la fonction.
Ce guide concerne l’API officielle DeepSeek utilisée côté développeur. Il ne décrit pas le chatbot intégré à deepseek-fr.ai ni un fournisseur tiers comme Fireworks.ai.
Ce guide montre comment implémenter ce cycle correctement avec l’API DeepSeek, en évitant les erreurs fréquentes : mauvais modèle, tool_call_id absent, arguments JSON non validés, confusion entre JSON Output et Tool Calls, ou oubli de reasoning_content en mode thinking.
Ce qu’un appel d’outil change réellement dans une application DeepSeek
Un LLM classique répond à partir du contexte fourni. Avec les appels d’outils, il peut demander à votre application d’utiliser une capacité externe : interroger une base de données, appeler une API métier, lire un catalogue, calculer un prix, créer un ticket, vérifier un statut de commande ou lancer une action dans un système interne.
Le flux est le suivant :
- L’utilisateur pose une question.
- Votre application envoie la conversation à DeepSeek avec une liste de fonctions dans
tools. - Le modèle répond soit par du texte, soit par un ou plusieurs
tool_calls. - Votre application lit le nom de la fonction et les arguments générés.
- Votre code valide ces arguments.
- Votre code exécute la fonction réelle.
- Votre application ajoute un message
role="tool"contenant le résultat et le bontool_call_id. - DeepSeek reçoit ce résultat et rédige la réponse finale.
Dans la documentation Chat Completion, DeepSeek décrit tools comme une liste de fonctions que le modèle peut appeler, avec des entrées JSON générées par le modèle. La même référence indique que le message de réponse peut contenir tool_calls, chacun avec un id, un type function, un nom de fonction et des arguments.
La conséquence pratique est importante : le modèle choisit ou propose l’action, mais votre backend reste responsable de la validation, des droits d’accès, de l’exécution, des logs, des erreurs et des limites de sécurité.
Modèles DeepSeek actuels à utiliser pour les Tool Calls
Au 10 juillet 2026, la documentation officielle DeepSeek indique que les modèles API actuels sont deepseek-v4-flash et deepseek-v4-pro. Les anciens noms deepseek-chat et deepseek-reasoner sont encore mentionnés pour compatibilité, mais DeepSeek annonce leur arrêt le 24 juillet 2026 à 15:59 UTC ; ils correspondent actuellement aux modes non-thinking et thinking de deepseek-v4-flash.
| Besoin | Modèle conseillé | Pourquoi |
|---|---|---|
| Tutoriel, prototype, chatbot support, outil interne simple | deepseek-v4-flash | Plus adapté aux flux rapides et économiques. |
| Agent complexe, raisonnement long, orchestration multi-étapes | deepseek-v4-pro | À privilégier quand la qualité de raisonnement est plus importante que la latence ou le coût. |
Ancien code utilisant deepseek-chat ou deepseek-reasoner | Migration vers deepseek-v4-flash ou deepseek-v4-pro | Les anciens alias ont une date de retrait annoncée. |
La page Models & Pricing indique aussi que deepseek-v4-flash et deepseek-v4-pro prennent en charge JSON Output et Tool Calls, et que les deux modèles supportent les modes thinking et non-thinking.
Pour un premier guide des appels d’outils avec DeepSeek, le mode non-thinking est souvent plus simple : il évite de gérer reasoning_content dans l’historique. Pour des agents multi-étapes plus complexes, le mode thinking peut être utile, mais il impose une gestion plus stricte de l’historique de conversation.
Prérequis avant d’écrire le code
DeepSeek expose une API compatible avec les formats OpenAI et Anthropic. Avec le format OpenAI, le base_url officiel est https://api.deepseek.com, et l’API peut être appelée via un SDK compatible OpenAI en changeant la configuration.
Avant de coder, préparez :
- une clé API DeepSeek stockée dans une variable d’environnement, jamais en dur dans le code ;
- un environnement Python 3.9 ou version ultérieure ;
- le package
openaiinstallé ; - une fonction métier réelle ou simulée ;
- une règle d’autorisation côté serveur ;
- une stratégie de validation des arguments générés par le modèle.
Installation :
pip install openai
Définissez ensuite DEEPSEEK_API_KEY dans votre gestionnaire de secrets, votre shell ou votre plateforme d’hébergement. Le code ci-dessous lit cette variable sans afficher ni stocker la clé.
Exemple complet en Python : interroger un statut de commande
L’exemple suivant utilise des données simulées pour éviter de dépendre d’une API réelle. Le cas d’usage est volontairement simple : un utilisateur authentifié demande le statut d’une commande, DeepSeek décide d’appeler l’outil get_order_status, votre application valide l’identifiant de commande, vérifie l’autorisation côté serveur, exécute la fonction, puis renvoie le résultat au modèle.
L’exemple utilise le mode non-thinking avec deepseek-v4-flash, afin de montrer le cycle Tool Call sans la complexité supplémentaire de reasoning_content.
import json
import os
import re
from typing import Any
from openai import OpenAI
MODEL = "deepseek-v4-flash"
BASE_URL = "https://api.deepseek.com"
MAX_TOOL_ROUNDS = 3
# Dans une vraie application, cet identifiant vient de votre session,
# de votre JWT ou de votre système d'authentification.
CURRENT_CUSTOMER_ID = "client_42"
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url=BASE_URL,
)
# Données simulées : remplacez cette partie par votre base de données
# ou votre API interne.
ORDERS = {
"FR-1001": {
"customer_id": "client_42",
"status": "expédiée",
"carrier": "Colissimo",
"estimated_delivery": "2026-07-11",
},
"FR-1002": {
"customer_id": "client_42",
"status": "préparation",
"carrier": None,
"estimated_delivery": "2026-07-13",
},
"FR-2001": {
"customer_id": "client_77",
"status": "livrée",
"carrier": "Chronopost",
"estimated_delivery": "2026-07-07",
},
}
TOOLS = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": (
"Récupère le statut d'une commande appartenant au client "
"actuellement authentifié. À utiliser uniquement lorsque "
"l'utilisateur fournit un identifiant de commande."
),
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Identifiant de commande au format FR-1001.",
"pattern": "^FR-[0-9]{4}$",
}
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]
def get_order_status(order_id: str) -> dict[str, Any]:
"""Fonction métier réelle côté serveur.
Le modèle ne reçoit pas customer_id comme argument :
l'autorisation vient de la session serveur.
"""
order = ORDERS.get(order_id)
# Ne révélez pas si une commande existe mais appartient à un autre client.
if order is None or order["customer_id"] != CURRENT_CUSTOMER_ID:
return {
"ok": False,
"error": "commande_introuvable_ou_non_autorisee",
}
return {
"ok": True,
"order_id": order_id,
"status": order["status"],
"carrier": order["carrier"],
"estimated_delivery": order["estimated_delivery"],
}
TOOL_REGISTRY = {
"get_order_status": get_order_status,
}
def parse_tool_arguments(raw_arguments: str) -> dict[str, Any]:
"""Valide les arguments JSON générés par le modèle avant exécution."""
try:
arguments = json.loads(raw_arguments)
except json.JSONDecodeError as exc:
raise ValueError("Arguments JSON invalides.") from exc
if not isinstance(arguments, dict):
raise ValueError("Les arguments doivent être un objet JSON.")
allowed_keys = {"order_id"}
unexpected_keys = set(arguments) - allowed_keys
if unexpected_keys:
raise ValueError(f"Champs non autorisés : {sorted(unexpected_keys)}")
order_id = arguments.get("order_id")
if not isinstance(order_id, str):
raise ValueError("order_id doit être une chaîne de caractères.")
if not re.fullmatch(r"FR-[0-9]{4}", order_id):
raise ValueError("order_id doit respecter le format FR-1001.")
return {"order_id": order_id}
def execute_tool_call(tool_call: Any) -> dict[str, Any]:
"""Exécute un tool call après allowlist et validation."""
function_name = tool_call.function.name
if function_name not in TOOL_REGISTRY:
return {
"ok": False,
"error": "outil_non_autorise",
}
try:
arguments = parse_tool_arguments(tool_call.function.arguments)
return TOOL_REGISTRY[function_name](**arguments)
except ValueError as exc:
return {
"ok": False,
"error": "arguments_invalides",
"details": str(exc),
}
def assistant_message_to_dict(message: Any) -> dict[str, Any]:
"""Convertit le message SDK en dict réutilisable dans l'historique."""
data = message.model_dump(exclude_unset=True)
# Certains runtimes d'agents préfèrent un content non nul
# lorsque le message assistant contient des tool_calls.
if data.get("content") is None:
data["content"] = ""
return data
messages = [
{
"role": "system",
"content": (
"Tu es un assistant de support e-commerce. "
"Si l'utilisateur demande le statut d'une commande, utilise l'outil disponible. "
"Ne révèle jamais de données appartenant à un autre client. "
"Réponds en français, de manière concise et utile."
),
},
{
"role": "user",
"content": "Où en est ma commande FR-1001 ?",
},
]
for _ in range(MAX_TOOL_ROUNDS):
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
tool_choice="auto",
extra_body={
"thinking": {"type": "disabled"}
},
)
message = response.choices[0].message
tool_calls = getattr(message, "tool_calls", None)
if not tool_calls:
print(message.content)
break
messages.append(assistant_message_to_dict(message))
for tool_call in tool_calls:
tool_result = execute_tool_call(tool_call)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result, ensure_ascii=False),
}
)
else:
raise RuntimeError("Trop de cycles d'appels d'outils sans réponse finale.")
Ce code applique trois règles importantes.
Premièrement, l’utilisateur authentifié n’est pas choisi par le modèle. CURRENT_CUSTOMER_ID vient du serveur, pas des arguments générés. C’est indispensable : un modèle peut demander une fonction, mais il ne doit jamais décider seul des droits d’accès.
Deuxièmement, l’outil est exécuté via une allowlist : si le modèle renvoie un nom de fonction qui n’existe pas dans TOOL_REGISTRY, l’appel est refusé.
Troisièmement, les arguments sont validés avant exécution. La référence Chat Completion de DeepSeek avertit que les arguments générés par le modèle peuvent ne pas être du JSON valide ou contenir des paramètres non définis par votre schéma ; elle recommande explicitement de valider les arguments dans votre code avant d’appeler la fonction.
Pourquoi tool_call_id est obligatoire
Quand DeepSeek demande un outil, chaque appel reçoit un identifiant. Lorsque vous renvoyez le résultat, le message role="tool" doit contenir le tool_call_id correspondant. Sans cela, le modèle ne peut pas relier proprement le résultat à l’appel initial.
La documentation Chat Completion définit tool_call_id comme le champ qui indique à quel appel d’outil le message répond. L’exemple Tool Calls officiel montre aussi le même cycle : récupérer tool.id, ajouter un message role="tool" avec le tool_call_id correspondant, puis refaire une requête pour obtenir la réponse finale.
Dans une application avec plusieurs appels d’outils, cette correspondance devient critique. Si le modèle demande deux fonctions, par exemple get_order_status et get_refund_policy, chaque résultat doit être renvoyé avec le bon identifiant.
Comprendre tool_choice
Le paramètre tool_choice contrôle si le modèle peut appeler un outil, doit appeler un outil, ne doit pas en appeler, ou doit appeler une fonction précise. La référence DeepSeek documente les valeurs none, auto et required, ainsi que la possibilité de forcer une fonction particulière avec une structure du type {"type": "function", "function": {"name": "my_function"}}. Elle précise aussi que auto est la valeur par défaut lorsque des outils sont fournis.
| Valeur | Effet pratique | Quand l’utiliser |
|---|---|---|
auto | Le modèle choisit entre répondre directement ou appeler un outil. | Cas général, recommandé pour commencer. |
none | Le modèle répond sans outil. | Désactiver temporairement les outils ou comparer le comportement. |
required | Le modèle doit appeler au moins un outil. | Quand la réponse doit impérativement passer par une source externe. |
| Fonction nommée | Le modèle est forcé d’appeler une fonction précise. | Workflow contrôlé, formulaire, étape imposée. |
En pratique, commencez avec auto. Utilisez required seulement si une réponse sans outil serait dangereuse ou inutile, par exemple pour vérifier un statut réel, une disponibilité ou une donnée interne.
Pour les workflows en mode thinking avec certains wrappers d’agents, testez attentivement la compatibilité de tool_choice. Une page officielle d’intégration DeepSeek pour Oh My Pi indique notamment que DeepSeek V4 en thinking mode peut rejeter tool_choice dans ce contexte, et qu’il faut préserver reasoning_content pour éviter des erreurs 400.
Mode non-thinking ou thinking : lequel choisir ?
Le mode non-thinking est plus simple pour un premier outil : vous envoyez tools, vous traitez tool_calls, vous renvoyez role="tool", puis vous récupérez la réponse finale.
Le mode thinking ajoute une capacité de raisonnement multi-étapes. La documentation Thinking Mode indique que le modèle peut effectuer plusieurs tours de raisonnement et d’appels d’outils avant la réponse finale. Elle précise aussi que, lorsqu’un tour implique des Tool Calls, reasoning_content doit être entièrement renvoyé à l’API dans les requêtes suivantes ; sinon, l’API peut retourner une erreur 400.
Utilisez donc cette règle simple :
| Situation | Mode conseillé |
|---|---|
| Appel d’outil simple, support client, récupération d’une donnée | Non-thinking |
| Agent qui planifie, vérifie, corrige et réessaie plusieurs étapes | Thinking |
| Code existant qui ne conserve pas tout le message assistant | Non-thinking, ou refactorisation avant thinking |
| Framework tiers dont vous ne maîtrisez pas l’historique des messages | Test strict avant production |
Si vous activez le mode thinking, ne reconstruisez pas manuellement les messages en supprimant des champs. Dans son exemple, DeepSeek montre que le message assistant contient content, reasoning_content et tool_calls, et que le plus simple est d’ajouter le message assistant complet à l’historique.
Strict Mode : utile, mais pas suffisant pour la sécurité
Le strict mode est une fonctionnalité Beta qui demande au modèle de respecter plus strictement le JSON Schema de la fonction. DeepSeek indique qu’il faut utiliser base_url="https://api.deepseek.com/beta", définir strict: true pour toutes les fonctions, et fournir un schéma accepté par le serveur.
Ce mode peut réduire les arguments malformés, mais il ne remplace pas la sécurité applicative. Même si les arguments respectent le schéma, vous devez toujours vérifier :
- que l’utilisateur a le droit d’exécuter l’action ;
- que la ressource demandée lui appartient ;
- que l’action n’est pas destructive sans confirmation ;
- que les valeurs sont acceptables pour vos règles métier ;
- que la fonction appelée est bien dans une allowlist ;
- que les erreurs ne révèlent pas de données sensibles.
DeepSeek liste plusieurs contraintes de schéma en strict mode. Par exemple, les objets doivent déclarer toutes leurs propriétés dans required et définir additionalProperties à false ; la documentation DeepSeek sur Tool Calls liste aussi les types JSON Schema supportés comme object, string, number, integer, boolean, array, enum et anyOf.
Retenez ceci : la validation de format n’est pas une autorisation. Elle empêche surtout les entrées mal structurées. Elle ne dit pas si l’utilisateur a le droit de lire une commande, modifier un paiement ou supprimer une ressource.
Tool Calls ou JSON Output : ne pas confondre
Les Tool Calls et JSON Output résolvent deux problèmes différents.
| Besoin | Utiliser |
|---|---|
| Obtenir une réponse structurée sans action externe | JSON Output |
| Demander au modèle quelle fonction appeler | Tool Calls |
| Lire une base de données, interroger une API, créer une action | Tool Calls |
| Extraire des champs d’un texte en JSON | JSON Output |
| Construire un agent qui alterne raisonnement, outils et réponse finale | Tool Calls |
JSON Output force le modèle à produire une réponse JSON valide via response_format: {"type": "json_object"}. La documentation DeepSeek recommande aussi d’inclure le mot “json” dans le prompt et de fournir un exemple du format attendu ; elle signale que le contenu peut occasionnellement être vide et que max_tokens doit être configuré raisonnablement pour éviter une coupure.
Avec Tool Calls, le JSON n’est qu’une partie du protocole : il sert à décrire les arguments d’une fonction. La réponse finale peut ensuite être en langage naturel.
Exemple :
- JSON Output : “Retourne les champs
nom,date,montantde cette facture.” - Tool Calls : “Vérifie dans notre système si la commande FR-1001 a été expédiée.”
Erreurs fréquentes et corrections
Erreur 400 après un appel d’outil
Une erreur 400 indique souvent un format de requête invalide. La page Error Codes de DeepSeek décrit 400 - Invalid Format comme un problème de format du corps de requête.
Dans un workflow Tool Calls, vérifiez surtout :
- le message assistant contenant
tool_callsa bien été ajouté à l’historique ; - chaque résultat d’outil a
role="tool"; - chaque résultat contient le bon
tool_call_id; contentest une chaîne JSON ou texte valide ;- en mode thinking,
reasoning_contentn’a pas été supprimé ; - le wrapper utilisé ne reconstruit pas mal les messages.
Le modèle n’appelle pas l’outil
Si le modèle répond directement alors que vous attendez un appel d’outil, commencez par améliorer la description de la fonction. Le champ description sert au modèle pour décider quand et comment appeler la fonction.
Vérifiez ensuite :
- que
toolsest bien envoyé dans la requête ; - que
tool_choicen’est pas défini ànone; - que la question utilisateur contient assez d’information ;
- que le nom de la fonction est clair ;
- que le schéma n’est pas inutilement complexe.
Si l’appel est obligatoire, utilisez tool_choice="required" ou forcez une fonction précise, mais seulement lorsque le workflow le justifie.
Les arguments JSON sont invalides
Même avec un schéma, ne supposez jamais que les arguments sont corrects. DeepSeek avertit que le modèle peut générer des arguments non valides ou inventer des paramètres non définis.
La correction est côté application :
- entourer
json.loadsavectry/except; - vérifier que le résultat est un objet ;
- refuser les champs inattendus ;
- valider types, formats et bornes ;
- retourner une erreur contrôlée au modèle si nécessaire.
Erreur 422
DeepSeek décrit 422 - Invalid Parameters comme une requête contenant des paramètres invalides.
Dans un appel Tool Calls, cela peut venir d’un modèle inexistant, d’un mauvais tool_choice, d’un paramètre non supporté, d’un schéma refusé en strict mode ou d’un format de message incompatible avec la référence Chat Completion.
Erreur 429 ou 503
429 indique que les requêtes sont envoyées trop rapidement, tandis que 503 signale une surcharge serveur selon la page Error Codes officielle.
Pour un agent à outils, ajoutez :
- backoff exponentiel ;
- limites par utilisateur ;
- limite de tours d’outils ;
- timeouts ;
- idempotency keys pour les actions sensibles ;
- reprise propre si un outil échoue.
Sécuriser les appels d’outils avant la production
Un système Tool Calls est plus proche d’un backend applicatif que d’un simple chatbot. Dès qu’un modèle peut demander une action, vous devez traiter sa sortie comme une entrée non fiable.
Checklist minimale :
- Allowlist des fonctions : n’exécutez jamais un nom de fonction arbitraire.
- Validation JSON : refusez les arguments mal formés.
- Validation métier : vérifiez formats, bornes, statuts et transitions autorisées.
- Authorization serveur : ne laissez jamais le modèle fournir l’identité utilisateur.
- Confirmation utilisateur : exigez une confirmation explicite avant suppression, paiement, email externe ou changement irréversible.
- Principe du moindre privilège : chaque outil doit avoir les permissions minimales.
- Logs propres : journalisez les erreurs sans clés API, mots de passe ni données sensibles.
- Timeouts et retries : évitez qu’un outil bloque toute la conversation.
- Limite de boucle : empêchez un agent de faire des appels indéfiniment.
- Isolation des outils : séparez lecture, écriture et actions destructives.
- Protection contre prompt injection : ne laissez pas un résultat d’outil non fiable donner des ordres système à votre agent.
La sécurité ne doit pas dépendre du prompt. Le prompt peut dire “n’accède jamais aux données d’un autre client”, mais la vraie protection doit être dans le code, comme dans l’exemple Python où l’identité client vient du serveur.
Utiliser DeepSeek via un fournisseur tiers ou localement
L’API officielle DeepSeek est la référence pour les champs tools, tool_calls, tool_choice, thinking et reasoning_content. Si vous utilisez un fournisseur tiers, un routeur LLM, LangChain, LangGraph, un IDE agentique ou un serveur local, vérifiez sa propre documentation : certains wrappers ne transmettent pas tous les champs, ou modifient les messages avant l’envoi.
DeepSeek publie des pages d’intégration avec des agents tiers, mais avertit dans au moins une intégration que l’agent est fourni par un tiers et que DeepSeek ne garantit pas son efficacité ni sa sécurité.
Pour tester un fournisseur tiers :
- envoyez un outil très simple ;
- vérifiez que
tool_callsarrive intact ; - vérifiez que
tool_call_idest conservé ; - testez le mode thinking si vous en avez besoin ;
- vérifiez le comportement de
tool_choice; - simulez un argument invalide ;
- simulez plusieurs tool calls ;
- vérifiez les erreurs 400 et 422.
Si un exemple fonctionne avec l’API officielle mais échoue via un wrapper, le problème vient souvent de la couche d’intégration, pas forcément du modèle.
Bonnes pratiques de conception des fonctions
Une bonne fonction pour Tool Calls doit être facile à choisir, facile à valider et difficile à détourner.
Préférez :
get_order_status(order_id)
à :
run_customer_action(action, resource, payload)
La première fonction a un périmètre clair. La seconde ouvre trop de possibilités : le modèle pourrait demander des actions imprévues, et votre validation deviendrait plus fragile.
Pour chaque outil, définissez :
- un nom court et explicite ;
- une description qui dit quand l’utiliser ;
- un schéma minimal ;
- peu d’arguments ;
- des enums quand les valeurs possibles sont connues ;
- aucun secret dans les arguments ;
- aucune identité utilisateur contrôlée par le modèle ;
- une réponse JSON stable ;
- des erreurs sûres et non bavardes.
Un outil doit faire une chose. Si vous avez besoin de lire une commande, annuler une commande et créer un remboursement, créez trois fonctions séparées avec des règles différentes.
FAQ
Qu’est-ce qu’un appel d’outil avec DeepSeek ?
C’est un mécanisme par lequel DeepSeek demande à votre application d’appeler une fonction externe avec des arguments structurés. Le modèle ne fait pas l’action lui-même : votre code valide, exécute et renvoie le résultat.
Quelle est la différence entre Tool Calling et Function Calling ?
Dans ce contexte, les deux expressions désignent pratiquement le même usage : fournir des fonctions au modèle pour qu’il puisse demander leur exécution. “Tool Calls” est le terme utilisé dans la documentation DeepSeek actuelle, tandis que “Function Calling” reste courant chez les développeurs et dans les écosystèmes compatibles OpenAI.
Quels modèles DeepSeek utiliser aujourd’hui ?
Utilisez deepseek-v4-flash ou deepseek-v4-pro. Les noms deepseek-chat et deepseek-reasoner sont des alias hérités annoncés comme retirés après le 24 juillet 2026 à 15:59 UTC.
Le mode strict suffit-il à sécuriser un appel d’outil ?
Non. Le strict mode aide à faire respecter le JSON Schema, mais il ne vérifie pas les droits utilisateur, les règles métier ou les actions sensibles. DeepSeek le présente comme une fonctionnalité Beta destinée à améliorer la conformité des sorties au schéma.
Pourquoi mon appel d’outil renvoie-t-il une erreur 400 en mode thinking ?
La cause fréquente est la perte de reasoning_content dans l’historique. DeepSeek indique que, pour les tours impliquant des Tool Calls en thinking mode, reasoning_content doit être entièrement renvoyé dans les requêtes suivantes, sinon l’API peut retourner 400.
Quand utiliser JSON Output au lieu des Tool Calls ?
Utilisez JSON Output si vous voulez seulement une réponse structurée. Utilisez Tool Calls si vous devez exécuter une fonction, appeler une API, lire une base de données ou construire un agent. JSON Output s’active avec response_format: {"type": "json_object"} et nécessite aussi une instruction explicite de produire du JSON.




