\n\n\n\n Modelli di Middleware per Agent: Un'Analisi Approfondita con Esempi Pratici - AgntKit \n

Modelli di Middleware per Agent: Un’Analisi Approfondita con Esempi Pratici

📖 16 min read3,136 wordsUpdated Apr 5, 2026

Introduzione all’Agent Middleware

La crescita di agenti AI sofisticati ha inaugurato una nuova era nello sviluppo software. Queste entità autonome, capaci di ragionamenti complessi, decisioni e interazioni, stanno diventando centrali in molte applicazioni. Tuttavia, orchestrare il loro comportamento, gestire il loro stato e garantire un funzionamento solido richiede spesso più di una semplice invocazione diretta. È qui che entrano in gioco i modelli di agent middleware. Simile al middleware web tradizionale, l’agente middleware intercetta e tratta richieste e risposte, ma nel contesto unico del ciclo di vita di un agente, della percezione, dell’azione e della comunicazione.

L’agent middleware funge da strato cruciale tra la logica principale dell’agente e il suo ambiente, o tra i diversi componenti di un sistema multi-agente. Fornisce un modo strutturato per iniettare preoccupazioni trasversali, migliorare le capacità, gestire lo stato e far rispettare le politiche senza ingombrare il codice decisionale principale dell’agente. In questo approfondimento, esploreremo i modelli comuni di agent middleware, comprenderemo le loro applicazioni pratiche e le illustreremo con esempi concreti, concentrandoci principalmente su framework basati su Python o implementazioni concettuali.

La Necessità dell’Agent Middleware

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

  • Separazione delle Preoccupazioni: Gli agenti hanno spesso intelligenza core (ad esempio, pianificazione, ragionamento) e preoccupazioni periferiche (ad esempio, registrazione, monitoraggio, autenticazione, trasformazione dei dati). Il middleware consente di gestire queste preoccupazioni esternamente.
  • 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 core.
  • Solidità e Resilienza: Il middleware può gestire errori, ritentativi e interruzione circuitale per interazioni esterne.
  • Osservabilità: La registrazione centralizzata, la raccolta di metriche e il tracciamento diventano molto più semplici.
  • Sicurezza e Applicazione delle Politiche: Autorizzazione, limitazione della velocità e convalida dell’input possono essere applicate in modo consistente.

Modelli Comuni di Agent Middleware

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

1. Il Modello Interceptor

Il modello Interceptor è forse il più fondamentale e ampiamente utilizzato. Consente di intercettare le chiamate ai metodi di un agente o alle sue interazioni con servizi esterni, eseguendo pre-elaborazione prima della chiamata e post-elaborazione dopo di essa. Questo è analogo alla Programmazione Orientata agli Aspetti (AOP) o al middleware tradizionale per richiesta/riposta.

Esempio Pratico: Interceptor di Registrazione e Metriche

Immagina un agente che esegue azioni basate su richieste degli utenti. 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: Esecuzione dell'azione '{action.name}' con payload {action.payload}")
 if action.name == "search_web":
 # Simula ricerca web
 time.sleep(0.5)
 return AgentResponse(success=True, result=f"Trovati risultati per '{action.payload}'")
 elif action.name == "send_email":
 # Simula invio email
 time.sleep(0.2)
 if "@" in str(action.payload): # Validazione semplice
 return AgentResponse(success=True, result=f"Email inviata a '{action.payload}'")
 else:
 return AgentResponse(success=False, error="Formato email non valido")
 else:
 return AgentResponse(success=False, error=f"Azione sconosciuta: {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-elaborazione dell'azione '{action.name}'")
 
 try:
 response = self.next_handler.execute_action(action)
 except Exception as e:
 logging.error(f"Interceptor: Errore durante l'azione '{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-elaborazione dell'azione '{action.name}'. Durata: {duration:.2f}ms. Successo: {response.success}")
 # In un sistema reale, invieresti metriche a Prometheus/Grafana ecc.
 return response


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

