\n\n\n\n Gestione degli errori dell’agente : Un tutorial pratico con esempi - AiDebug \n

Gestione degli errori dell’agente : Un tutorial pratico con esempi

📖 13 min read2,467 wordsUpdated Apr 4, 2026

Introduzione : La realtà inevitabile degli errori degli agenti

Nel mondo dinamico degli agenti AI, dove i sistemi interagiscono con ambienti imprevedibili, API esterne e catene logiche complesse, gli errori non sono un’eccezione ma un’inevitabilità. Da una risposta API malformattata a un timeout, un’anomalia logica o un’input utente inaspettato, i punti di fallimento potenziali sono molti. Errori non gestiti possono portare a malfunzionamenti dell’agente, cicli infiniti, risultati errati, scarse esperienze utente e persino vulnerabilità di sicurezza. Pertanto, una buona gestione degli errori non è solo una prassi migliore; è un requisito fondamentale per costruire agenti AI affidabili, resilienti e pronti per la produzione.

Questo tutorial ti guiderà attraverso gli aspetti pratici dell’implementazione di strategie efficaci di gestione degli errori per i tuoi agenti AI. Esploreremo i tipi di errori comuni, discuteremo vari meccanismi di gestione e forniremo esempi concreti in Python per illustrare questi concetti. Alla fine, avrai una solida comprensione di come anticipare, rilevare e recuperare elegantemente dagli errori, garantendo che i tuoi agenti funzionino in modo ottimale anche quando le cose vanno male.

Comprendere i tipi di errori comuni degli agenti

Prima di poter gestire gli errori, dobbiamo capire quali tipi di errori siamo suscettibili di incontrare. Gli errori degli agenti si classificano generalmente in alcune categorie:

1. Errori di API/Servizi Esterni

  • Problemi di Rete : Timeout, connessione rifiutata, fallimenti nella risoluzione DNS.
  • Limiti di Frequenza delle API : Superare il numero autorizzato di richieste in un determinato lasso di tempo.
  • Chiavi API Non Valide/Errori di Autenticazione : Credenziali errate che impediscono l’accesso.
  • Risposte Malformate : API che restituiscono strutture JSON, XML o HTML inattese.
  • Codici di Stato HTTP : 4xx (errori lato client come 404 Non Trovato, 400 Richiesta Errata, 401 Non Autorizzato) e 5xx (errori lato server come 500 Errore Interno del Server, 503 Servizio Non Disponibile).

2. Errori di Input/Output (I/O)

  • File Non Trovato : Tentativa di lettura o scrittura in un file inesistente.
  • Permesso Negato : Assenza di accesso in lettura/scrittura necessario ai file o directory.
  • Disco Pieno : Nessuno spazio disponibile sul dispositivo per nuovi dati.

3. Errori Logici dell’Agente

  • Errori di Tipo : Operazioni eseguite su tipi di dati incompatibili (ad esempio, sommare una stringa a un intero).
  • Errori di Valore : Tipo di dati corretto ma valore inappropriato (ad esempio, convertire ‘abc’ in intero).
  • Errori di Indice : Accesso a un indice di lista o array che è fuori dai limiti.
  • Errori di Chiave : Accesso a una chiave non esistente in un dizionario.
  • ZeroDivisionError : Tentativa di divisione di un numero per zero.
  • Cicli Infiniti : L’agente si blocca in un compito ripetitivo senza condizione di termine.

4. Errori di Risorse

  • Esaurimento della Memoria : L’agente consuma troppa RAM, portando a un crash.
  • Sovraccarico della CPU : Operazioni computazionali intensive che rallentano o bloccano l’agente.

Strategie Fondamentali di Gestione degli Errori

Il meccanismo principale di gestione degli errori in Python è il blocco try-except-finally-else. Scomponiamo i suoi componenti e poi esploriamo strategie più avanzate.

1. Il Blocco try-except : Catturare le Eccezioni

Questo è il fondamento della gestione degli errori. Il codice che potrebbe sollevare un’eccezione è posto all’interno del blocco try. Se si verifica un’eccezione, l’esecuzione passa immediatamente al blocco except corrispondente.

Esempio di Base : Gestione di un ValueError

def convert_to_int(value_str):
 try:
 num = int(value_str)
 print(f"Conversione riuscita di '{value_str}' in intero : {num}")
 return num
 except ValueError:
 print(f"Errore : Impossibile convertire '{value_str}' in un intero. Si prega di fornire una stringa di numero valida.")
 return None

convert_to_int("123")
convert_to_int("hello")
convert_to_int("3.14") # Questo solleverà anche un ValueError se int() viene usato direttamente

Catturare più Eccezioni

Puoi catturare diversi tipi di eccezioni con più blocchi except o raggrupparli.

