Module 2 — Modèles, consignes et analyseurs de sortie
Trois briques suffisent pour un premier appel utile : un modèle chat, un gabarit de consigne, un analyseur qui transforme la réponse en objet Python typé. Ce module fixe la mécanique de chacune en gardant le fil rouge : extraire d'un ticket de restaurant scanné les champs qu'attend notre système de notes de frais.
L'interface chat unifiée
langchain-core définit l'interface BaseChatModel. Un modèle instancié expose toujours les mêmes verbes, que l'implémentation soit ChatOpenAI, ChatAnthropic ou ChatOllama :
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
modele = ChatOpenAI(model="gpt-4o-mini", temperature=0)
reponse = modele.invoke([
SystemMessage(content="Vous êtes un assistant de notes de frais."),
HumanMessage(content="Le taxi du soir est-il remboursable ?"),
])
print(reponse.content)
.invoke(...) renvoie un AIMessage avec un content (le texte) et des response_metadata (jetons utilisés, latence, finish_reason). .stream(...) retourne un itérable de fragments pour un rendu progressif. .batch([...]) traite plusieurs entrées en parallèle avec un contrôle explicite de la concurrence — utile pour ré-annoter mille tickets sans écrire de boucle asynchrone.
Trois paramètres à connaître par cœur. temperature=0 pour toute tâche déterministe (extraction, classification, appel d'outil) ; max_tokens pour couper une réponse trop longue ; timeout pour éviter qu'un incident réseau ne fige le processus. Les autres paramètres du fournisseur (top_p, pénalités) passent en model_kwargs.
Les gabarits de consigne
Concaténer des f-string à la main est la source du 80 % des bogues d'un premier prototype. Un ChatPromptTemplate sépare la structure des messages des variables à injecter :
from langchain_core.prompts import ChatPromptTemplate
consigne = ChatPromptTemplate.from_messages([
("system", "Vous extrayez les champs d'un ticket de restaurant. Répondez uniquement par du JSON valide."),
("human", "Voici le texte OCR d'un ticket :\n\n{texte}\n\nDate d'aujourd'hui : {date}."),
])
messages = consigne.format_messages(texte="RESTAURANT LA POSTE 14/03/2026 27,50 EUR", date="2026-03-15")
Trois avantages tangibles. La consigne système est déclarée une fois, jamais retapée. Les variables sont explicitées : si on oublie date, format_messages lève une KeyError avant l'appel API, pas au milieu de la production. Enfin, le gabarit est composable avec | : consigne | modele devient une chaîne réutilisable.
Un point qui piège : les accolades {} dans le texte du gabarit sont interprétées. Un exemple JSON inséré littéralement doit doubler ses accolades ({{ et }}) ou passer par une variable.
Les analyseurs de sortie
Un LLM répond en texte. Une application a besoin d'un objet. L'analyseur (OutputParser) fait le pont.
Le plus simple, StrOutputParser, extrait .content du message et retourne une chaîne. Utile pour clore un pipeline dont on veut le texte brut.
Pour de l'extraction structurée, la voie moderne s'appuie sur pydantic :
from pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser
class LigneTicket(BaseModel):
marchand: str = Field(description="Nom du marchand")
montant: float = Field(description="Montant total en euros, séparateur décimal '.'")
date: str = Field(description="Date au format ISO AAAA-MM-JJ")
categorie: str = Field(description="restauration, transport, hebergement, autre")
analyseur = PydanticOutputParser(pydantic_object=LigneTicket)
L'analyseur fournit deux services. À la construction, analyseur.get_format_instructions() génère une consigne prête à injecter qui décrit le schéma attendu. À la sortie, analyseur.invoke(reponse) valide le JSON et retourne une instance LigneTicket. Un champ manquant ou un type incorrect lève une ValidationError avant que l'objet ne pollue le reste du pipeline.
Sorties structurées natives
Depuis 2024, la plupart des fournisseurs proposent un mode « sortie structurée » garanti côté serveur, plus fiable qu'un simple Répondez en JSON :
modele_structure = ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(LigneTicket)
ticket = modele_structure.invoke("Extrayez : RESTAURANT LA POSTE 14/03/2026 27,50 EUR")
with_structured_output(...) accepte une classe pydantic ou un schéma JSON et renvoie directement un objet valide. En interne, LangChain choisit entre trois mécanismes selon le fournisseur : JSON mode, appels de fonction, ou grammaire contrainte. Cette méthode est aujourd'hui la voie par défaut pour extraire du structuré ; réservez PydanticOutputParser aux modèles qui ne supportent pas encore la sortie stricte.
Gérer les échecs d'analyse
Un modèle échoue parfois : il renvoie du texte hors JSON, un champ mal typé, une clé oubliée. Trois lignes de défense empilées :
- Contraindre à la source.
with_structured_outputréduit le taux d'échec d'un ordre de grandeur par rapport à un simpleRépondez en JSON. - Envelopper d'un
OutputFixingParser. À la premièreValidationError, ce parseur redemande au modèle de corriger sa sortie en lui montrant l'erreur. Coûte un appel supplémentaire, sauve la journée. - Journaliser et rejouer. Un
try/except ValidationErrorqui écrit la sortie brute dans un fichier de quarantaine évite de perdre les cas problématiques ; ils alimentent le jeu d'évaluation du module 9.
Un temperature=0.7 en extraction structurée est une erreur silencieuse : la sortie devient inconstante d'un appel à l'autre alors qu'on cherche exactement le contraire. Toute tâche d'extraction, de classification et d'appel d'outil demande temperature=0.
En résumé
- L'interface
BaseChatModeluniformise.invoke,.stream,.batchentre fournisseurs ;temperature,max_tokensettimeoutsont les trois paramètres à toujours fixer. - Un
ChatPromptTemplatesépare la structure des variables et détecte les variables oubliées avant l'appel API. with_structured_outputavec un modèlepydanticest la voie par défaut de l'extraction structurée en 2026 ;PydanticOutputParserreste utile pour les modèles sans sortie stricte.OutputFixingParserrécupère la plupart des erreurs d'analyse en redemandant une correction au modèle ; toujours associer untemperature=0en extraction.
Module suivant : composer plusieurs Runnable en LCEL pour enchaîner et paralléliser ces briques.