2. Fondamenti per costruire agenti
Nel Capitolo 1, hai fatto la tua prima chiamata a un LLM e hai visto il flusso base richiesta-risposta. Ora dobbiamo capire cosa stiamo effettivamente costruendo: sistemi di IA agentica. Questo capitolo stabilisce i concetti fondamentali che userai per tutto il libro.
Alla fine di questo capitolo, capirai:
- Cosa rende un sistema di IA "agentico" (e perché conta)
- Perché esistono framework come LangChain e LangGraph
- Come funzionano davvero gli LLM sotto il cofano (e perché questo influisce sulla progettazione degli agenti)
- L'economia dell'uso degli LLM (token, costi e scelta del modello)
- Come scrivere prompt efficaci per sistemi di agenti
Questo è un capitolo concettuale: torneremo a fare coding pratico nel Capitolo 3. Ma questi concetti sono fondamentali per comprendere le decisioni di design che prenderai quando costruirai agenti.
2.1) Cos'è l'IA agentica? (Chatbot vs agente)
Quando la maggior parte delle persone pensa alle "applicazioni di IA", immagina chatbot: fai una domanda, l'IA risponde, la conversazione finisce. Ma l'IA agentica è fondamentalmente diversa. Un agente non si limita a rispondere: agisce in modo autonomo per raggiungere obiettivi.
La distinzione fondamentale
Partiamo da una definizione chiara:
Chatbot: Un sistema che risponde all'input dell'utente con testo generato. L'interazione è puramente conversazionale. Il chatbot non ha la capacità di compiere azioni nel mondo oltre a generare il suo prossimo messaggio.
Agente: Un sistema che può:
- Percepire il suo ambiente (ad esempio, leggere lo stato di un ordine, controllare l'inventario)
- Decidere quali azioni intraprendere (ad esempio, scegliere tra: creare ticket, inviare email o fare escalation)
- Agire in base a quelle decisioni (ad esempio, chiamare API, aggiornare un database)
- Iterare finché non raggiunge un obiettivo (ad esempio, verificare ordine → creare ticket → confermare → fatto)
La differenza chiave è autonomia e azione. Un chatbot è reattivo; un agente è proattivo.
Esempio concreto: supporto clienti
Vediamo questa distinzione in pratica.
Approccio chatbot:
Utente: "Voglio un rimborso per l'ordine #12345"
Chatbot: "Capisco che desideri un rimborso. Per elaborarlo, per favore:
1. Visita il nostro portale rimborsi su example.com/refunds
2. Inserisci il numero dell'ordine
3. Seleziona un motivo
4. Invia il modulo
5. Attendi 24-48 ore per la revisione manuale
C'è qualcos'altro in cui posso aiutarti?"Il chatbot fornisce informazioni ma non compie alcuna azione. L'utente deve fare tutto il lavoro.
Approccio agente:
Utente: "Voglio un rimborso per l'ordine #12345"
Agente (ragionamento interno):
1. L'utente vuole un rimborso per l'ordine #12345
2. Devo verificare che questo ordine esista
3. [Chiama get_order_details(order_id="12345")]
4. Ordine trovato, idoneo al rimborso
5. [Chiama create_refund_ticket(order_id="12345", reason="customer_request")]
6. Ticket creato: TICKET-789
Agente: "Ho creato il ticket di rimborso TICKET-789 per l'ordine #12345.
Il nostro team rimborsi lo elaborerà entro 3-5 giorni lavorativi.
Riceverai a breve un'email di conferma."L'agente ha compiuto un'azione: ha verificato l'ordine, ha creato un ticket e ha confermato l'esito. Il problema dell'utente è risolto senza passaggi manuali.
Perché questo conta nello sviluppo
Comprendere questa distinzione determina il modo in cui progetti il tuo sistema:
Sviluppo di chatbot:
- Focus sulla qualità delle risposte e sul flusso della conversazione
- Preoccupazione principale: generare testo utile e accurato
- Architettura semplice: prompt → LLM → risposta
- Nessuna necessità di integrazioni esterne
Sviluppo di agenti:
- Focus sul processo decisionale e sull'esecuzione delle azioni
- Preoccupazioni principali: scegliere le azioni corrette, gestire errori, mantenere lo stato
- Architettura complessa: percezione → ragionamento → selezione dell'azione → esecuzione → verifica
- Richiede integrazioni di strumenti, gestione degli errori, gestione dello stato
Lo spettro dell'autonomia
Non tutti gli agenti sono ugualmente autonomi. Esiste uno spettro:
Livello 1: Azioni assistite
- L'agente suggerisce azioni, l'utente approva ognuna
- Esempio: "Posso creare un ticket di rimborso. Vuoi che proceda?"
- Approccio più sicuro per operazioni ad alto rischio
Livello 2: Autonomia vincolata
- L'agente agisce entro vincoli predefiniti
- Esempio: può creare ticket e inviare email, ma non può processare rimborsi oltre $500 né accedere direttamente ai sistemi di pagamento
- Il più comune nei sistemi in produzione (bilanciamento tra efficienza e sicurezza)
Livello 3: Autonomia completa
- L'agente agisce in modo indipendente per raggiungere obiettivi
- Esempio: gestisce l'intero flusso di lavoro di rimborso senza intervento umano
- Richiede robuste guardrail e monitoraggio
Caratteristiche chiave degli agenti
In sintesi, un sistema di IA agentica ha queste proprietà core:
- Uso di strumenti: può chiamare funzioni, API e servizi esterni (senza questo, è solo un chatbot)
- Orientato agli obiettivi: lavora verso risultati specifici, non si limita a rispondere
- Multi-step: scompone compiti complessi in sequenze di azioni
- Adattivo: adatta il comportamento in base a risultati intermedi
- Con stato: mantiene il contesto attraverso più interazioni
Le prime due sono essenziali: senza strumenti e obiettivi, non hai un agente. Le altre sono fattori di qualità che distinguono i buoni agenti da quelli eccellenti.
Ora capisci cosa sono gli agenti e perché sono potenti.
2.2) Perché LangChain e LangGraph?
Potresti chiederti: "Perché mi servono i framework? Non posso chiamare direttamente l'API di OpenAI?" Esploriamo perché esistono i framework e quali problemi risolvono.
La complessità dello sviluppo di agenti
Costruire un semplice chatbot con chiamate API dirette è semplice:
import openai
response = openai.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": "Ciao!"}]
)
print(response.choices[0].message.content)Questo funziona bene per casi d'uso di base. Ma non appena vuoi costruire un agente, la complessità esplode:
Sfida 1: Flussi di lavoro multi-step Il tuo agente deve:
- Recuperare documenti pertinenti da una base di conoscenza
- Decidere quale strumento chiamare in base all'intento dell'utente
- Eseguire lo strumento e gestire gli errori
- Formattare i risultati e rispondere all'utente
Ogni passaggio richiede un'orchestrazione attenta, gestione degli errori e gestione dello stato.
Sfida 2: Astrazione del provider E se volessi:
- Passare da OpenAI ad Anthropic o Google?
- Usare modelli diversi per task diversi?
- Fare fallback su un modello più economico se quello principale fallisce?
Con chiamate API dirette, dovresti riscrivere codice significativo per ogni provider.
Sfida 3: Memoria della conversazione Gli agenti devono ricordare il contesto:
- Messaggi precedenti nella conversazione
- Documenti recuperati da query precedenti
- Risultati intermedi dalle chiamate agli strumenti
Gestire manualmente questo stato è soggetto a errori e tedioso.
Sfida 4: Integrazione degli strumenti Il tuo agente deve:
- Definire gli strumenti disponibili con schemi
- Permettere all'LLM di scegliere quale strumento chiamare
- Fare il parsing degli argomenti degli strumenti dall'output dell'LLM
- Eseguire strumenti in modo sicuro con validazione
- Gestire errori degli strumenti e logica di retry
Questo richiede molto codice boilerplate e considerazioni di sicurezza a ogni passaggio.
Sfida 5: Routing complesso Gli agenti reali necessitano logica condizionale:
- "Se l'utente chiede dei rimborsi, recupera i documenti della policy"
- "Se l'utente vuole un rimborso, crea un ticket"
- "Se la domanda è fuori tema, rifiuta con cortesia"
Implementare tutto questo con istruzioni if-else diventa rapidamente non manutenibile.
Cosa offre LangChain
LangChain è un framework per costruire applicazioni LLM. Fornisce:
1. Astrazione del modello
Interfaccia unificata per diversi provider di LLM (OpenAI, Anthropic, Google, ecc.).
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# Stessa interfaccia, provider diversi
openai_llm = ChatOpenAI(model="gpt-5-mini")
anthropic_llm = ChatAnthropic(model="claude-4-5-sonnet")
# Entrambi usano .invoke() con lo stesso formato di messaggio
response = openai_llm.invoke([{"role": "user", "content": "Ciao"}])Passa da un provider all'altro senza riscrivere la logica dell'applicazione.
2. Catene componibili (LCEL)
LangChain Expression Language - una sintassi per collegare componenti in pipeline.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Componi una pipeline con l'operatore | (come le pipe Unix)
chain = prompt | llm | output_parser
# Esegui l'intera pipeline con una singola chiamata
result = chain.invoke({"input": "domanda dell'utente"})Costruisci flussi di lavoro complessi senza passare manualmente i dati tra i passaggi. Impareremo LCEL nel Capitolo 6.
3. Memoria della conversazione
Gestione dello storico dei messaggi per conversazioni con stato.
from langchain_core.chat_history import InMemoryChatMessageHistory
# Helper per lo storico dei messaggi
history = InMemoryChatMessageHistory()
history.add_user_message("Ciao")
history.add_ai_message("Ciao!")
# Recupera i messaggi quando necessario
messages = history.messages
response = llm.invoke(messages)Astrazione dello storage dei messaggi con classi helper invece di gestire liste manualmente. A questo stadio, lo storico viene ancora passato esplicitamente al modello. Lo integreremo nelle catene nel Capitolo 8. LangGraph (Capitoli 15+) lo rende ancora più semplice con la gestione dello stato integrata.
4. Integrazione degli strumenti
Sistema basato su decorator per esporre funzioni Python agli LLM.
from langchain_core.tools import tool
@tool
def create_ticket(order_id: str, reason: str) -> str:
"""Crea un ticket di supporto per un ordine."""
# Implementazione qui
return f"Ticket creato per {order_id}"
# LangChain gestisce la generazione dello schema e l'integrazione con l'LLMTrasforma qualsiasi funzione Python in uno strumento che può essere scoperto e chiamato da LLM con tool abilitati o da agenti — senza scrivere manualmente schemi JSON.
5. Loader di documenti e vector store
Componenti pronti all'uso per caricare documenti da varie fonti e memorizzarli come embedding ricercabili.
from langchain_community.document_loaders import TextLoader
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# Carica i documenti
docs = TextLoader("support_docs.txt").load()
# Crea un indice ricercabile
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
# Recupera documenti pertinenti
results = vectorstore.similarity_search("policy rimborso")Costruisci sistemi RAG usando loader e vector store predefiniti invece di implementare parsing, embedding e recupero da zero.
Cosa offre LangGraph
LangGraph estende LangChain per flussi di lavoro di agenti complessi. Fornisce:
1. Gestione esplicita dello stato
Definisci tutti i dati dell'agente in un unico schema tipizzato invece di spargerli tra variabili.
from typing import TypedDict, Optional
from langchain_core.messages import BaseMessage
from langchain_core.documents import Document
class AgentState(TypedDict):
messages: list[BaseMessage]
retrieved_docs: list[Document]
current_step: Optional[str]
# Tutti i dati dell'agente vivono qui - un solo punto da ispezionare quando fai debuggingNiente più caccia a dove vivono i dati o a come fluiscono tra i passaggi. I cambiamenti di stato sono espliciti: i nodi leggono da state e restituiscono aggiornamenti. Il tuo IDE completa automaticamente i nomi dei campi, e i type checker catturano errori prima del runtime.
2. Flussi di lavoro basati su grafi
Costruisci flussi di lavoro dichiarando i passaggi (nodi) e le loro connessioni (archi), non scrivendo codice di orchestrazione.
graph = StateGraph(AgentState)
# Definisci nodi (passaggi nel tuo flusso di lavoro)
graph.add_node("retrieve", retrieve_docs)
graph.add_node("answer", generate_answer)
# Definisci archi (transizioni tra passaggi)
graph.add_edge("retrieve", "answer")Definisci la struttura—"retrieve esegue, poi answer esegue"—e LangGraph gestisce l'esecuzione. Non c'è bisogno di scrivere codice di orchestrazione per passare lo stato tra i passaggi. Il flusso di controllo, come sequenziamento o branching, è dichiarato nella struttura del grafo stesso.
3. Routing condizionale
Decision-making a runtime su quale percorso seguire nel flusso di lavoro.
from langchain_core.messages import BaseMessage
def route_request(state):
last_message = state["messages"][-1].content
if "refund" in last_message:
return "create_ticket"
else:
return "answer_question"
graph.add_conditional_edges(
"classify",
route_request,
{
"create_ticket": "create_ticket",
"answer_question": "answer_question",
}
)L'intuizione chiave: la funzione di routing restituisce il nome del nodo successivo ("create_ticket" o "answer_question").
Perché questo conta: la tua logica decisionale è separata dall'esecuzione. Vuoi cambiare le regole di routing? Modifica una funzione. Vuoi vedere tutti i percorsi possibili? Guarda la definizione del grafo. Vuoi fare debug del percorso preso? Ispeziona la traccia di esecuzione—senza scavare tra chiamate di funzioni annidate.
4. Checkpointing e persistenza
Lo stato viene automaticamente checkpointato dopo ogni passaggio, abilitando recupero da crash e flussi di lavoro pausa-ripresa.
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver("agent_state.db")
graph = graph.compile(checkpointer=checkpointer)
# Lo stato viene salvato automaticamente a ogni passaggio
result = graph.invoke(
input_state,
config={"configurable": {"thread_id": "user-123"}}
)Dopo ogni passaggio, LangGraph checkpointa lo stato corrente su storage persistente. Se il processo va in crash, l'esecuzione può riprendere dall'ultimo checkpoint associato allo stesso thread ID.
Il checkpointing abilita flussi di lavoro pausa-e-ripresa, come l'attesa di approvazione umana, quando combinato con routing condizionale o interrupt.
Quando usare ciascun framework
Usa LangChain quando:
- Costruisci catene semplici (prompt → LLM → parser)
- Implementi sistemi RAG
- Gestisci contesto basato su messaggi (passando esplicitamente lo storico chat)
- Astrarre tra provider di LLM
Usa LangGraph quando:
- Costruisci flussi di lavoro di agenti multi-step
- Implementi logica di routing condizionale
- Gestisci stato complesso tra i passaggi
- Hai bisogno di checkpointing e recupero da crash
2.3) Come funzionano gli LLM (per chi costruisce agenti)
Per costruire agenti efficaci, devi capire come funzionano davvero gli LLM. Non riguarda la matematica dei transformer: riguarda il modello mentale che determina come progetti i sistemi di agenti.
Il meccanismo centrale: predizione dei token
Ecco l'intuizione fondamentale: gli LLM non "conoscono" fatti nel modo in cui lo fanno i database. Predicono il prossimo token.
Scomponiamolo con un esempio concreto.
Input: "The capital of France is"
Cosa potresti pensare che succeda:
- L'LLM "cerca" la capitale della Francia nella sua base di conoscenza
- L'LLM "recupera" la risposta: Paris
- L'LLM restituisce "Paris"
Cosa succede realmente:
- L'LLM converte l'input in token:
["The", "capital", "of", "France", "is"] - L'LLM calcola la distribuzione di probabilità su TUTTI i possibili token successivi
- Token successivo più probabile:
"Paris"(probabilità più alta) - L'LLM campiona dalla distribuzione (di solito scegliendo la probabilità più alta)
- L'LLM restituisce "Paris"
L'LLM non "sa" che Parigi è la capitale. Predice che "Paris" è il token successivo più probabile dato il pattern di input.
(Nota: tokenizzazione e probabilità sono concettuali e variano per modello e tokenizer.)
Perché questo conta per gli agenti
Questo modello di predizione dei token ha implicazioni profonde per il design degli agenti:
Implicazione 1: gli LLM possono allucinare
Poiché gli LLM predicono token (non recuperano fatti), possono generare informazioni plausibili ma errate.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("Qual è la capitale di Atlantide?")
print(response.content)
# Nota: l'output reale può variare.
# I modelli moderni potrebbero riconoscere Atlantide come fittizia e rifiutarsi di rispondere.
# Il punto chiave:
# senza un fact-checking esplicito, gli LLM possono generare informazioni errate dal suono plausibile.Possibile output (storico o non vincolato):
La capitale di Atlantide è Etheria. Secondo i resoconti di Platone, Etheria era una città portuale situata sulla punta più orientale dell'isola, e il suo nome derivava dalla credenza che arrivasse fino ai cieli.L'LLM genera una risposta che suona ragionevole anche se Atlantide è fittizia. Per gli agenti, questo significa:
- Non fidarti mai ciecamente dell'output dell'LLM
- Valida i fatti rispetto a fonti autorevoli
- Implementa guardrail
Implicazione 2: il contesto è tutto
Gli LLM vedono solo i token che fornisci. Non hanno memoria delle conversazioni precedenti a meno che tu non includa esplicitamente quel contesto.
# Prima chiamata
response1 = llm.invoke("Mi chiamo Alice")
print(response1.content) # "Piacere di conoscerti, Alice!"
# Seconda chiamata (invocazione separata)
response2 = llm.invoke("Come mi chiamo?")
print(response2.content) # "Non ho accesso al tuo nome..."La seconda chiamata non ha alcun contesto dalla prima. Per gli agenti, questo significa:
- Devi gestire lo storico della conversazione
- I limiti della finestra di contesto contano
- La gestione dello stato è critica
Implicazione 3: i prompt sono istruzioni, non query
Poiché gli LLM predicono token, il modo in cui formuli il prompt influisce drasticamente sulla qualità dell'output.
# Prompt debole (stile query)
response = llm.invoke("policy rimborso")
# Output: "Cosa vorresti sapere sulla policy di rimborso?"
# Prompt forte (stile istruzione)
response = llm.invoke(
"Sei un addetto al supporto clienti. Spiega la nostra policy di rimborso in modo chiaro e conciso."
)
# Output: "La nostra policy di rimborso consente resi entro 30 giorni..."Per gli agenti, questo significa:
- I prompt sono il tuo principale meccanismo di controllo
- Il prompt engineering è una competenza core
- I messaggi di sistema impostano il comportamento dell'agente
Implicazione 4: l'output strutturato richiede guida
Gli LLM generano naturalmente testo libero. Ottenere output strutturato (JSON, formati specifici) richiede istruzioni esplicite.
# Senza guida sulla struttura
response = llm.invoke("Estrai l'ID ordine da: 'Voglio un rimborso per l'ordine #12345'")
print(response.content)
# Output: "L'ID ordine è 12345" (testo semplice, formato incoerente)
# Con guida sulla struttura
response = llm.invoke(
'Estrai l\'ID ordine e restituisci SOLO un oggetto JSON con formato: {"order_id": "..."}\n\n'
"Testo: 'Voglio un rimborso per l'ordine #12345'"
)
print(response.content)
# Output: {"order_id": "12345"} (strutturato, parsabile)Per gli agenti, questo significa:
- Usa schemi per vincolare il formato dell'output
- Specifica esplicitamente i formati di output
- Valida e fai parsing delle risposte dell'LLM
Il testo libero è ottimizzato per gli umani. Gli agenti richiedono formattazione esplicita per garantire leggibilità per le macchine.
Determinismo, stocasticità e LLM moderni
Gli LLM sono fondamentalmente sistemi probabilistici. Generano testo predicendo i token successivi più probabili, non eseguendo regole deterministiche. Di conseguenza, lo stesso input non garantisce sempre lo stesso output.
Nei modelli precedenti, gli sviluppatori controllavano esplicitamente questa casualità usando parametri come temperature. Valori più bassi producevano output più prevedibili, mentre valori più alti incoraggiavano variazione e creatività.
Molti moderni modelli orientati al ragionamento non espongono più parametri come temperature o top_p. Invece, gestiscono internamente strategie di decoding e sampling per dare priorità a un ragionamento stabile e strutturato. Tuttavia, questo non significa che questi modelli siano completamente deterministici.
Anche questi modelli non garantiscono output identici. Gli output possono variare per via di:
- Sampling interno: il modello può seguire percorsi di ragionamento diversi, producendo output che variano in struttura, dettaglio o formulazione.
- Aggiornamenti del modello: i provider aggiornano continuamente i modelli senza preavviso, quindi lo stesso prompt può produrre risposte diverse nel tempo.
- Filtri di sicurezza: la moderazione dei contenuti può far sì che il modello risponda direttamente in un caso ma esiti, rifiuti o riformuli in un altro.
- Policy sugli strumenti: nei sistemi di agenti, il modello può invocare strumenti diversi—o nessuno strumento—per lo stesso input, cambiando i percorsi di esecuzione.
Cosa significa per chi costruisce agenti:
La preoccupazione chiave non è il tuning dei parametri: è la prevedibilità. I sistemi di agenti dovrebbero essere progettati assumendo che gli output dell'LLM possano variare in formulazione, struttura o perfino conclusioni, a meno che non siano vincolati esplicitamente.
Questo porta a diversi principi di design concreti per i sistemi di agenti:
- Non fare mai affidamento su formulazioni esatte per decisioni logiche — il flusso di controllo dovrebbe dipendere da segnali strutturati (schemi, enum, flag), non dal matching di frasi specifiche nell'output del modello.
- Forza la struttura ai confini — ogni volta che un output dell'LLM viene consumato dal codice, vincolalo usando schemi, validator o formati rigorosi così che il programma non debba mai "interpretare" testo libero.
- Verifica tutto ciò che conta — fatti che influiscono su denaro, permessi o azioni irreversibili devono essere controllati con strumenti o sistemi esterni, non fidandosi solo del modello.
- Tratta gli output dell'LLM come proposte, non decisioni — il modello suggerisce cosa fare, ma il sistema decide se, quando e come agire.
Nei moderni sistemi di agenti, l'affidabilità deriva dal design di sistema, non dal tuning dei parametri. Più il task è critico, meno libertà dovrebbe avere il modello—e più struttura dovrebbe imporre il tuo agente.
Punto chiave: costruisci agenti affidabili attraverso schemi, validazione e integrazione di strumenti—non sperando in output LLM coerenti.
Nella prossima sezione esploreremo le implicazioni economiche dell'elaborazione basata su token.
2.4) Token: la risorsa fondamentale
I token sono l'unità di base che gli LLM elaborano. Comprendere i token è essenziale perché definiscono sia cosa è possibile (vincoli) sia cosa è costoso (costi) nei sistemi di agenti.
Cos'è un token?
Un token è la più piccola unità di testo su cui un LLM ragiona e che genera.
A seconda della lingua e del contesto, un token può rappresentare:
- Una parola (
agent) - Parte di una parola (
calculat,ion) - Un numero o simbolo (
#,123) - Punteggiatura o spazi
I token non sono caratteri e non sono parole — sono unità specifiche del modello create dal tokenizer.
Ogni informazione inviata al modello o generata dal modello viene misurata in token:
- Istruzioni di sistema
- Messaggi dell'utente
- Documenti recuperati
- Descrizioni degli strumenti
- Output del modello
I token sono la valuta fondamentale dell'interazione con gli LLM.
I token come vincolo di sistema
I token non sono solo un costo: sono un limite rigido su ciò che il tuo agente può fare in una singola richiesta.
Ogni LLM ha una finestra di contesto: un numero massimo fisso di token che può processare in una volta.
Scenario di esempio: Il tuo agente di supporto clienti ha bisogno di:
- Istruzioni di sistema: 200 token
- Ultimi 10 messaggi: ~2.000 token
- 3 articoli di help recuperati: ~1.500 token
- Risposta generata: ~200 token
- Totale: 3.900 token
Se la finestra di contesto del tuo modello è di 4.000 token, sei al 97,5% della capacità. Un altro messaggio lungo e il sistema smette di funzionare.
(I modelli moderni tipicamente hanno finestre di contesto da 128K+ token, ma il principio resta: il contesto è finito, e devi progettare tenendo conto di questo limite.)
Cosa succede quando superi il limite:
- I messaggi più vecchi vengono eliminati → l'agente dimentica il contesto precedente, rompendo la continuità della conversazione
- I documenti recuperati vengono troncati → informazioni critiche vanno perse, portando a risposte errate
- La richiesta fallisce completamente → il sistema non può rispondere affatto
Non puoi pagare per più spazio. La finestra di contesto è un limite rigido—come cercare di far entrare 2 litri in una bottiglia da 1 litro.
Ecco perché agenti di lunga durata devono gestire attivamente cosa resta in contesto e cosa no. La gestione dei token è una preoccupazione architetturale core, non un dettaglio di ottimizzazione.
Cosa influenzano direttamente i token
Oltre al limite immediato della finestra di contesto, i token influenzano due decisioni di design critiche:
1. Strategia di memoria: storico completo vs sintesi
Tenere l'intero storico della conversazione preserva i dettagli ma fa crescere continuamente l'uso di token.
Un'alternativa comune è la sintesi della memoria:
- Sostituire i messaggi più vecchi con un riepilogo compatto
- Preservare l'intento riducendo il costo in token
Questo trade-off influenza:
- Costo
- Accuratezza
- Coerenza dell'agente nel lungo periodo
Il design della memoria è quindi un problema di gestione dei token.
2. Dimensione dei segmenti RAG e strategia di recupero
Nella Retrieval-Augmented Generation (RAG), i documenti vengono divisi in chunk prima del recupero.
-
Chunk grandi
- Meno chiamate di recupero
- Maggior costo in token per richiesta
- Più contesto irrilevante
-
Chunk piccoli
- Minor costo in token
- Maggiore precisione
- Rischio di perdere informazioni chiave
La dimensione dei chunk è una decisione di design critica — influisce direttamente sia sui costi sia sulla qualità della risposta.
Economia dei token
Comprendere i costi dei token ti aiuta a costruire sistemi economicamente efficienti.
Prezzi tipici (2026):
| Modello | Input (per 1M token) | Output (per 1M token) |
|---|---|---|
| GPT-5 | $1.25 | $10.00 |
| GPT-5-mini | $0.25 | $2.00 |
| Claude 4.5 Sonnet | $3.00 | $15.00 |
| Gemini 3 Pro | $2.00 | $12.00 |
| Gemini 3 Flash | $0.50 | $3.00 |
Intuizione chiave: i token di output costano 4–8 volte più dei token di input, il che significa che una generazione non controllata è spesso il principale driver di costo nei sistemi in produzione.
Stima rapida dei costi:
Per una richiesta tipica con 500 token di input e 50 token di output usando GPT-5-mini:
- Input: (500 / 1.000.000) × $0.25 = $0.000125
- Output: (50 / 1.000.000) × $2.00 = $0.0001
- Totale: ~$0.000225 per richiesta
A 10.000 richieste/giorno: ~$67.5/mese
Ottimizzazione dei costi nella pratica
Strategia 1: abbina il modello alla complessità del task
Usa modelli più piccoli ed economici per task semplici:
from langchain_openai import ChatOpenAI
# Modello costoso per ragionamento complesso
complex_llm = ChatOpenAI(model="gpt-5")
# Modello economico per task semplici
simple_llm = ChatOpenAI(model="gpt-5-nano")
def get_llm_for_task(task_type):
if task_type == "complex_reasoning":
return complex_llm
else:
return simple_llmStrategia 2: bilancia dimensione del contesto e qualità
Includi solo il contesto necessario:
# Efficiente: includi solo i chunk pertinenti
relevant_chunks = retrieve_top_k(user_question, k=3) # ~500 token
prompt = f"Contesto: {relevant_chunks}\n\nDomanda: {user_question}"Importante: una riduzione troppo aggressiva del contesto può peggiorare l'accuratezza delle risposte. Bilancia il risparmio sui costi con la qualità.
Strategia 3: controlla la lunghezza dell'output
Limita quanto il modello genera:
# Con controllo dei costi
llm = ChatOpenAI(model="gpt-5-mini", max_tokens=100)
response = llm.invoke(messages)
# Massimo 100 token di outputPunto chiave: la gestione dei token non riguarda solo la riduzione dei costi—riguarda la progettazione di sistemi di agenti affidabili e scalabili entro vincoli rigidi di risorse.
Nella prossima sezione esploreremo il panorama dei modelli e impareremo come scegliere il modello giusto per ciascun task.
2.5) Comprendere il panorama dei modelli
Scegliere l'LLM giusto per il tuo agente richiede di bilanciare:
- Finestra di contesto: quanto testo può processare il modello?
- Costo: quanto costa ogni richiesta?
- Latenza: quanto velocemente risponde il modello?
- Capacità: quanto bene ragiona il modello?
Esploriamo il panorama dei modelli del 2026.
Famiglie principali di modelli
Modelli OpenAI GPT
| Modello | Finestra di contesto | Costo input | Costo output | Latenza | Ideale per |
|---|---|---|---|---|---|
| GPT-5 | 400K token | $1.25 / 1M | $10.00 / 1M | ~2–4s | Ragionamento complesso, codice avanzato |
| GPT-5-mini | 400K token | $0.25 / 1M | $2.00 / 1M | ~1.5–3s | Task generali, chat, sintesi |
| GPT-5-nano | 400K token | $0.05 / 1M | $0.40 / 1M | ~1–2s | Classificazione, estrazione |
Modelli Anthropic Claude
| Modello | Finestra di contesto | Costo input | Costo output | Latenza | Ideale per |
|---|---|---|---|---|---|
| Claude Opus 4.5 | 200K token | $5.00 / 1M | $25.00 / 1M | ~2–4s | Ragionamento profondo, analisi |
| Claude Sonnet 4.5 | 200K token | $3.00 / 1M | $15.00 / 1M | ~1.5–3s | Prestazioni bilanciate, coding |
Modelli Google Gemini
| Modello | Finestra di contesto | Costo input | Costo output | Latenza | Ideale per |
|---|---|---|---|---|---|
| Gemini 3.0 Pro | 1M token | $2.00 / 1M | $12.00 / 1M | ~3–5s | Contesto enorme, ricerca |
| Gemini 3.0 Flash | 1M token | $0.50 / 1M | $3.00 / 1M | ~1–2s | Applicazioni ad alto throughput |
Funzionalità specifiche del provider
OpenAI:
- Ideale per: agenti general-purpose, flussi di lavoro ampi multi-dominio, function calling
- Forte in: ragionamento versatile, ottimo tooling ed ecosistema per developer, aggiornamenti frequenti dei modelli
Anthropic:
- Ideale per: flussi di lavoro sensibili alla sicurezza, ragionamento strutturato ed esteso
- Forte in: analisi profonda, output metodici, extended thinking con forte allineamento
Google:
- Ideale per: ingestione di contesto massivo e task multimodali
- Forte in: analisi di documenti su larga scala, comprensione multimodale, elaborazione ad alto throughput
Abbinare i modelli ai task
Scegli in base a complessità del task, dimensione del contesto e necessità di latenza:
Per complessità del task
Task semplici → modelli ottimizzati per i costi (GPT-5-nano, GPT-5-mini):
- Classificazione dell'intento, analisi del sentiment, estrazione di keyword, formattazione semplice
- Scegli quando: il costo è la preoccupazione principale
Task moderati → modello bilanciato (GPT-5-mini):
- Question answering, sintesi, selezione degli strumenti
- Scegli quando: devi bilanciare costo e qualità
Task complessi → modelli orientati alle prestazioni (GPT-5, Claude Sonnet, Gemini Pro):
- Ragionamento multi-step, generazione di codice, analisi dettagliata
- Scegli quando: conta la capacità di ragionamento, il costo è accettabile
Massima complessità → modelli premium (Claude Opus):
- Ragionamento profondamente complesso, decisioni mission-critical
- Scegli quando: l'accuratezza è prioritaria, il costo è secondario
Per necessità di latenza
Real-Time (~1s o meno di latenza percepita) → modelli veloci (GPT-5-nano, Gemini Flash):
- Chat user-facing
- Applicazioni interattive
Near Real-Time (1-3s) → la maggior parte dei modelli:
- Task standard degli agenti
Batch (>3s) → modelli capaci:
- Analisi in background
Considerazioni sulla finestra di contesto
Task standard: tutti i principali modelli supportano 200K+ token, sufficienti per la maggior parte dei flussi di lavoro di agenti.
Casi speciali:
- Servono 400K token: famiglia GPT-5 (analisi di documenti completi, grandi codebase)
- Servono 1M token: modelli Gemini (interi libri, set di documenti enormi)
Consiglio pratico: anche con finestre di contesto ampie, il recupero selettivo (RAG) di solito produce risultati migliori.
Punti chiave
- Non esiste un singolo modello "migliore" → modelli diversi eccellono in task diversi
- Abbina il modello alla complessità del task → non pagare troppo per task semplici
- Finestra di contesto ≠ meglio → usa recupero selettivo
- La latenza influisce sull'esperienza utente → considera i tempi di risposta per task interattivi
In pratica: la maggior parte degli agenti usa più modelli—modelli economici per task semplici, modelli capaci per ragionamento complesso. Lo implementeremo nei capitoli successivi.
Nella prossima sezione impareremo come controllare il comportamento del modello tramite prompt efficaci.
2.6) Fondamenti di prompting per sistemi di agenti
I prompt sono la tua interfaccia principale per controllare il comportamento degli LLM. Per gli agenti, un prompting efficace è critico: determina se il tuo agente prende decisioni corrette, chiama gli strumenti giusti e produce output affidabili.
L'anatomia di un prompt
Un prompt completo ha tre componenti:
1. Messaggio di sistema (ruolo e vincoli) Definisce la persona, le capacità e i limiti dell'agente.
2. Contesto (informazioni pertinenti) Fornisce le informazioni necessarie per completare il task.
3. Istruzione (task specifico) Dice all'agente esattamente cosa fare.
Vediamolo in pratica:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-5-mini")
response = llm.invoke([
# Messaggio di sistema: definisci ruolo e vincoli
{
"role": "system",
"content": """Sei un addetto al supporto clienti per TechCorp.
Le tue capacità:
- Rispondere a domande sulle policy di rimborso
- Creare ticket di supporto
- Fornire guida per il troubleshooting
I tuoi vincoli:
- Rispondi solo a domande sui prodotti TechCorp
- Non fare mai promesse sulle tempistiche di rimborso
- Sii sempre cortese e professionale"""
},
# Messaggio utente: contesto + istruzione
{
"role": "user",
"content": """Contesto: il cliente ha acquistato il laptop modello X500 il 2026-01-15.
Oggi è il 2026-02-20. La nostra policy di rimborso consente resi entro 30 giorni.
Istruzione: il cliente vuole un rimborso. Cosa dovrei dirgli?"""
}
])
print(response.content)Output:
Capisco che desideri un rimborso per il tuo laptop modello X500. Purtroppo,
dato che l'acquisto è stato il 15 gennaio e oggi è il 20 febbraio, siamo
oltre la finestra di reso di 30 giorni. Tuttavia, sarò felice di creare un ticket di supporto
per valutare altre opzioni, come assistenza in garanzia o sostituzione.
Vuoi che proceda?Messaggi di sistema: impostare il comportamento dell'agente
Il messaggio di sistema è dove definisci la personalità e le capacità del tuo agente. Questa è la parte più importante del prompting per agenti.
Messaggio di sistema debole:
system_message = "Sei un assistente utile."Messaggio di sistema forte:
system_message = """Sei un addetto al supporto clienti per TechCorp.
RUOLO:
Aiuti i clienti con richieste di rimborso, domande sui prodotti e problemi tecnici.
CAPACITÀ:
- Rispondere alle domande usando la documentazione fornita
- Creare ticket di supporto quando necessario
- Fornire troubleshooting passo-passo
VINCOLI:
- Rispondi solo a domande sui prodotti TechCorp
- Se non sai qualcosa, dillo - non tirare mai a indovinare
- Cita sempre le fonti quando usi la documentazione
- Non promettere mai tempistiche o esiti specifici
TONO:
Professionale, empatico e orientato alla soluzione.
"""Perché la versione forte funziona meglio:
- Capacità esplicite → l'agente sa cosa può fare
- Vincoli chiari → l'agente sa cosa evitare
- Tono definito → personalità coerente
- Istruzioni specifiche → riduce l'ambiguità
Chiarezza dell'istruzione: sii specifico
Gli LLM seguono le istruzioni alla lettera. Istruzioni vaghe producono risultati inaffidabili.
Istruzione vaga:
instruction = "Aiuta il cliente con il suo rimborso."Istruzione specifica:
instruction = """Analizza la richiesta del cliente e determina:
1. L'ordine è idoneo per il rimborso? (Controlla la data di acquisto rispetto alla policy di rimborso)
2. Se idoneo: spiega il processo di rimborso
3. Se non idoneo: spiega perché e offri alternative
Formatta la risposta come:
- Idoneità: [SÌ/NO]
- Motivo: [Spiegazione breve]
- Prossimi passi: [Cosa dovrebbe fare il cliente]
"""Gestione del contesto: fornisci ciò che serve
Gli agenti hanno bisogno di contesto per prendere decisioni, ma troppo contesto spreca token e confonde il modello.
Over-contextualization (spreco):
context = f"""
Storia dell'azienda: TechCorp è stata fondata nel 1995...
Catalogo prodotti: vendiamo 500+ prodotti tra cui...
Policy di rimborso: {refund_policy_text}
Policy di spedizione: {shipping_policy_text}
Policy di garanzia: {warranty_policy_text}
Storico cliente: {full_customer_history}
"""
# 5000+ token, per lo più irrilevantiContesto selettivo (efficiente):
context = f"""
Policy rilevante: {refund_policy_text}
Dettagli ordine: {order_details}
"""
# 200 token, tutto rilevanteFormattazione dell'output: struttura le risposte
Per gli agenti, spesso serve output strutturato (JSON, formati specifici) invece di testo libero.
Output non strutturato (difficile da parsare):
response = llm.invoke([
{"role": "system", "content": "Sei un addetto al supporto."},
{"role": "user", "content": "Dovremmo creare un ticket per questa richiesta di rimborso?"}
])
print(response.content)
# Output: "Sì, penso che dovremmo creare un ticket perché..."
# Problema: difficile da parsare, formato incoerenteOutput strutturato (facile da parsare):
response = llm.invoke([
{"role": "system", "content": """Sei un addetto al supporto.
Rispondi sempre in questo formato JSON:
{
"action": "CREATE_TICKET" or "ANSWER_QUESTION" or "ESCALATE",
"reason": "Spiegazione breve",
"response_text": "Cosa dire al cliente"
}"""},
{"role": "user", "content": "Il cliente vuole un rimborso per l'ordine #12345, acquistato 40 giorni fa"}
])
print(response.content)Output:
{
"action": "CREATE_TICKET",
"reason": "Ordine fuori dalla finestra di rimborso di 30 giorni, serve revisione manuale",
"response_text": "Ho creato un ticket di supporto per esaminare la tua richiesta di rimborso. Il nostro team ti contatterà entro 24 ore."
}Useremo schemi Pydantic per un output strutturato robusto nel Capitolo 7.
Esempi few-shot: mostra, non limitarti a dire
Per task complessi, fornire esempi è più efficace che istruzioni lunghe.
Zero-shot (solo istruzioni):
prompt = """Estrai l'ID ordine, il nome del prodotto e il problema dai messaggi del cliente.
Messaggio del cliente: "Il mio laptop X500 ordine #12345 non si accende"
"""
# Il modello potrebbe faticare con il formatoFew-shot (con esempi):
prompt = """Estrai l'ID ordine, il nome del prodotto e il problema dai messaggi del cliente.
Esempio 1:
Input: "Il mio laptop X500 ordine #12345 non si accende"
Output: {"order_id": "12345", "product": "laptop X500", "issue": "non si accende"}
Esempio 2:
Input: "Ordine 67890 - telefono non si carica"
Output: {"order_id": "67890", "product": "telefono", "issue": "non si carica"}
Ora estrai da questo messaggio:
Input: "Il mio tablet ordine #11111 ha lo schermo rotto"
Output:
"""Il modello apprende il pattern dagli esempi e lo applica in modo coerente.
Pattern di prompt engineering per agenti
Pattern 1: Chain of Thought (ragionamento)
Per decisioni complesse, chiedi al modello di "pensare passo per passo":
prompt = """Devi decidere se creare un ticket di supporto.
Pensa a questo passo per passo:
1. Cosa sta chiedendo il cliente?
2. Si può rispondere con la documentazione esistente?
3. Serve intervento manuale?
4. Che azione dovremmo intraprendere?
Messaggio del cliente: "Voglio un rimborso per l'ordine #12345 ma ho perso la ricevuta"
Ragionamento:
"""Il modello mostrerà esplicitamente il suo ragionamento, rendendo le decisioni più trasparenti e affidabili.
Pattern 2: Generazione vincolata (sicurezza)
Limita i possibili output del modello:
prompt = """Classifica l'intento del cliente. Rispondi con ESATTAMENTE UNA di queste opzioni:
- REFUND_REQUEST
- PRODUCT_QUESTION
- TECHNICAL_ISSUE
- OFF_TOPIC
Messaggio del cliente: "Come faccio a reimpostare la password?"
Classificazione:
"""Questo impedisce al modello di generare output imprevisti. Vincolando esplicitamente le opzioni di output:
- Previene errori di parsing (sempre una delle quattro opzioni definite)
- Blocca azioni non intenzionali (impedisce l'esecuzione di azioni non definite)
- Semplifica il debugging (spazio di output limitato rende i problemi più facili da tracciare)
Pattern 3: Self-critique (qualità)
Chiedi al modello di verificare e migliorare il proprio output:
prompt = """Genera una risposta al cliente, poi criticane la risposta.
Messaggio del cliente: "Voglio un rimborso"
Step 1 - Genera risposta:
[La tua risposta qui]
Step 2 - Critica:
- Questa risposta è accurata?
- È utile?
- Segue la policy aziendale?
- Cosa potrebbe essere migliorato?
Step 3 - Risposta finale (incorporando la critica):
[Risposta migliorata qui]
"""Questo approccio multi-step spesso produce output di qualità più elevata perché:
- Individua errori presto: il modello rivede il proprio ragionamento prima di finalizzare
- Migliora tono e chiarezza: l'auto-riflessione aiuta a identificare linguaggio poco chiaro o inappropriato
- Garantisce conformità alle policy: lo step di critica verifica l'aderenza alle linee guida
Errori comuni nel prompting
Errore 1: presumere che il modello "sappia" cose
Gli LLM non hanno accesso a informazioni in tempo reale o contesto implicito. Fornisci sempre esplicitamente tutti i dati necessari.
# Bad: presume che il modello conosca la data corrente
prompt = "Questo ordine è idoneo per il rimborso? Ordine #12345"
# Good: fornisci tutte le informazioni necessarie
prompt = f"""Questo ordine è idoneo per il rimborso?
Ordine #12345
acquistato {purchase_date}
Oggi: {current_date}
Policy: resi entro 30 giorni
"""Errore 2: istruzioni ambigue
Verbi vaghi come "gestisci", "processa" o "occupati di" lasciano troppo spazio all'interpretazione. Sii esplicito sull'azione esatta necessaria.
# Bad: cosa significa "gestisci"?
prompt = "Gestisci questa richiesta di rimborso"
# Good: azione esplicita
prompt = "Determina se questa richiesta di rimborso è idonea. Se sì, crea un ticket. Se no, spiega perché."Errore 3: sovraccarico di contesto
Includere informazioni irrilevanti spreca token, aumenta la latenza e può confondere il modello. Recupera solo ciò che serve per il task specifico.
# Bad: 10.000 token di contesto, per lo più irrilevanti
prompt = f"""
{entire_knowledge_base}
Domanda: Qual è la policy di rimborso?
"""
# Good: recupera solo la sezione rilevante
prompt = f"""
{refund_policy_section}
Domanda: Qual è la policy di rimborso?
"""Errore 4: formattazione incoerente
Quando il formato dell'output varia, il codice downstream si rompe. Specifica sempre il formato esatto che ti aspetti, specialmente per dati strutturati.
# Bad: a volte JSON, a volte testo semplice
prompt = "Rispondi con la tua decisione"
# Good: specifica sempre il formato
prompt = 'Rispondi in formato JSON: {"decision": "...", "reason": "..."}'Punti chiave
Un prompting efficace per agenti richiede:
- Messaggi di sistema chiari → definire ruolo, capacità, vincoli
- Istruzioni specifiche → dire al modello esattamente cosa fare
- Contesto selettivo → fornire solo le informazioni pertinenti
- Output strutturato → specificare il formato esplicitamente
- Esempi few-shot → mostrare il pattern desiderato
- Prompt di ragionamento → chiedere thinking passo-passo
Il prompting è una competenza che migliora con la pratica. In tutto il libro, vedrai questi pattern applicati in sistemi di agenti reali.
Riepilogo del capitolo
Ora comprendi i concetti fondamentali per costruire sistemi di IA agentica:
IA agentica vs chatbot:
- Gli agenti agiscono autonomamente per raggiungere obiettivi
- Gli agenti usano strumenti e prendono decisioni multi-step
- Gli agenti mantengono stato e si adattano in base ai risultati
Perché i framework contano:
- LangChain fornisce astrazione del modello, catene, memoria, strumenti e RAG
- LangGraph aggiunge gestione dello stato, routing e checkpointing
- I framework riducono boilerplate e abilitano flussi di lavoro complessi
Meccanica degli LLM:
- Gli LLM predicono token, non recuperano fatti
- Il contesto è esplicito, non implicito
- I prompt sono istruzioni, non query
- L'output strutturato richiede guida
- Le finestre di contesto sono finite
Economia dei token:
- I token di output costano 4–8x più dei token di input
- La scelta del modello ha un impatto di costo 10–100x
- Il contesto selettivo riduce drasticamente i costi
- Monitoraggio e budget prevengono spese fuori controllo
Scelta del modello:
- Abbina il modello alla complessità del task
- Considera i requisiti di contesto e i vincoli di latenza
- Usa architetture multi-modello per ottimizzare i costi
- Parti economico, fai upgrade solo se necessario
Fondamenti di prompting:
- I messaggi di sistema definiscono il comportamento dell'agente
- Istruzioni specifiche producono risultati affidabili
- Il contesto selettivo migliora la qualità e riduce i costi
- Output strutturato abilita parsing e validazione
- Gli esempi few-shot insegnano pattern in modo efficace