\n\n\n\n Gestire gli Errori degli Agenti: Un Tutorial Pratico - AiDebug \n

Gestire gli Errori degli Agenti: Un Tutorial Pratico

📖 6 min read1,150 wordsUpdated Apr 4, 2026

Introduzione alla Gestione degli Errori degli Agenti

Nel mondo degli agenti AI, una solida gestione degli errori non è solo una buona pratica; è una necessità. Mentre gli agenti interagiscono con ambienti dinamici, API esterne e dati complessi, sono destinati a incontrare situazioni inaspettate. Interruzioni di rete, risposte API non valide, input utente malformati e incoerenze logiche sono solo alcuni esempi. Un agente ben progettato deve essere in grado di recuperare, informare o adattarsi in modo elegante. Senza una gestione efficace degli errori, un agente può rapidamente diventare fragile, fallendo silenziosamente o bloccandosi completamente, portando a esperienze utente scadenti e operazioni inaffidabili.

Questo tutorial esplorerà gli aspetti pratici della gestione degli errori degli agenti. Esamineremo varie strategie, dimostreremo comuni insidie e forniremo esempi concreti utilizzando Python, un linguaggio popolare per costruire agenti AI. Il nostro obiettivo è fornirti le conoscenze e gli strumenti per costruire agenti più resilienti, affidabili e user-friendly.

Perché la Gestione degli Errori è Cruciale per gli Agenti?

  • Affidabilità: Previene i blocchi e garantisce un funzionamento continuo.
  • Esperienza Utente: Fornisce feedback significativi invece di errori criptici.
  • Debugging: Centralizza il logging degli errori, rendendo più facile identificare e risolvere i problemi.
  • Gestione delle Risorse: Consente una corretta pulizia (ad esempio, chiusura delle connessioni, rilascio dei blocchi).
  • Adattabilità: Permette agli agenti di ripetere operazioni o cambiare strategia quando affrontano guasti temporanei.

Comprendere gli Scenari Comuni di Errore degli Agenti

Prima di esplorare l’implementazione, categoriamo i tipi di errori che un agente incontra comunemente:

1. Errori di Servizio Esterno (API, Database, Rete)

Questi sono forse i più frequenti. Un agente spesso si affida a servizi esterni per dati, calcoli o azioni. Esempi includono:

  • Problemi di rete: Timeout di connessione, fallimenti di risoluzione DNS, host irraggiungibile.
  • Errori API: HTTP 4xx (errori client come 404 Non Trovato, 401 Non Autorizzato, 400 Richiesta Malformata), HTTP 5xx (errori server come 500 Errore Interno del Server, 503 Servizio Non Disponibile), limitazione della velocità (429 Troppe Richieste).
  • Errori di database: Fallimenti di connessione, timeout della query, violazioni di vincoli.

2. Errori di Validazione Input/Output

Gli agenti elaborano varie forme di input, dai prompt dell’utente ai dati dei sensori. Input non valido può portare a comportamenti inaspettati:

  • Input utente malformato: Input non numerico dove si aspetta un numero, formati di data non validi.
  • Parametri mancanti: Argomenti richiesti non forniti.
  • Valori fuori dal range: Una lettura di temperatura che risulta fisicamente impossibile.

3. Errori di Logica Interna

Questi errori derivano dal codice o dallo stato dell’agente:

  • Fallimenti di affermazione: Condizioni che ci si aspetta siano vere non lo sono.
  • Indice fuori dai limiti: Tentativo di accedere a un elemento oltre la lunghezza di una lista.
  • Errori di tipo: Operare su dati con un tipo non corretto (ad esempio, tentare di sommare una stringa a un intero).
  • Esaurimento delle risorse: Esaurimento della memoria o dei descrittori di file.

4. Cambiamenti Ambientali Inaspettati

