Sortie JSON avec DeepSeek : retourner un JSON valide avec l’API DeepSeek

Pour obtenir une sortie JSON avec DeepSeek, configurez response_format avec la valeur {"type": "json_object"}, demandez explicitement une réponse au format json dans le prompt et fournissez un exemple de la structure attendue. Votre application doit ensuite récupérer message.content, vérifier finish_reason, parser la chaîne reçue et valider les données avant de les utiliser.

La documentation officielle précise également qu’un budget max_tokens insuffisant peut tronquer le document et que l’API peut occasionnellement retourner un contenu vide.

Point essentiel : un JSON syntaxiquement valide n’est pas nécessairement conforme à votre schéma, à vos règles métier ou à la réalité. Le mode JSON garantit la validité syntaxique JSON du message généré, pas la validité fonctionnelle de chaque valeur.

Vérification éditoriale : 10 juillet 2026. Pour un nouveau projet, utilisez deepseek-v4-flash ou deepseek-v4-pro. Les anciens identifiants deepseek-chat et deepseek-reasoner sont à éviter dans toute nouvelle intégration : DeepSeek indique qu’ils seront entièrement retirés et rendus inaccessibles après le 24 juillet 2026 à 15:59 UTC.

La configuration minimale pour recevoir un JSON valide

Quatre éléments doivent être réunis dans la même requête.

ÉlémentConfiguration recommandée
Format de réponseresponse_format={"type": "json_object"}
InstructionDemander explicitement un objet json valide
Structure attendueFournir les propriétés, types et valeurs autorisées
Budget de sortiePrévoir assez de max_tokens pour terminer l’objet

Le paramètre response_format doit être un objet contenant une propriété type. Il ne faut donc pas envoyer simplement "json_object" sous la forme d’une chaîne.

{
  "response_format": {
    "type": "json_object"
  }
}

Sur l’API officielle DeepSeek, les deux valeurs actuellement documentées pour response_format.type sont text et json_object. La référence Chat Completions ne documente pas de type json_schema pour contraindre directement le message final de l’assistant.

Le prompt doit également contenir une instruction explicite utilisant le terme json. Sans cette instruction, DeepSeek avertit que le modèle peut générer une longue suite d’espaces jusqu’à atteindre la limite de génération, donnant l’impression que la requête est bloquée.

Un prompt efficace ressemble à ceci :

Analyse le message fourni.

Retourne uniquement un objet json valide, sans bloc Markdown, sans commentaire
et sans texte avant ou après l’objet.

L’objet doit contenir exactement les propriétés suivantes :

{
  "categorie": "technique",
  "priorite": "haute",
  "resume": "Description factuelle du problème",
  "actions": ["Première action", "Deuxième action"]
}

Valeurs autorisées pour "categorie" :
- facturation
- technique
- compte
- autre

Valeurs autorisées pour "priorite" :
- basse
- normale
- haute

N’invente aucune information absente du message.

L’exemple aide le modèle à comprendre la forme souhaitée. Il ne remplace cependant pas une validation effectuée dans votre application.

Quel modèle DeepSeek utiliser pour une sortie structurée ?

Les identifiants actuels de l’API sont :

  • deepseek-v4-flash pour les extractions simples, les classifications et les traitements où la rapidité est prioritaire ;
  • deepseek-v4-pro pour les instructions plus complexes, les entrées longues ou les analyses nécessitant davantage de raisonnement.

DeepSeek présente V4 Flash comme le modèle rapide et efficace de la gamme, tandis que V4 Pro vise les tâches plus exigeantes. Les deux modèles prennent en charge les modes avec et sans raisonnement.

Pour une classification courte, comme l’analyse d’un ticket de support, deepseek-v4-flash constitue un choix cohérent. La qualité du résultat dépend néanmoins davantage de la précision du prompt et de la validation applicative que du seul choix du modèle.

Tester JSON Output avec cURL

Cette requête permet de tester l’API officielle indépendamment d’un SDK :

