Analyseurs de Sortie dans LangChain : Structuration des Réponses de Modèles

Les analyseurs de sortie (Output Parsers) de LangChain sont des composants fondamentaux qui permettent de transformer les réponses brutes générées par les modèles de langage en formats plus structurés et exploitables par programme. Ils facilitent l'intégration des LLM dans des applications en convertissant le texte libre en objets, JSON, listes ou autres structures de données.

Rôle des Analyseurs de Sortie

Les analyseurs de sortie accomplissent plusieurs fonctions clés :

  • Structuration : Ils convertissent des réponses textuelles non structurées en formats de données structurés (par exemple, JSON, listes, dates).
  • Validation : Ils garantissent que la sortie adhère à un format et à un contenu attendus, améliorant la fiabilité.
  • Standardisation : Ils assurent un format de sortie cohérent, simplifiant le traitement ultérieur.

LangChain propose une variété d'analyseurs, chacun adapté à des besoins spécifiques :

  • CommaSeparatedListOutputParser : Pour des sorties sous forme de listes séparées par des virgules.
  • DatetimeOutputParser : Pour convertir des descriptions textuelles de dates en objets datetime.
  • JsonOutputParser : Pour structurer les sorties en objets JSON.
  • EnumOutputParser : Pour contraindre le modèle à choisir parmi un ensemble prédéfini d'options.
  • Ainsi que des analyseurs personnalisés et des mécanismes de correction des erreurs.

Analyseur de Liste Séparée par des Virgules

L'analyseur CommaSeparatedListOutputParser est conçu pour transformer une chaîne de caractères séparée par des virgules en une liste Python. Voici un exemple d'utilisation :

from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# Initialisation du modèle de langage
llm_model = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Définition de l'analyseur pour les listes
list_parser = CommaSeparatedListOutputParser()

# Récupération des instructions de formatage pour le prompt
format_guide = list_parser.get_format_instructions()
print(f"Instructions de formatage:\n{format_guide}\n")

# Définition du template de prompt
prompt_template = PromptTemplate(
    template="Veuillez me fournir une liste de cinq marques populaires de {domaine}, sans aucune description.\n{format_guide}",
    input_variables=["domaine"],
    partial_variables={"format_guide": format_guide},
)

# Création de la chaîne de traitement
processing_chain = prompt_template | llm_model | list_parser

# Invocation de la chaîne avec un sujet spécifique
result_list = processing_chain.invoke({"domaine": "automobiles"})
print(f"Résultat obtenu: {result_list}")
print(f"Type de la sortie: {type(result_list)}")

Le résultat sera une liste Python, par exemple :

Instructions de formatage:
Your response should be a list of comma separated values, eg: `foo, bar, baz` or `foo,bar,baz`

Résultat obtenu: ['Toyota', 'Volkswagen', 'Ford', 'Honda', 'Hyundai']
Type de la sortie: <class 'list'>

Analyseur de Date et Heure

L'analyseur DatetimeOutputParser est capable de convertir une description textuelle de date et d'heure en un objet Python datetime, offrant une grande flexibilité pour gérer des expressions temporelles variées.

from langchain.output_parsers import DatetimeOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
import datetime

# Initialisation du modèle de langage
chat_model = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Définition de l'analyseur pour les dates
date_parser = DatetimeOutputParser()

# Récupération des instructions de formatage
date_format_hint = date_parser.get_format_instructions()
print(f"Instructions de formatage pour la date:\n{date_format_hint}\n")

# Définition du template de prompt pour une date spécifique
date_prompt = PromptTemplate(
    template="Veuillez me fournir la date exacte à laquelle {evenement} a eu lieu. {date_format_hint}",
    input_variables=["evenement"],
    partial_variables={"date_format_hint": date_format_hint},
)

# Création de la chaîne de traitement
date_chain = date_prompt | chat_model | date_parser

# Invocation avec un événement historique
event_date = date_chain.invoke({"evenement": "le débarquement de Normandie"})
print(f"Date de l'événement: {event_date}")
print(f"Type de la sortie: {type(event_date)}")

Un exemple de sortie serait :

Instructions de formatage pour la date:
Write a datetime string that matches the following pattern: '%Y-%m-%dT%H:%M:%S.%fZ'.

Date de l'événement: 1944-06-06 00:00:00
Type de la sortie: <class 'datetime.datetime'>

Analyseur JSON

Le JsonOutputParser transforme les sorties non structurées du modèle en objets JSON, ce qui est idéal pour des applications nécessitant des données fortement typées.

JSON Basique

from langchain_core.output_parsers import JsonOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# Initialisation du modèle
json_llm = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Création de l'analyseur JSON
basic_json_parser = JsonOutputParser()