Agenti in ambienti dinamici potrebbero incontrare situazioni non esplicitamente codificate:

  • File non trovato: Un file di configurazione richiesto è mancante.
  • Problemi di permessi: L’agente non ha accesso necessario a una risorsa.
  • Guasti hardware: Malfunzionamento del sensore o errori del disco.

Fondamenti della Gestione degli Errori in Python

Il meccanismo principale di Python per la gestione degli errori è il blocco try-except-finally.


import logging

# Configura il logging di base
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def divide_numbers(a, b):
 try:
 result = a / b
 logging.info(f"Divisione riuscita: {a} / {b} = {result}")
 return result
 except ZeroDivisionError:
 logging.error("Errore: Impossibile dividere per zero!")
 return None
 except TypeError:
 logging.error("Errore: Entrambi gli input devono essere numeri.")
 return None
 except Exception as e:
 # Cattura eventuali altri errori inaspettati
 logging.error(f"Si è verificato un errore inaspettato: {e}")
 return None
 finally:
 # Questo blocco viene eseguito sempre, indipendentemente dal fatto che si sia verificata un'eccezione
 logging.info("Tentativo di divisione concluso.")

# Esempi:
print(divide_numbers(10, 2)) # Divisione riuscita
print(divide_numbers(10, 0)) # ZeroDivisionError
print(divide_numbers(10, "a")) # TypeError
print(divide_numbers(None, 5)) # Altro TypeError

Analizziamo i componenti:

  • try: Il codice che potrebbe sollevare un’eccezione.
  • except ExceptionType as e: Cattura tipi specifici di eccezioni. Puoi avere più blocchi except per diversi tipi di errore. La parte as e ti consente di accedere all’oggetto eccezione per maggiori dettagli.
  • except Exception as e: Un catch-all generale per eventuali altre eccezioni. È buona pratica catturare prima le eccezioni specifiche e poi una generale.
  • finally: Il codice in questo blocco sarà sempre eseguito, che si sia verificata un’eccezione o meno. È ideale per operazioni di pulizia (ad esempio, chiusura di file, rilascio di risorse).
  • else (opzionale): Il codice qui viene eseguito solo se il blocco try completa senza eccezioni.

Strategie Pratiche di Gestione degli Errori per Agenti

1. Gestione delle Eccezioni e Logging Specifici

Cerca sempre di catturare eccezioni specifiche piuttosto che ampie, dove possibile. Questo consente di attuare recuperi mirati e chiarire il logging.


import requests
import time
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def fetch_data_from_api(url, timeout=5):
 try:
 response = requests.get(url, timeout=timeout)
 response.raise_for_status() # Solleva HTTPError per risposte errate (4xx o 5xx)
 logging.info(f"Dati recuperati con successo da {url}")
 return response.json()
 except requests.exceptions.Timeout:
 logging.warning(f"Timeout per richiesta API {url}")
 return None
 except requests.exceptions.ConnectionError as e:
 logging.error(f"Errore di connessione di rete per {url}: {e}")
 return None
 except requests.exceptions.HTTPError as e:
 logging.error(f"Errore HTTP {e.response.status_code} per {url}: {e.response.text}")
 return None
 except requests.exceptions.RequestException as e:
 # Cattura eventuali altri errori relativi alla richiesta
 logging.error(f"Si è verificato un errore di richiesta inaspettato per {url}: {e}")
 return None
 except ValueError as e:
 # Errore di decoding JSON se response.json() fallisce
 logging.error(f"Impossibile decodificare JSON da {url}: {e}")
 return None

# Esempio di utilizzo:
# print(fetch_data_from_api("https://api.github.com/users/octocat"))
# print(fetch_data_from_api("https://nonexistent-api.com")) # ConnectionError
# print(fetch_data_from_api("https://httpbin.org/status/500")) # HTTPError
# print(fetch_data_from_api("https://httpbin.org/delay/6", timeout=2)) # Timeout

2. Ritentativi con Backoff Esponenziale

