Skip to main content
Lesson 15 of 16 · Mastering Prompts with Mistral

Workshop: Structured Extraction

Lesson 15 of 16 Updated July 28, 2026 Beginner
On this page
  1. Workshop Objective
  2. Exercise 1: JSON Extraction from Free Text
  3. Exercise 2: Structured Document Summary
  4. Exercise 3: Technical Translation with Format Preservation
  5. Advanced Techniques for Extraction
  6. Key Takeaways

Workshop Objective

This workshop builds on a skill introduced in the previous one: structured extraction, which means transforming free text into data usable by a program. It bridges natural language and your information systems—the invoice received as a PDF becomes a line in your accounting software, the monitoring article becomes a dashboard entry. You’ll practice three exercises with Mistral models: JSON extraction, document summarization in a mandated format, and technical translation with formatting preservation. You’ll also revisit two techniques from the parameters section: assistant pre-filling and stop sequences.

Exercise 1: JSON Extraction from Free Text

Starting scenario: you automate the processing of invoices received by email. The text varies from one supplier to another, but you always need to extract the same fields. The system prompt locks down everything possible: pure JSON without comments, null for missing fields (not “not provided” or empty strings, since your code will check is None), amounts as numbers without currency symbols, dates in ISO 8601, VAT separated from the pre-tax amount. Each of these rules prevents a downstream parsing bug.

messages = [
    {
        "role": "system",
        "content": """You are a data extractor specialized in accounting documents.
Strict rules:
- Return ONLY valid JSON, no comments or additional text
- If a field is not found, use null (not "not provided", not "")
- Amounts must be numbers (no euro symbol in the value)
- Dates in ISO 8601 format (YYYY-MM-DD)
- VAT must be extracted separately from the pre-tax amount"""
    },
    {
        "role": "user",
        "content": """Extract data from this invoice:

"FACTURE N° 2026-0847
Date : 15 mars 2026

De : CloudServ SAS
     42 rue de la Paix, 75002 Paris
     SIRET : 823 456 789 00012

À : TechCorp SARL
    12 avenue des Champs, 69001 Lyon

Désignation : Hébergement cloud - Pack Premium
Période : 01/03/2026 au 31/03/2026
Montant HT : 1 250,00 EUR
TVA 20% : 250,00 EUR
Total TTC : 1 500,00 EUR

Conditions de paiement : 30 jours fin de mois
IBAN : FR76 3000 4000 0500 0001 2345 678"

JSON Schema:
{
  "numero_facture": "string",
  "date_facture": "string (YYYY-MM-DD)",
  "fournisseur": {
    "nom": "string",
    "adresse": "string",
    "siret": "string"
  },
  "client": {
    "nom": "string",
    "adresse": "string"
  },
  "lignes": [
    {
      "designation": "string",
      "periode": "string",
      "montant_ht": "number"
    }
  ],
  "tva_taux": "number",
  "tva_montant": "number",
  "total_ttc": "number",
  "conditions_paiement": "string",
  "iban": "string"
}"""
    },
    {
        "role": "assistant",
        "content": "{"
    }
]

response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    temperature=0.0,
    stop=["\n}"]
)
# Reconstruct the JSON: "{" + response + "\n}"

Expected result:

{
  "numero_facture": "2026-0847",
  "date_facture": "2026-03-15",
  "fournisseur": {
    "nom": "CloudServ SAS",
    "adresse": "42 rue de la Paix, 75002 Paris",
    "siret": "823 456 789 00012"
  },
  "client": {
    "nom": "TechCorp SARL",
    "adresse": "12 avenue des Champs, 69001 Lyon"
  },
  "lignes": [
    {
      "designation": "Hébergement cloud - Pack Premium",
      "periode": "01/03/2026 au 31/03/2026",
      "montant_ht": 1250.00
    }
  ],
  "tva_taux": 20,
  "tva_montant": 250.00,
  "total_ttc": 1500.00,
  "conditions_paiement": "30 jours fin de mois",
  "iban": "FR76 3000 4000 0500 0001 2345 678"
}

Notice the combination of assistant pre-filling (the "{" message) and the stop sequence: the first forces the model directly into JSON without preamble like “Here is the extracted data:”, the second prevents it from continuing after the closing brace. You then reconstruct the full JSON on the code side.

Exercise 2: Structured Document Summary

Second scenario: you feed an internal monitoring dashboard with summaries of long reports. To ensure uniform display, each summary must follow exactly the same format—the system prompt enforces this, field by field, with length limits.

