5. Anteprima degli Agenti — Il Momento "Aha!"
Fino a questo punto, hai costruito sistemi in cui tu controlli il flusso. Scrivi il prompt, chiami l'LLM ed elabori la risposta. L'LLM è potente, ma sta comunque solo seguendo le tue istruzioni.
Questo capitolo introduce un cambiamento fondamentale: e se fosse l'LLM a decidere cosa succede dopo?
Perché Esiste Questo Capitolo
Il Problema: Se continui a costruire chatbot fino al Capitolo 11 senza questa anteprima, probabilmente penserai che "gli agenti sono solo chatbot migliori."
La Verità: Gli agenti(agent) sono fondamentalmente diversi. Prendono decisioni che controllano il tuo programma, non si limitano a generare testo.
La Soluzione: Questo capitolo ti mostra dove stiamo andando, così quando costruirai pipeline e gestione dello stato nei prossimi capitoli, capirai perché ogni pezzo è importante.
Cosa Imparerai (E Cosa Non Imparerai)
Questo capitolo (Capitolo 5):
- Un'anteprima minimale di 15 minuti del pensiero agentico
- Come l'output dell'LLM può attivare diversi percorsi di codice
- La differenza concettuale tra routing e cicli degli agenti
Capitoli successivi (A partire dal Capitolo 12):
- Sistemi di agenti reali con strumenti, cicli e auto-correzione
- Implementazioni pronte per la produzione
- Pattern avanzati come il coordinamento multi-agente
Perché il divario? Prima di costruire agenti, hai bisogno delle fondamenta: ingegneria dei prompt (Cap 4), pipeline di esecuzione (Cap 6), output strutturato (Cap 7), gestione dello stato(state) (Cap 8) e integrazione della conoscenza (Cap 9-11).
Una nota sull'approccio di apprendimento: Questo capitolo si concentra sui concetti, non sui dettagli implementativi. Le tecniche effettive di progettazione e sviluppo arrivano nel Capitolo 12 e oltre. Non preoccuparti se non sai come implementare tutto ciò che vedi qui—è intenzionale. Capire cosa sia concettualmente un agente è sufficiente per ora.
Cominciamo.
5.1) Da Chatbot a Decision Maker
Il Concetto: Invece di rispondere alla domanda, l'LLM decide quale strumento(tool) usare
Nei Capitoli 3 e 4, hai costruito sistemi di chat in cui l'LLM genera risposte testuali. Il flusso era semplice:
- L'utente fa una domanda
- L'LLM genera una risposta
- Visualizzi la risposta
Questa è una catena(chain) statica: il flusso è predeterminato. L'unico compito dell'LLM è produrre testo.
Ora cambiamo il tipo di richiesta. Un utente chiede: "Quanto fa 847 × 923?"
Il tuo chatbot del Capitolo 3 tenterebbe di rispondere a questo, ma gli LLM non calcolano effettivamente—predicono token plausibili. Per "2 + 2", la risposta "4" appare così spesso nei dati di addestramento che l'LLM la indovina correttamente. Ma per "847 × 923"—un calcolo che l'LLM non ha mai visto—genererà qualcosa che sembra un numero ma probabilmente è sbagliato.
Quello che vuoi davvero:
- L'LLM riconosce: "Questo è un problema matematico"
- L'LLM decide: "Usa lo strumento calcolatrice, non la mia predizione di token"
- Python esegue: 847 × 923 = 781,781
- Il sistema restituisce: La risposta corretta
Il compito dell'LLM non è calcolare—è instradare allo strumento che può farlo.
Ecco l'intuizione chiave: l'LLM non deve risolvere la matematica—deve decidere di usare una calcolatrice.
Questo è il passaggio da chatbot a decision maker. L'LLM esamina la richiesta e la instrada allo strumento appropriato. Sta prendendo una decisione sul flusso del programma, non solo generando testo.
Visualizziamo questa differenza:
Nella catena statica, l'LLM cerca di rispondere a tutto direttamente. Nel sistema di routing dinamico, l'LLM instrada la richiesta allo strumento giusto.
Il Visuale: Confronta una "Catena Lineare" (Statica) vs. un "Router" (Dinamico)
Ecco come una catena statica gestisce qualsiasi richiesta dell'utente:
L'LLM cerca di rispondere a tutto direttamente. Che tu chieda "Qual è la capitale della Francia?" o "Quanto è il 15% di 240?", l'LLM genera una risposta testuale. Potrebbe indovinare correttamente la capitale (ha visto "Parigi" molte volte nei dati di addestramento), ma probabilmente sbaglierà il calcolo percentuale.
Il problema: L'LLM usa lo stesso approccio per tutte le domande—predizione di token—anche quando esistono strumenti migliori.
Ora introduciamo un sistema che può gestire diversi tipi di richieste in modo diverso:
- Domande fattuali: "Qual è la capitale della Francia?" → Usa uno strumento di ricerca
- Problemi matematici: "Quanto è il 15% di 240?" → Usa una calcolatrice
- Conversazionale: "Come stai?" → L'LLM risponde direttamente
Ecco un router dinamico che rende questo possibile:
L'LLM esamina l'input e sceglie un percorso in base al tipo di richiesta. Questa è logica di routing, e l'LLM agisce come router.
La differenza chiave: Invece di generare sempre testo, l'LLM ora decide come gestire ogni richiesta—inoltrandola allo strumento appropriato.
Punto Chiave: Da Generare Risposte a Scegliere Azioni
Questo è il cambiamento fondamentale nei sistemi agentici:
Uso tradizionale dell'LLM: Fai una domanda, l'LLM scrive una risposta.
Uso agentico dell'LLM: Fai una domanda, l'LLM decide cosa fare, e il tuo codice esegue quella decisione.
L'output dell'LLM non è più solo testo per l'utente—sono istruzioni per il tuo programma.
Pensala così: in un chatbot tradizionale, l'LLM è un dipendente che risponde alle domande dei clienti. In un sistema agentico, l'LLM è un manager che decide quale dipartimento dovrebbe gestire ogni richiesta.
Questa capacità decisionale è l'essenza dei sistemi agentici. L'LLM non si limita a rispondere—controlla cosa fa il tuo programma dopo. In base all'input, può attivare una calcolatrice, chiamare un'API di ricerca o rispondere direttamente. Il comportamento del programma cambia in base alla decisione dell'LLM.
Ma come implementiamo effettivamente questo? Vediamo il codice minimo che lo rende possibile.
5.2) L'Esecuzione Minimale
Il Trigger: Un prompt di sistema che forza output strutturato
Per far agire l'LLM come router, dobbiamo vincolare il suo output. Invece di generare una risposta completa, vogliamo che produca una decisione strutturata—un oggetto JSON che dice al nostro codice sia cosa fare che quali dati usare.
Ecco un prompt di sistema che fa questo:
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
import json
llm = ChatOpenAI(model="gpt-4o-mini")
system_prompt = """Sei un assistente di routing. Analizza la richiesta dell'utente e rispondi con JSON.
Formato di output:
{
"action": "CALC" | "SEARCH" | "CHAT",
"input": "input pulito per lo strumento"
}
Esempi:
Utente: "Calcola 15 per 7"
Output: {"action": "CALC", "input": "15 * 7"}
Utente: "Qual è la capitale del Giappone?"
Output: {"action": "SEARCH", "input": "capitale del Giappone"}
Utente: "Ciao!"
Output: {"action": "CHAT", "input": "Ciao!"}
Estrai le informazioni essenziali e formattale per lo strumento appropriato.
Produci solo JSON valido, nient'altro."""
def get_routing_decision(user_input: str) -> dict:
messages = [
SystemMessage(content=system_prompt),
HumanMessage(content=user_input)
]
response = llm.invoke(messages)
# Analizza la risposta JSON
try:
decision = json.loads(response.content.strip())
return decision
except json.JSONDecodeError:
# Fallback se l'LLM non produce JSON valido
return {"action": "CHAT", "input": user_input}
# Testalo con input DIVERSI dagli esempi
print(get_routing_decision("Quanto fa 25 * 48?"))
# Output: {'action': 'CALC', 'input': '25 * 48'}
print(get_routing_decision("Chi ha scritto Amleto?"))
# Output: {'action': 'SEARCH', 'input': 'autore di Amleto'}
print(get_routing_decision("Come stai oggi?"))
# Output: {'action': 'CHAT', 'input': 'Come stai oggi?'}Questo prompt è deliberatamente restrittivo. Non stiamo chiedendo all'LLM di essere creativo—gli stiamo chiedendo di classificare l'input ed estrarre le informazioni rilevanti in un formato strutturato.
Nota cosa sta succedendo qui:
- L'LLM legge la domanda dell'utente
- Determina l'intento (calcolo, ricerca fattuale, conversazione)
- Estrae e pulisce le informazioni essenziali
- Produce un oggetto JSON con sia l'azione che l'input pulito
- Il nostro codice riceve questi dati strutturati e instrada di conseguenza
L'LLM non sta rispondendo alla domanda—sta decidendo cosa dovrebbe rispondere alla domanda.
Il Ponte: Collegare le Decisioni dell'LLM all'Esecuzione degli Strumenti
Ora dobbiamo collegare la decisione dell'LLM all'effettiva esecuzione degli strumenti. Ecco il ponte minimo:
def safe_calculator(expression: str) -> float:
try:
# Usa eval con namespace ristretto per sicurezza
# Nota: eval() ha rischi di sicurezza.
# In produzione, usa una libreria di parsing matematico come sympy o valutazione basata su ast.
result = eval(expression, {"__builtins__": {}}, {})
return float(result)
except:
raise ValueError(f"Impossibile calcolare: {expression}")
def search(query: str) -> str:
"""Funzione di ricerca placeholder"""
# In realtà, questa chiamerebbe un'API di ricerca
# Ora riceve query pulita come "autore di Amleto"
return f"Risultati di ricerca per: {query}"
def route_and_execute(user_input: str) -> str:
"""Ottieni la decisione dell'LLM ed esegui lo strumento appropriato"""
decision = get_routing_decision(user_input)
action = decision["action"]
tool_input = decision["input"] # Input pulito dall'LLM
if action == "CALC":
try:
result = safe_calculator(tool_input)
return f"Risultato del calcolo: {result}"
except ValueError as e:
return f"Impossibile eseguire il calcolo: {e}"
elif action == "SEARCH":
result = search(tool_input)
return result
else: # CHAT
# Per query conversazionali, lascia che l'LLM risponda direttamente
response = llm.invoke([HumanMessage(content=tool_input)])
return response.content
# Testa il flusso completo
print(route_and_execute("Quanto fa 25 * 48?"))
# L'LLM estrae "25 * 48" → la calcolatrice riceve input pulito
# Output: Risultato del calcolo: 1200.0
print(route_and_execute("Chi ha scritto Amleto?"))
# L'LLM riformatta in "autore di Amleto" → query di ricerca migliore
# Output: Risultati di ricerca per: autore di Amleto
print(route_and_execute("Come stai oggi?"))
# Output: Sto bene, grazie per aver chiesto!Questo è il pattern di routing minimo nella sua forma più semplice:
- Decisione LLM: Ottieni la decisione di routing dall'LLM
- Esecuzione Codice: Il codice Python controlla la decisione
- Invocazione Strumento: Viene chiamata la funzione appropriata
- Restituzione Risultato: L'output viene formattato e restituito
L'intuizione critica: L'output dell'LLM non è più testo per l'utente—sono dati strutturati che controllano il comportamento del tuo programma. Il campo action dice al tuo codice quale percorso prendere, e il campo input fornisce i dati puliti per quello strumento. Questa risposta JSON diventa logica eseguibile.
Questo è fondamentalmente diverso da un chatbot. In un chatbot, l'output dell'LLM va direttamente all'utente. Qui, l'output dell'LLM va al tuo codice, che poi decide cosa eseguire.
Quello che abbiamo costruito in questa sezione è un router—un precursore degli agenti completi. Dimostra il principio fondamentale (l'LLM controlla il flusso), ma manca dell'iterazione e dell'auto-correzione che definiscono i veri cicli degli agenti.
Questo semplice sistema di routing ha una limitazione importante: non può correggere i propri errori. Esploriamo perché questo è importante e cosa viene dopo.
5.3) Comprendere la Terminologia: Router vs Agente
Quello che abbiamo appena costruito è un router. Ma come si confronta con gli agenti completi? Stabiliamo definizioni chiare:
Router (quello che abbiamo appena costruito)
- Prende una decisione di classificazione per richiesta
- Sceglie quale strumento/percorso eseguire
- Esegue una volta e restituisce
- Nessuna iterazione, nessuno stato, nessuna auto-correzione
- Esempio: Classificatore di email, rilevatore di intenti, selettore di strumenti
Assistente con chiamata di strumenti (Capitolo 13)
- L'LLM può invocare strumenti direttamente tramite API di chiamata di funzioni
- Ancora tipicamente single-turn (una richiesta → una risposta)
- Più sofisticato del routing, ma non necessariamente iterativo
- Esempio: "Cerca il meteo e riassumi" in una chiamata
Agente (ciclo completo) (Capitoli 14-17)
- Itera attraverso cicli pensa → agisci → osserva
- Mantiene lo stato tra le iterazioni
- Può rivedere le decisioni in base ai risultati
- Implementa l'auto-correzione
- Esempio: Assistente di debug che prova correzioni finché il codice funziona
Sistema agentico (termine ombrello)
- Qualsiasi sistema in cui l'output dell'LLM influenza il flusso di controllo
- Include router, assistenti con chiamata di strumenti e agenti completi
- "Agentico" descrive la proprietà; "agente" descrive un'architettura specifica
- Esempio: Tutti i precedenti mostrano comportamento agentico
Quello che abbiamo costruito in 5.2 è un router—prende una decisione e la esegue. Questo funziona bene per compiti di classificazione semplici, ma non può gestire situazioni che richiedono più passaggi, verifica o correzione di rotta. Se la decisione iniziale è subottimale o se il compito si rivela più complesso del previsto, il router non ha meccanismi per adattarsi.
Gli agenti risolvono questa limitazione introducendo l'iterazione. Possono osservare il risultato di un'azione, riconsiderare il loro approccio e riprovare. Questo li rende adatti per compiti complessi e multi-step in cui il percorso verso la soluzione non è chiaro dall'inizio.
Nei Capitoli 14-17, imparerai come costruire questi cicli iterativi degli agenti.
Nota: Recentemente, modelli di ragionamento come o1 e o3 possono gestire alcuni compiti multi-step internamente, riducendo la necessità di cicli espliciti in certi scenari. Esploreremo quando usare cicli vs. modelli di ragionamento mentre costruisci agenti reali nella Parte IV.
Cosa dovresti capire ora:
- I sistemi agentici iniziano con il flusso di controllo: L'output dell'LLM determina cosa fa il tuo codice dopo
- Il routing è la forma più semplice: Una decisione, uno strumento, un risultato—quello che abbiamo costruito in 5.2
- Gli agenti reali hanno bisogno di cicli: Per gestire compiti multi-step, verifica e recupero da errori (Capitoli 14-17)
- L'auto-correzione è la differenza chiave: Gli agenti possono osservare i risultati e adattare il loro approccio
Cosa non abbiamo coperto (e non faremo fino ai capitoli successivi):
- Come comporre pipeline di esecuzione (Capitolo 6)
- Come gestire output strutturato (Capitolo 7)
- Come gestire la memoria delle conversazioni (Capitolo 8)
- Come definire correttamente gli strumenti (Capitolo 12)
- Come implementare cicli di agenti con auto-correzione (Capitoli 14-17)
- Come rendere gli agenti pronti per la produzione (Capitoli 18-26)
Questo capitolo riguardava il cambiamento concettuale—capire cosa rende un sistema agentico. I dettagli implementativi arrivano dopo.
Nel prossimo capitolo, continueremo a costruire le fondamenta pratiche: comporre pipeline di esecuzione con LangChain Expression Language (LCEL). Questi sono i blocchi di costruzione che userai quando implementeremo agenti reali nella Parte IV.
Il momento "aha!" è completo. Ora capisci che i sistemi agentici sono quelli in cui l'LLM decide, e il tuo codice esegue. Tutto il resto—strumenti, cicli, gestione dello stato—riguarda il rendere questo ciclo decisione-esecuzione più robusto e capace.
Continuiamo a costruire le fondamenta.