def process_data(data_list, index):
 try:
 value = data_list[index]
 result = 10 / value
 print(f"Risultato : {result}")
 except IndexError:
 print(f"Errore : L'indice {index} è fuori limiti per la lista.")
 except ZeroDivisionError:
 print(f"Errore : Impossibile dividere per zero. Il valore all'indice {index} è zero.")
 except TypeError as e:
 print(f"Errore : Incompatibilità di tipo durante l'operazione : {e}")
 except Exception as e: # Catturare qualsiasi altro errore inaspettato
 print(f"Si è verificato un errore inaspettato : {e}")

process_data([1, 2, 0, 4], 0) # Risultato : 10.0
process_data([1, 2, 0, 4], 2) # Errore : Impossibile dividere per zero...
process_data([1, 2, 0, 4], 5) # Errore : L'indice 5 è fuori limiti...
process_data(['a', 2], 0) # Errore : Incompatibilità di tipo...

2. Il Blocco finally : Assicurare la Pulizia

Il codice all’interno di un blocco finally viene eseguito sempre, che si sia verificata o meno un’eccezione. È ideale per operazioni di pulizia, come chiudere file, rilasciare lock o terminare connessioni di rete.

def read_file_gracefully(filename):
 file = None
 try:
 file = open(filename, 'r')
 content = file.read()
 print(f"Contenuto del file :\n{content}")
 except FileNotFoundError:
 print(f"Errore : File '{filename}' non trovato.")
 except IOError as e:
 print(f"Errore durante la lettura del file '{filename}' : {e}")
 finally:
 if file:
 file.close()
 print(f"File '{filename}' chiuso.")

# Creare un file fittizio per i test
with open("test_file.txt", "w") as f:
 f.write("Ciao, Agente !")

read_file_gracefully("test_file.txt")
read_file_gracefully("non_existent_file.txt")

3. Il Blocco else : Codice per il Successo

Il blocco else viene eseguito solo se il blocco try termina senza eccezioni. È un buon posto per inserire codice che deve essere eseguito solo se l’operazione iniziale ha avuto successo.

def perform_api_call(url):
 import requests # Supponiamo che requests sia installato
 try:
 response = requests.get(url, timeout=5)
 response.raise_for_status() # Solleva HTTPError per le risposte errate (4xx o 5xx)
 except requests.exceptions.Timeout:
 print(f"L' chiamata API a {url} ha superato il timeout.")
 return None
 except requests.exceptions.RequestException as e:
 print(f"L' chiamata API a {url} è fallita : {e}")
 return None
 else:
 print(f"L' chiamata API a {url} è riuscita. Stato : {response.status_code}")
 return response.json()
 finally:
 print("Tentativo di chiamata API terminato.")

# Esempio di utilizzo (sostituire con URL reali per i test)
perform_api_call("https://jsonplaceholder.typicode.com/todos/1") # Successo
perform_api_call("https://httpbin.org/status/500") # Errore server
perform_api_call("https://invalid-url-that-does-not-exist.com") # Eccezione di richiesta

Modelli Avanzati di Gestione degli Errori per Agenti

1. Tentativi con Backoff Esponenziale

Per errori transitori (come problemi di rete, sovraccarichi temporanei delle API o limiti di frequenza), riprovare l’operazione dopo un breve intervallo può essere efficace. Il backoff esponenziale aumenta il ritardo tra i retry, impedendo al tuo agente di sovraccaricare il servizio e permettendogli di riprendersi.

import time
import random

def reliable_api_call(url, max_retries=5, initial_delay=1):
 for attempt in range(max_retries):
 try:
 # Simulare una chiamata API inaffidabile che fallisce a volte
 if random.random() < 0.6 and attempt < max_retries - 1: # 60 % di probabilità di fallimento fino all'ultimo tentativo
 raise requests.exceptions.RequestException("Errore API transitorio simulato")

 response = requests.get(url, timeout=5)
 response.raise_for_status()
 print(f"Tentativo {attempt + 1} : Chiamata API riuscita a {url}.")
 return response.json()
 except requests.exceptions.RequestException as e:
 print(f"Tentativo {attempt + 1} : Chiamata API fallita a {url} : {e}")
 if attempt < max_retries - 1:
 delay = initial_delay * (2 ** attempt) + random.uniform(0, 1)
 print(f"Nuovo tentativo fra {delay:.2f} secondi...")
 time.sleep(delay)
 else:
 print(f"Numero massimo di tentativi raggiunto per {url}. Abbandono.")
 return None
 return None

# Esempio di utilizzo
# reliable_api_call("https://jsonplaceholder.typicode.com/todos/1")

2. Modello di Interruttore

Quando un servizio esterno fallisce costantemente, riprovare in continuazione può sprecare risorse e degradare ulteriormente il servizio. Il modello di interruttore automatico impedisce a un agente di chiamare ripetutamente un servizio non funzionante. 'Apre' il circuito (ferma le chiamate) dopo un certo numero di fallimenti, attende un periodo di attesa, poi 'mezzo-apre' per testare se il servizio si è ripreso.

