Pydantic AI apporte la même expérience développeur qui a fait de FastAPI le framework Python incontournable pour les services web au monde des agents IA. Au lieu de manipuler des réponses LLM brutes en espérant que les chaînes correspondent à votre schéma attendu, Pydantic AI vous offre une sécurité de types complète, une validation automatique et des sorties structurées — soutenu par la bibliothèque Pydantic qui valide déjà les données dans des millions d'applications en production.
Dans ce tutoriel, vous allez construire un Agent de recherche de contenu qui effectue des recherches sur le web, extrait les faits clés et renvoie un rapport de recherche entièrement typé — le tout en moins de 200 lignes de Python.
Ce que vous allez construire
À la fin de ce tutoriel, vous aurez :
- Un agent Pydantic AI fonctionnel avec utilisation d'outils et sortie structurée
- L'injection de dépendances pour un contexte réutilisable (client HTTP, base de données, config)
- Le support du streaming pour des réponses en temps réel
- La gestion des conversations multi-tours
- Un agent de recherche de contenu prêt pour la production
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Python 3.11 ou version ultérieure installé
- Une connaissance de base des annotations de types Python et de async/await
- Une clé API d'OpenAI, Anthropic ou tout fournisseur pris en charge
pipouuvpour la gestion des paquets
Pourquoi Pydantic AI ?
La plupart des frameworks LLM traitent les sorties structurées comme un ajout secondaire — vous obtenez des chaînes JSON brutes et les analysez vous-même, en espérant que le modèle n'a pas inventé des champs supplémentaires. Pydantic AI résout ce problème au niveau du framework :
- Réponses type-safe : définissez un modèle Pydantic, récupérez une instance validée. Pas d'analyse manuelle.
- Nouvelle tentative automatique : si le LLM renvoie un JSON invalide ou des types incorrects, Pydantic AI relance avec l'erreur de validation renvoyée au modèle.
- Conception orientée outils : enregistrez des fonctions Python comme outils avec un seul décorateur. Les arguments sont validés automatiquement.
- Indépendant du fournisseur : basculez entre OpenAI, Anthropic, Gemini, Mistral, Ollama et des dizaines d'autres en changeant une seule chaîne.
- Logfire natif : observabilité de première classe sans configuration si vous utilisez Logfire de Pydantic.
Étape 1 : Installer Pydantic AI
Installez avec votre fournisseur préféré. L'extra openai inclut le client OpenAI :
pip install 'pydantic-ai[openai]'
# ou pour Anthropic
pip install 'pydantic-ai[anthropic]'
# ou pour tout inclure
pip install 'pydantic-ai[all]'Avec uv (recommandé) :
uv add 'pydantic-ai[openai]'Configurez votre clé API dans l'environnement :
export OPENAI_API_KEY="sk-..."
# ou
export ANTHROPIC_API_KEY="sk-ant-..."Étape 2 : Votre premier agent
La classe Agent est la primitive centrale. Vous déclarez le modèle, les instructions et optionnellement un output_type :
from pydantic_ai import Agent
agent = Agent(
'openai:gpt-4o',
instructions="Vous êtes un assistant utile. Soyez concis et précis.",
)
result = agent.run_sync('Quelle est la capitale de la Tunisie ?')
print(result.output) # "La capitale de la Tunisie est Tunis."Pour le code asynchrone (recommandé en production), utilisez await agent.run(...) :
import asyncio
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', instructions='Soyez concis.')
async def main():
result = await agent.run("Expliquez asyncio en une phrase.")
print(result.output)
asyncio.run(main())Changer de fournisseur est un changement d'une seule ligne :
# OpenAI
agent = Agent('openai:gpt-4o')
# Anthropic
agent = Agent('anthropic:claude-sonnet-4-6')
# Gemini
agent = Agent('google-gla:gemini-2.5-flash')
# Ollama local
agent = Agent('ollama:llama3.2')Étape 3 : Sorties structurées avec les modèles Pydantic
C'est là que Pydantic AI brille. Passez n'importe quel modèle Pydantic comme output_type et l'agent garantit le retour d'une instance validée :
from pydantic import BaseModel
from pydantic_ai import Agent
class MovieReview(BaseModel):
title: str
year: int
rating: float # de 0.0 à 10.0
summary: str
pros: list[str]
cons: list[str]
recommended: bool
agent = Agent(
'openai:gpt-4o',
output_type=MovieReview,
instructions="Vous êtes un critique de cinéma. Retournez des critiques structurées.",
)
result = agent.run_sync('Critiquez le film Inception.')
review = result.output # type : MovieReview — entièrement validé
print(f"{review.title} ({review.year}) — {review.rating}/10")
print(f"Recommandé : {review.recommended}")
print("Pour :", review.pros)Si le modèle renvoie une réponse invalide (types incorrects, champs manquants), Pydantic AI envoie automatiquement l'erreur de validation au modèle et demande une réponse corrigée.
Étape 4 : Outils de fonctions
Les outils permettent au LLM d'appeler des fonctions Python pendant une conversation. Enregistrez-les avec @agent.tool (avec contexte) ou @agent.tool_plain (sans contexte) :
import httpx
from pydantic_ai import Agent, RunContext
agent = Agent(
'openai:gpt-4o',
instructions="Vous êtes un assistant météo. Utilisez les outils pour récupérer des données réelles.",
)
@agent.tool_plain
async def get_weather(city: str) -> str:
"""Récupérer la météo actuelle pour une ville donnée."""
async with httpx.AsyncClient() as client:
response = await client.get(
f'https://wttr.in/{city}?format=3'
)
return response.text
async def main():
result = await agent.run('Quel temps fait-il à Tunis en ce moment ?')
print(result.output)
asyncio.run(main())La docstring devient la description de l'outil envoyée au LLM. Les paramètres de la fonction sont automatiquement convertis en schéma JSON pour le modèle.
Outils avec RunContext (injection de dépendances)
Quand les outils ont besoin d'accéder à des ressources partagées (base de données, client HTTP, config), utilisez @agent.tool avec RunContext :
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class ResearchDeps:
http_client: httpx.AsyncClient
api_key: str
max_results: int = 5
agent = Agent(
'anthropic:claude-sonnet-4-6',
deps_type=ResearchDeps,
instructions="Vous êtes un assistant de recherche avec des capacités de recherche web.",
)
@agent.tool
async def search_web(ctx: RunContext[ResearchDeps], query: str) -> list[str]:
"""Rechercher sur le web et retourner une liste d'extraits de résultats pertinents."""
response = await ctx.deps.http_client.get(
'https://api.search.example.com/search',
params={'q': query, 'limit': ctx.deps.max_results},
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
)
results = response.json()
return [r['snippet'] for r in results.get('items', [])]Le RunContext transporte vos dépendances dans chaque appel d'outil sans état global ni couplage caché.
Étape 5 : Exécuter des agents avec des dépendances
Passez les dépendances au moment de l'exécution en utilisant l'argument deps :
async def main():
async with httpx.AsyncClient() as client:
deps = ResearchDeps(
http_client=client,
api_key='your-search-api-key',
max_results=3,
)
result = await agent.run(
"Recherchez les derniers développements en informatique quantique.",
deps=deps,
)
print(result.output)Étape 6 : Réponses en streaming
Pour les tâches longues, diffusez la sortie au fur et à mesure :
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', instructions="Rédigez du contenu technique détaillé.")
async def stream_demo():
async with agent.run_stream("Expliquez comment fonctionne TCP/IP.") as stream:
async for text_chunk in stream.stream_text():
print(text_chunk, end='', flush=True)
print()
result = await stream.get_output()
asyncio.run(stream_demo())Étape 7 : Conversations multi-tours
Pydantic AI suit automatiquement l'historique des conversations. Utilisez message_history pour poursuivre une conversation :
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', instructions="Vous êtes un tuteur de programmation.")
async def multi_turn():
# Premier tour
result1 = await agent.run("Qu'est-ce qu'un décorateur Python ?")
print('Tour 1 :', result1.output)
# Deuxième tour — passe les messages précédents comme contexte
result2 = await agent.run(
"Montrez-moi un exemple pratique de cela.",
message_history=result1.new_messages(),
)
print('Tour 2 :', result2.output)
asyncio.run(multi_turn())Étape 8 : Contrôler le comportement de l'agent
Limitez le nombre de tours du modèle et d'appels d'outils par exécution :
from pydantic_ai import Agent
from pydantic_ai.usage import UsageLimits
agent = Agent('openai:gpt-4o')
result = await agent.run(
"Recherchez et résumez 5 articles scientifiques récents sur l'IA.",
usage_limits=UsageLimits(
request_limit=10, # max tours LLM
tool_calls_limit=20, # max exécutions d'outils
),
)Étape 9 : Exemple complet — Agent de recherche de contenu
Tout assembler : un agent de recherche de contenu prêt pour la production qui accepte un sujet, recherche des informations et renvoie un ResearchReport typé.
import asyncio
import httpx
from dataclasses import dataclass
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
@dataclass
class Deps:
client: httpx.AsyncClient
brave_api_key: str | None = None
class ArticleSummary(BaseModel):
title: str
url: str
key_points: list[str] = Field(min_length=1, max_length=5)
class ResearchReport(BaseModel):
topic: str
executive_summary: str = Field(min_length=50)
articles: list[ArticleSummary] = Field(min_length=1, max_length=5)
key_trends: list[str] = Field(min_length=3, max_length=10)
recommended_actions: list[str] = Field(min_length=1, max_length=5)
confidence_score: float = Field(ge=0.0, le=1.0)
research_agent = Agent(
'anthropic:claude-sonnet-4-6',
deps_type=Deps,
output_type=ResearchReport,
instructions="""
Vous êtes un analyste de recherche expert. Quand on vous donne un sujet :
1. Utilisez l'outil de recherche pour trouver des informations récentes (2-3 recherches)
2. Utilisez fetch_article pour obtenir le contenu complet des résultats les plus pertinents
3. Synthétisez les résultats dans un ResearchReport structuré
Soyez approfondi mais concis. Concentrez-vous sur les insights actionnables.
""",
)
@research_agent.tool
async def search(ctx: RunContext[Deps], query: str) -> list[dict]:
"""Rechercher sur le web des informations récentes sur un sujet."""
if ctx.deps.brave_api_key:
resp = await ctx.deps.client.get(
'https://api.search.brave.com/res/v1/web/search',
headers={'Accept': 'application/json', 'X-Subscription-Token': ctx.deps.brave_api_key},
params={'q': query, 'count': 5},
)
resp.raise_for_status()
data = resp.json()
return [
{'title': r['title'], 'url': r['url'], 'description': r.get('description', '')}
for r in data.get('web', {}).get('results', [])
]
return [
{'title': f'Article sur {query}', 'url': f'https://example.com/{query}', 'description': f'Couverture complète de {query}'},
]
@research_agent.tool
async def fetch_article(ctx: RunContext[Deps], url: str) -> str:
"""Récupérer et retourner le contenu textuel d'un article."""
try:
resp = await ctx.deps.client.get(url, timeout=10.0, follow_redirects=True)
resp.raise_for_status()
return resp.text[:3000]
except Exception as e:
return f"Impossible de récupérer l'article : {e}"
async def research(topic: str) -> ResearchReport:
async with httpx.AsyncClient(timeout=30.0) as client:
deps = Deps(client=client)
result = await research_agent.run(
f'Recherchez ce sujet en profondeur : {topic}',
deps=deps,
)
return result.output
async def main():
report = await research("Frameworks d'agents IA en Python 2026")
print(f'Sujet : {report.topic}')
print(f'Score de confiance : {report.confidence_score:.0%}')
print(f'\nRésumé exécutif :\n{report.executive_summary}')
print(f'\nTendances clés :')
for trend in report.key_trends:
print(f' - {trend}')
if __name__ == '__main__':
asyncio.run(main())Étape 10 : Résilience multi-fournisseurs
Pour les marchés MENA où la latence ou la disponibilité des services peut varier, configurez une chaîne de secours :
from pydantic_ai import Agent
from pydantic_ai.models.fallback import FallbackModel
# Essayez Claude en premier, puis GPT-4o, puis Gemini
fallback_model = FallbackModel(
'anthropic:claude-sonnet-4-6',
'openai:gpt-4o',
'google-gla:gemini-2.5-flash',
)
agent = Agent(fallback_model, output_type=ResearchReport)Étape 11 : Tester votre agent
Pydantic AI fournit TestModel et FunctionModel pour les tests unitaires sans appeler de vrai LLM :
import pytest
from pydantic_ai.models.test import TestModel
def test_research_agent_structure():
with research_agent.override(model=TestModel()):
result = research_agent.run_sync(
'Sujet de test',
deps=Deps(client=None),
)
assert isinstance(result.output, ResearchReport)Résolution des problèmes
Boucles ValidationError : si le modèle continue de renvoyer des données invalides, ajoutez des instructions plus strictes ou simplifiez votre modèle Pydantic. La request_limit par défaut est 10 — augmentez-la pour les schémas complexes.
L'outil n'est pas appelé : assurez-vous que la docstring décrit clairement quand l'outil doit être utilisé. Le LLM décide quand appeler les outils en fonction de la description.
Async dans du code synchrone : utilisez agent.run_sync() avec parcimonie — dans les frameworks web (FastAPI), utilisez toujours await agent.run().
Prochaines étapes
- Explorer les workflows multi-agents où un agent délègue à des sous-agents spécialisés
- Ajouter le traçage Logfire pour la surveillance en production
- Intégrer Pydantic AI avec FastAPI pour des endpoints LLM type-safe
- Utiliser des évaluations notées par le modèle pour tester la qualité des agents à grande échelle
Conclusion
Pydantic AI supprime le code répétitif qui rend les applications LLM fragiles et difficiles à maintenir. En traitant la validation, l'utilisation des outils et l'abstraction des fournisseurs comme des citoyens de première classe, il vous permet de vous concentrer sur ce que votre agent doit faire — pas sur l'analyse de chaînes en espérant que le LLM a suivi votre schéma. Que vous construisiez un assistant de recherche, un processeur de documents ou un bot de support client, Pydantic AI vous donne la confiance nécessaire pour déployer des agents IA type-safe en production.