\n\n\n\n Modelli di Middleware per Agenti: Un'Esplorazione Approfondita con Esempi Pratici - AgntKit \n

Modelli di Middleware per Agenti: Un’Esplorazione Approfondita con Esempi Pratici

📖 17 min read3,211 wordsUpdated Apr 5, 2026

Introduzione all’Agente Middleware

L’ascesa di agenti IA sofisticati ha segnato l’inizio di una nuova era nello sviluppo software. Queste entità autonome, capaci di ragionamento complesso, presa di decisioni e interazione, diventano centrali in molte applicazioni. Tuttavia, orchestrare il loro comportamento, gestire il loro stato e garantire il loro buon funzionamento richiede spesso più di una semplice invocazione diretta. È qui che intervengono i modelli di agente middleware. Simile ai middleware web tradizionali, l’agente middleware intercetta e elabora richieste e risposte, ma nel contesto unico del ciclo di vita di un agente, dalla sua percezione, alla sua azione e alla sua comunicazione.

L’agente middleware funge da strato cruciale tra la logica centrale dell’agente e il suo ambiente, o tra diversi componenti di un sistema multi-agenti. Fornisce un modo strutturato per iniettare preoccupazioni trasversali, migliorare le capacità, gestire lo stato e far rispettare politiche senza appesantire il codice principale di presa di decisione dell’agente. In questa esplorazione approfondita, esamineremo i modelli di agente middleware comuni, comprenderemo le loro applicazioni pratiche e li illustreremo con esempi concreti, concentrandoci principalmente su framework o implementazioni concettuali basate su Python.

Il Bisogno di Agente Middleware

Prima di esplorare i modelli, comprendiamo perché l’agente middleware sia indispensabile:

  • Separazione delle Preoccupazioni: Gli agenti spesso hanno un’intelligenza centrale (ad esempio, pianificazione, ragionamento) e preoccupazioni periferiche (ad esempio, registrazione, monitoraggio, autenticazione, trasformazione dei dati). Il middleware consente di gestire queste preoccupazioni in modo esterno.
  • Modularità e Riutilizzabilità: Le funzionalità comuni possono essere incapsulate in componenti middleware riutilizzabili.
  • Estensibilità: Nuove funzionalità o comportamenti possono essere aggiunti agli agenti senza modificare la loro logica centrale.
  • Affidabilità e Resilienza: Il middleware può gestire errori, tentativi e circuit breaking per le interazioni esterne.
  • Osservabilità: La registrazione centralizzata, la raccolta di metriche e il tracciamento diventano molto più facili.
  • Sicurezza e Applicazione delle Politiche: L’autorizzazione, la limitazione della larghezza di banda e la validazione degli input possono essere applicate in modo coerente.

Modelli Comuni di Agente Middleware

Classificheremo i modelli di agente middleware in base alla loro funzione principale e alla loro interazione con il ciclo di vita dell’agente.

1. Il Modello dell’Intercettore

Il modello dell’Intercettore è forse il più fondamentale e il più ampiamente utilizzato. Ti consente di intercettare le chiamate ai metodi di un agente o le sue interazioni con servizi esterni, eseguendo un pretrattamento prima della chiamata e un post-trattamento dopo. Questo è analogo alla Programmazione Orientata agli Aspetti (POA) o al middleware tradizionale di richiesta/riposta.

Esempio Pratico: Intercettore di Registrazione e Metriche

Immagina un agente che esegue azioni basate su input dell’utente. Vogliamo registrare ogni azione eseguita e misurare il suo tempo di esecuzione.


import time
import logging

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

class AgentAction:
 def __init__(self, name, payload):
 self.name = name
 self.payload = payload

 def __repr__(self):
 return f"Action(name='{self.name}', payload={self.payload})"

class AgentResponse:
 def __init__(self, success, result=None, error=None):
 self.success = success
 self.result = result
 self.error = error

 def __repr__(self):
 return f"Response(success={self.success}, result={self.result}, error={self.error})"

