Se hai provato a mettere in produzione un’applicazione basata su LLM negli ultimi due anni, sai bene quanto possa essere frustrante.
Si parte sempre con un prototipo che sembra magico, per poi ritrovarsi a scrivere centinaia di righe di codice solo per estrarre un JSON valido da una risposta testuale o per gestire le allucinazioni del modello.
Molti framework, pur volendo semplificare la vita, aggiungono strati di astrazione così densi da farti perdere il controllo su cosa stia realmente accadendo realmente.
È qui che entra in gioco PydanticAI.
Non si tratta di un esperimento o di una libreria di nicchia: è un framework ufficiale, open source, sviluppato dagli stessi creatori di Pydantic, la libreria di validazione dati diventata ormai uno standard di fatto nell’ecosistema Python.
L’obiettivo? Portare l’eleganza e la solidità che abbiamo imparato ad amare in FastAPI direttamente nello sviluppo di agenti AI.
Perché ora
Siamo entrati in una nuova fase dell’intelligenza artificiale. Se il 2024 è stato l’anno dello stupore per i modelli multimodali capaci di vedere e sentire, il 2026 è l’anno della concretezza. La pressione si è spostata dal semplice ‘far parlare l’IA’ al ‘farla funzionare in produzione’ in modo affidabile.
In un mercato che non accetta più prototipi fragili o risposte imprevedibili, PydanticAI nasce per rispondere a un’esigenza precisa: trasformare gli LLM da entità creative a componenti software rigorosi e tipizzati.
Cos’è e a cosa serve PydanticAI?
In sintesi, PydanticAI è un framework progettato per costruire agenti AI garantendo type safety e validazione strutturata end-to-end.
Il suo slogan ufficiale è chiaro:
“GenAI Agent Framework, the Pydantic way”.
Se finora hai usato Pydantic solo come “parser” per formattare gli output di framework come LangChain o LlamaIndex, qui il paradigma cambia.
PydanticAI gestisce l’intero ciclo di vita dell’agente: dal prompt di sistema, all’esecuzione dei tool, fino al loop di ragionamento e alla validazione automatica.
Perché dovresti prenderlo in considerazione?
-
Stop agli output imprevedibili: Definisci cosa vuoi ricevere usando normali modelli Pydantic, dataclasses o tipi nativi Python. Il framework converte tutto in JSON schema, istruisce il modello e valida severamente la risposta.
-
Tool calling intelligente: Se fornisci all’agente delle funzioni Python (tool), lui saprà quando chiamarle, gestendo lo stato e correggendo in autonomia gli errori se i dati non combaciano con la firma della tua funzione.
-
Agnostico sui modelli: Passare da OpenAI a Claude, o da Gemini a un modello locale su Ollama, richiede la modifica di una sola stringa di codice.
-
Pensato per la produzione: Supporta nativamente dependency injection, esecuzione asincrona e un’integrazione fluida con Logfire per monitorare esattamente cosa fa il tuo agente.
Se vuoi costruire sistemi affidabili, mantenendo un approccio “Pythonico” e dichiarativo, questa è la strada giusta.
Vediamo come metterci le mani.
Passo 1: Installazione e Setup
Iniziamo preparando l’ambiente. Apri il terminale e installa la libreria:
pip install pydantic-ai
(Consiglio: se sei agli inizi e vuoi testare le funzionalità complete, usa pip install "pydantic-ai[examples]").
A questo punto, dobbiamo far comunicare il framework con un Large Language Model.
Il metodo più pulito per gestire le chiavi API è utilizzare le variabili d’ambiente. PydanticAI è in grado di leggerle in automatico senza sporcare il tuo codice.
Se usi OpenAI (che useremo in questo tutorial), apri il terminale e digita:
# Su Linux/macOS
export OPENAI_API_KEY="sk-tuachiave..."
# Su Windows (PowerShell)
$ENV:OPENAI_API_KEY = "sk-tuachiave..."
Nota: PydanticAI supporta anche ANTHROPIC_API_KEY, GOOGLE_API_KEY, GROQ_API_KEY, ecc. In fase di sviluppo, ti sconsiglio di inserire le chiavi nel codice; usa piuttosto un file .env caricato tramite la libreria python-dotenv.
Passo 2: Il nostro primo Agente (Hello World)
Creare un agente di base è disarmante per la sua semplicità.
Iniziamo con un’interazione testuale classica.
from pydantic_ai import Agent
# Inizializziamo l'agente specificando il modello e le istruzioni di base
agent = Agent(
'openai:gpt-4o',
instructions='Rispondi sempre in italiano in modo chiaro, amichevole e molto conciso.'
)
# Eseguiamo l'agente
result = agent.run_sync("Chi ha inventato la pizza Margherita?")
print(result.output)
Eseguendo questo script, otterrai una risposta testuale. Fin qui nulla di rivoluzionario, ma abbiamo stabilito la connessione.
Ora alziamo il livello.
Passo 3: Output Strutturato (La vera magia)
La maggior parte delle applicazioni software non sa cosa farsene di un blocco di testo libero. Ha bisogno di dati strutturati per popolare database o frontend. Qui PydanticAI mostra i muscoli.
Imponiamo all’agente di restituire sempre un oggetto Python con una struttura precisa.
from pydantic import BaseModel
from pydantic_ai import Agent
# 1. Definiamo la struttura dati esatta che vogliamo
class CittaInfo(BaseModel):
nome: str
paese: str
popolazione: int
curiosita: str
# 2. Leghiamo la struttura all'agente
agent = Agent(
'openai:gpt-4o',
output_type=CittaInfo, # Il cuore del framework: forziamo l'output
instructions="Sei un esperto di geografia. Fornisci dati accurati."
)
result = agent.run_sync("Dimmi tutto su Tokyo")
# result.output non è una stringa, è una vera istanza di CittaInfo!
print(f"Città: {result.output.nome}")
print(f"Abitanti: {result.output.popolazione}")
Cosa succede se l’LLM sbaglia?
Se il modello dimentica la “curiosita” o formatta la popolazione come testo invece che come intero, PydanticAI rileva l’errore di validazione e, in automatico, rimanda il prompt all’LLM chiedendogli di correggere l’errore.
Il tuo codice successivo è letteralmente blindato e type-safe.
Passo 4: Dotare l’Agente di Strumenti (Tool Calling)
Un agente AI senza accesso al mondo esterno è solo un pappagallo molto colto. Diamo al nostro agente la capacità di eseguire funzioni Python reali.
Immaginiamo di voler creare un assistente meteorologico.
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
class MeteoRisposta(BaseModel):
citta: str
temperatura: float
condizione: str
consiglio_abbigliamento: str
agent = Agent(
'openai:gpt-4o',
output_type=MeteoRisposta,
instructions="Sei un meteorologo utile. Usa il tool a tua disposizione per recuperare i dati reali, poi elaborali per dare un consiglio."
)
# Definiamo un tool usando l'apposito decoratore
@agent.tool
async def ottieni_meteo(ctx: RunContext, citta: str) -> dict:
"""Restituisce il meteo attuale per una città specifica."""
# In un'app reale, qui faresti una chiamata a un'API come Open-Meteo
meteo_database = {
"Roma": {"temp": 18.5, "cond": "soleggiato"},
"Milano": {"temp": 12.0, "cond": "nuvoloso"}
}
# Se non troviamo la città, restituiamo un dato standard
return meteo_database.get(citta, {"temp": 15.0, "cond": "variabile"})
# Mettiamolo alla prova
result = agent.run_sync("Che tempo fa a Milano oggi?")
print(result.output)
# Output: citta='Milano' temperatura=12.0 condizione='nuvoloso' consiglio_abbigliamento='Mettiti una giacca leggera...'
Un piccolo dettaglio ingegneristico fondamentale:
Nota come la nostra funzione ottieni_meteo restituisca un semplice dizionario con temperatura e condizione, ma l’agente debba restituire una MeteoRisposta che include anche un “consiglio_abbigliamento”.
L’agente capisce di dover chiamare la funzione, prelevare i dati grezzi, “ragionarci” sopra per generare il consiglio, e infine incapsulare tutto nell’oggetto Pydantic validato.
Passo 5: Verso la Produzione (Esecuzione Asincrona)
Se stai sviluppando una web app (ad esempio con FastAPI), non puoi permetterti chiamate sincrone che bloccano il thread principale mentre aspetti la risposta dell’LLM. PydanticAI è costruito per l’asincronia nativa.
Ti basta sostituire run_sync con run all’interno di un loop asincrono:
import asyncio
from pydantic_ai import Agent
async def main():
agent = Agent('openai:gpt-4o')
# Usa 'await' per le operazioni asincrone
result = await agent.run("Qual è la distanza tra la Terra e la Luna?")
print(result.output)
if __name__ == "__main__":
asyncio.run(main())
Dal punto di vista ingegneristico, questo codice risolve uno dei problemi più grandi della GenAI: l’interfacciamento tra linguaggio naturale (non deterministico) e sistemi software (deterministici).
Nota bene cosa succede:
l’utente fa una domanda generica (“Che tempo fa a Roma?”). L’LLM capisce di aver bisogno di dati e chiama la nostra funzione Python ottieni_meteo. La funzione restituisce un semplice dizionario.
A questo punto, l’LLM riprende il controllo, analizza il dizionario e lo formatta forzatamente per farlo combaciare con la classe MeteoRisposta che abbiamo imposto.
Se l’LLM dimentica un campo (es. la temperatura), Pydantic intercetta l’errore prima che l’app vada in crash, rimanda l’errore all’LLM e gli “chiede” di correggersi in automatico. È un ciclo di auto-guarigione (self-correction) integrato in 20 righe di codice.
Quando NON usare PydanticAI
Per quanto potente, PydanticAI potrebbe non essere la scelta ideale in ogni scenario:
-
Prototipazione ultra-rapida “usa e getta”: Se devi solo testare un prompt al volo e non ti interessa la struttura dei dati o la manutenibilità futura, il client ufficiale di OpenAI o Anthropic è più leggero.
-
Sistemi basati pesantemente su grafi complessi: Se la tua applicazione richiede una logica a nodi estremamente intricata e ciclica, framework come LangGraph offrono ancora astrazioni più specifiche per quel tipo di controllo del flusso.
-
Se non conosci (e non vuoi usare) Pydantic: Il framework è “opinionated”. Se preferisci gestire la validazione dei dati manualmente o con altre librerie, forzare l’uso di PydanticAI risulterebbe controproducente.
⚡ PydanticAI Cheat Sheet (2026)
| Componente | Classe / Metodo | Descrizione Rapida |
| Inizializzazione | Agent(model, ...) |
Crea l’entità agente. Configura istruzioni e modelli. |
| Modello | 'openai:gpt-4o' |
Stringa identificativa (es. anthropic:claude-3-5-sonnet). |
| Esecuzione Sync | agent.run_sync(prompt) |
Esegue l’agente in modalità bloccante (script/notebook). |
| Esecuzione Async | await agent.run(prompt) |
Esegue l’agente in modalità non bloccante (FastAPI/Web). |
| Output Strutturato | output_type=BaseModel |
Parametro dell’Agent per forzare risposte validate. |
| Definizione Tool | @agent.tool |
Decoratore per trasformare funzioni Python in strumenti per l’IA. |
| Contesto Tool | ctx: RunContext[T] |
Fornisce accesso alle dipendenze (ctx.deps) e allo stato. |
| Dipendenze | deps=mio_oggetto |
Oggetto passato a .run() per iniettare DB o API client. |
| Streaming | agent.run_stream(prompt) |
Genera la risposta iterativamente (per UI in tempo reale). |
| Self-Correction | Automatico | Se l’output fallisce la validazione Pydantic, l’agente riprova. |
Oltre il Chatbot: Verso il Software Intelligente
Costruire un agente che risponde a una domanda è facile. Costruire un sistema che decide autonomamente quali dati recuperare, valida ogni singola informazione e si corregge in caso di errore, è ingegneria.
PydanticAI non cerca di nascondere la complessità degli LLM sotto strati di magia, ma ti dà gli strumenti per governarla. Se il tuo obiettivo è costruire agenti che non solo rispondono, ma si comportano come componenti software affidabili, prevedibili e scalabili, PydanticAI è il passo naturale per la tua evoluzione come sviluppatore IA.
👉 PydanticAI Avanzato: Gestire Database e Stato con la Dependency Injection