curl https://api.deepseek.com/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
  --data @- <<'JSON'
{
  "model": "deepseek-v4-flash",
  "messages": [
    {
      "role": "system",
      "content": "Tu classes des tickets de support. Retourne uniquement un objet json valide, sans Markdown ni texte supplémentaire. Utilise exactement les propriétés categorie, priorite, resume et actions. Les catégories autorisées sont facturation, technique, compte et autre. Les priorités autorisées sont basse, normale et haute. Exemple JSON : {\"categorie\":\"technique\",\"priorite\":\"haute\",\"resume\":\"Une phrase factuelle\",\"actions\":[\"Une action concrète\"]}"
    },
    {
      "role": "user",
      "content": "Depuis la mise à jour, la page de paiement affiche une erreur 502. Trois clients sont bloqués depuis ce matin."
    }
  ],
  "thinking": {
    "type": "disabled"
  },
  "response_format": {
    "type": "json_object"
  },
  "max_tokens": 500,
  "stream": false
}
JSON

La réponse HTTP complète contient différentes métadonnées. L’objet demandé se trouve sous forme de chaîne dans :

choices[0].message.content

Une forme de contenu attendue serait par exemple :

{
  "categorie": "technique",
  "priorite": "haute",
  "resume": "Une erreur 502 bloque trois clients sur la page de paiement.",
  "actions": [
    "Vérifier les journaux du service de paiement",
    "Contrôler la dernière mise à jour déployée"
  ]
}

Cette sortie est un exemple de structure, pas la reproduction d’un résultat garanti. Le modèle peut formuler autrement le résumé ou les actions.

Implémentation complète en Python avec parsing et validation

L’exemple suivant réalise tout le parcours nécessaire :

  1. lecture sécurisée de la clé API ;
  2. appel de DeepSeek ;
  3. activation de JSON Output ;
  4. contrôle de finish_reason ;
  5. détection d’un contenu vide ;
  6. parsing avec json.loads() ;
  7. validation des propriétés et des types avec Pydantic.

Installez les dépendances :

pip install openai pydantic

Définissez ensuite la clé dans une variable d’environnement :

export DEEPSEEK_API_KEY="votre_cle_api"

Voici l’implémentation :

import json
import os
from json import JSONDecodeError
from typing import Literal

from openai import OpenAI
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class AnalyseTicket(BaseModel):
    model_config = ConfigDict(extra="forbid")

    categorie: Literal["facturation", "technique", "compte", "autre"]
    priorite: Literal["basse", "normale", "haute"]
    resume: str = Field(min_length=1, max_length=240)
    actions: list[str] = Field(min_length=1, max_length=5)


api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
    raise RuntimeError("La variable DEEPSEEK_API_KEY est absente.")

client = OpenAI(
    api_key=api_key,
    base_url="https://api.deepseek.com",
)

system_prompt = """
Tu classes un ticket de support.

Retourne uniquement un objet json valide, sans Markdown,
sans commentaire et sans texte avant ou après l'objet.

Exemple de structure JSON attendue :
{
  "categorie": "technique",
  "priorite": "haute",
  "resume": "Résumé factuel en une phrase",
  "actions": ["Première action concrète", "Deuxième action concrète"]
}

Règles :
- Utilise exactement les propriétés categorie, priorite, resume et actions.
- categorie doit être facturation, technique, compte ou autre.
- priorite doit être basse, normale ou haute.
- actions doit être un tableau non vide.
- N'invente aucune information absente du message.
""".strip()

messages = [
    {
        "role": "system",
        "content": system_prompt,
    },
    {
        "role": "user",
        "content": (
            "Depuis la mise à jour, la page de paiement affiche une erreur 502. "
            "Trois clients sont bloqués depuis ce matin."
        ),
    },
]

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=messages,
    response_format={"type": "json_object"},
    max_tokens=500,
    stream=False,
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    },
)

choice = response.choices[0]

if choice.finish_reason != "stop":
    raise RuntimeError(
        "Génération interrompue avant une fin normale : "
        f"{choice.finish_reason}"
    )

raw_content = choice.message.content

if not raw_content or not raw_content.strip():
    raise RuntimeError("DeepSeek a renvoyé un contenu vide.")

try:
    payload = json.loads(raw_content)
