Construire des agents LLM qui retournent fiabillement des données structurées est l'un des défis majeurs en ingénierie IA. Bien que les grands modèles de langage (LLM) excelllent dans la génération de texte créatif, ils sont sujets à des hallucinations et des erreurs de formatage lorsqu'ils doivent produire du JSON strict. Les approches traditionnelles impliquent souvent des expressions régulières fragiles ou des boucles de réessai qui analysent du texte brut, ce qui conduit à des systèmes de production instables.
Entrez PydanticAI, un framework d'agents Python conçu pour rendre le travail avec les LLM sûr, prévisible et convivial pour les développeurs. En exploitant la puissance des modèles Pydantic, PydanticAI garantit que les sorties des LLM sont validées contre des schémas stricts avant d'être renvoyées à votre application. Cet article explore comment utiliser PydanticAI pour construire des agents sûrs de type capables d'extraire des structures JSON complexes et imbriquées à partir de texte non structuré.
Pourquoi la sûreté de type est importante dans les pipelines LLM
Dans des langages dynamiques comme Python, le "duck typing" est souvent suffisant pour le prototypage. Cependant, lorsque la sortie d'un LLM sert d'entrée à une écriture en base de données en aval, à un appel API ou à un moteur de logique métier, l'ambiguïté est fatale. Si le modèle renvoie une chaîne de caractères là où un entier est attendu, ou s'il omet un champ obligatoire, votre application peut planter ou, pire, échouer silencieusement avec des données corrompues.
PydanticAI résout ce problème en intégrant la validation de schéma directement dans la boucle d'exécution de l'agent. Au lieu de demander au modèle "du JSON", vous définissez une classe Python à l'aide de Pydantic. PydanticAI traduit automatiquement cette définition de classe en un schéma JSON que le LLM comprend. Si la réponse du LLM ne correspond pas au schéma, PydanticAI peut déclencher automatiquement une boucle d'auto-correction, invitant le modèle à corriger ses erreurs avant que les données ne soient exposées à votre application.
Définition de structures de sortie complexes
L'une des fonctionnalités remarquables de PydanticAI est son support pour les modèles Pydantic complexes. Vous pouvez utiliser des modèles imbriqués, des énumérations et des validateurs personnalisés pour imposer des règles de logique métier directement sur la sortie du LLM.
Considérons un scénario où nous devons extraire les détails d'une facture d'un e-mail. Les données incluent le nom du client, une liste de lignes avec des totaux calculés et une date. Nous pouvons définir cette structure avec une sûreté de type complète :
from pydantic import BaseModel, Field, field_validator
from datetime import date
from pydantic_ai import Agent
class LineItem(BaseModel):
description: str = Field(..., description="Brève description de l'article")
quantity: int = Field(..., gt=0)
price: float = Field(..., ge=0)
def total(self) -> float:
return self.quantity * self.price
class InvoiceData(BaseModel):
customer_name: str = Field(..., description="Nom légal complet du client")
invoice_date: date
line_items: list[LineItem]
total_amount: float
@field_validator('total_amount')
@classmethod
def check_total(cls, v: float, info) -> float:
if 'line_items' in info.data:
calculated = sum(item.total() for item in info.data['line_items'])
if abs(v - calculated) > 0.01:
raise ValueError("Le montant total ne correspond pas à la somme des lignes")
return v
agent = Agent("openai:gpt-4o", result_type=InvoiceData)
Remarquez le @field_validator. Cela ne sert pas seulement à la forme des données ; il impose la cohérence logique. Si le LLM hallucine un total qui ne correspond pas à la somme de ses lignes, la validation échoue et PydanticAI invitera le modèle à reconsidérer.
Exécution de l'agent et gestion des réponses
Une fois l'agent défini, son exécution est simple. La méthode run renvoie un objet typé, ce qui signifie que vous bénéficiez d'un support IDE complet et de la vérification des types dans votre IDE. Il n'est pas nécessaire d'utiliser data['customer_name'] ; vous pouvez utiliser data.customer_name.
import asyncio
email_text = """
Objet : Facture #12345
Bonjour John,
Voici la facture pour octobre.
Article 1 : Widget A x2 @ 50,00 $
Article 2 : Widget B x1 @ 100,00 $
Total : 200,00 $
Date : 2023-10-27
"""
async def main():
result = await agent.run(email_text)
# result.data est un objet InvoiceData entièrement validé
print(f"Client : {result.data.customer_name}")
print(f"Total : {result.data.total_amount}")
# Accès sûr aux données imbriquées sans KeyError ou AttributeError
for item in result.data.line_items:
print(f"- {item.description} : {item.total()}")
asyncio.run(main())
Meilleures pratiques pour les exports complexes
- Gardez les modèles plats lorsque c'est possible : Bien que l'imbrication soit prise en charge, les structures profondément imbriquées peuvent parfois confondre les LLM. Aplatir le schéma lorsque c'est logiquement permis améliore souvent la précision.
- Utilisez des descriptions de champs descriptives : Les LLM s'appuient sur le paramètre
descriptiondes champs Pydantic pour comprendre le contexte. Soyez explicite sur les exigences de format (par exemple, "date ISO 8601"). - Exploitez les énumérations : Si un champ a un ensemble fixe de valeurs (comme des codes de statut), utilisez des classes
enumPython. Cela réduit considérablement la probabilité de données catégorielles invalides.
Conclusion
PydanticAI représente un pas en avant significatif pour rendre les applications LLM prêtes pour la production. En déplaçant la charge de la validation des données de l'analyse a posteriori vers la vérification de type prééminente, vous éliminez toute une classe d'erreurs d'exécution. Pour les développeurs construisant des agents qui doivent interagir avec des pipelines de données structurées, PydanticAI offre une solution propre, pythonique et robuste. Adoptez la sûreté de type, et vos agents LLM vous remercieront avec des sorties fiables et prévisibles.