Pipeline di testing e valutazione automatizzata che ho costruito end-to-end per Camilla, il chatbot di assistenza ai bandi distribuito a livello nazionale per la Pubblica Amministrazione italiana. Il cuore è un valutatore orientato ai task: uno scheduler basato su DAG con regole di dipendenza dichiarate in PostgreSQL e worker FastAPI che scalano orizzontalmente consentendo di testare in parallelo il comportamento dell’assistente su più bandi. Il volume di dati aggiunti al chatbot e da testare coincide con tutti i bandi pubblici pubblicati su INPA, circa 100 bandi nuovi al giorno.
Sull’orchestrazione ho scelto deliberatamente la strada più semplice che reggesse i requisiti, senza integrare framework di workflow: Postgres è l’unico punto di contatto del sistema. Il container che sottomette la run e i worker che eseguono i task non parlano tra loro: tutti leggono e scrivono lo stato sul database, e le dipendenze del DAG vengono risolte da una funzione SQL che sblocca i task man mano che i prerequisiti si completano. Ogni task ha retry, timeout e un ciclo di vita esplicito (in coda, in esecuzione, completato, fallito, annullato), e le risorse si governano nel modo più prosaico possibile: docker compose –scale per aggiungere o togliere worker. Un minore overhead nell’orchestrazione rende più facile capire perché una run si è fermata e farla ripartire.
avvio run (Docker) worker FastAPI × N
(docker compose --scale)
│ ▲ │
▼ │ ▼
Postgres: coda, stato task, DAG in funzione SQL
│
│ per bando: evidenze ──► verdetto ok/ko
│ globali: red-teaming, diagnostica Weaviate
▼
dashboard + email al servizio
Se un task fallisce, quelli sani vengono comunque portati a termine prima di fermare la run: rilanciando la run, verranno automaticamente ripresi solo i task falliti.
La valutazione usa LLM-as-a-judge con rubriche multiple, specifiche per tipo di test: pseudo-ground-truth (fatti verificabili estratti dai documenti sorgente e ri-interrogati sul bot), conversazioni multi-turno con cambio dinamico di ambito, controlli sul comportamento del motore di ricerca, test di qualità delle collezioni di embedding. Inoltre viene anche verificato il corretto uso delle risorse, tramite un contratto di “tool necessario” per ogni turno: un’affermazione prodotta senza aver invocato il tool di retrieval corrispondente viene segnalata come potenziale fallimento di grounding.
Sopra questa infrastruttura ho progettato una suite di 200+ scenari multi-turno per la tenuta del ruolo istituzionale sotto pressione avversariale: resistenza a prompt injection e jailbreak, protezione dei confini informativi, equità e non discriminazione, integrità temporale, recupero del comportamento di base dopo l’attacco. La tassonomia è di mia progettazione, derivata dalle interazioni in produzione e integrata con la OWASP Top 10 per le applicazioni LLM. Costo e latenza di ogni ciclo di valutazione sono tracciati in una dashboard finale che consente al servizio di governare e monitorare le performance del chatbot su centinaia di nuovi documenti giornalmente.
Sorprendentemente, uno degli aspetti più difficili da centrare durante la progettazione e il test di questa pipeline è stata la verifica dei metadati. Come spiegato nella pagina dedicata al motore di ricerca, il funzionamento e2e di Camilla si basa sulla corretta metadatazione di ciascun bando: quali titoli di studio e requisiti prevede, sedi lavorative e altre informazioni. Queste informazioni sono già state associate ai bandi nel momento in cui vengono testati dal valutatore, ma il servizio continuava a segnalarci imprecisioni e omissioni rispetto a quello che era effettivamente scritto nel testo del bando. Perciò, ho aggiunto un task specifico per fare entity extraction di tutti i metadati dal testo del bando e confrontare quanto estratto con il set di metadati già presente sul bando, segnalando ogni discrepanza.
Le difficoltà sono emerse quando ho dovuto fare i conti con i dati reali: i bandi pubblici sono documenti incredibilmente eterogenei, ma soprattutto le entità desiderate (geografiche, titoli di studio) comparivano spessissimo con significati diversi da quello atteso. Ad esempio, un nome di città dove si sarebbero svolte le prove scritte (ma non relativo ad una sede di lavoro), o titoli di studio che costituivano titolo preferenziale (più punti in graduatoria), ma che non erano “necessari”. Inoltre, i dati erano spesso contenuti in tabelle di pagine e pagine, e comparivano nei formati più svariati (specialmente per i titoli di studio): con codice laurea, senza codice, con o senza ambito di studio, in elenchi spezzati o che indicavano equipollenze.
È stato quindi fondamentale un approccio a più step, individuando prima gli articoli del bando che contenevano informazioni su requisiti, sedi o profili. Fatto ciò, proseguiamo con regex ovunque possibile, per estrarre una serie di “isole” di testo (non troppo sconnesse tra loro, in modo da conservare il contesto circostante), e poi procediamo infine con l’entity extraction, i cui system prompt sono stati rivisti decine di volte, in modo da esplicitare ogni regola di business necessaria. Il confronto tra metadati estratti e associati al bando attualmente viene fatto il più possibile in maniera deterministica (es. eliminando da ambo i lati i titoli di studio a parità di codice laurea e con altre regex), per lasciare solo i veri casi ambigui all’LLM.
Una prima descrizione della pipeline è nell’articolo su Camilla; un articolo dedicato al red-teaming e alla valutazione comportamentale è in arrivo su Agenda Digitale.