You are currently viewing Outlines : forcer les LLM open source à produire du JSON structuré en production

Outlines : forcer les LLM open source à produire du JSON structuré en production

Outlines : forcer les LLM open source à produire du JSON structuré en production

Un LLM qui répond en langage naturel, c’est impressionnant. Un LLM qui répond en JSON valide, conforme à un schéma précis, à chaque appel, sans exception, c’est exploitable en production. C’est exactement ce que permet Outlines, bibliothèque open source (licence Apache 2.0) de génération structurée qui contraint la sortie d’un modèle de langage au niveau même du processus de sampling des tokens.

Le problème : des sorties imprévisibles

Quand on intègre un LLM dans un pipeline automatisé : extraction d’entités, classification, alimentation d’une base de données, appel d’outils via MCP, on attend une sortie structurée : du JSON, un choix parmi N options, un entier, une date. Or, même les meilleurs modèles produisent régulièrement des sorties malformées : accolades manquantes, champs inventés, types incorrects, texte parasite avant ou après le JSON. Les solutions classiques (prompts « réponds en JSON », post-processing regex, retry loops) sont fragiles et coûteuses en tokens.

La solution : contraindre le sampling, pas le prompt

Outlines intervient à un niveau fondamental : au lieu de demander poliment au modèle de produire du JSON, il interdit mathématiquement la génération de tokens qui violeraient le schéma cible. À chaque étape de génération, un masque logit est appliqué pour ne conserver que les tokens valides selon la grammaire ou le schéma JSON fourni. Le résultat est garanti conforme, pas « presque toujours », mais toujours.

La bibliothèque supporte plusieurs modes de contrainte :

  • JSON Schema : on fournit un schéma JSON (ou un modèle Pydantic) et chaque sortie est garantie conforme. Types, champs requis, enums, patterns regex dans les valeurs : tout est respecté.
  • Expressions régulières : pour des sorties simples (date, email, code postal, identifiant), on définit un pattern regex et la sortie le respecte caractère par caractère.
  • Grammaires CFG : pour des formats complexes (SQL, code Python, XML), on peut fournir une grammaire context-free complète.
  • Choix multiples : le modèle est contraint à répondre exactement l’une des options fournies, utile pour la classification.

Intégration avec la stack open source

Outlines s’intègre nativement avec les briques déjà couvertes sur ce site. Avec vLLM, la génération structurée est disponible directement via le paramètre guided_json ou guided_regex de l’API compatible OpenAI, vLLM utilise Outlines en interne pour le guided decoding. Avec Ollama, le support du format JSON est disponible via le paramètre format depuis la version 0.5. Pour des contraintes plus fines (regex, grammaires), on utilise Outlines directement avec transformers ou llama-cpp-python.

L’intégration dans un pipeline n8n ou LangGraph est directe : l’agent appelle le endpoint vLLM avec un schéma JSON, reçoit une réponse garantie parseable, et la transmet à l’étape suivante sans try/catch ni retry. Le coût en fiabilité passe de « 90 % du temps ça marche » à « 100 % garanti ».

Exemple concret : extraction structurée depuis un document

Imaginons un pipeline RAG qui indexe des délibérations de conseil municipal dans Qdrant. Pour chaque document ingéré via Docling, un LLM local extrait les métadonnées structurées : date de la délibération, numéro, thème, décision (adoptée/rejetée/ajournée), montant budgétaire éventuel. Sans Outlines, il faut parser la sortie texte du LLM avec des heuristiques fragiles. Avec Outlines et un schéma Pydantic :

from pydantic import BaseModel
from enum import Enum

class Decision(str, Enum):
    adoptee = "adoptée"
    rejetee = "rejetée"
    ajournee = "ajournée"

class Deliberation(BaseModel):
    date: str          # format YYYY-MM-DD
    numero: str
    theme: str
    decision: Decision
    montant_euros: float | None

Chaque appel produit un JSON valide, typé, directement injectable dans PostgreSQL ou dans les métadonnées Qdrant. Zéro post-processing, zéro erreur de parsing.

Performance et limites

Le surcoût de la génération structurée est minime : le masque logit est précalculé sous forme d’un automate fini (index FSM), ce qui rend l’application du masque quasi instantanée à chaque étape. Sur vLLM, l’impact sur le débit est de l’ordre de 1 à 5 %, négligeable face au gain en fiabilité.

Les limites sont connues : la contrainte structurelle ne garantit pas la justesse sémantique (le modèle peut produire un JSON valide avec des valeurs absurdes), et les schémas très complexes avec de nombreuses propriétés optionnelles peuvent ralentir la compilation de l’index. Pour les cas les plus exigeants, combiner Outlines avec Langfuse pour le monitoring et un validateur métier en aval reste la bonne pratique.

Déploiement rapide

En quelques lignes, on ajoute la génération structurée à un modèle servi par vLLM :

# Lancer vLLM avec guided decoding activé (par défaut)
docker run --gpus all -p 8000:8000 \
  vllm/vllm-openai:latest \
  --model mistralai/Mistral-7B-Instruct-v0.3

# Appel API avec contrainte JSON Schema
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistralai/Mistral-7B-Instruct-v0.3",
    "messages": [{"role": "user", "content": "Extrais les métadonnées de cette délibération : ..."}],
    "guided_json": {
      "type": "object",
      "properties": {
        "date": {"type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$"},
        "numero": {"type": "string"},
        "theme": {"type": "string"},
        "decision": {"type": "string", "enum": ["adoptée", "rejetée", "ajournée"]},
        "montant_euros": {"type": "number"}
      },
      "required": ["date", "numero", "theme", "decision"]
    }
  }'

La réponse est un JSON garanti conforme au schéma, prêt pour l’étape suivante du pipeline.

En résumé

Outlines transforme un LLM de « générateur de texte imprévisible » en « fonction typée fiable ». C’est la brique qui manquait entre le serving (vLLM, Ollama) et l’orchestration (n8n, LangGraph) pour rendre les pipelines IA réellement robustes en production. Combiné avec les outils déjà déployés : Docling pour l’ingestion, pgvector ou Qdrant pour le stockage vectoriel, Langfuse pour l’observabilité, il complète une stack IA open source où chaque maillon est contrôlé et fiable.