except JSONDecodeError as exc:
    raise RuntimeError(
        "Le contenu reçu n'est pas un JSON analysable : "
        f"ligne {exc.lineno}, colonne {exc.colno}."
    ) from exc

try:
    ticket = AnalyseTicket.model_validate(payload)
except ValidationError as exc:
    raise RuntimeError(
        "Le JSON est valide, mais il ne respecte pas le modèle applicatif."
    ) from exc

print(ticket.model_dump_json(indent=2))

La bibliothèque standard Python lève une JSONDecodeError lorsqu’une chaîne ne constitue pas un document JSON analysable. Cette vérification couvre la syntaxe, mais pas la présence des propriétés attendues ni leur signification.

Pydantic ajoute ici plusieurs contraintes :

  • rejet des propriétés inconnues grâce à extra="forbid" ;
  • contrôle des valeurs autorisées avec Literal ;
  • longueur maximale du résumé ;
  • présence d’au moins une action ;
  • nombre maximal d’actions.

Une troisième couche doit encore vérifier les règles propres à votre application. Par exemple, une priorité haute peut nécessiter un signal concret dans le texte, comme un service indisponible, plusieurs utilisateurs bloqués ou une échéance critique.

JSON valide, schéma valide et données fiables : trois notions différentes

Une réponse peut réussir le parsing tout en restant inutilisable.

Considérez cet objet :

{
  "categorie": "urgent",
  "priorite": 12,
  "resume": "",
  "actions": []
}

Il s’agit d’un JSON syntaxiquement valide. Pourtant :

  • urgent n’appartient pas aux catégories autorisées ;
  • priorite devrait être une chaîne et non un nombre ;
  • le résumé est vide ;
  • aucune action n’est fournie.

Le mode json_object protège principalement contre les erreurs de syntaxe telles qu’une accolade manquante, une chaîne non fermée ou du texte placé autour de l’objet. Il ne transmet pas à l’API un schéma permettant d’imposer vos propriétés, vos enums et vos règles métier.

La chaîne de traitement recommandée est donc :

Réponse DeepSeek
      ↓
Contrôle du contenu et de finish_reason
      ↓
Parsing JSON
      ↓
Validation du schéma applicatif
      ↓
Validation des règles métier
      ↓
Utilisation des données

Avec JSON Schema, required permet d’exiger certaines propriétés et additionalProperties: false permet de refuser les clés non prévues. Sans cette dernière règle, les propriétés supplémentaires sont autorisées par défaut.

JSON Output ou Tool Calls en mode strict ?

DeepSeek propose deux mécanismes différents qu’il ne faut pas confondre.

En pratique, JSON Output structure la réponse finale de l’assistant, tandis que Tool Calls structure les arguments d’une fonction que votre application peut ensuite décider d’exécuter ou non.

MécanismeUsage principalGarantie
JSON OutputRetourner une réponse finale structurée dans message.contentJSON syntaxiquement valide
Tool CallsDemander au modèle de choisir une fonction et de produire ses argumentsArguments destinés à un outil
Tool Calls avec strict: trueContraindre les arguments d’une fonction à un JSON Schema pris en chargeRespect du schéma de la fonction
Validation localeContrôler toute donnée avant son utilisationRègles structurelles et métier définies par l’application

Utilisez JSON Output lorsque le résultat final doit être un objet à stocker, afficher ou transmettre à une autre partie de votre programme.

Utilisez Tool Calls lorsque le modèle doit sélectionner une action, par exemple :

  • créer un ticket ;
  • rechercher un produit ;
  • interroger une base de données ;
  • planifier une opération ;
  • appeler un service métier.

Le modèle ne réalise pas lui-même la fonction : il produit un nom de fonction et des arguments, puis votre application décide si elle exécute réellement l’action.

Contraindre un appel d’outil avec strict: true

Le mode strict de DeepSeek est actuellement indiqué comme Beta. Il nécessite :

  1. l’URL de base https://api.deepseek.com/beta ;
  2. la propriété strict: true pour les fonctions ;
  3. un JSON Schema compatible avec les contraintes documentées.

Exemple de définition :

