\n\n\n\n Gestire gli errori dell’agente: Un tutorial pratico con esempi - AiDebug \n

Gestire gli errori dell’agente: Un tutorial pratico con esempi

📖 11 min read2,124 wordsUpdated Apr 4, 2026

Introduzione: La realtà inevitabile degli errori degli agenti

Nel mondo degli agenti IA, dove entità autonome interagiscono con ambienti dinamici, l’unica costante è il cambiamento – e con esso, l’inevitabilità degli errori. Che il tuo agente navighi in un’API complessa, gestisca input degli utenti o prenda decisioni basate su dati in tempo reale, situazioni inaspettate si presenteranno. Queste possono variare da interruzioni di rete e formati di dati non validi a risposte inattese da servizi esterni o incoerenze logiche nel processo di ragionamento dell’agente. Senza una gestione degli errori adeguata, un agente può rapidamente cadere in uno stato di mancata risposta, di comportamento errato o addirittura di crash completo, minando la sua affidabilità e la fiducia che gli viene accordata. Questo tutorial esplorerà gli aspetti critici della gestione degli errori degli agenti, fornendo strategie pratiche e esempi di codice per costruire agenti IA più resilienti e solidi.

Pensa alla gestione degli errori non come a una riflessione postuma, ma come a una parte integrante del design del tuo agente. È la rete di sicurezza che cattura le cadute inattese, permettendo al tuo agente di riprendersi con grazia, di apprendere dai propri errori o, perlomeno, di fornire feedback significativi. Esploreremo vari tipi di errori, discuteremo delle strategie proactive e reattive, e dimostreremo come implementare meccanismi efficaci di gestione degli errori in un contesto pratico.

Comprendere lo spazio degli errori dell’agente

Prima di poter gestire gli errori, dobbiamo prima comprendere la loro natura e le loro origini comuni. Gli errori degli agenti possono essere largamente classificati in diversi tipi:

  • Errori di input/output: Questi si verificano quando l’agente interagisce con sistemi esterni. Gli esempi includono ritardi di attesa di rete, limiti di rate di API, risposte JSON mal formate, errori di file non trovato o input utente non validi.
  • Errori logici (bug): Difetti nel codice o nella logica di ragionamento dell’agente. Anche se un buon test mira a minimizzare questi errori, possono comunque comparire in scenari complessi e nuovi.
  • Errori ambientali: Problemi con l’ambiente operativo dell’agente, come insufficiente memoria, insufficiente spazio su disco o riavvii di sistema inaspettati.
  • Errori di servizio esterno: Errori provenienti da API o servizi di terzi di cui l’agente si fida, come un fallimento di connessione a un database o un LLM che restituisce una risposta vuota.
  • Violazioni di vincoli: Quando l’agente tenta un’azione che infrange regole o vincoli predeterminati, come tentare di accedere a una risorsa senza un’autenticazione appropriata.

Ogni tipo di errore richiede spesso una strategia di gestione leggermente diversa, che va da semplici nuove tentativi a fasi più complesse di ripristino dello stato o intervento umano.

Strategie proattive: Prevenire gli errori prima che si verifichino

Il miglior errore è quello che non si verifica mai. Le strategie proattive si concentrano sulla prevenzione degli errori attraverso un design accurato, validazione e buona sanificazione degli input.

1. Validazione e sanificazione degli input

Qualunque dato venga ricevuto da un agente, sia esso da un utente, da un’API, o da un sensore, deve essere validato e ripulito prima di essere elaborato. Questo previene problemi comuni come attacchi di iniezione, dati mal formati o valori fuori campo.


def validate_user_input(user_query: str) -> bool:
 """Valida le entrate utente per problemi comuni."""
 if not isinstance(user_query, str) or not user_query.strip():
 print("Errore: La richiesta dell'utente non può essere vuota.")
 return False
 if len(user_query) > 500: # Esempio di vincolo di lunghezza
 print("Errore: La richiesta dell'utente supera la lunghezza massima.")
 return False
 # Controlli aggiuntivi: ripulire per caratteri speciali, schemi potenzialmente dannosi
 # Per semplicità, qui controlliamo solo la validità di base
 return True