messages = [
    {
        "role": "system",
        "content": """You produce structured summaries for a monitoring dashboard.
Mandatory format for each summary:
{
  "titre_court": "max 60 characters",
  "resume": "max 3 sentences, factual, no opinion",
  "impact_business": "low | medium | high",
  "secteurs": ["list of affected sectors"],
  "action_requise": true/false,
  "action_detail": "description if action required, null otherwise"
}"""
    },
    {
        "role": "user",
        "content": """Summarize this article for our monitoring dashboard:

"L'Union européenne a adopté le 28 mars 2026 un nouveau règlement sur
l'intelligence artificielle (AI Act 2.0) qui renforce les obligations de
transparence pour les systèmes de génération de texte et d'images. Les
entreprises qui déploient des modèles de plus de 10 milliards de paramètres
devront désormais publier un rapport d'évaluation des risques trimestriel et
mettre en place un système de traçabilité des contenus générés. Les sanctions
pour non-conformité peuvent atteindre 6% du chiffre d'affaires mondial annuel.
Le règlement entre en vigueur le 1er janvier 2027, avec une période de
transition de 6 mois pour les entreprises déjà en activité. Les PME de moins
de 50 salariés bénéficient d'un régime allégé avec des obligations réduites
de reporting."

Return the structured JSON."""
    }
]

response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    temperature=0.1
)

Here’s what you should get—a factual summary that condenses without distorting, and an action field that turns monitoring into decision:

{
  "titre_court": "AI Act 2.0 : nouvelles obligations de transparence IA",
  "resume": "L'UE adopte un renforcement de l'AI Act imposant des rapports trimestriels pour les grands modèles. Les sanctions atteignent 6% du CA mondial. Entrée en vigueur le 1er janvier 2027 avec régime allégé pour les PME.",
  "impact_business": "élevé",
  "secteurs": ["intelligence artificielle", "technologie", "réglementation"],
  "action_requise": true,
  "action_detail": "Évaluer la conformité de nos modèles et planifier la mise en place du reporting trimestriel avant janvier 2027."
}

The temperature here goes to 0.1 instead of 0.0: a summary requires slight latitude in wording, unlike pure extraction where every value comes from the source text.

Exercise 3: Technical Translation with Format Preservation

Third scenario: translate technical documentation from French to English while preserving Markdown formatting and technical terms exactly as they are. The trap in technical translation lies here: an overzealous translator will translate temperature as “température” in a code example, and the documentation will cease to match the actual API. The workaround is to explicitly list what is untouchable.

messages = [
    {
        "role": "system",
        "content": """You are a technical translator from French to English.

Rules:
- Preserve Markdown formatting EXACTLY (headers, lists, code blocks)
- Do NOT translate terms in backticks
- Do NOT translate variable, function, or parameter names
- Code blocks remain identical (translate comments only)
- Adapt idiomatic expressions to the English technical context
- Use American English"""
    },
    {
        "role": "user",
        "content": """Translate this documentation passage:

## Configuration de l'API

Pour commencer, installez le package `mistralai` :

```bash
pip install mistralai
```

### Authentification

Vous devez configurer votre clé API avant tout appel :

```python
# Initialisation du client Mistral
client = Mistral(api_key="votre-clé")
```

**Important** : ne stockez jamais votre clé API en dur dans le code.
Utilisez plutôt des variables d'environnement ou un gestionnaire de secrets.

### Paramètres disponibles

- `temperature` : contrôle la créativité (défaut : 0.7)
- `top_p` : filtrage par probabilité cumulée (défaut : 1.0)
- `max_tokens` : limite de tokens en sortie"""
    }
]

response = client.chat.complete(
    model="mistral-large-latest",
    messages=messages,
    temperature=0.2
)

To validate the result, check point by point: Markdown headers (##, ###) are preserved, terms in backticks (mistralai, temperature, top_p, max_tokens) remain in English, code blocks are identical except for the translated comment, bold (**Important**) is preserved, and lists keep their structure. Any deviation on one of these points signals a rule to reinforce in the system prompt.

Advanced Techniques for Extraction

One golden rule runs through this workshop: never blindly trust the model’s output. After extraction, always validate the JSON on the code side:

import json

try:
    data = json.loads(response.choices[0].message.content)
    # Validate required fields
    required_fields = ["numero_facture", "date_facture", "total_ttc"]
    for field in required_fields:
        if field not in data or data[field] is None:
            print(f"Missing field: {field}")
except json.JSONDecodeError:
    print("Invalid response, retry with temperature=0")

And if despite everything the model doesn’t return valid JSON, follow an escalation path:

  1. Retry with temperature=0.0
  2. Use assistant pre-filling to force the format
  3. Add an example (one-shot) of the expected output
  4. As a last resort, parse the response with a regex to extract the JSON

Key Takeaways

  • Assistant pre-filling and stop sequences guarantee valid JSON
  • Always provide the exact JSON schema in the prompt
  • Temperature 0 for extraction, 0.1-0.2 for summaries
  • Systematically validate the output on the code side
  • For technical translation, explicitly list what must not be translated