Il framework è definito. Le fondamenta teoriche sono gettate. I 15 pilastri illuminano la via da seguire. Ma ora viene il momento della verità: come si trasforma una vision in codice funzionante?
Ogni architetto di sistema sa che il primo mattone è il più importante. Determina la stabilità di tutto ciò che verrà dopo. Nel nostro caso, questo primo mattone non era un database, né un'API, né un'interfaccia utente. Era qualcosa di molto più specifico e strategico: il nostro primo agente AI.
La Domanda Fondamentale: Che Tipo di Agente?
Di fronte alla pagina bianca di VS Code, la prima domanda che ci siamo posti non è stata "quale tecnologia usare?" o "come strutturare il database?". È stata una domanda molto più strategica: che tipo di personalità AI dovevamo creare per primo?
Un agente generico, in grado di fare un po' di tutto? O un agente specializzato, esperto in un dominio specifico?
La risposta è arrivata dal nostro Pilastro #4 (Scalabile & Auto-apprendente). Invece di costruire un monolito intelligente, dovevamo pensare fin dall'inizio a un sistema di specialisti. Come un'azienda che assume esperti in diversi campi piuttosto che tuttofare, il nostro team AI doveva essere composto da professionisti digitali, ognuno eccellente nel proprio dominio.
Vantaggi dell'Approccio a Specialisti | Descrizione | Pilastro di Riferimento |
---|---|---|
Scalabilità | Possiamo aggiungere nuovi ruoli (es. "Data Scientist") senza modificare il codice, semplicemente aggiungendo una nuova configurazione nel database. | #4 (Scalabile & Auto-apprendente) |
Manutenibilità | È molto più semplice fare il debug e migliorare il prompt di un "Email Copywriter" che modificare un prompt monolitico di 2000 righe. | #10 (Codice Production-Ready) |
Performance AI | Un LLM a cui viene dato un ruolo e un contesto specifici ("Tu sei un esperto di finanza...") produce risultati di qualità nettamente superiore rispetto a un prompt generico. | #2 (AI-Driven) |
Riusabilità | Lo stesso SpecialistAgent può essere istanziato con diverse configurazioni in diversi workspace, promuovendo il riutilizzo del codice. | #4 (Componenti Riusabili) |
💡 Insight: Il Problema del "Micromanaging AI"
Come sottolinea Tomasz Tunguz nel suo articolo "Micromanaging AI" (2024), oggi trattiamo gli LLM come "stagisti del liceo": altissima motivazione, ma competenza ancora bassa che richiede micromanagement step-by-step.
Questo approccio funziona per il primo agente, ma diventa un incubo di scalabilità. Immaginati a gestire 10 agenti, ognuno che richiede continui chiarimenti, autorizzazioni, e correzioni manuali. È il perfetto scenario da "centralino umano" che copia-incolla output tra Slack channel.
La nostra soluzione: Invece di trattare ogni agente come uno stagista, li progettiamo come consulenti senior specializzati. Con ruoli chiari, processi definiti, e soprattutto - autonomia controllata. È il passaggio dall'era del "prompting artigianale" ai sistemi che scalano senza supervisione umana continua.
class Agent(BaseModel):
id: UUID = Field(default_factory=uuid4)
workspace_id: UUID
name: str
role: str
seniority: str
status: str = "active"
# Campi che definiscono la "personalità" e le competenze
system_prompt: Optional[str] = None
llm_config: Optional[Dict[str, Any]] = None
tools: Optional[List[Dict[str, Any]]] = []
# Dettagli per un'intelligenza più profonda
hard_skills: Optional[List[Dict]] = []
soft_skills: Optional[List[Dict]] = []
background_story: Optional[str] = None
La logica di esecuzione, invece, risiede nel modulo specialist_enhanced.py
. La funzione execute
è il cuore pulsante dell'agente. Non contiene logica di business, ma orchestra le fasi del "ragionamento" di un agente.
"War Story": Il Primo Crash – Oggetto vs. Dizionario
Il nostro primo SpecialistAgent
era pronto. Abbiamo lanciato il primo test di integrazione e, quasi subito, il sistema è andato in crash.
ERROR: 'Task' object has no attribute 'get'
File "/app/backend/ai_agents/tools.py", line 123, in get_memory_context_for_task
task_name = current_task.get("name", "N/A")
AttributeError: 'Task' object has no attribute 'get'
Questo errore, apparentemente banale, nascondeva una delle lezioni più importanti di tutto il nostro percorso. Il problema non era un dato mancante, ma un disallineamento di "tipo" tra i componenti del sistema.
Componente | Tipo di Dato Gestito | Problema |
---|---|---|
Executor | Oggetto Pydantic Task |
Passava un oggetto strutturato e tipizzato. |
Tool get_memory_context |
Dizionario Python dict |
Si aspettava un semplice dizionario per poter usare il metodo .get() . |
La soluzione immediata fu semplice, ma la lezione fu profonda.
Codice di riferimento della Correzione: backend/ai_agents/tools.py
# Il task corrente potrebbe essere un oggetto Pydantic o un dizionario
if isinstance(current_task, Task):
# Se è un oggetto Pydantic, lo convertiamo in un dizionario
# per garantire la compatibilità con le funzioni a valle.
current_task_dict = current_task.dict()
else:
# Se è già un dizionario, lo usiamo direttamente.
current_task_dict = current_task
# Da qui in poi, usiamo sempre current_task_dict
task_name = current_task_dict.get("name", "N/A")