def process_user_request(query: str):
 if not validate_user_input(query):
 return {"status": "error", "message": "Input non valido fornito."}
 # Proseguire con l'elaborazione della richiesta valida
 print(f"Elaborazione della richiesta: {query}")
 return {"status": "success", "data": f"Risposta a: {query}"}

print(process_user_request(""))
print(process_user_request("Parlami del tempo a Londra."))

2. Indicazioni di tipo e analisi statica

I linguaggi di programmazione moderni offrono indicazioni di tipo (per esempio, mypy di Python) e strumenti di analisi statica che possono rilevare moltissimi errori di programmazione comuni prima dell’esecuzione. Questo è particolarmente utile in sistemi di agenti più grandi dove diversi componenti interagiscono.


from typing import Optional

def fetch_data_from_api(url: str, timeout: int = 5) -> Optional[dict]:
 """Recupera dati da un'API con un timeout specificato."""
 # Le indicazioni di tipo garantiscono che 'url' sia una stringa e 'timeout' sia un int.
 # Gli strumenti di analisi statica possono segnalare se stai tentando di passare un tipo errato.
 pass # L'implementazione reale andrebbe qui

3. Interruttori automatici

Ispirati all’ingegneria elettrica, gli interruttori automatici impediscono a un agente di tentare ripetutamente di accedere a un servizio esterno non funzionante. Se un servizio fallisce costantemente, il circuito “salta”, impedendo ulteriori chiamate per un periodo definito, consentendo al servizio di riprendersi e conservando le risorse dell’agente.


import time

class CircuitBreaker:
 def __init__(self, failure_threshold: int = 3, recovery_timeout: int = 60):
 self.failure_threshold = failure_threshold
 self.recovery_timeout = recovery_timeout
 self.failures = 0
 self.last_failure_time = 0
 self.is_open = False

 def call(self, func, *args, **kwargs):
 if self.is_open:
 if time.time() - self.last_failure_time > self.recovery_timeout:
 print("Circuito tenta di chiudersi...")
 # Tentare di ripristinare dopo il timeout
 self.is_open = False
 self.failures = 0
 else:
 raise CircuitBreakerOpenError("Il circuito è aperto. Il servizio è probabilmente guasto.")

 try:
 result = func(*args, **kwargs)
 self.reset()
 return result
 except Exception as e:
 self.record_failure()
 raise e

 def record_failure(self):
 self.failures += 1
 self.last_failure_time = time.time()
 if self.failures >= self.failure_threshold:
 self.is_open = True
 print(f"Circuito aperto! Troppe interruzioni: {self.failures}")

 def reset(self):
 self.failures = 0
 self.is_open = False
 self.last_failure_time = 0
 print("Circuito ripristinato.")

class CircuitBreakerOpenError(Exception):
 pass

# Esempio d'uso:
# external_service_failures = 0
# def unreliable_api_call():
# global external_service_failures
# if external_service_failures < 4: # Simulazione di guasti iniziali
# external_service_failures += 1
# raise ConnectionError("Errore di connessione API simulato")
# print("Chiamata API riuscita!")
# return {"data": "some_data"}

# cb = CircuitBreaker()
# for i in range(10):
# try:
# print(f"Tentativo {i+1}:")
# cb.call(unreliable_api_call)
# except (ConnectionError, CircuitBreakerOpenError) as e:
# print(f"Errore catturata: {e}")
# time.sleep(1)

Strategie reattive: Gestire gli errori quando si verificano

Anche con le migliori misure proattive, gli errori si verificheranno inevitabilmente. Le strategie reattive si concentrano su come un agente risponde a queste eccezioni di esecuzione.

1. Degrado elegante e opzioni di emergenza

Quando il servizio principale fallisce, un agente dovrebbe idealmente degradare elegantemente piuttosto che bloccarsi. Questo può implicare l'uso di una risposta in cache, un'alternativa più semplice, o anche informare l'utente della limitazione temporanea.


def get_weather_data(city: str) -> Optional[dict]:
 try:
 # Tentare di chiamare l'API meteo principale
 # response = api_client.get(f"weather.com/api/{city}")
 # return response.json()
 raise ConnectionError("Fuga API simulata") # Simulare un guasto
 except ConnectionError:
 print("Avviso: API meteo principale non disponibile. Uso di emergenza.")
 # Tornare a un servizio più semplice, forse meno preciso, o a dati memorizzati in cache
 if city == "London":
 return {"city": "Londra", "temperature": "15°C", "condition": "Nuvoloso (cache)"}
 else:
 return {"city": city, "temperature": "N/A", "condition": "Sconosciuto (emergenza)"}
 except Exception as e:
 print(f"Si è verificato un errore imprevisto durante il recupero della meteo: {e}")
 return None

print(get_weather_data("London"))
print(get_weather_data("New York"))

2. Tentativi con ritardo esponenziale

Per errori transitori (come problemi di rete o indisponibilità temporanea del servizio), riprovare l'operazione può spesso risolvere il problema. Il ritardo esponenziale aumenta il tempo tra i tentativi, impedendo all'agente di sovraccaricare un servizio in difficoltà e dando il tempo di riprendersi.


import time
import random

def call_unreliable_service(attempt: int):
 """Simula una chiamata a un servizio inaffidabile."""
 if attempt < 3: # Riuscita al 3° tentativo
 print(f"La chiamata al servizio è fallita al tentativo {attempt+1}.")
 raise ConnectionError("Servizio temporaneamente non disponibile")
 print(f"La chiamata al servizio è riuscita al tentativo {attempt+1}!")
 return {"data": "Recuperato con successo!"}

def retry_with_backoff(func, max_retries: int = 5, initial_delay: float = 1.0):
 for attempt in range(max_retries):
 try:
 return func(attempt)
 except ConnectionError as e:
 delay = initial_delay * (2 ** attempt) + random.uniform(0, 1) # Ritardo esponenziale con jitter
 print(f"Errore: {e}. Nuovo tentativo tra {delay:.2f} secondi...")
 time.sleep(delay)
 except Exception as e:
 print(f"Si è verificato un errore irreversibile: {e}")
 raise
 raise ConnectionError(f"Fallimento dopo {max_retries} tentativi.")

# Esempio d'uso:
# try:
# result = retry_with_backoff(call_unreliable_service)
# print(f"Risultato finale: {result}")
# except ConnectionError as e:
# print(f"L'operazione è infine fallita: {e}")

3. Registrazione e Monitoraggio Centralizzati degli Errori

Quando si verifica un errore, è fondamentale registrare informazioni dettagliate al riguardo. Questo include il timestamp, il tipo di errore, lo stack delle chiamate, lo stato pertinente dell'agente e eventuali dati contestuali. La registrazione centralizzata (ad esempio, utilizzando la stack ELK, Splunk o servizi di logging nel cloud) consente agli sviluppatori di monitorare la salute degli agenti, identificare problemi ricorrenti e diagnosticare efficacemente le problematiche.


import logging

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

def perform_critical_task(data):
 try:
 # Simulare un'attività che può fallire
 if not isinstance(data, dict) or "key" not in data:
 raise ValueError("Formato dati non valido")
 result = 10 / data["key"]
 logging.info(f"Attività completata con successo con il risultato: {result}")
 return result
 except ValueError as e:
 logging.error(f"Errore di validazione dei dati: {e}. Dati in ingresso: {data}")
 # Ribaltare o restituire una risposta di errore specifica
 raise
 except ZeroDivisionError:
 logging.error("Tentativo di divisione per zero. Assicurati che 'key' non sia 0.")
 raise
 except Exception as e:
 logging.critical(f"Si è verificato un errore critico inatteso: {e}", exc_info=True)
 raise

# Esempio d'uso:
# try:
# perform_critical_task({"key": 2})
# perform_critical_task({"wrong_key": 5})
# perform_critical_task({"key": 0})
# except Exception:
# pass # Gestito tramite la registrazione, ma può essere catturato per ulteriori azioni dell'agente

4. Intervento Umano per Errori Non Gestiti

Per errori complessi o nuovi che l'agente non può risolvere autonomamente, la soluzione più appropriata consiste spesso nell'escalare a un operatore umano. Questo consente all'agente di continuare a lavorare su altre attività mentre un umano indaga e fornisce potenzialmente una soluzione o istruzioni aggiornate. Questo è particolarmente rilevante per gli agenti che interagiscono con sistemi del mondo reale dove un recupero autonomo errato potrebbe essere dannoso.


class HumanInterventionNeeded(Exception):
 pass

def process_complex_request(request_data: dict):
 try:
 # ... logica complessa coinvolgente più servizi esterni ...
 # Simulare un caso limite non gestito
 if request_data.get("unhandled_case"):
 raise HumanInterventionNeeded("L'agente ha incontrato uno scenario nuovo e non gestito.")

 print("Richiesta complessa elaborata con successo.")
 return {"status": "success"}
 except HumanInterventionNeeded as e:
 logging.warning(f"Escalazione verso un umano: {e}. Dati della richiesta: {request_data}")
 # Inviare un'allerta, mandare un'email, creare un ticket o notificare un operatore umano tramite una dashboard
 return {"status": "escalated", "message": str(e)}
 except Exception as e:
 logging.error(f"Errore inatteso durante l'elaborazione della richiesta complessa: {e}", exc_info=True)
 return {"status": "error", "message": "Errore interno di elaborazione."}

# Esempio d'uso:
# print(process_complex_request({"data": "normal"}))
# print(process_complex_request({"data": "special", "unhandled_case": True}))

Migliori Pratiche per la Gestione degli Errori dell'Agente

  • Specificità: Cattura eccezioni specifiche piuttosto che generali (ad esempio, ValueError invece di un' Exception generica). Questo consente un recupero più mirato.
  • Idempotenza: Progettare operazioni affinché siano idempotenti quando possibile. Questo significa che l'esecuzione dell'operazione più volte ha lo stesso effetto di eseguirla una sola volta, semplificando la logica di ripetizione.
  • Gestione dello Stato: In caso di errore, assicurati che lo stato interno dell'agente resti coerente o possa essere ripristinato in modo sicuro a uno stato conosciuto come valido.
  • Feedback per l'Utente: Se l'agente interagisce con gli utenti, fornisci messaggi d'errore chiari, concisi e utili. Evita il gergo tecnico.
  • Testing: Testa attentamente i percorsi di errore. Test unitari, test di integrazione e ingegneria del caos (iniezione deliberata di fallimenti) sono cruciali.
  • Documentazione: Documenta gli scenari di errore comuni e le loro strategie di gestione previste per la manutenzione e il debug futuri.

Conclusione

Costruire agenti IA resilienti richiede un approccio approfondito alla gestione degli errori. Combinando tecniche di prevenzione proattive come la validazione degli input e i circuit breaker con strategie reattive come la degradazione elegante, i ripetizioni e una registrazione solida, puoi migliorare notevolmente la stabilità e l'affidabilità del tuo agente. Ricorda che la gestione degli errori non consiste solo nel catturare le eccezioni; si tratta di progettare il tuo agente per anticipare i fallimenti, riprendersi in modo intelligente e mantenere la sua integrità operativa anche di fronte a sfide inaspettate. Man mano che gli agenti IA diventano sempre più integrati nei nostri sistemi, padroneggiare la gestione degli errori non è più un lusso, ma una necessità fondamentale per il loro dispiegamento riuscito e il funzionamento a lungo termine.

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

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