\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,474 wordsUpdated Apr 4, 2026

Introduzione: La Realtà Inevitabile degli Errori degli Agenti

Nel dinamico mondo 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 potenziali di fallimento sono numerosi. Gli errori non gestiti possono portare a crash degli agenti, loop infiniti, output errati, esperienze utente scadenti e persino vulnerabilità di sicurezza. Pertanto, una gestione degli errori solida non è solo una buona pratica; è 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 comuni di errori, 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 in modo elegante dagli errori, garantendo che i tuoi agenti funzionino ottimamente anche quando le cose vanno male.

Comprendere i Tipi Comuni di Errori degli Agenti

Prima di poter gestire gli errori, dobbiamo capire quali tipi di errori siamo propensi a incontrare. Gli errori degli agenti generalmente rientrano in alcune categorie:

1. Errori di API/Servizi Esterni

  • Problemi di Rete: Timeout, connessione rifiutata, fallimenti nella risoluzione DNS.
  • Limiti di Frequenza delle API: Superamento del numero di richieste consentito in un determinato intervallo di tempo.
  • Chiavi API Non Valide/Errori di Autenticazione: Credenziali errate che impediscono l’accesso.
  • Risposte Malformattate: API che restituisce strutture JSON, XML o HTML inaspettate.
  • Codici di Stato HTTP: 4xx (errori client come 404 Not Found, 400 Bad Request, 401 Unauthorized) e 5xx (errori server come 500 Internal Server Error, 503 Service Unavailable).

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

  • File Non Trovato: Tentativo di leggere o scrivere in un file non esistente.
  • Permesso Negato: Mancanza del necessario accesso in lettura/scrittura a file o directory.
  • Disco Pieno: Nessuno spazio rimasto sul dispositivo per nuovi dati.