Per errori transitori (come problemi di rete, temporanea non disponibilità del servizio o limiti di frequenza), ripetere l’operazione dopo un ritardo è una strategia efficace. Il backoff esponenziale aumenta il ritardo tra i tentativi, prevenendo il sovraccarico del servizio e permettendogli di recuperare.


import requests
import time
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def fetch_data_with_retries(url, max_retries=3, initial_delay=1):
 for attempt in range(max_retries):
 try:
 response = requests.get(url, timeout=5)
 response.raise_for_status()
 logging.info(f"Tentativo {attempt + 1}: Dati ottenuti con successo da {url}")
 return response.json()
 except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e:
 status_code = getattr(e, 'response', None) and e.response.status_code
 if status_code == 429: # Limite di frequenza
 logging.warning(f"Tentativo {attempt + 1}: Raggiunto il limite di frequenza per {url}. Riprovando...")
 elif status_code and 500 <= status_code < 600: # Errore del server
 logging.warning(f"Tentativo {attempt + 1}: Errore del server ({status_code}) per {url}. Riprovando...")
 elif isinstance(e, requests.exceptions.Timeout): # Timeout
 logging.warning(f"Tentativo {attempt + 1}: Timeout per {url}. Riprovando...")
 elif isinstance(e, requests.exceptions.ConnectionError): # Errore di connessione
 logging.warning(f"Tentativo {attempt + 1}: Errore di connessione per {url}. Riprovando...")
 else:
 # Per altri errori HTTP (ad es., 404, 400), non riprovare per impostazione predefinita
 logging.error(f"Tentativo {attempt + 1}: Errore HTTP irreversibile {status_code} per {url}. Aborto dei tentativi.")
 return None

 if attempt < max_retries - 1:
 delay = initial_delay * (2 ** attempt) # Ritardo esponenziale
 logging.info(f"Attesa di {delay:.1f} secondi prima del prossimo tentativo...")
 time.sleep(delay)
 else:
 logging.error(f"Tutti i {max_retries} tentativi non riusciti per {url}.")
 return None
 except requests.exceptions.RequestException as e:
 logging.error(f"Si è verificato un errore di richiesta irreversibile per {url}: {e}. Aborto.")
 return None
 except ValueError as e:
 logging.error(f"Impossibile decodificare JSON da {url}: {e}. Aborto.")
 return None
 return None

# Test con un'API instabile o un endpoint limitato nella frequenza
# print(fetch_data_with_retries("https://httpbin.org/status/503")) # Dovrebbe riprovare
# print(fetch_data_with_retries("https://httpbin.org/delay/1", max_retries=1)) # Dovrebbe avere successo immediatamente

3. Validazione e Sanitizzazione degli Input

Prevenire errori validando gli input nella fase più precoce possibile. Questo è particolarmente importante per gli agenti a contatto con l'utente.


import re
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def process_user_command(command_str):
 if not isinstance(command_str, str):
 logging.error("Tipo di comando non valido: Deve essere una stringa.")
 raise ValueError("Il comando deve essere una stringa.")
 
 command_str = command_str.strip().lower()

 if not command_str:
 logging.warning("Comando vuoto ricevuto.")
 return "Si prega di fornire un comando."

 # Esempio: Controllo di un modello specifico
 if re.match(r"^set temperature \d+\.$", command_str):
 try:
 temp_value = int(command_str.split(' ')[2].replace('.', ''))
 if 0 <= temp_value <= 100:
 logging.info(f"Impostazione della temperatura a {temp_value}°C.")
 return f"Temperatura impostata a {temp_value}°C."
 else:
 logging.error(f"Valore di temperatura non valido: {temp_value}. Deve essere tra 0 e 100.")
 return "La temperatura deve essere compresa tra 0 e 100 gradi Celsius."
 except (ValueError, IndexError):
 logging.error(f"Comando 'set temperature' malformato: {command_str}")
 return "Formato del comando 'set temperature' non valido. Ci si aspetta 'set temperature [valore]'."
 elif command_str == "status":
 logging.info("Controllo dello stato del dispositivo.")
 return "Dispositivo operativo."
 else:
 logging.warning(f"Comando sconosciuto ricevuto: '{command_str}'")
 return "Non capisco quel comando."