# Récupération des instructions de formatage
json_hint = basic_json_parser.get_format_instructions()
print(f"Instructions de formatage JSON:\n{json_hint}\n")

# Définition du prompt
json_prompt = PromptTemplate(
    template="Décrivez cinq films {genre} célèbres, en indiquant leur titre et réalisateur. {json_hint}",
    input_variables=["genre"],
    partial_variables={"json_hint": json_hint},
)

# Chaîne de traitement
json_chain = json_prompt | json_llm | basic_json_parser

# Invocation
movies_data = json_chain.invoke({"genre": "fantastique"})
print(f"Données des films: {movies_data}")
print(f"Type de la sortie: {type(movies_data)}")

La sortie ressemblera à un dictionnaire Python (qui est l'équivalent JSON) :

Instructions de formatage JSON:
Return a JSON object.

Données des films: {'films': [{'titre': 'Le Seigneur des Anneaux : La Communauté de l\'Anneau', 'réalisateur': 'Peter Jackson'}, {'titre': 'Harry Potter à l\'école des sorciers', 'réalisateur': 'Chris Columbus'}, {'titre': 'Le Labyrinthe de Pan', 'réalisateur': 'Guillermo del Toro'}, {'titre': 'Blade Runner', 'réalisateur': 'Ridley Scott'}, {'titre': 'Inception', 'réalisateur': 'Christopher Nolan'}]}
Type de la sortie: <class 'dict'>

Déclaration de Modèles de Données avec Pydantic

L'intégration de Pydantic permet de définir un schéma strict pour les objets JSON attendus. Cela assure une validation forte et une structure de données prévisible.

from typing import List
from langchain_core.output_parsers import JsonOutputParser
from langchain.prompts import PromptTemplate
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

# Initialisation du modèle
llm_for_pydantic = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Définition du modèle Pydantic pour les informations d'un film
class MovieInfo(BaseModel):
    title: str = Field(description="Titre du film")
    director: str = Field(description="Réalisateur du film")
    genre: str = Field(description="Genre principal du film")

# Modèle Pydantic pour une liste de films
class MovieList(BaseModel):
    movies: List[MovieInfo] = Field(description="Liste des films populaires")

# Création de l'analyseur JSON basé sur le modèle Pydantic
pydantic_json_parser = JsonOutputParser(pydantic_object=MovieList)

# Récupération des instructions de formatage avec le schéma Pydantic
pydantic_format_hint = pydantic_json_parser.get_format_instructions()
print(f"Instructions de formatage Pydantic:\n{pydantic_format_hint}\n")

# Définition du prompt
pydantic_prompt = PromptTemplate(
    template="Fournissez les détails de cinq films populaires de {country}, en incluant leur titre, réalisateur et genre. {pydantic_format_hint}",
    input_variables=["country"],
    partial_variables={"pydantic_format_hint": pydantic_format_hint},
)

# Chaîne de traitement
pydantic_chain = pydantic_prompt | llm_for_pydantic | pydantic_json_parser

# Invocation
country_movies = pydantic_chain.invoke({"country": "France"})
print(f"Films français: {country_movies}")
print(f"Type de la sortie: {type(country_movies)}")

La sortie sera un dictionnaire structuré selon le modèle Pydantic :

Instructions de formatage Pydantic:
The output should be formatted as a JSON instance that conforms to the JSON schema below.
... (schema JSON détaillé) ...

Films français: {'movies': [{'title': 'Le Dîner de Cons', 'director': 'Francis Veber', 'genre': 'Comédie'}, {'title': 'Amélie Poulain', 'director': 'Jean-Pierre Jeunet', 'genre': 'Romance'}, {'title': 'Intouchables', 'director': 'Olivier Nakache et Éric Toledano', 'genre': 'Comédie dramatique'}, {'title': 'La Haine', 'director': 'Mathieu Kassovitz', 'genre': 'Drame'}, {'title': 'Le Cinquième Élément', 'director': 'Luc Besson', 'genre': 'Science-fiction'}]}
Type de la sortie: <class 'dict'>

Analyseur d'Énumération

L'analyseur EnumOutputParser permet de limiter les réponses du modèle à un ensemble prédéfini de valeurs d'une énumération Python, ce qui est utile pour les choix multiples ou la classification.

from langchain.output_parsers.enum import EnumOutputParser
from enum import Enum
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# Initialisation du modèle
enum_llm = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Définition d'une énumération pour les humeurs
class MoodOptions(Enum):
    JOYFUL = "Joyeuse"
    CALM = "Calme"
    ENERGETIC = "Énergique"
    REFLECTIVE = "Réfléchie"

# Définition de l'analyseur d'énumération
enum_parser = EnumOutputParser(enum=MoodOptions)

# Récupération des instructions de formatage
enum_format_hint = enum_parser.get_format_instructions()
print(f"Instructions de formatage pour l'énumération:\n{enum_format_hint}\n")

# Définition du prompt
enum_prompt = PromptTemplate(
    template="Choisissez l'option qui décrit le mieux l'état d'esprit associé au concept suivant.\n{enum_format_hint}\nQuestion: {concept}\nRéponse:",
    input_variables=["concept"],
    partial_variables={"enum_format_hint": enum_format_hint},
)

# Chaîne de traitement
enum_chain = enum_prompt | enum_llm | enum_parser

# Invocation
selected_mood = enum_chain.invoke({"concept": "lever du soleil"})
print(f"Humeur associée: {selected_mood}")
print(f"Type de la sortie: {type(selected_mood)}")

La sortie sera un membre de l'énumération :

Instructions de formatage pour l'énumération:
Select one of the following options: Joyeuse, Calme, Énergique, Réfléchie

Humeur associée: MoodOptions.CALM
Type de la sortie: <enum 'MoodOptions'>

Attnetion : Si le modèle ne peut pas faire correspondre sa réponse à une option d'énumération existante, une erreur OutputParserException sera levée.

Analyseur de Sortie Personnalisé

Pour les cas où les analyseurs intégrés ne suffisent pas, il est possible de définir un analyseur personnalisé en utilisant une simple fonction Python. Cette fonction recevra la sortie brute du modèle et la transformera selon une logique spécifique.

from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage

# Initialisation du modèle
custom_llm = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Définition d'une fonction d'analyse personnalisée
def count_words(model_output: AIMessage) -> int:
    """Compte le nombre de mots dans la réponse du modèle."""
    return len(model_output.content.split())

# Définition du prompt
custom_prompt = PromptTemplate(
    template="Veuillez énumérer cinq plats typiques de la cuisine {cuisine}, sans description.",
    input_variables=["cuisine"],
)

# Création de la chaîne avec l'analyseur personnalisé
custom_chain = custom_prompt | custom_llm | count_words

# Invocation
word_count = custom_chain.invoke({"cuisine": "japonaise"})
print(f"Nombre de mots dans la réponse: {word_count}")

Le résultat sera la longueur calculée par la fonction :

Nombre de mots dans la réponse: 10

Analyseur de Correction de Sortie (OutputFixingParser)

L'OutputFixingParser est une fonctionnalité puissante qui tente de corriger les sorties du modèle qui n'ont pas réussi à être analysées par un analyseur primaire. Il est particulièrement utile lorsque le modèle génère une sortie qui est presque correcte mais contient de petites erreurs de formatage.

from typing import List
from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
from langchain_openai import ChatOpenAI

# Initialisation du modèle
fixing_llm = ChatOpenAI(openai_api_key="YOUR_API_KEY", openai_api_base='http://127.0.0.1:1234/v1')

# Modèle Pydantic pour un artiste et ses œuvres
class Artist(BaseModel):
    artist_name: str = Field(description="Nom de l'artiste")
    major_works: List[str] = Field(description="Liste des œuvres majeures de l'artiste")

# Création de l'analyseur Pydantic standard
pydantic_parser = PydanticOutputParser(pydantic_object=Artist)

# Une sortie mal formatée intentionnellement
misformatted_output = "{'artist_name':'Vincent van Gogh','major_works':['La Nuit étoilée','Les Tournesols'" # Manque la parenthèse fermante

try:
    # Tentative d'analyse de la sortie mal formatée (cela devrait échouer)
    pydantic_parser.parse(misformatted_output)
except Exception as e:
    print(f"Erreur d'analyse initiale attendue: {e}\n")

# Utilisation de OutputFixingParser pour corriger l'erreur
fixing_parser = OutputFixingParser.from_llm(parser=pydantic_parser, llm=fixing_llm)

# Tentative d'analyse avec le fixeur
corrected_content = fixing_parser.parse(misformatted_output)
print(f"Contenu corrigé: {corrected_content}")
print(f"Type du contenu corrigé: {type(corrected_content)}")

Le OutputFixingParser interagit avec le LLM pour lui demander de corriger son propre formatage, aboutissant à une sortie valide :

Erreur d'analyse initiale attendue: Value error, Invalid JSON: {'artist_name':'Vincent van Gogh','major_works':['La Nuit étoilée','Les Tournesols'

Contenu corrigé: artist_name='Vincent van Gogh' major_works=['La Nuit étoilée', 'Les Tournesols']
Type du contenu corrigé: <class '__main__.Artist'>

Étiquettes: langchain Output Parsers LLM Python pydantic

Publié le 19 juillet à 19h44