{
  "type": "function",
  "function": {
    "name": "enregistrer_ticket",
    "strict": true,
    "description": "Enregistre un ticket de support validé.",
    "parameters": {
      "type": "object",
      "properties": {
        "categorie": {
          "type": "string",
          "enum": [
            "facturation",
            "technique",
            "compte",
            "autre"
          ]
        },
        "priorite": {
          "type": "string",
          "enum": [
            "basse",
            "normale",
            "haute"
          ]
        },
        "resume": {
          "type": "string"
        }
      },
      "required": [
        "categorie",
        "priorite",
        "resume"
      ],
      "additionalProperties": false
    }
  }
}

Dans ce mode, toutes les propriétés de chaque objet doivent être déclarées comme obligatoires et additionalProperties doit être défini sur false. La documentation répertorie notamment les types object, string, number, integer, boolean, array, enum et anyOf, avec des restrictions propres à certains mots-clés JSON Schema.

Même en mode strict, votre serveur doit continuer à contrôler :

  • les autorisations de l’utilisateur ;
  • l’existence des ressources ;
  • les plages de valeurs métier ;
  • les doublons ;
  • les limites de consommation ;
  • les conséquences de l’action ;
  • les données sensibles.

Le respect d’un schéma ne constitue pas une autorisation d’exécuter une opération.

Corriger une réponse vide, bloquée ou tronquée

La requête semble bloquée et génère des espaces

Vérifiez que le prompt demande explicitement une sortie json. Il est insuffisant d’activer uniquement :

{
  "response_format": {
    "type": "json_object"
  }
}

Ajoutez une instruction directe :

Retourne uniquement un objet json valide.

DeepSeek documente le risque d’une génération continue d’espaces lorsque le modèle n’est pas explicitement invité à produire du JSON.

message.content est vide

La documentation de JSON Output indique que l’API peut occasionnellement renvoyer un contenu vide et suggère de modifier le prompt pour réduire ce problème.

Votre application doit donc tester la valeur avant tout parsing :

raw_content = response.choices[0].message.content

if not raw_content or not raw_content.strip():
    raise RuntimeError("Réponse JSON vide.")

Une stratégie de reprise raisonnable consiste à :

  1. limiter le nombre de nouvelles tentatives ;
  2. renforcer l’instruction de format lors de la seconde tentative ;
  3. conserver le même schéma attendu ;
  4. ne pas réessayer automatiquement une opération non idempotente ;
  5. journaliser le type d’erreur sans enregistrer de données sensibles.

Évitez toute boucle de retry illimitée.

Le JSON est coupé avant la dernière accolade

Inspectez finish_reason avant d’appeler json.loads().

La valeur length indique que la génération a atteint max_tokens ou la limite totale de contexte. Les autres valeurs actuellement documentées incluent notamment stop, content_filter, tool_calls et insufficient_system_resource.

choice = response.choices[0]

if choice.finish_reason == "length":
    raise RuntimeError(
        "La sortie JSON a probablement été tronquée. "
        "Augmentez max_tokens ou réduisez la taille demandée."
    )

Pour corriger le problème :

  • augmentez raisonnablement max_tokens ;
  • réduisez le nombre de propriétés demandées ;
  • limitez la longueur des tableaux et des textes ;
  • divisez les extractions massives en plusieurs requêtes ;
  • désactivez le raisonnement pour les tâches d’extraction simples lorsque vous n’en avez pas besoin ;
  • évitez de demander simultanément un objet structuré et de longues explications.

Ne complétez pas automatiquement une accolade manquante pour faire passer le parsing. Un JSON réparé artificiellement peut masquer des propriétés absentes ou des données coupées.

Le JSON est valide, mais les propriétés sont incorrectes

Dans ce cas, le problème n’est plus syntaxique. Renforcez le prompt avec :

  • la liste exacte des propriétés ;
  • les valeurs autorisées ;
  • un exemple complet ;
  • l’interdiction des propriétés supplémentaires ;
  • la conduite à tenir lorsqu’une information est absente ;
  • une validation locale après réception.

Par exemple, au lieu de demander :