class AgentCore:
 def execute_action(self, action: AgentAction) -> AgentResponse:
 logging.info(f"AgentCore: Executing action '{action.name}' with payload {action.payload}")
 if action.name == "search_web":
 # Simulare ricerca web
 time.sleep(0.5)
 return AgentResponse(success=True, result=f"Found results for '{action.payload}'")
 elif action.name == "send_email":
 # Simulare invio email
 time.sleep(0.2)
 if "@" in str(action.payload): # Validazione semplice
 return AgentResponse(success=True, result=f"Email sent to '{action.payload}'")
 else:
 return AgentResponse(success=False, error="Invalid email format")
 else:
 return AgentResponse(success=False, error=f"Unknown action: {action.name}")


class LoggingMetricsInterceptor:
 def __init__(self, next_handler):
 self.next_handler = next_handler

 def execute_action(self, action: AgentAction) -> AgentResponse:
 start_time = time.perf_counter()
 logging.info(f"Interceptor: Pre-processing action '{action.name}'")
 
 try:
 response = self.next_handler.execute_action(action)
 except Exception as e:
 logging.error(f"Interceptor: Error during action '{action.name}': {e}")
 response = AgentResponse(success=False, error=str(e))

 end_time = time.perf_counter()
 duration = (end_time - start_time) * 1000 # millisecondi
 logging.info(f"Interceptor: Post-processing action '{action.name}'. Duration: {duration:.2f}ms. Success: {response.success}")
 # In un sistema reale, invieresti metriche a Prometheus/Grafana ecc.
 return response


# Collegamento dell'agente con il middleware
agent = LoggingMetricsInterceptor(AgentCore())

# Caso di test
print("\n--- Test 1 : Ricerca Web Riuscita ---")
response1 = agent.execute_action(AgentAction("search_web", "ultime notizie IA"))
print(f"Risposta Finale : {response1}")

print("\n--- Test 2 : Invio Email Riuscito ---")
response2 = agent.execute_action(AgentAction("send_email", "[email protected]"))
print(f"Risposta Finale : {response2}")

print("\n--- Test 3 : Fallimento Invio Email (Errore di Validazione) ---")
response3 = agent.execute_action(AgentAction("send_email", "email-sbagliata"))
print(f"Risposta Finale : {response3}")

print("\n--- Test 4 : Azione Sconosciuta ---")
response4 = agent.execute_action(AgentAction("unknown_task", "data"))
print(f"Risposta Finale : {response4}")

In questo esempio, LoggingMetricsInterceptor incapsula AgentCore. Ogni chiamata a execute_action passa prima tramite l’intercettore, che registra, misura il tempo, poi passa il controllo al gestore successivo (AgentCore), e infine elabora la risposta.

2. Il Modello della Catena di Responsabilità

Il modello della Catena di Responsabilità consente a più gestori (componenti middleware) di elaborare una richiesta in sequenza. Ogni gestore decide se deve elaborare la richiesta, passarla al gestore successivo nella catena o interrompere il processo. Questo è ideale per scenari in cui possono applicarsi più condizioni o trasformazioni all’input o all’output di un agente.

Esempio Pratico: Catena di Validazione e Trasformazione degli Input

Consideriamo un agente che riceve comandi in linguaggio naturale. Prima che l’agente principale elabori il comando, potremmo voler validare l’input, pulirlo o tradurlo in un formato strutturato.


class Command:
 def __init__(self, original_text: str, processed_data: dict = None):
 self.original_text = original_text
 self.processed_data = processed_data if processed_data is not None else {}
 self.is_valid = True
 self.error_message = None

 def __repr__(self):
 return f"Command(original='{self.original_text}', processed={self.processed_data}, valid={self.is_valid}, error='{self.error_message}')"

class AgentRequestHandler:
 def handle_command(self, command: Command) -> Command:
 raise NotImplementedError

class InputSanitizer(AgentRequestHandler):
 def __init__(self, next_handler: AgentRequestHandler = None):
 self.next_handler = next_handler

 def handle_command(self, command: Command) -> Command:
 if not command.is_valid:
 return command # Ferma se già invalido

 # Sanitizzazione semplice: rimuovere spazi prima/dopo, convertire in minuscolo
 command.processed_data['sanitized_text'] = command.original_text.strip().lower()
 logging.info(f"Sanitizer: Sanitizzato '{command.original_text}' in '{command.processed_data['sanitized_text']}'")

 if self.next_handler:
 return self.next_handler.handle_command(command)
 return command

