Using client.chat.parse()
On this page
The parse() Method: The Heart of Custom Structured Outputs
client.chat.parse() is the structured equivalent of client.chat.complete(). Instead of returning free text or raw JSON that you must parse yourself, it returns a typed object matching your Pydantic model directly. The change might look cosmetic, but it actually eliminates an entire layer of code.
The contrast is clearest side by side. With complete(), you retrieve a string and transform it yourself. With parse(), the object arrives ready to use, and the raw JSON remains available if you need it.
# JSON Mode — complete() returns JSON text
response = client.chat.complete(
model="mistral-large-latest",
messages=messages,
response_format={"type": "json_object"}
)
raw_json: str = response.choices[0].message.content
data = json.loads(raw_json) # Manual parsing
# Custom — parse() returns a typed object
response = client.chat.parse(
model="mistral-large-latest",
messages=messages,
response_format=MonSchema
)
obj: MonSchema = response.choices[0].message.parsed # Pydantic object
raw_json: str = response.choices[0].message.content # Raw JSON also available
The difference lies in accessing .parsed: a directly usable Pydantic object with IDE autocompletion and type validation.
A Complete End-to-End Call
The following example walks through the five steps of a real extraction: define the schema with clear descriptions, create the client, call parse(), use the returned object, and retrieve the JSON representation if needed.
from mistralai.client import Mistral
from pydantic import BaseModel, Field
from typing import Optional
# 1. Define the schema
class BookInfo(BaseModel):
title: str = Field(description="Book title")
authors: list[str] = Field(description="List of authors")
year: Optional[int] = Field(default=None, description="Publication year")
genre: str = Field(description="Main literary genre")
summary: str = Field(description="Summary in 2-3 sentences")
# 2. Create the client
client = Mistral(api_key="your-api-key")
# 3. Call parse()
response = client.chat.parse(
model="mistral-large-latest",
messages=[
{
"role": "system",
"content": "You are an expert librarian. Extract information about the mentioned book."
},
{
"role": "user",
"content": "I loved '1984' by George Orwell. It's a dystopian novel published in 1949 describing a totalitarian society under constant surveillance."
}
],
temperature=0
)
# 4. Use the parsed object
book = response.choices[0].message.parsed
print(f"Title: {book.title}")
print(f"Authors: {', '.join(book.authors)}")
print(f"Year: {book.year}")
print(f"Genre: {book.genre}")
print(f"Summary: {book.summary}")
# 5. Raw JSON is also available
raw = response.choices[0].message.content
print(f"\nRaw JSON: {raw}")
Notice what the system prompt does not contain: no description of the expected format.
The Auto-Prepended System Prompt
The SDK handles this. When you use parse(), Mistral automatically adds a prefix to the system prompt:
Your output should be an instance of a JSON object following this schema: { ... }
This prefix contains the JSON schema generated from your Pydantic model. You do not need to describe the format in your prompt—the SDK does it for you. This frees up your system message to do what it does best: provide business context and set quality requirements, such as precision for amounts or how to handle missing data.
response = client.chat.parse(
model="mistral-large-latest",
messages=[
{
"role": "system",
# No need to describe the JSON format—it's automatic
# Focus on CONTEXT and QUALITY
"content": """You are an expert financial analyst.
Be precise with amounts and percentages.
If data is not available in the text, use null."""
},
{"role": "user", "content": report_text}
],
response_format=FinancialReport
)
.parsed vs .content: Two Distinct Uses
The typed object, accessible via .parsed, is what you manipulate in your application logic. It exposes attributes with their types, supports iteration, converts to a dictionary with model_dump(), and serializes with model_dump_json().
book = response.choices[0].message.parsed
# Typed access with IDE autocompletion
print(book.title) # str
print(book.authors) # list[str]
print(book.year) # Optional[int]
# Iterate over lists
for author in book.authors:
print(f" - {author}")
# Convert to dictionary
book_dict = book.model_dump()
# Serialize to JSON
book_json = book.model_dump_json(indent=2)
Raw JSON, on the other hand, remains useful at system boundaries: for logging, database storage, or passing the response as-is to another API.
raw = response.choices[0].message.content
# This is a JSON string, useful for:
# - Logging
# - Database storage
# - Sending to another API
import json
data = json.loads(raw)
What Can Still Fail
A guaranteed schema doesn’t make your code immune to everything. The API can return a network or service error, and .parsed can be None if the response was interrupted. The pattern below handles both cases separately—SDKError for the API, ValidationError for the schema—and retries only a bounded number of times.
from mistralai.client import Mistral
from mistralai.client.errors import SDKError
from pydantic import ValidationError
client = Mistral(api_key="your-api-key")
def extract_structured(text: str, schema, max_retries: int = 2):
"""Structured extraction with error handling."""
for attempt in range(max_retries + 1):
try:
response = client.chat.parse(
model="mistral-large-latest",
messages=[
{"role": "system", "content": "Extract the requested information."},
{"role": "user", "content": text}
],
response_format=schema,
temperature=0,
max_tokens=1024
)
parsed = response.choices[0].message.parsed
if parsed is None:
raise ValueError("Parsed is None—response may be truncated")
return parsed
except SDKError as e:
print(f"API error (attempt {attempt + 1}): {e}")
if attempt == max_retries:
raise
except ValidationError as e:
print(f"Validation error (attempt {attempt + 1}): {e}")
if attempt == max_retries:
raise
# Usage
try:
book = extract_structured("'The Little Prince' by Saint-Exupéry, 1943", BookInfo)
print(f"Extracted: {book.title} by {', '.join(book.authors)}")
except Exception as e:
print(f"Final failure: {e}")
The reflex seen with JSON Mode still applies here: a response cut off by max_tokens produces an incomplete or null object, and finish_reason tells you this before you discover it further down the pipeline.
response = client.chat.parse(
model="mistral-large-latest",
messages=messages,
response_format=MonSchema,
max_tokens=256
)
if response.choices[0].finish_reason != "stop":
print(f"Warning: finish_reason = {response.choices[0].finish_reason}")
# "length" = truncated response, increase max_tokens
Key Takeaways
client.chat.parse()replacesclient.chat.complete()for Custom Structured Outputs.parsedreturns a typed Pydantic object;.contentreturns raw JSON- The schema is automatically added to the system prompt—no need to describe the format
- Always check
finish_reasonand handleSDKErrorandValidationErrorerrors - Use
model_dump()to convert the parsed object to a Python dictionary