Analyse ce ticket et réponds en JSON.

précisez :

Retourne uniquement un objet json avec exactement les propriétés
categorie, priorite, resume et actions.

Ne crée aucune autre propriété.

Si la catégorie ne peut pas être déterminée, utilise "autre".
N’invente aucune information absente du message.

Le prompt améliore la probabilité d’obtenir la bonne structure. Le validateur reste l’autorité finale.

Utiliser JSON Output avec le thinking mode

Les modèles V4 actuels prennent en charge les modes avec et sans raisonnement. Dans l’interface OpenAI de DeepSeek, le thinking mode est activé par défaut. Avec le SDK Python OpenAI, le paramètre thinking doit être transmis dans extra_body.

Pour le désactiver :

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=messages,
    response_format={"type": "json_object"},
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    },
)

Pour l’activer :

response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    response_format={"type": "json_object"},
    reasoning_effort="high",
    extra_body={
        "thinking": {
            "type": "enabled"
        }
    },
)

Lorsque le raisonnement est activé, DeepSeek sépare :

  • reasoning_content, qui contient le raisonnement du modèle ;
  • content, qui contient la réponse finale.

Pour obtenir l’objet JSON, parsez uniquement :

response.choices[0].message.content

Ne concaténez pas reasoning_content et content avant le parsing, car le raisonnement n’est pas le document JSON final.

Pour une extraction courte et déterminée par des règles simples, le thinking mode n’est généralement pas nécessaire au fonctionnement du pipeline. Pour une analyse ambiguë ou une classification dépendant d’un contexte complexe, vous pouvez l’activer, puis valider la réponse de la même manière.

Parser une sortie JSON en streaming

L’API Chat Completions peut envoyer la réponse sous forme de Server-Sent Events lorsque stream vaut true. Dans ce cas, chaque fragment ne constitue pas un document JSON complet : il faut concaténer les valeurs de delta.content, attendre la fin du flux, vérifier finish_reason, puis parser l’ensemble.

import json

stream = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=messages,
    response_format={"type": "json_object"},
    max_tokens=500,
    stream=True,
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    },
)

parts: list[str] = []
finish_reason = None

for chunk in stream:
    for choice in chunk.choices:
        if choice.delta.content:
            parts.append(choice.delta.content)

        if choice.finish_reason is not None:
            finish_reason = choice.finish_reason

if finish_reason != "stop":
    raise RuntimeError(
        f"Flux interrompu avant une fin normale : {finish_reason}"
    )

raw_content = "".join(parts)

if not raw_content.strip():
    raise RuntimeError("Le flux ne contient aucune sortie JSON.")

payload = json.loads(raw_content)

N’appelez pas json.loads() sur chaque fragment. Une portion telle que :

{"categorie":"tech

est nécessairement invalide tant que les fragments suivants ne sont pas arrivés.

Rendre l’intégration suffisamment fiable pour la production

Avant d’utiliser une sortie DeepSeek dans une base de données, une automatisation ou un service externe, appliquez cette checklist :

  • stocker la clé API uniquement côté serveur ;
  • utiliser une variable d’environnement ou un gestionnaire de secrets ;
  • vérifier finish_reason avant le parsing ;
  • refuser les contenus vides ;
  • limiter la taille du contenu accepté avant de le parser ;
  • capturer les erreurs de syntaxe JSON ;
  • valider les propriétés, les types et les enums ;
  • interdire les propriétés inconnues lorsque le cas d’usage l’exige ;
  • appliquer les règles métier après la validation structurelle ;
  • limiter et temporiser les nouvelles tentatives ;
  • ne pas réessayer aveuglément les opérations créant un paiement, une commande ou une ressource ;
  • journaliser les métadonnées techniques plutôt que les données sensibles ;
  • tester les réponses manquantes, incomplètes et contradictoires ;
  • surveiller les modifications des modèles et des paramètres de l’API ;
  • ne jamais exécuter automatiquement une action sensible sur la seule décision du modèle.

Pour les opérations à fort impact, traitez la réponse du modèle comme une entrée non fiable jusqu’à ce que votre application l’ait entièrement validée.