Skip to main content
Lesson 3 of 12 · AI Reasoning with Mistral: reasoning_effort, Small 4 and Large 4

Thinking chunks: reading the model's reasoning

Lesson 3 of 12 Updated November 2, 2026 Developer
On this page
  1. Two response shapes
  2. Anatomy of a thinking chunk
  3. Parsing the response in Python
  4. Displaying reasoning in an interface
  5. Streaming in three phases
  6. Key takeaways

Lesson rewritten on November 2, 2026 based on the Handling thinking chunks section of the Mistral documentation. The previous version read reasoning from a text field in the thinking chunk: that field does not exist; reasoning is a list of text chunks.

Two response shapes

With reasoning_effort="high", the message.content field of a response is no longer a string but a list of chunks. With reasoning_effort="none", it’s again a plain string, with no trace of reasoning. Robust code always handles both shapes: that’s the golden rule of this lesson.

Anatomy of a thinking chunk

Two chunk types appear in the list:

  • ThinkChunk (type: "thinking") carries the reasoning trace. Its thinking field is itself a list of TextChunk fragments;
  • TextChunk (type: "text") carries the final answer, in its text field.

Here’s the shape of a Mistral Small 4 response, taken from the official example (texts abbreviated):

{
  "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..."}
      ]
    }
  }]
}

The Python SDK object model follows this structure exactly: ThinkChunk.thinking holds the reasoning chunks, and each TextChunk exposes its content via .text.

Parsing the response in Python

The official code distinguishes chunks with isinstance, after ruling out the plain-string case:

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("--- reasoning ---")
            for inner in chunk.thinking:
                if isinstance(inner, TextChunk):
                    print(inner.text)
            print("--- end of reasoning ---")
        elif isinstance(chunk, TextChunk):
            print(chunk.text)

In an application, you’ll want a function that returns reasoning and answer separately. We’ll call it separate and reuse it later in the course:

from mistralai.client.models import TextChunk, ThinkChunk

def separate(content):
    """Return (reasoning, answer) for content that is either a string or a list of chunks."""
    if isinstance(content, str):
        return "", content
    reasoning, answer = [], []
    for chunk in content or []:
        if isinstance(chunk, ThinkChunk):
            reasoning.extend(i.text for i in chunk.thinking if isinstance(i, TextChunk))
        elif isinstance(chunk, TextChunk):
            answer.append(chunk.text)
    return "".join(reasoning), "".join(answer)

reasoning, answer = separate(chat_response.choices[0].message.content)

Displaying reasoning in an interface

Separating the two parts lets you decide what the user sees. Three common approaches:

  • Hide the reasoning and show only the answer, for a consumer-facing assistant;
  • Collapse the reasoning into a block the user can open if they wish, for an expert tool;
  • Show the full reasoning, for an educational tutor where the process matters as much as the result.
def render_html(content, show_reasoning=False):
    reasoning, answer = separate(content)
    html = f"<div class='answer'>{answer}</div>"
    if show_reasoning and reasoning:
        html = (
            "<details><summary>View model reasoning</summary>"
            f"<pre>{reasoning}</pre></details>" + html
        )
    return html

In a real project, escape the text before inserting it into HTML (for example with html.escape): both reasoning and answer are generated text, not trusted markup.

Streaming in three phases

In streaming, delta.content changes shape as the response unfolds:

  1. Reasoning phase: delta.content is a list containing a ThinkChunk;
  2. Transition: a single list containing both the closing ThinkChunk and the first TextChunk;
  3. Answer phase: delta.content is a plain string.

The following code, adapted from the official example, displays both phases by tracking a state flag:

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

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

for event in client.chat.stream(
    model="mistral-large-4-0",
    messages=[{"role": "user", "content": "What is 17 × 23?"}],
    reasoning_effort="high",
):
    delta = event.data.choices[0].delta.content
    if not delta:
        continue
    if isinstance(delta, str):            # answer phase
        if in_reasoning:
            print("\n--- end of reasoning ---")
            in_reasoning = False
        print(delta, end="", flush=True)
        continue
    for chunk in delta:                   # reasoning phase and transition
        if isinstance(chunk, ThinkChunk):
            if not in_reasoning:
                print("--- reasoning ---")
                in_reasoning = True
            for inner in chunk.thinking:
                if isinstance(inner, TextChunk):
                    print(inner.text, end="", flush=True)
        elif isinstance(chunk, TextChunk):
            if in_reasoning:
                print("\n--- end of reasoning ---")
                in_reasoning = False
            print(chunk.text, end="", flush=True)

Streaming changes the experience: instead of waiting for a long reasoning process to finish in front of a frozen screen, the user sees the model at work, or at least an activity indicator if you choose to hide the reasoning.

Key takeaways

  • content is a string with none, a list of chunks with high: always handle both
  • ThinkChunk.thinking is a list of TextChunk; the final answer lives in a top-level TextChunk
  • A separate function that returns (reasoning, answer) simplifies the rest of your code
  • In streaming: reasoning list, mixed transition, then answer strings
  • Escape both reasoning and answer before displaying them