# Esempi:
print(process_user_command(" Set Temperature 25. "))
print(process_user_command("set temperature 105."))
print(process_user_command("set temperature abc."))
print(process_user_command("status"))
print(process_user_command("turn on lights"))
# process_user_command(123) # Questo solleverà un ValueError

4. Eccezioni Personalizzate per Logica Specifica degli Agenti

Per errori specifici del dominio del tuo agente, definire eccezioni personalizzate. Questo migliora la leggibilità del codice e consente una gestione degli errori più dettagliata ai livelli superiori dell'architettura del tuo agente.


import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class AgentError(Exception):
 """Eccezione base per tutti gli errori correlati agli agenti."""
 pass

class SensorReadError(AgentError):
 """Sollevata quando un sensore non riesce a fornire dati validi."""
 def __init__(self, sensor_id, message="Impossibile leggere dal sensore."):
 self.sensor_id = sensor_id
 self.message = f"{message} ID sensore: {sensor_id}"
 super().__init__(self.message)

class ActionFailedError(AgentError):
 """Sollevata quando un'azione dell'agente non può essere completata."""
 def __init__(self, action_name, reason="Motivo sconosciuto."):
 self.action_name = action_name
 self.reason = reason
 self.message = f"L'azione '{action_name}' è fallita: {reason}"
 super().__init__(self.message)

def read_temperature_sensor(sensor_id):
 # Simula la lettura del sensore, a volte fallisce
 if sensor_id == "temp_001":
 # Simula una lettura riuscita
 return 22.5
 elif sensor_id == "temp_002":
 # Simula un errore del sensore
 raise SensorReadError(sensor_id, "Malfunzionamento hardware rilevato.")
 else:
 raise SensorReadError(sensor_id, "Sensore non trovato.")

def activate_heater(target_temp):
 if target_temp > 30:
 raise ActionFailedError("activate_heater", "Temperatura target troppo alta.")
 logging.info(f"Riscaldatore attivato per raggiungere {target_temp}°C.")
 return True

def agent_main_loop():
 try:
 current_temp = read_temperature_sensor("temp_001")
 logging.info(f"Temperatura attuale: {current_temp}°C")
 activate_heater(25)

 # Questo fallirà
 read_temperature_sensor("temp_002")

 except SensorReadError as e:
 logging.error(f"L'agente non può procedere a causa di un errore del sensore: {e.sensor_id} - {e.message}")
 # L'agente potrebbe passare a un sensore alternativo o avvisare un operatore umano
 except ActionFailedError as e:
 logging.error(f"L'agente non è riuscito a eseguire l'azione '{e.action_name}': {e.reason}")
 # L'agente potrebbe provare un'azione alternativa o registrare per un intervento manuale
 except AgentError as e:
 logging.error(f"Si è verificato un errore generale dell'agente: {e}")
 except Exception as e:
 logging.critical(f"Si è verificato un errore critico non gestito: {e}")

agent_main_loop()

5. Gestione e Segnalazione degli Errori Centralizzata

Per agenti complessi, è utile centralizzare la segnalazione degli errori. Questo può comportare l'invio degli errori a un sistema di monitoraggio (ad es., Sentry, stack ELK), un avviso via email o un file di log dedicato.


import logging
import sys
# import sentry_sdk # Decommenta e configura per l'integrazione Sentry nel mondo reale

logging.basicConfig(
 level=logging.ERROR, # Imposta il livello base a ERROR per questo gestore
 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 handlers=[
 logging.FileHandler("agent_errors.log"), # Log su un file
 logging.StreamHandler(sys.stdout) # Stampa anche sulla console
 ]
)

