Aller au contenu principal
Leçon 3 sur 12 · Raisonnement IA avec Mistral — reasoning_effort, Small 4 et Large 4

Les thinking chunks : lire la réflexion du modèle

Leçon 3 sur 12 Mis à jour le 2 novembre 2026 Développeur
Sur cette page
  1. Deux formes de réponse
  2. Anatomie d’un fragment de réflexion
  3. Parser la réponse en Python
  4. Afficher la réflexion dans une interface
  5. Le streaming en trois phases
  6. Points clés à retenir

Leçon réécrite le 2 novembre 2026 d’après la section Handling thinking chunks de la documentation Mistral. La version précédente lisait la réflexion dans un champ text du fragment de réflexion : ce champ n’existe pas, la réflexion est une liste de fragments texte.

Deux formes de réponse

Avec reasoning_effort="high", le champ message.content d’une réponse n’est plus une chaîne mais une liste de fragments. Avec reasoning_effort="none", c’est de nouveau une simple chaîne, sans trace de réflexion. Un code robuste accepte toujours les deux formes : c’est la règle de base de cette leçon.

Anatomie d’un fragment de réflexion

Deux types de fragments apparaissent dans la liste :

  • ThinkChunk (type: "thinking") porte la trace de réflexion. Son champ thinking est lui-même une liste de fragments TextChunk ;
  • TextChunk (type: "text") porte la réponse finale, dans son champ text.

Voici, tirée de l’exemple officiel, la forme d’une réponse de Mistral Small 4 (textes abrégés) :

{
  "model": "mistral-small-2603",
  "usage": {"prompt_tokens": 77, "completion_tokens": 1114, "total_tokens": 1191},
  "choices": [{
    "finish_reason": "stop",
    "message": {
      "role": "assistant",
      "content": [
        {"type": "thinking", "thinking": [
          {"type": "text", "text": "Okay, let's try to figure out John's age..."}
        ]},
        {"type": "text", "text": "Let's denote the ages of the sisters as follows..."}
      ]
    }
  }]
}

Le modèle d’objets du SDK Python suit exactement cette structure : ThinkChunk.thinking contient les fragments de réflexion, et chaque TextChunk expose son contenu dans .text.

Parser la réponse en Python

Le code officiel distingue les fragments avec isinstance, après avoir écarté le cas de la chaîne simple :

from mistralai.client.models import TextChunk, ThinkChunk

content = chat_response.choices[0].message.content

if isinstance(content, str):          # reasoning_effort="none"
    print(content)
else:
    for chunk in content:
        if isinstance(chunk, ThinkChunk):
            print("--- réflexion ---")
            for inner in chunk.thinking:
                if isinstance(inner, TextChunk):
                    print(inner.text)
            print("--- fin de la réflexion ---")
        elif isinstance(chunk, TextChunk):
            print(chunk.text)

Dans une application, on préfère une fonction qui renvoie séparément la réflexion et la réponse. Nous l’appellerons separer et la réutiliserons dans la suite du cours :

from mistralai.client.models import TextChunk, ThinkChunk

def separer(content):
    """Renvoie (réflexion, réponse) pour un contenu en chaîne ou en liste de fragments."""
    if isinstance(content, str):
        return "", content
    reflexion, reponse = [], []
    for chunk in content or []:
        if isinstance(chunk, ThinkChunk):
            reflexion.extend(i.text for i in chunk.thinking if isinstance(i, TextChunk))
        elif isinstance(chunk, TextChunk):
            reponse.append(chunk.text)
    return "".join(reflexion), "".join(reponse)

reflexion, reponse = separer(chat_response.choices[0].message.content)

Afficher la réflexion dans une interface

Séparer les deux parties vous laisse décider de ce que voit l’utilisateur. Trois partis pris se rencontrent :

  • masquer la réflexion et n’afficher que la réponse, pour un assistant grand public ;
  • replier la réflexion dans un bloc que l’utilisateur ouvre s’il le souhaite, pour un outil d’expert ;
  • montrer la réflexion en entier, pour un tuteur pédagogique où la démarche compte autant que le résultat.
def rendu_html(content, montrer_reflexion=False):
    reflexion, reponse = separer(content)
    html = f"<div class='reponse'>{reponse}</div>"
    if montrer_reflexion and reflexion:
        html = (
            "<details><summary>Voir la réflexion du modèle</summary>"
            f"<pre>{reflexion}</pre></details>" + html
        )
    return html

Dans un vrai projet, échappez le texte avant de l’insérer dans du HTML (par exemple avec html.escape) : la réflexion comme la réponse sont du texte généré, pas du balisage de confiance.

Le streaming en trois phases

En streaming, delta.content change de forme au fil de la réponse :

  1. phase de réflexion : delta.content est une liste qui contient un ThinkChunk ;
  2. transition : une seule liste qui contient à la fois le ThinkChunk de clôture et le premier TextChunk ;
  3. phase de réponse : delta.content est une simple chaîne.

Le code suivant, adapté de l’exemple officiel, affiche les deux phases en suivant un indicateur d’état :

import os
from mistralai.client import Mistral
from mistralai.client.models import TextChunk, ThinkChunk

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
en_reflexion = False

for event in client.chat.stream(
    model="mistral-large-4-0",
    messages=[{"role": "user", "content": "Combien font 17 × 23 ?"}],
    reasoning_effort="high",
):
    delta = event.data.choices[0].delta.content
    if not delta:
        continue
    if isinstance(delta, str):            # phase de réponse
        if en_reflexion:
            print("\n--- fin de la réflexion ---")
            en_reflexion = False
        print(delta, end="", flush=True)
        continue
    for chunk in delta:                   # phase de réflexion et transition
        if isinstance(chunk, ThinkChunk):
            if not en_reflexion:
                print("--- réflexion ---")
                en_reflexion = True
            for inner in chunk.thinking:
                if isinstance(inner, TextChunk):
                    print(inner.text, end="", flush=True)
        elif isinstance(chunk, TextChunk):
            if en_reflexion:
                print("\n--- fin de la réflexion ---")
                en_reflexion = False
            print(chunk.text, end="", flush=True)

Le streaming change l’expérience : au lieu d’attendre la fin d’une longue réflexion devant un écran figé, l’utilisateur voit le modèle travailler, ou au moins un indicateur d’activité si vous choisissez de masquer la réflexion.

Points clés à retenir

  • content est une chaîne avec none, une liste de fragments avec high : acceptez toujours les deux
  • ThinkChunk.thinking est une liste de TextChunk ; la réponse finale est dans un TextChunk de premier niveau
  • Une fonction separer qui renvoie (réflexion, réponse) simplifie tout le reste du code
  • En streaming : liste de réflexion, transition mixte, puis chaînes de réponse
  • Échappez la réflexion comme la réponse avant de les afficher