class CommandValidator(AgentRequestHandler):
 def __init__(self, next_handler: AgentRequestHandler = None):
 self.next_handler = next_handler

 def handle_command(self, command: Command) -> Command:
 if not command.is_valid:
 return command
 
 sanitized_text = command.processed_data.get('sanitized_text', command.original_text)
 if len(sanitized_text) < 5:
 command.is_valid = False
 command.error_message = "Il comando è troppo corto."
 logging.warning(f"Validator: Comando non valido '{sanitized_text}' - troppo corto.")
 return command # Ferma il trattamento se invalido
 
 logging.info(f"Validator: Comando '{sanitized_text}' superato la validazione della lunghezza.")

 if self.next_handler:
 return self.next_handler.handle_command(command)
 return command

class IntentRecognizer(AgentRequestHandler):
 def __init__(self, next_handler: AgentRequestHandler = None):
 self.next_handler = next_handler

 def handle_command(self, command: Command) -> Command:
 if not command.is_valid:
 return command
 
 sanitized_text = command.processed_data.get('sanitized_text', command.original_text)
 if "schedule" in sanitized_text or "book" in sanitized_text:
 command.processed_data['intent'] = 'schedule_event'
 command.processed_data['params'] = {'topic': 'meeting'}
 elif "weather" in sanitized_text:
 command.processed_data['intent'] = 'get_weather'
 command.processed_data['params'] = {'location': 'current'}
 else:
 command.processed_data['intent'] = 'unknown'

 logging.info(f"IntentRecognizer: Intento rilevato '{command.processed_data['intent']}' per '{sanitized_text}'")

 if self.next_handler:
 return self.next_handler.handle_command(command)
 return command

class AgentCoreProcessor(AgentRequestHandler):
 def handle_command(self, command: Command) -> Command:
 if not command.is_valid:
 logging.error(f"Core: Impossibile elaborare il comando non valido: {command.error_message}")
 return command
 
 logging.info(f"Core: Elaborazione del comando con l'intento '{command.processed_data.get('intent')}' e i parametri {command.processed_data.get('params')}")
 command.processed_data['core_result'] = f"Eseguito {command.processed_data.get('intent')} con {command.processed_data.get('params')}"
 return command


# Costruzione della catena
core_processor = AgentCoreProcessor()
intent_recognizer = IntentRecognizer(core_processor)
validator = CommandValidator(intent_recognizer)
sanitizer = InputSanitizer(validator)

# Punto di ingresso per i comandi
agent_entry_point = sanitizer

# Comandi di test
print("\n--- Test 1: Comando di pianificazione valido ---")
cmd1 = Command(" Per favore programma una riunione per me ")
processed_cmd1 = agent_entry_point.handle_command(cmd1)
print(f"Comando trattato finale: {processed_cmd1}")

print("\n--- Test 2: Comando meteo valido ---")
cmd2 = Command("Che tempo fa?")
processed_cmd2 = agent_entry_point.handle_command(cmd2)
print(f"Comando trattato finale: {processed_cmd2}")

print("\n--- Test 3: Comando non valido corto ---")
cmd3 = Command("ciao")
processed_cmd3 = agent_entry_point.handle_command(cmd3)
print(f"Comando trattato finale: {processed_cmd3}")

print("\n--- Test 4: Comando sconosciuto ---")
cmd4 = Command("raccontami una barzelletta")
processed_cmd4 = agent_entry_point.handle_command(cmd4)
print(f"Comando trattato finale: {processed_cmd4}")

Qui, un Command oggetto percorre una catena: InputSanitizer -> CommandValidator -> IntentRecognizer -> AgentCoreProcessor. Ogni componente modifica l’oggetto Command o imposta il suo flag is_valid. Se un componente invalida il comando, i componenti successivi possono fermare il trattamento delicatamente.

3. Il modello Adapter per strumenti/APIs esterne

