Pydantic AI applique aux agents la même idée que Pydantic aux API : rendre les contrats explicites. Nous allons créer un assistant support qui consulte une commande via un outil et retourne une structure validée.
Installer le projet
~~~bash python -m venv .venv source .venv/bin/activate pip install -U pydantic-ai python-dotenv ~~~
Configurez la clé du fournisseur choisi dans l’environnement. Le nom de modèle dépend du provider ; centralisez-le dans une variable plutôt que de le répéter.
Définir les contrats
~~~python from dataclasses import dataclass from pydantic import BaseModel, Field
@dataclass class SupportDeps: order_repository: "OrderRepository" customer_id: str
class SupportAnswer(BaseModel): answer: str needs_human: bool = False confidence: float = Field(ge=0, le=1) ~~~
"SupportDeps" contient les services disponibles pendant un run. "SupportAnswer" décrit la sortie attendue par le reste de l’application.
Créer l’agent et son outil
~~~python from pydantic_ai import Agent, RunContext
agent = Agent( "openai:gpt-5-mini", deps_type=SupportDeps, output_type=SupportAnswer, system_prompt=( "Tu aides un client à comprendre sa commande. " "N'invente jamais un statut et demande une reprise humaine si nécessaire." ), )
@agent.tool async def get_order(ctx: RunContext[SupportDeps], order_id: str) -> dict: """Retourne une commande appartenant au client connecté.""" order = await ctx.deps.order_repository.get(order_id) if order is None or order["customer_id"] != ctx.deps.customer_id: return {"found": False} return { "found": True, "status": order["status"], "estimated_delivery": order.get("estimated_delivery"), } ~~~
Le contrôle d’autorisation reste dans l’outil. Le modèle ne doit jamais choisir lui-même quel client il est autorisé à consulter.
Exécuter l’agent
~~~python deps = SupportDeps( order_repository=repository, customer_id="customer-42", )
result = await agent.run( "Où en est la commande CMD-123 ?", deps=deps, )
answer = result.output print(answer.answer, answer.needs_human, answer.confidence) ~~~
La sortie est un objet Python validé. Si le modèle ne respecte pas le schéma, le framework peut lui demander de corriger sa réponse dans les limites configurées.
Rendre les instructions dynamiques
Utilisez une fonction de "system_prompt" lorsqu’une instruction dépend du contexte fiable, par exemple le rôle de l’utilisateur ou la langue. N’injectez pas directement un texte non fiable dans les instructions système.
Tester sans appeler les vrais services
Injectez un faux repository dans "SupportDeps". Testez au minimum : commande existante, commande appartenant à un autre client, identifiant inconnu, erreur du repository et sortie demandant une reprise humaine. Le typage ne prouve pas la justesse métier ; ajoutez des assertions sur le contenu et les outils appelés.
Production
Fixez des limites de requêtes et de tours, donnez aux outils des schémas étroits, imposez les autorisations côté code et journalisez identifiant de trace, modèle, latence et consommation. Une action destructive doit exiger confirmation et clé d’idempotence. Versionnez prompts et jeux de tests avec le code.
FAQ
Une sortie typée élimine-t-elle les hallucinations ?
Non. Elle garantit la forme, pas la vérité. Les valeurs critiques doivent venir d’un outil ou être vérifiées par des règles métier.
Pourquoi injecter les dépendances ?
Cela sépare l’agent de l’infrastructure, évite les variables globales et rend les tests beaucoup plus simples.