Implementare un interruttore automatico completo da zero può essere complesso. Librerie come pybreaker (per Python) forniscono implementazioni solide.

Esempio Concettuale (Semplificato)

import time

class CircuitBreaker:
 def __init__(self, failure_threshold=3, recovery_timeout=10, reset_timeout=5):
 self.failure_threshold = failure_threshold
 self.recovery_timeout = recovery_timeout # Tempo nello stato 'aperto' prima di passare a mezzo-aperto
 self.reset_timeout = reset_timeout # Tempo nello stato 'mezzo-aperto' prima di chiudere
 self.failures = 0
 self.state = "CLOSED" # CHIUSO, APERTO, MEZZO-APERTO
 self.last_failure_time = None

 def call(self, func, *args, **kwargs):
 if self.state == "OPEN":
 if time.time() - self.last_failure_time > self.recovery_timeout:
 self.state = "HALF-OPEN"
 print("Circuit Breaker: Passaggio allo stato MEZZO-APERTO.")
 else:
 raise CircuitBreakerOpenError("Il circuito è APERTO. Il servizio è probabilmente offline.")
 
 try:
 result = func(*args, **kwargs)
 self._success()
 return result
 except Exception as e:
 self._failure()
 raise e

 def _success(self):
 if self.state == "HALF-OPEN":
 print("Circuit Breaker: Servizio ripristinato! Passaggio allo stato CHIUSO.")
 self._reset()
 elif self.state == "CLOSED":
 self.failures = 0 # Resettare i fallimenti in caso di successo nello stato CHIUSO

 def _failure(self):
 self.failures += 1
 self.last_failure_time = time.time()
 if self.state == "HALF-OPEN" or self.failures >= self.failure_threshold:
 self.state = "OPEN"
 print(f"Circuit Breaker: Numero di fallimenti raggiunto {self.failures}. Passaggio allo stato APERTO.")

 def _reset(self):
 self.failures = 0
 self.state = "CLOSED"
 self.last_failure_time = None

class CircuitBreakerOpenError(Exception):
 pass

# --- Esempio di utilizzo ---
cb = CircuitBreaker()

def unreliable_service():
 # Simulare un servizio che fallisce per un certo periodo, poi si riprende
 if time.time() % 20 < 10: # Fallisce nei primi 10 secondi di ogni ciclo di 20 secondi
 print(" [Servizio]: Simulazione di un fallimento...")
 raise ValueError("Servizio temporaneamente non disponibile")
 else:
 print(" [Servizio]: Simulazione di un successo.")
 return "Dati del servizio"

# Simulare l'interazione dell'agente nel tempo
# for _ in range(30):
# try:
# print(f"Agente che tenta di chiamare il servizio. Stato del CB: {cb.state}")
# result = cb.call(unreliable_service)
# print(f"Agente ha ricevuto: {result}")
# except CircuitBreakerOpenError as e:
# print(f"Agente bloccato dal Circuit Breaker: {e}")
# except Exception as e:
# print(f"Agente ha gestito un errore di servizio: {e}")
# time.sleep(1)

3. Classi di eccezione personalizzate

Per agenti complessi, definire le proprie classi di eccezione personalizzate può rendere la gestione degli errori più semantica e organizzata. Questo consente di catturare errori specifici a livello di agente senza catturare eccezioni Python più ampie e meno specifiche.

class AgentError(Exception):
 """Eccezione base per tutti gli errori specifici dell'agente."""
 pass

class ToolExecutionError(AgentError):
 """Sollevata quando uno strumento specifico dell'agente non riesce a eseguire."""
 def __init__(self, tool_name, original_error):
 self.tool_name = tool_name
 self.original_error = original_error
 super().__init__(f"Strumento '{tool_name}' fallito: {original_error}")

class MalformedInputError(AgentError):
 """Sollevata quando l'agente riceve un input che non corrisponde al formato atteso."""
 def __init__(self, input_data, expected_format):
 self.input_data = input_data
 self.expected_format = expected_format
 super().__init__(f"Input malformato: '{input_data}'. Formato atteso: {expected_format}")

def execute_tool_logic(tool_name, input_value):
 if tool_name == "calculator":
 try:
 return 10 / int(input_value) # Simulare un calcolo, potenziale ZeroDivisionError
 except (ValueError, ZeroDivisionError) as e:
 raise ToolExecutionError(tool_name, e) from e # Catena delle eccezioni
 elif tool_name == "data_parser":
 if not isinstance(input_value, dict):
 raise MalformedInputError(input_value, "dizionario")
 return input_value.get("key", "default")
 else:
 raise AgentError(f"Strumento sconosciuto: {tool_name}")