Anche se non è rigorosamente un middleware nel senso di intercettazione delle richieste-risposte, il modello Adapter è cruciale per consentire agli agenti di interagire con vari strumenti e APIs esterni in modo standardizzato. Un adattatore incapsula un servizio di terze parti, fornendo un’interfaccia coerente affinché l’agente lo utilizzi, astrando le specificità dell’API esterna.

Esempio pratico: Accesso unificato agli strumenti

Un agente potrebbe aver bisogno di chiamare un’API meteo, un’API di calendario e un motore di ricerca. Ognuno ha un’interfaccia diversa. Gli adattatori normalizzano queste interazioni.


import requests
import json

class ToolAdapter:
 def execute(self, tool_name: str, params: dict) -> dict:
 raise NotImplementedError

class WeatherAPIAdapter(ToolAdapter):
 BASE_URL = "https://api.weatherapi.com/v1"
 API_KEY = "YOUR_WEATHER_API_KEY" # Sostituisci con la chiave reale

 def execute(self, tool_name: str, params: dict) -> dict:
 if tool_name == "get_current_weather":
 location = params.get("location", "London")
 try:
 response = requests.get(f"{self.BASE_URL}/current.json?key={self.API_KEY}&q={location}")
 response.raise_for_status() # Solleva un HTTPError per risposte errate (4xx o 5xx)
 data = response.json()
 return {
 "temperature_c": data['current']['temp_c'],
 "condition": data['current']['condition']['text'],
 "location": data['location']['name']
 }
 except requests.exceptions.RequestException as e:
 logging.error(f"Errore API meteo: {e}")
 return {"error": str(e)}
 return {"error": f"Strumento meteo sconosciuto: {tool_name}"}

class CalendarAPIAdapter(ToolAdapter):
 def execute(self, tool_name: str, params: dict) -> dict:
 if tool_name == "create_event":
 title = params.get("title")
 start_time = params.get("start_time")
 end_time = params.get("end_time")
 logging.info(f"Calendar: Creazione dell'evento '{title}' da {start_time} a {end_time}")
 # Simula una chiamata API
 time.sleep(0.1)
 return {"status": "success", "event_id": "cal_123", "title": title}
 elif tool_name == "list_events":
 date = params.get("date")
 logging.info(f"Calendar: Elenco degli eventi per {date}")
 # Simula una chiamata API
 time.sleep(0.1)
 return {"status": "success", "events": [{"title": "Sync di squadra", "time": "10:00"}]}
 return {"error": f"Strumento calendario sconosciuto: {tool_name}"}

class AgentToolbox:
 def __init__(self):
 self._adapters = {}

 def register_adapter(self, adapter_name: str, adapter: ToolAdapter):
 self._adapters[adapter_name] = adapter
 logging.info(f"Toolbox: Adattatore '{adapter_name}' registrato")

 def use_tool(self, adapter_name: str, tool_name: str, params: dict) -> dict:
 adapter = self._adapters.get(adapter_name)
 if not adapter:
 return {"error": f"Nessun adattatore registrato per '{adapter_name}'"}
 logging.info(f"Toolbox: Utilizzo dello strumento '{tool_name}' tramite l'adattatore '{adapter_name}' con i parametri {params}")
 return adapter.execute(tool_name, params)


# Inizializzare la cassetta degli attrezzi dell'agente
agent_toolbox = AgentToolbox()
agent_toolbox.register_adapter("weather", WeatherAPIAdapter())
agent_toolbox.register_adapter("calendar", CalendarAPIAdapter())

# Agente che utilizza la sua cassetta degli attrezzi
print("\n--- Agente che utilizza lo strumento Meteo ---")
weather_info = agent_toolbox.use_tool("weather", "get_current_weather", {"location": "New York"})
print(f"Informazioni Meteo: {weather_info}")

print("\n--- Agente che utilizza lo strumento Calendario (Creare Evento) ---")
calendar_event = agent_toolbox.use_tool("calendar", "create_event", {"title": "Revisione progetto", "start_time": "2023-10-27 14:00", "end_time": "2023-10-27 15:00"})
print(f"Evento Calendario: {calendar_event}")

