Va bene, ragazzi, Alex Petrov qui, in diretta da agntai.net. Siamo a metà marzo 2026, e sto combattendo con qualcosa che mi infastidisce da un po’: il problema della “black box” negli agenti AI. Non solo il modello stesso, ma l’intero ciclo agentico. Stiamo costruendo questi sistemi incredibili che prendono decisioni, pianificano e eseguono, ma spesso, quando qualcosa va storto, capire *perché* sembra un’impresa impossibile, come cercare di fare il debug di una conversazione che si svolge in una stanza buia.
Oggi voglio parlare di qualcosa che è diventato assolutamente critico nel mio lavoro: costruire l’osservabilità nelle architetture degli agenti AI fin dall’inizio. Non come un pensiero secondario, non come uno strumento di monitoraggio aggiuntivo, ma come parte intrinseca di come progettiamo e implementiamo questi sistemi complessi. Perché, diciamolo chiaramente, se non riesci a vedere cosa sta facendo, pensando e con cosa sta lottando il tuo agente, stai volando al buio, e questo è una ricetta per il disastro in qualsiasi ambiente di produzione.
L’agente invisibile: i miei incubi di debug
Ricordo un progetto specifico dello scorso anno. Avevamo un agente progettato per gestire una pipeline di dati complessa, identificando anomalie, suggerendo correzioni e persino eseguendone alcune dopo l’approvazione umana. Sulla carta, era brillante. Nella pratica, era un incubo da debug. Ogni tanto si bloccava in un ciclo, o prendeva una decisione apparentemente illogica che si traduceva in problemi più grandi. Il nostro approccio iniziale era semplicemente registrare l’azione finale e ogni tanto lo stato interno. Non era sufficiente.
Quando si bloccava, tutto ciò che vedevamo nei log era una sequenza ripetitiva di “controllo stato,” “recupero dati,” “controllo stato.” Perché? Cosa stava cercando? Quale punto di decisione stava fallendo? Non avevamo idea. Era come cercare di diagnosticare un problema dell’auto guardando solo l’odometro e la spia ‘controllo motore’. Abbiamo trascorso giorni, a volte settimane, a riprodurre scenari specifici, aggiungendo più dichiarazioni di stampa e rieseguendo i test. Era lento, doloroso e profondamente inefficiente.
Quell’esperienza ha evidenziato una verità semplice: se vuoi costruire agenti AI affidabili, devi vedere dentro le loro teste, o almeno, dentro il loro flusso di processo. Devi comprendere il loro ragionamento, i cambiamenti del loro stato interno, il loro utilizzo degli strumenti e i loro punti di decisione. Non si tratta solo di registrazione; si tratta di un’osservabilità strutturata, contestuale e accessibile.
Oltre la registrazione di base: cosa significa “osservabilità” per gli agenti?
Quando parlo di osservabilità, penso a tre pilastri principali: log, metriche e tracce. Siamo tutti familiari con questi nei software tradizionali, ma per gli agenti AI assumono una sfumatura leggermente diversa.
Log: narrazioni dettagliate dei passi agentici
Non si tratta solo di `print(“L’agente ha fatto X”)`. Si tratta di una registrazione strutturata che cattura il contesto. Pensalo come se l’agente stesse scrivendo un diario del suo processo di pensiero e delle sue azioni. Ogni voce di log dovrebbe raccontare una storia:
- Qual era l’input? (ad esempio, prompt dell’utente, dati sensoriali, evento interno)
- Qual era lo stato attuale? (ad esempio, contenuti della memoria, obiettivo attivo, risultato dell’azione precedente)
- Quale passaggio di ragionamento interno è avvenuto? (ad esempio, “fase di pianificazione avviata,” “selezione dello strumento basata su X,” “critica del piano Y”)
- Qual è stata la chiamata esatta allo strumento, compresi gli argomenti?
- Qual è stato l’output grezzo dello strumento?
- Qual è stata l’interpretazione dell’agente riguardo a quell’output?
- Qual è stata la decisione successiva presa, e perché? (ad esempio, “deciso di affinare il piano a causa di un errore nell’output dello strumento,” “selezionata l’azione successiva A in base all’obiettivo B”)
Un modo semplice per implementare questo è utilizzare una libreria di logging strutturato (come il modulo `logging` di Python con formattatori JSON, o `structlog`). Ogni volta che il tuo agente apporta un cambiamento significativo allo stato interno, chiama uno strumento o valuta una decisione, registralo con il contesto pertinente.
import logging
import json
from datetime import datetime
# Imposta il logger strutturato
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class JsonFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"agent_id": getattr(record, 'agent_id', 'unknown'),
"context": getattr(record, 'context', {}),
"step_type": getattr(record, 'step_type', 'generic'),
"details": getattr(record, 'details', {})
}
return json.dumps(log_entry)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
class MyAgent:
def __init__(self, agent_id):
self.agent_id = agent_id
self.memory = []
self.current_goal = None
def perceive(self, input_data):
logger.info("Agente ha percepito un nuovo input.",
extra={"agent_id": self.agent_id,
"step_type": "perception",
"details": {"input": input_data}})
self.memory.append(f"Percepito: {input_data}")
# ... elabora input ...
def plan(self):
# Simula pianificazione
plan_steps = ["controllo_stato", "recupero_dati", "analizza_dati"]
self.current_goal = "Elaborare i dati"
logger.info("L'agente ha creato un nuovo piano.",
extra={"agent_id": self.agent_id,
"step_type": "planning",
"context": {"goal": self.current_goal},
"details": {"plan": plan_steps}})
return plan_steps
def execute_tool(self, tool_name, args):
logger.info(f"Agente eseguendo lo strumento: {tool_name} con args {args}",
extra={"agent_id": self.agent_id,
"step_type": "tool_execution",
"context": {"current_goal": self.current_goal},
"details": {"tool": tool_name, "arguments": args}})
# Simula l'esecuzione dello strumento
if tool_name == "recupero_dati":
result = {"status": "success", "data": "some_data"}
else:
result = {"status": "error", "message": "Strumento sconosciuto"}
logger.info(f"Lo strumento {tool_name} ha restituito: {result}",
extra={"agent_id": self.agent_id,
"step_type": "tool_result",
"context": {"current_goal": self.current_goal},
"details": {"tool": tool_name, "result": result}})
return result
def run(self, input_data):
self.perceive(input_data)
plan_steps = self.plan()
for step in plan_steps:
if step == "recupero_dati":
self.execute_tool("recupero_dati", {"source": "api"})
logger.info("L'agente ha terminato il ciclo corrente.",
extra={"agent_id": self.agent_id,
"step_type": "cycle_end",
"details": {"memory_size": len(self.memory)}})
# Esempio d'uso
agent = MyAgent(agent_id="data_processor_001")
agent.run("Il nuovo report deve essere elaborato.")
Metriche: quantificare la salute e le performance dell’agente
I log ci danno la narrazione, ma le metriche ci danno il battito. Questi sono valori numerici che tracciano le performance dell’agente, l’uso delle risorse e gli stati interni nel tempo. Pensa a:
- Latenza nel prendere decisioni: Quanto tempo impiega l’agente per passare dall’input all’azione?
- Frequenza d’uso degli strumenti: Quali strumenti vengono chiamati più spesso? Ce ne sono alcuni che non vengono mai usati?
- Numero di iterazioni di pianificazione: Quante volte l’agente affina il suo piano prima di agire? Un numero elevato potrebbe indicare complessità o difficoltà.
- Utilizzo della memoria: Quanta memoria consuma l’agente? Sta crescendo senza controllo?
- Tassi di successo/fallimento delle azioni: Alcuni strumenti falliscono più spesso?
- Utilizzo dei token: Critico per gli agenti basati su LLM: quanti token vengono consumati per interazione o per completamento obiettivo?
Puoi utilizzare librerie come le librerie client di Prometheus o semplicemente incrementare i contatori nel tuo codice e spingerli a un database time-series. La chiave è definire queste metriche *prima* di effettuare il deployment, considerando quale informazione sarebbe utile se qualcosa andasse storto.
from prometheus_client import Counter, Histogram, generate_latest
import time
# Definire le metriche di Prometheus
AGENT_ACTIONS_TOTAL = Counter('agent_actions_total', 'Numero totale di azioni intraprese dall\'agente', ['agent_id', 'action_type', 'status'])
PLANNING_DURATION_SECONDS = Histogram('planning_duration_seconds', 'Durata della fase di pianificazione in secondi', ['agent_id'])
TOOL_CALLS_TOTAL = Counter('tool_calls_total', 'Numero totale di chiamate agli strumenti', ['agent_id', 'tool_name'])
TOOL_CALL_DURATION_SECONDS = Histogram('tool_call_duration_seconds', 'Durata delle chiamate agli strumenti in secondi', ['agent_id', 'tool_name'])
class MyAgentWithMetrics(MyAgent): # Eredita dal nostro agente precedente
def __init__(self, agent_id):
super().__init__(agent_id)
self.agent_id = agent_id
def plan(self):
start_time = time.time()
plan_steps = super().plan() # Chiama il metodo di pianificazione originale
PLANNING_DURATION_SECONDS.labels(agent_id=self.agent_id).observe(time.time() - start_time)
AGENT_ACTIONS_TOTAL.labels(agent_id=self.agent_id, action_type="plan", status="success").inc()
return plan_steps
def execute_tool(self, tool_name, args):
TOOL_CALLS_TOTAL.labels(agent_id=self.agent_id, tool_name=tool_name).inc()
start_time = time.time()
result = super().execute_tool(tool_name, args) # Chiama l'execute_tool originale
TOOL_CALL_DURATION_SECONDS.labels(agent_id=self.agent_id, tool_name=tool_name).observe(time.time() - start_time)
status = "success" if result.get("status") == "success" else "failure"
AGENT_ACTIONS_TOTAL.labels(agent_id=self.agent_id, action_type=f"tool_{tool_name}", status=status).inc()
return result
# Per esporre le metriche, di solito esegui un piccolo server HTTP:
# from http.server import BaseHTTPRequestHandler, HTTPServer
# class MetricsHandler(BaseHTTPRequestHandler):
# def do_GET(self):
# self.send_response(200)
# self.send_header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
# self.end_headers()
# self.wfile.write(generate_latest())
#
# if __name__ == '__main__':
# # Esempio d'uso
# agent_metric = MyAgentWithMetrics(agent_id="data_processor_002")
# agent_metric.run("Un altro report da elaborare.")
# # Poi interrogheresti http://localhost:8000/metrics (o qualunque porta tu utilizzi)
# # print(generate_latest().decode('utf-8')) # Per un output diretto nella console
Tracce: Seguire il Percorso dell’Agente
Le tracce forniscono una panoramica end-to-end di una singola richiesta o realizzazione di un obiettivo, mostrando la sequenza di eventi e le loro relazioni tra diversi componenti. Per un agente AI, una “traccia” potrebbe rappresentare l’intero ciclo di vita dell’elaborazione di un singolo input dell’utente, dalla percezione all’azione finale, mostrando tutta la pianificazione interna, le chiamate agli strumenti e i passaggi di ragionamento lungo il percorso.
Immagina un diagramma a cascata dove ogni barra rappresenta un’operazione distinta: “Analizza l’Intenzione dell’Utente,” “Genera Piano Iniziale,” “Chiama Strumento X,” “Valuta Output dello Strumento,” “Affina Piano,” “Esegui Azione Finale.” Ognuno di questi “span” avrebbe il proprio tempo di inizio/fine, durata e metadati associati (come il prompt LLM utilizzato, la sua risposta o gli argomenti dello strumento).
È qui che strumenti come OpenTelemetry brillano. Si strumenta il codice per creare spans per ogni passaggio significativo dell’agente. Questi spans vengono poi correlati, consentendo di visualizzare l’intero flusso. Quando il tuo agente si blocca o prende una decisione sbagliata, una traccia può mostrarti esattamente *dove* nella sequenza è avvenuto il problema, quanto è durata quella particolare fase e quali input/output sono stati coinvolti.
Per gli agenti, il tracciamento è particolarmente potente perché ci consente di visualizzare la *catena di ragionamento*. Puoi vedere il prompt iniziale, il processo di pensiero del LLM, lo strumento che ha deciso di chiamare, l’output dello strumento e come il LLM poi ha interpretato quell’output per formulare il suo prossimo passo. Questo è inestimabile per il debug e la comprensione dei comportamenti emergenti.
Progettare per l’Osservabilità: Non un’Idea dell’Ultimo Minuto
La lezione più grande che ho imparato è che l’osservabilità deve essere un principio di design fondamentale, non qualcosa che cerchi di aggiungere all’ultimo minuto. Quando stai schizzando l’architettura del tuo agente, chiediti:
- Quali sono i punti decisionali critici? Questi sono i candidati ideali per i log strutturati.
- Quali informazioni avrei bisogno per capire perché un agente ha intrapreso una specifica azione? Assicurati che i tuoi log lo catturino.
- Quali sono gli indicatori di prestazione chiave per questo agente? Definisci metriche per essi.
- Come scorre un unico obiettivo o richiesta attraverso i vari componenti dell’agente? Questa è la tua opportunità di tracciamento.
- Come collegherò le informazioni attraverso diverse istanze dell’agente o attraverso diverse parti di un sistema di agenti distribuito? I campi unici `agent_id` e `request_id` nei log/tracce sono tuoi alleati.
Pensa a costruire uno “strato di osservabilità” nel tuo framework per agenti. Questo strato intercetta eventi chiave (chiamate agli strumenti, interazioni con il LLM, cambiamenti di stato) ed emette automaticamente log strutturati, metriche e tracce senza ingombrare la logica centrale del tuo agente.
Pratiche Azionabili per il Prossimo Progetto con Agenti
- Abbraccia i Log Strutturati: Dimentica le semplici istruzioni di stampa. Utilizza una libreria di logging che ti permetta di allegare coppie chiave-valore alle tue voci di log. Includi sempre `agent_id`, `request_id` (se applicabile), `step_type`, e il `context` rilevante nei tuoi log.
- Strumenta per Metriche Chiave: Identifica 3-5 metriche critiche che definiscono la salute e le prestazioni del tuo agente (ad es., latenza decisionale, tasso di successo degli strumenti, utilizzo dei token). Implementa librerie client come Prometheus o simili per esporle.
- Pianifica per il Tracciamento: Anche se non implementi subito OpenTelemetry completo, mappa mentalmente gli “span” nel ciclo di vita del tuo agente. Pensa a come li collegheresti. Questo renderà molto più facile l’implementazione futura del tracciamento.
- Centralizza i Tuoi Dati: Non limitarti a registrare in un file. Invia i tuoi log e metriche a un sistema centralizzato (ad es., stack ELK, Grafana Loki, Prometheus + Grafana). Questo è cruciale per visualizzazioni aggregate e analisi storica.
- Visualizza Tutto: I log e le metriche grezze sono utili, ma cruscotti e visualizzazioni delle tracce li rendono azionabili. Dedica tempo a costruire cruscotti significativi in Grafana o a utilizzare interfacce di tracciamento per comprendere il comportamento del tuo agente.
- Pratica lo “Sviluppo Guidato dall’Osservabilità”: Prima di scrivere un ciclo agente complesso, pensa a come osserverai il suo comportamento. Questo pensiero anticipato risparmia un enorme tempo di debug in seguito.
Costruire agenti AI affidabili e pronti per la produzione è difficile. Sono sistemi intrinsecamente complessi e non deterministici. Ma integrando l’osservabilità fin dall’inizio, possiamo trasformare quelle scatole nere in scatole traslucide, permettendoci di comprendere, debuggare e, in definitiva, fidarci molto di più dei nostri agenti. Non è solo una buona pratica; è una necessità per chiunque prenda sul serio il deploy di questi sistemi nel mondo reale.
Continua a costruire, continua a osservare, e ci vediamo la prossima volta qui su agntai.net!
🕒 Published: