\n\n\n\n Gestire gli errori dell'agent: Un tutorial pratico con esempi - AiDebug \n

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

📖 11 min read2,143 wordsUpdated Apr 4, 2026

Introduzione : La realtà ineludibile 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 stia navigando attraverso un’API complessa, elaborando input utente o prendendo decisioni basate su dati in tempo reale, sorgeranno situazioni inaspettate. Queste possono variare da guasti di rete e formati di dati non validi a risposte inattese da servizi esterni o incoerenze logiche all’interno del proprio processo di ragionamento dell’agente. Senza una gestione degli errori adeguata, un agente può rapidamente cadere in uno stato di inefficienza, comportamento errato o persino un crash completo, minando la sua affidabilità e la fiducia riposta in esso. Questo tutorial esplorerà gli aspetti critici della gestione degli errori negli agenti, fornendo strategie pratiche ed esempi di codice per costruire agenti IA più resilienti e solidi.

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

Comprendere l’area degli errori degli agenti

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

  • Errori di input/output: Si verificano quando un agente interagisce con sistemi esterni. Esempi includono tempi di attesa di rete, limiti di frequenza delle API, risposte JSON malformate, 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 buoni test mirano a minimizzare questi errori, possono comunque sorgere in scenari complessi e nuovi.
  • Errori ambientali: Problemi con l’ambiente operativo dell’agente, come memoria insufficiente, spazio su disco limitato o riavvii di sistema inaspettati.
  • Errori di servizio esterno: Errori provenienti da API o servizi di terze parti su cui l’agente fa affidamento, come un guasto di connessione al database o un LLM che restituisce una risposta vuota.
  • Violazioni di vincoli: Quando l’agente tenta di eseguire un’azione che infrange regole o vincoli predeterminati, come provare ad accedere a una risorsa senza una corretta autenticazione.

Ogni tipo di errore richiede spesso una strategia di gestione leggermente diversa, che varia dai semplici nuovi tentativi a riscontri di stato più complessi o a un 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 una progettazione accurata, una validazione e una buona disinfezione degli input.

1. Validazione e disinfezione degli input

Tutti i dati ricevuti da un agente, che provengano da un utente, da un’API o da un sensore, devono essere validati e disinfettati prima di essere elaborati. Questo previene problemi comuni come attacchi di injection, dati malformati o valori fuori limite.


def validate_user_input(user_query: str) -> bool:
 """Valida l'input dell'utente per problemi comuni."""
 if not isinstance(user_query, str) or not user_query.strip():
 print("Errore: La query dell'utente non può essere vuota.")
 return False
 if len(user_query) > 500: # Esempio di vincolo di lunghezza
 print("Errore: La query dell'utente supera la lunghezza massima.")
 return False
 # Verifiche aggiuntive: disinfettare per caratteri speciali, pattern potenzialmente dannosi
 # Per motivi di semplicità, verifichiamo solo la validità di base qui
 return True

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

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

2. Indirizzamenti di tipo e analisi statica

I linguaggi di programmazione moderni offrono indirizzamenti di tipo (ad esempio, mypy di Python) e strumenti di analisi statica che possono rilevare molti errori di programmazione comuni prima dell’esecuzione. Questo è particolarmente utile nei 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."""
 # Gli indirizzamenti di tipo garantiscono che 'url' sia una stringa e 'timeout' sia un intero.
 # Gli strumenti di analisi statica possono segnalare se provi a passare un tipo errato.
 pass # L'implementazione reale andrebbe qui

3. Interruttori automatici

Ispirati dall’ingegneria elettrica, gli interruttori automatici impediscono a un agente di tentare ripetutamente di accedere a un servizio esterno che fallisce. Se un servizio fallisce costantemente, il circuito “salta”, impedendo ulteriori chiamate per un periodo definito, consentendo al servizio di recuperare e preservando 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 in fase di chiusura...")
 # Prova a ripristinare dopo il timeout
 self.is_open = False
 self.failures = 0
 else:
 raise CircuitBreakerOpenError("Il circuito è aperto. Il servizio è probabilmente offline.")

 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! Troppi fallimenti: {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 di utilizzo :
# external_service_failures = 0
# def unreliable_api_call():
# global external_service_failures
# if external_service_failures < 4: # Simula fallimenti iniziali
# external_service_failures += 1
# raise ConnectionError("Errore di connessione API simulata")
# 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 intercettato: {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 reagisce a queste eccezioni di esecuzione.

1. Degradazione elegante e soluzioni di emergenza

Quando un servizio principale fallisce, un agente dovrebbe idealmente degradare in modo elegante anziché bloccarsi. Questo può comportare l'utilizzo di una risposta memorizzata, un'alternativa più semplice o persino informare l'utente su una limitazione temporanea.


def get_weather_data(city: str) -> Optional[dict]:
 try:
 # Prova a chiamare l'API meteo principale
 # response = api_client.get(f"weather.com/api/{city}")
 # return response.json()
 raise ConnectionError("Fallimento dell'API simulato") # Simula un fallimento
 except ConnectionError:
 print("Avviso: API meteo principale non disponibile. Utilizzo di un ripiego.")
 # Ripiego su un servizio più semplice, forse meno preciso, o dati memorizzati
 if city == "Londra":
 return {"city": "Londra", "temperature": "15C", "condition": "Nuvoloso (memorizzato)"}
 else:
 return {"city": city, "temperature": "N/A", "condition": "Sconosciuto (ripiego)"}
 except Exception as e:
 print(f"Si è verificato un errore inaspettato durante il recupero del meteo: {e}")
 return None

print(get_weather_data("Londra"))
print(get_weather_data("New York"))

2. Nuovi tentativi con backoff esponenziale

Per errori temporanei (come glitch di rete o una temporanea indisponibilità del servizio), ripetere l'operazione può spesso risolvere il problema. Il backoff esponenziale aumenta il tempo tra i nuovi tentativi, evitando che l'agente sovraccarichi un servizio in difficoltà e dandogli il tempo di riprendersi.


import time
import random

def call_unreliable_service(attempt: int):
 """Simula una chiamata a un servizio inaffidabile."""
 if attempt < 3: # Riesce al terzo tentativo
 print(f"La chiamata al servizio è fallita al tentativo {attempt+1}.")
 raise ConnectionError("Servizio temporaneamente non disponibile")
 print(f"La chiamata al servizio ha avuto successo 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) # Backoff 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 di utilizzo:
# 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. Logging e monitoraggio centralizzati degli errori

Quando si verifica un errore, è fondamentale registrare informazioni dettagliate al riguardo. Questo include l'orario, il tipo di errore, il trace della pila, lo stato pertinente dell'agente e qualsiasi dato contestuale. Il logging centralizzato (ad esempio, utilizzando ELK stack, Splunk o servizi di logging nel cloud) consente agli sviluppatori di monitorare la salute dell'agente, identificare problemi ricorrenti e diagnosticare efficacemente i problemi.


import logging

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

def perform_critical_task(data):
 try:
 # Simulare un compito che potrebbe fallire
 if not isinstance(data, dict) or "key" not in data:
 raise ValueError("Formato di dati non valido")
 result = 10 / data["key"]
 logging.info(f"Compito completato 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}")
 # Opzionalmente rilanciare 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 imprevisto: {e}", exc_info=True)
 raise

# Esempio di utilizzo:
# try:
# perform_critical_task({"key": 2})
# perform_critical_task({"wrong_key": 5})
# perform_critical_task({"key": 0})
# except Exception:
# pass # Gestito dal logging, ma può essere catturato per un'azione aggiuntiva dell'agente

4. Intervento umano per errori non gestiti

Per errori complessi o nuovi che l'agente non può risolvere autonomamente, la soluzione più adeguata consiste spesso nell'escalation a un operatore umano. In questo modo, l'agente può continuare a operare su altre attività mentre un umano analizza e fornisce potenzialmente una soluzione o indicazioni aggiornate. Ciò è particolarmente rilevante per gli agenti che interagiscono con sistemi reali dove un recupero autonomo errato potrebbe essere dannoso.


class HumanInterventionNeeded(Exception):
 pass

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

 print("Richiesta complessa trattata con successo.")
 return {"status": "success"}
 except HumanInterventionNeeded as e:
 logging.warning(f"Escalation a un umano: {e}. Dati della richiesta: {request_data}")
 # Attivare un allerta, inviare un'email, creare un ticket, o notificare un operatore umano tramite un cruscotto
 return {"status": "escalated", "message": str(e)}
 except Exception as e:
 logging.error(f"Errore inatteso durante il trattamento della richiesta complessa: {e}", exc_info=True)
 return {"status": "error", "message": "Errore di trattamento interno."}

# Esempio di utilizzo:
# print(process_complex_request({"data": "normal"}))
# print(process_complex_request({"data": "special", "unhandled_case": True}))

Best practice per la gestione degli errori degli agenti

  • Specificità: Catturare eccezioni specifiche piuttosto che generiche (ad esempio, ValueError anziché una Exception generica). Questo consente un recupero più mirato.
  • Idempotenza: Progettare le operazioni in modo che siano idempotenti per quanto possibile. Ciò significa che eseguire l'operazione più volte ha lo stesso effetto di farlo una sola volta, semplificando la logica di ripetizione.
  • Gestione dello stato: In caso di errore, garantire che lo stato interno dell'agente rimanga coerente o possa essere riportato in modo sicuro a uno stato noto e buono.
  • Feedback per l'utente: Se l'agente interagisce con gli utenti, fornire messaggi di errore chiari, concisi e utili. Evitare gergo tecnico.
  • Test: Testare a fondo i percorsi di errore. I test unitari, i test di integrazione e l'ingegneria del caos (iniezione deliberata di fallimenti) sono fondamentali.
  • Documentazione: Documentare gli scenari di errore comuni e le loro strategie di gestione previste per la manutenzione e il debug futuri.

Conclusione

Costruire agenti di IA resilienti richiede un approccio completo alla gestione degli errori. Combinando tecniche di prevenzione proattive come la validazione degli input e i circuit breaker con strategie reattive come il degrado graduale, i ripetuti tentativi e un logging solido, è possibile migliorare notevolmente la stabilità e l'affidabilità del proprio agente. Ricorda che la gestione degli errori non consiste solo nel catturare eccezioni; si tratta di progettare il tuo agente per anticipare il fallimento, recuperare in modo intelligente e mantenere la propria integrità operativa anche di fronte a sfide inattese. Man mano che gli agenti di 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 e 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