print("\n--- Agente che utilizza lo strumento Calendario (Elencare Eventi) ---")
list_events = agent_toolbox.use_tool("calendar", "list_events", {"date": "2023-10-27"})
print(f"Eventi elencati: {list_events}")

print("\n--- Agente che tenta di utilizzare uno strumento non registrato ---")
unknown_tool = agent_toolbox.use_tool("search_engine", "google_search", {"query": "tendenze IA"})
print(f"Risultato Strumento Sconosciuto: {unknown_tool}")

Qui, AgentToolbox agisce come un registro centrale per le istanze di ToolAdapter. L’agente non ha bisogno di conoscere le specificità di come chiamare WeatherAPIAdapter o CalendarAPIAdapter; deve solo richiedere uno strumento per nome e fornire parametri. Ogni adattatore traduce quindi questa richiesta generica in chiamate API specifiche richieste.

4. Il modello Registro/Localizzatore di servizio

Il modello Registro o Localizzatore di servizio è comunemente usato per fornire agli agenti accesso a vari servizi, capacità o altri agenti in un sistema multi-agente. Invece di codificare direttamente le dipendenze, gli agenti interrogano un registro centrale per scoprire e ottenere riferimenti ai componenti necessari in tempo reale. Questo migliora la flessibilità e il disaccoppiamento.

Esempio pratico: Scoperta dinamica delle capacità dell’agente

Immagina un agente che ha bisogno di una capacità specifica, come la sintesi di testo o la generazione di immagini. Non dovrebbe avere bisogno di sapere quale servizio specifico fornisca ciò, solo che la capacità esiste.


class Capability:
 def execute(self, data: str) -> str:
 raise NotImplementedError

class TextSummarizer(Capability):
 def execute(self, text: str) -> str:
 logging.info(f"Riepilogo del testo: '{text[:30]}...' ")
 # Simulazione della chiamata LLM o della logica di riepilogo
 time.sleep(0.3)
 return f"Riepilogo di '{text[:20]}...': È una versione concisa."

class ImageGenerator(Capability):
 def execute(self, prompt: str) -> str:
 logging.info(f"Generazione immagine per il prompt: '{prompt}'")
 # Simulazione della chiamata API per la generazione dell'immagine
 time.sleep(0.7)
 return f"URL dell'immagine per '{prompt}': https://image.gen/id-123"

class CapabilityRegistry:
 def __init__(self):
 self._capabilities = {}

 def register_capability(self, name: str, capability: Capability):
 if name in self._capabilities:
 logging.warning(f"La capacità '{name}' è già registrata. Sostituzione.")
 self._capabilities[name] = capability
 logging.info(f"Registro: Capacità '{name}' registrata.")

 def get_capability(self, name: str) -> Capability:
 capability = self._capabilities.get(name)
 if not capability:
 logging.error(f"Registro: Capacità '{name}' non trovata.")
 raise ValueError(f"Capacità '{name}' non trovata.")
 return capability


class Agent:
 def __init__(self, registry: CapabilityRegistry):
 self.registry = registry

 def process_request(self, request_type: str, data: str) -> str:
 try:
 capability = self.registry.get_capability(request_type)
 result = capability.execute(data)
 return f"L'Agente ha trattato '{request_type}': {result}"
 except ValueError as e:
 return f"L'Agente ha fallito a trattare '{request_type}': {e}"
 except Exception as e:
 return f"L'Agente ha incontrato un errore inaspettato per '{request_type}': {e}"


# Configurazione del registro
registry = CapabilityRegistry()
registry.register_capability("summarize", TextSummarizer())
registry.register_capability("generate_image", ImageGenerator())

# Creare un agente con accesso al registro
agent_app = Agent(registry)

# L'agente utilizza le capacità
print("\n--- Agente che richiede un riepilogo ---")
summary_result = agent_app.process_request("summarize", "Il rapido volpacchiotto saltella sopra il cane pigro. È un pangramma classico usato per mostrare tutte le lettere dell'alfabeto.")
print(summary_result)

print("\n--- Agente che richiede la generazione di un'immagine ---")
image_result = agent_app.process_request("generate_image", "una città futuristica al tramonto")
print(image_result)