# Configura un logger separato per eventi specifici dell'agente
agent_logger = logging.getLogger('agent.core')
agent_logger.setLevel(logging.INFO)
agent_logger.addHandler(logging.StreamHandler(sys.stdout))

# # Esempio di configurazione Sentry (richiede `pip install sentry-sdk`)
# sentry_sdk.init(
# dsn="YOUR_SENTRY_DSN",
# traces_sample_rate=1.0
# )

def handle_critical_error(exception, context="Contesto sconosciuto"):
 logging.critical(f"ERRORE CRITICO in {context}: {exception}", exc_info=True)
 # sentry_sdk.capture_exception(exception) # Invia a Sentry
 # Facoltativamente, inviare un avviso via email o SMS qui
 # sys.exit(1) # Per errori irreversibili, l'agente potrebbe dover terminare

def perform_risky_operation(data):
 try:
 # Simula un'operazione che potrebbe fallire
 if not isinstance(data, dict) or 'value' not in data:
 raise ValueError("Formato dati non valido.")
 result = 100 / data['value']
 agent_logger.info(f"Operazione rischiosa riuscita con risultato: {result}")
 return result
 except ZeroDivisionError as e:
 logging.error("Tentativo di divisione per zero nell'operazione rischiosa.")
 # Potenzialmente prova un piano di emergenza o informa l'utente
 return None
 except ValueError as e:
 handle_critical_error(e, context="perform_risky_operation - validazione dati")
 return None
 except Exception as e:
 handle_critical_error(e, context="perform_risky_operation - errore generale")
 return None

# Esempi:
perform_risky_operation({'value': 5})
perform_risky_operation({'value': 0})
perform_risky_operation('not a dict')
perform_risky_operation({'key': 'no_value_key'})

Best Practices per la Gestione degli Errori negli Agenti

  • Fallire in Fretta, Fallire Fortemente (quando appropriato): Per errori logici irreversibili, è spesso meglio terminare subito con un messaggio di errore chiaro piuttosto che continuare in uno stato incoerente.
  • Non Sopprimere Silenziosamente gli Errori: Evita i blocchi except vuoti (except: pass) in quanto nascondono informazioni critiche. Almeno registra l'errore.
  • Fornire Feedback Utente Significativo: Se l'agente interagisce con gli utenti, traduci gli errori interni in messaggi comprensibili.
  • Registrare Informazioni Contestuali: Quando registri un errore, includi dati rilevanti (ad es., parametri di input, stato dell'agente, timestamp, ID utente) per facilitare il debug.
  • Distingui tra Errori Recuperabili e Irreversibili: Progetta il tuo agente per tentare il recupero in caso di errori temporanei, ma termina o segnala per errori critici e irreversibili.
  • Monitorare i Tassi di Errore: Utilizza strumenti di monitoraggio per tracciare quanto spesso si verificano diversi tipi di errori. Tassi di errore elevati possono indicare problemi sottostanti.
  • Testare i Percorsi di Errore: Testa esplicitamente come si comporta il tuo agente in varie condizioni di errore. Non testare solo il percorso felice.
  • Chiusura Graduale: Implementa blocchi finally o gestori di contesto (with) per garantire che le risorse siano rilasciate correttamente anche durante un errore.

Conclusione

Costruire agenti AI resilienti richiede un approccio deliberato e approfondito alla gestione degli errori. Comprendendo scenari di errore comuni, utilizzando i meccanismi di eccezione di Python e implementando strategie come il ripristino, la convalida e le eccezioni personalizzate, puoi creare agenti che non solo sono più solidi, ma anche più facili da debug e mantenere. Ricorda, un agente che può gestire con grazia i propri fallimenti è un agente di cui ci si può fidare per operare in modo affidabile nel mondo reale.

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

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