# Esempio di utilizzo
try:
 execute_tool_logic("calculator", "0")
except ToolExecutionError as e:
 print(f"Agente ha catturato un errore di strumento: {e.tool_name} -> {e.original_error}")
except MalformedInputError as e:
 print(f"Agente ha catturato un input malformato: {e.input_data}")
except AgentError as e:
 print(f"Agente ha catturato un errore generale: {e}")

try:
 execute_tool_logic("data_parser", "not_a_dict")
except ToolExecutionError as e:
 print(f"Agente ha catturato un errore di strumento: {e.tool_name} -> {e.original_error}")
except MalformedInputError as e:
 print(f"Agente ha catturato un input malformato: {e.input_data}")
except AgentError as e:
 print(f"Agente ha catturato un errore generale: {e}")

4. Monitoraggio centralizzato degli errori e reportistica

Sebbene sia cruciale gestire gli errori localmente, è altrettanto importante centralizzare il monitoraggio degli errori. Ciò fornisce visibilità sul comportamento dell'agente, aiuta a fare debug dei problemi e consente un monitoraggio proattivo.

Il modulo logging di Python è potente per questo. Puoi configurare diversi livelli di registrazione (DEBUG, INFO, WARNING, ERROR, CRITICAL) e inviare i log a diverse destinazioni (console, file, servizi di logging esterni).

import logging

# Configurare il logging
logging.basicConfig(
 level=logging.ERROR, # Registra solo ERROR e CRITICAL per impostazione predefinita
 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 handlers=[
 logging.FileHandler("agent_errors.log"),
 logging.StreamHandler()
 ]
)

agent_logger = logging.getLogger('my_agent')

def perform_risky_operation(value):
 try:
 result = 100 / int(value)
 agent_logger.info(f"Operazione riuscita con il valore {value}. Risultato: {result}")
 return result
 except ValueError as e:
 agent_logger.error(f"Input invalido per l'operazione: '{value}'. Dettagli: {e}", exc_info=True) # exc_info=True aggiunge uno stack trace
 return None
 except ZeroDivisionError as e:
 agent_logger.critical(f"Errore critico: Tentativo di divisione per zero con il valore '{value}'. Dettagli: {e}", exc_info=True)
 # Potenzialmente sollevare un allerta qui
 return None

perform_risky_operation("5")
perform_risky_operation("abc")
perform_risky_operation("0")

Migliori pratiche per la gestione degli errori degli agenti

  • Essere specifici: Catturare eccezioni specifiche piuttosto che classi Exception ampie. Questo impedisce di catturare errori inaspettati e rende il tuo codice più prevedibile.
  • Fallire rapidamente (ma con grazia): Per errori irrimediabili, è spesso meglio fallire rapidamente e fornire informazioni diagnostiche chiare piuttosto che continuare con uno stato corrotto.
  • Registrare tutto: Registrare errori con dettagli sufficienti (inclusi stack trace tramite exc_info=True) per aiutare nel debug.
  • Feedback all'utente: Se il tuo agente interagisce con gli utenti, fornire messaggi di errore chiari, concisi e utili che li guidino su cosa è andato storto e come poterlo risolvere. Evitare gergo tecnico.
  • Idempotenza: Progettare operazioni affinché siano idempotenti il più possibile. Ciò significa che ripetere un'operazione (per esempio, dopo un nuovo tentativo) ha lo stesso effetto che eseguirla una volta, evitando effetti collaterali indesiderati.
  • Monitoraggio e allerta: Integrare il logging degli errori con sistemi di monitoraggio che possono avvisarti su fallimenti critici, consentendo un intervento rapido.
  • Testare i percorsi di errore: Testare esplicitamente come il tuo agente si comporta in varie condizioni di errore. Non testare solo il percorso felice.
  • Non ignorare gli errori silenziosamente: Evitare except Exception: pass. Questo nasconde problemi e rende il debug un incubo. Se devi ignorare un errore, almeno registralo.

Conclusione

Costruire agenti di IA resilienti richiede un approccio proattivo e approfondito alla gestione degli errori. Comprendendo i tipi di errori comuni, utilizzando i potenti meccanismi di gestione delle eccezioni di Python e adottando modelli avanzati come i nuovi tentativi e gli interruttori automatici, puoi migliorare notevolmente la stabilità e l'affidabilità dei tuoi agenti. Non dimenticare di registrare efficacemente gli errori, fornire feedback significativi e testare continuamente le tue strategie di gestione degli errori. Un sistema di gestione degli errori ben progettato non riguarda solo la risoluzione dei problemi quando si verificano, ma anche l'impedire che impattino sulle prestazioni del tuo agente e sulla fiducia degli utenti sin dall'inizio.

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

Learn more →
Browse Topics: ci-cd | debugging | error-handling | qa | testing
Scroll to Top