print("\n--- Agente che richiede una capacità sconosciuta ---")
unknown_result = agent_app.process_request("translate", "hello world")
print(unknown_result)

Il CapabilityRegistry funge da localizzatore di servizio. L’Agent non istanzia direttamente TextSummarizer o ImageGenerator; richiede al registro una capacità per nome logico. Questo consente di sostituire, aggiornare o aggiungere capacità senza modificare la logica fondamentale dell’agente.

Combinazione di modelli Middleware

Nei sistemi di agenti reali, questi modelli sono spesso combinati. Ad esempio, un comando utente in arrivo potrebbe prima passare attraverso una Catena di Responsabilità per validazione e riconoscimento dell’intento. L’intento identificato potrebbe quindi innescare un’azione che utilizza il Registro/Localizzatore di Servizio per trovare un Adattatore appropriato per uno strumento esterno. L’esecuzione di questo strumento potrebbe quindi essere avvolta da un Intercettatore per la registrazione e la gestione degli errori.

Esempio: Un Flusso di Interazione dell’Agente Multilivello

Bozziamo brevemente come potrebbe apparire:


# 1. Richiesta in arrivo (ad esempio, da un'interfaccia di chat utente)
user_input = "Per favore, programma una riunione riguardante i risultati del T4 per domani alle 15."

# 2. Catena di Responsabilità per il pre-trattamento
# InputSanitizer -> CommandValidator -> IntentRecognizer
command_object = Command(user_input)
processed_command = agent_entry_point.handle_command(command_object) # Usa la catena dell'esempio precedente

if processed_command.is_valid and processed_command.processed_data.get('intent') == 'schedule_event':
 # 3. La logica fondamentale dell'agente decide di utilizzare uno strumento
 intent_params = processed_command.processed_data.get('params', {})
 
 # 4. Utilizzare il Registro/Localizzatore di Servizio per ottenere l'adattatore appropriato
 # L'agente sa di avere bisogno di un adattatore 'calendar' per 'schedule_event'
 
 # 5. L'esecuzione dello strumento stesso è avvolta da un Intercettatore
 # (Immagina agent_toolbox.use_tool essendo avvolto da un Intercettatore generico ToolCallInterceptor)
 # Per semplificare, chiameremo direttamente la toolbox qui, ma immagina che sia un proxy.

 # Simulare l'analisi del tempo a partire dall'input originale
 event_title = intent_params.get('topic', 'Riunione Generica')
 start_time_str = "2023-10-28 15:00" # Analizzato da user_input da un IntentRecognizer più sofisticato
 end_time_str = "2023-10-28 16:00"

 print("\n--- Agente che orchestra l'uso dello strumento ---")
 tool_call_result = agent_toolbox.use_tool(
 "calendar", 
 "create_event", 
 {"title": event_title, "start_time": start_time_str, "end_time": end_time_str}
 )
 print(f"Risultato della chiamata allo strumento: {tool_call_result}")
else:
 print(f"L'Agente non è riuscito a trattare la richiesta: {processed_command.error_message or 'Intent non valido'}")

Questo flusso dimostra come diversi modelli middleware possano essere composti per creare un’architettura dell’agente solida e mantenibile.

Conclusione

I modelli middleware per agenti sono essenziali per costruire sistemi di agenti AI scalabili, solidi e mantenibili. Applicando modelli come l’Intercettatore, la Catena di Responsabilità, l’Adattatore e il Registro/Localizzatore di Servizio, gli sviluppatori possono gestire efficacemente le preoccupazioni trasversali, integrare funzionalità diverse ed astrarre le complessità. Questi modelli favoriscono la modularità, il riutilizzo e l’estensibilità, consentendo agli agenti di evolvere e interagire con il loro ambiente in modo più intelligente e affidabile. Man mano che gli agenti AI diventano più sofisticati e integrati nella nostra vita quotidiana, una comprensione approfondita e un’applicazione pratica di questi modelli middleware saranno essenziali per il successo.

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

Learn more →
Browse Topics: comparisons | libraries | open-source | reviews | toolkits
Scroll to Top