3. Errori di Logica dell’Agente

  • Errori di Tipo: Operazioni eseguite su tipi di dati incompatibili (ad es., sommare una stringa a un intero).
  • Errori di Valore: Tipo di dato corretto ma valore inappropriato (ad es., convertire ‘abc’ in un 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: Tentativo di dividere un numero per zero.
  • Loop Infiniti: L’agente rimane bloccato in un compito ripetitivo senza una condizione di terminazione.

4. Errori di Risorse

  • Esaurimento della Memoria: L’agente consuma troppa RAM, portando a un crash.
  • Carico della CPU: Compiti computazionalmente intensivi che rallentano o bloccano l’agente.

Strategie Fondamentali di Gestione degli Errori

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

1. Il Blocco try-except: Catturare Eccezioni

Questo è il fulcro della gestione degli errori. Il codice che potrebbe sollevare un’eccezione è inserito nel blocco try. Se si verifica un’eccezione, l’esecuzione salta immediatamente al corrispondente blocco except.

Esempio di Base: Gestire 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 numerica valida.")
 return None

convert_to_int("123")
convert_to_int("hello")
convert_to_int("3.14") # Questo solleverà anche ValueError se si usa int() direttamente

Catturare Più Eccezioni

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

def process_data(data_list, index):
 try:
 value = data_list[index]
 result = 10 / value
 print(f"Risultato: {result}")
 except IndexError:
 print(f"Errore: Indice {index} è fuori dai 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: Mismatch di tipo durante l'operazione: {e}")
 except Exception as e: # Cattura di qualsiasi altro errore imprevisto
 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: Indice 5 è fuori dai limiti...
process_data(['a', 2], 0) # Errore: Mismatch di tipo...

2. Il Blocco finally: Assicurare il Cleanup

Il codice all’interno di un blocco finally verrà sempre eseguito, indipendentemente dal fatto che si sia verificata o meno un’eccezione. Questo è ideale per operazioni di cleanup 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 nella lettura del file '{filename}': {e}")
 finally:
 if file:
 file.close()
 print(f"File '{filename}' chiuso.")

# Crea un file fittizio per il testing
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 dovrebbe essere eseguito solo se l’operazione iniziale è stata completata con successo.

def perform_api_call(url):
 import requests # Assumendo che requests sia installato
 try:
 response = requests.get(url, timeout=5)
 response.raise_for_status() # Solleva HTTPError per risposte errate (4xx o 5xx)
 except requests.exceptions.Timeout:
 print(f"La chiamata API a {url} è scaduta.")
 return None
 except requests.exceptions.RequestException as e:
 print(f"La chiamata API a {url} è fallita: {e}")
 return None
 else:
 print(f"La 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 il testing)
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

Pattern Avanzati di Gestione degli Errori per Agenti

1. Ripetizioni con Backoff Esponenziale

Per errori transitori (come glitch di rete, sovraccarichi temporanei delle API o limiti di frequenza), ripetere l’operazione dopo un breve ritardo può essere efficace. Il backoff esponenziale aumenta il ritardo tra le ripetizioni, impedendo al tuo agente di sovraccaricare il servizio e dando tempo per recuperare.

import time
import random

def reliable_api_call(url, max_retries=5, initial_delay=1):
 for attempt in range(max_retries):
 try:
 # Simula una chiamata API inaffidabile che a volte fallisce
 if random.random() < 0.6 and attempt < max_retries - 1: # 60% di possibilità 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"Ritentando tra {delay:.2f} secondi...")
 time.sleep(delay)
 else:
 print(f"Numero massimo di tentativi raggiunto per {url}. Arrendendosi.")
 return None
 return None

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

2. Pattern del Circuito Interruttore

Quando un servizio esterno sta fallendo costantemente, ripetere continuamente può sprecare risorse e degradare ulteriormente il servizio. Il pattern del circuito interruttore impedisce a un agente di invocare ripetutamente un servizio che sta fallendo. 'Apre' il circuito (smette di effettuare chiamate) dopo un certo numero di fallimenti, attende un periodo di timeout e poi 'riapre parzialmente' per testare se il servizio si è ripreso.

Implementare un circuito interruttore 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 metà aperto
 self.reset_timeout = reset_timeout # Tempo nello stato 'metà aperto' prima di chiudere
 self.failures = 0
 self.state = "CLOSED" # CHIUSO, APERTO, METÀ-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 METÀ-APERTO.")
 else:
 raise CircuitBreakerOpenError("Il circuito è APERTO. Il servizio è probabilmente inattivo.")
 
 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 # Azzera i fallimenti al 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: I fallimenti hanno 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():
 # Simula un servizio che fallisce per un po', poi si riprende
 if time.time() % 20 < 10: # Fallisce per i 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 dal servizio"

# Simula l'interazione dell'agente nel tempo
# for _ in range(30):
# try:
# print(f"Agente sta cercando di chiamare il servizio. Stato 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 del 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ù generali e meno specifiche.

class AgentError(Exception):
 """Eccezione base per tutti gli errori specifici degli agenti."""
 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 rispetta il formato atteso."""
 def __init__(self, input_data, expected_format):
 self.input_data = input_data
 self.expected_format = expected_format
 super().__init__(f"Input non valido: '{input_data}'. Formato atteso: {expected_format}")

def execute_tool_logic(tool_name, input_value):
 if tool_name == "calculator":
 try:
 return 10 / int(input_value) # Simula il calcolo, potenziale ZeroDivisionError
 except (ValueError, ZeroDivisionError) as e:
 raise ToolExecutionError(tool_name, e) from e # Chaining exceptions
 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 dello strumento: {e.tool_name} -> {e.original_error}")
except MalformedInputError as e:
 print(f"Agente ha catturato un input non valido: {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 dello strumento: {e.tool_name} -> {e.original_error}")
except MalformedInputError as e:
 print(f"Agente ha catturato un input non valido: {e.input_data}")
except AgentError as e:
 print(f"Agente ha catturato un errore generale: {e}")

4. Registrazione e Segnalazione degli Errori Centralizzata

Sebbene gestire gli errori localmente sia cruciale, è altrettanto importante centralizzare la registrazione degli errori. Questo fornisce visibilità sul comportamento dell'agente, aiuta a risolvere i problemi e consente un monitoraggio proattivo.

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

import logging

# Configura la registrazione
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 valore {value}. Risultato: {result}")
 return result
 except ValueError as e:
 agent_logger.error(f"Input non valido per l'operazione: '{value}'. Dettagli: {e}", exc_info=True) # exc_info=True aggiunge il traceback
 return None
 except ZeroDivisionError as e:
 agent_logger.critical(f"Errore critico: Tentativo di divisione per zero con valore '{value}'. Dettagli: {e}", exc_info=True)
 # Potenzialmente inviare un avviso qui
 return None

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

Best Practices per la Gestione degli Errori degli Agenti

  • Essere Specifici: Catturare eccezioni specifiche piuttosto che classi generali Exception. Questo previene la cattura di errori inaspettati e rende il tuo codice più prevedibile.
  • Fallire Velocemente (ma con Grazia): Per errori irreversibili, è spesso meglio fallire rapidamente e fornire informazioni diagnostiche chiare piuttosto che continuare con uno stato corrotto.
  • Registrare Tutto: Registrare gli errori con sufficiente dettaglio (inclusi i traceback usando exc_info=True) per facilitare il debug.
  • Feedback per l'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 potenzialmente risolverlo. Evita il gergo tecnico.
  • Idempotenza: Progettare le operazioni in modo che siano idempotenti ove possibile. Ciò significa che ripetere un'operazione (ad esempio, dopo un tentativo di ripetizione) ha lo stesso effetto di eseguirla una sola volta, prevenendo effetti collaterali indesiderati.
  • Monitoraggio e Segnalazione: Integrare la registrazione degli errori con sistemi di monitoraggio che possono avvisarti di fallimenti critici, consentendo un intervento rapido.
  • Testare i Percorsi di Errore: Testare esplicitamente come si comporta il tuo agente in varie condizioni di errore. Non testare solo il percorso felice.
  • Non Sopprimere gli Errori Silenziosamente: Evita except Exception: pass. Questo nasconde problemi e rende il debug un incubo. Se devi ignorare un errore, almeno registralo.

Conclusione

Costruire agenti AI resilienti richiede un approccio proattivo e approfondito alla gestione degli errori. Comprendendo i tipi di errore comuni, utilizzando i potenti meccanismi di gestione delle eccezioni di Python e adottando schemi avanzati come il ripristino e i circuit breaker, puoi migliorare significativamente la stabilità e l'affidabilità dei tuoi agenti. Ricorda di registrare gli errori in modo efficace, 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 il prevenire che influiscano sulle prestazioni del tuo agente e sulla fiducia degli utenti fin 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