# Casi di prova
print("\n--- Test 1: Ricerca web riuscita ---")
response1 = agent.execute_action(AgentAction("search_web", "ultime notizie AI"))
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: Invio email non riuscito (Errore di validazione) ---")
response3 = agent.execute_action(AgentAction("send_email", "bad-email"))
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 avvolge AgentCore. Qualsiasi chiamata a execute_action passa prima attraverso l’interceptor, che registra, misura il tempo, quindi passa il controllo al gestore successivo (AgentCore), e infine elabora la risposta.

2. Il Modello Chain of Responsibility

Il modello Chain of Responsibility consente a più gestori (componenti middleware) di elaborare una richiesta in sequenza. Ogni gestore decide se elaborare la richiesta, passarla al gestore successivo nella catena, o fermare l’elaborazione. Questo è ideale per scenari in cui condizioni o trasformazioni multiple potrebbero applicarsi all’input o all’output di un agente.

Esempio Pratico: Catena di Validazione e Trasformazione dell’Input

Considera un agente che riceve comandi in linguaggio naturale. Prima che l’agente core elabori il comando, potremmo voler convalidare l’input, sanificarlo 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à non valido

 # Semplice sanificazione: rimuovi spazi iniziali/finali, converti in minuscolo
 command.processed_data['sanitized_text'] = command.original_text.strip().lower()
 logging.info(f"Sanitizer: Sanificato '{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 l'elaborazione se non valido
 
 logging.info(f"Validator: Comando '{sanitized_text}' ha superato la convalida 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: Intent 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 intent '{command.processed_data.get('intent')}' e 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)

# Il punto di entrata per i comandi
agent_entry_point = sanitizer

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

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

print("\n--- Test 3: Comando corto non valido ---")
cmd3 = Command("ciao")
processed_cmd3 = agent_entry_point.handle_command(cmd3)
print(f"Comando Elaborato 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 Elaborato Finale: {processed_cmd4}")

Qui, un Command oggetto attraversa 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 l’elaborazione senza problemi.

3. Il Pattern Adapter per Strumenti/API Esterne

Sebbene non sia rigorosamente middleware nel senso di intercezione richiesta-risposta, il pattern Adapter è cruciale per consentire agli agenti di interagire con diversi strumenti esterni e API in modo standardizzato. Un adattatore incapsula un servizio di terze parti, fornendo un’interfaccia coerente per l’agente da utilizzare, astrarre le specifiche dell’API esterna.

Esempio Pratico: Accesso Unificato agli Strumenti

Un agente potrebbe aver bisogno di chiamare una API meteo, una API 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() # Genera un'eccezione HTTPError per risposte non valide (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"Calendario: 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"Calendario: Elenco eventi per {date}")
 # Simula una chiamata API
 time.sleep(0.1)
 return {"status": "success", "events": [{"title": "Sincronizzazione del Team", "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 registrato '{adapter_name}'")

 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 adattatore '{adapter_name}' con parametri {params}")
 return adapter.execute(tool_name, params)


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

# L'agente utilizza la propria 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 (Crea 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 (Elenco Eventi) ---")
list_events = agent_toolbox.use_tool("calendar", "list_events", {"date": "2023-10-27"})
print(f"Eventi Elencati: {list_events}")

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

Qui, AgentToolbox funge da registro centrale per le istanze di ToolAdapter. L’agente non ha bisogno di conoscere le specifiche su come chiamare WeatherAPIAdapter o CalendarAPIAdapter; deve solo richiedere uno strumento per nome e fornire parametri. Ogni adattatore poi traduce questa richiesta generica nelle chiamate API specifiche richieste.

4. Il Pattern Registry/Service Locator

Il pattern Registry o Service Locator è comunemente utilizzato per fornire agli agenti accesso a vari servizi, capacità o altri agenti all’interno di un sistema multi-agente. Invece di hardcodificare le dipendenze, gli agenti interrogano un registro centrale per scoprire e ottenere riferimenti ai componenti necessari a runtime. Questo aumenta la flessibilità e il disaccoppiamento.

Esempio Pratico: Scoperta Dinamica delle Capacità dell’Agente

Immagina un agente che necessiti di una capacità specifica, come il riassunto del testo o la generazione di immagini. Non dovrebbe avere bisogno di sapere quale servizio specifico fornisca questo, 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"Riassumendo il testo: '{text[:30]}...' ")
 # Simulate LLM call or summarization logic
 time.sleep(0.3)
 return f"Riassunto di '{text[:20]}...': Questa è una versione concisa."

class ImageGenerator(Capability):
 def execute(self, prompt: str) -> str:
 logging.info(f"Generando immagine per il prompt: '{prompt}'")
 # Simulate image generation API call
 time.sleep(0.7)
 return f"URL 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"Capacità '{name}' già registrata. Sovrascrivendo.")
 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 elaborato '{request_type}': {result}"
 except ValueError as e:
 return f"L'agente non è riuscito a elaborare '{request_type}': {e}"
 except Exception as e:
 return f"L'agente ha incontrato un errore inatteso per '{request_type}': {e}"


# Configura il registro
registry = CapabilityRegistry()
registry.register_capability("summarize", TextSummarizer())
registry.register_capability("generate_image", ImageGenerator())

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

# L'agente utilizza le capacità
print("\n--- L'agente richiede un riassunto ---")
summary_result = agent_app.process_request("summarize", "La veloce volpe marrone salta sopra il cane pigro. Questo è un classico pangramma usato per mostrare tutte le lettere dell'alfabeto.")
print(summary_result)

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

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

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

Combinazione di Schemi Middleware

Negli agenti reali, questi schemi sono spesso combinati. Ad esempio, un comando utente in arrivo potrebbe passare prima attraverso una Catena di Responsabilità per la validazione e il riconoscimento dell’intento. L’intento identificato potrebbe quindi attivare un’azione che usa il Registro/Service Locator per trovare un Adattatore appropriato per uno strumento esterno. L’esecuzione di quello strumento potrebbe poi essere avvolta da un Interceptor per logging e gestione degli errori.

Esempio: Un Flusso di Interazione dell’Agente a Più Livelli

Schizziamo brevemente come potrebbe apparire:


# 1. Richiesta in Arrivo (es. da un'interfaccia di chat utente)
user_input = "Per favore programma una riunione sui risultati del Q4 per domani alle 15:00."

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

if processed_command.is_valid and processed_command.processed_data.get('intent') == 'schedule_event':
 # 3. La logica centrale dell'agente decide di utilizzare uno strumento
 intent_params = processed_command.processed_data.get('params', {})
 
 # 4. Usa il Registro/Service Locator 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 Interceptor
 # (Immagina agent_toolbox.use_tool essere avvolto da un generico ToolCallInterceptor)
 # Per semplicità, chiameremo direttamente la toolbox qui, ma immagina che sia proxyato.

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

 print("\n--- L'agente 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 Chiamata Strumento: {tool_call_result}")
else:
 print(f"L'agente non è riuscito a elaborare la richiesta: {processed_command.error_message or 'Intento non valido'}")

Questo flusso dimostra come diversi schemi middleware possono essere composti per creare un’architettura per agenti solida e mantenibile.

Conclusione

Gli schemi middleware per agenti sono essenziali per costruire sistemi di agenti AI scalabili, solidi e mantenibili. Applicando schemi come Interceptor, Catena di Responsabilità, Adattatore e Registro/Service Locator, gli sviluppatori possono gestire efficacemente preoccupazioni trasversali, integrare funzionalità diverse e astrarre complessità. Questi schemi promuovono la modularità, la riutilizzabilità e l’estensibilità, consentendo agli agenti di evolversi e interagire con i loro ambienti in modo più intelligente e affidabile. Man mano che gli agenti AI diventano più sofisticati e integrati nella nostra vita quotidiana, una profonda comprensione e applicazione pratica di questi schemi middleware sarà fondamentale 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