Cosa abbiamo imparato costruendo il nostro widget di supporto (chat con i tuoi contenuti)
Un teardown tecnico di cio che conta davvero quando costruisci un widget chat di supporto: failure mode del retrieval, navigazione documenti-come-file, caching, controllo accessi e guardrail per risposte affidabili.
Cosa abbiamo imparato costruendo il nostro widget di supporto (chat con i tuoi contenuti)
Abbiamo costruito il nostro widget di supporto: una chat bubble che integri in un sito web cosi i visitatori possono fare domande e ottenere risposte dai tuoi contenuti (docs, knowledge base, pagine servizi, policy).Se ne hai mai rilasciato uno, sai che il pitch e semplice e la realta e complicata.I problemi che incontri raramente sono “problemi di modello”. Sono problemi di ingegneria:
il retrieval si rompe in modi prevedibili
la latenza distrugge la fiducia prima che arrivi la prima risposta
il controllo accessi e facile da sbagliare
la prompt injection diventa “avvelenamento dei contenuti”
le risposte devono essere verificabili, altrimenti aumenti il rischio di supporto
Questo post e un teardown dettagliato: architettura, modello dati, strategia di retrieval, design degli strumenti, caching, RBAC, pattern UI e i guardrail che hanno reso affidabile il nostro widget.
Cosa stavamo costruendo (requisiti e vincoli)
Abbiamo fissato alcuni requisiti non negoziabili fin dall’inizio:
Primo token veloce: gli utenti devono vedere una risposta rapidamente, anche se l’assistente deve “cercare” in background.
Risposte grounded: le risposte devono essere supportate dai contenuti che pubblichiamo davvero.
Read-only di default: l’assistente non deve modificare i contenuti.
Pronto per piu lingue: il sito e localizzato; l’assistente non deve mischiare lingue con leggerezza.
Escalation sicura: quando non puo rispondere, deve instradare l’utente verso contatto/supporto senza allucinare.
Da questi abbiamo derivato una definizione semplice di successo:
L’assistente deve comportarsi come un ingegnere prudente che legge il nostro sito, non come uno scrittore creativo che improvvisa.
Un modello mentale utile: “support bot = ricerca + navigazione + sintesi”
Un buon assistente di supporto fa tre cose, in sequenza:
Cerca: identifica pagine/sezioni candidate.
Naviga: apre pagine, scansiona heading, segue link, raffina la ricerca.
Sintetizza: produce una risposta breve e corretta con indicazioni su da dove arriva.
Molte implementazioni fanno solo (1) e (3). Saltano (2), ed e li che muore la precisione.
Lezione 1: un RAG “solo chunk” fallisce quando la risposta e distribuita su piu pagine
Il retrieval top-K di chunk si rompe in modi consistenti:
la risposta e divisa tra due pagine
la “sintassi esatta” e in un blocco di codice che non e entrato in ranking
la risposta giusta e su una pagina con scarsa sovrapposizione lessicale
la domanda dell’utente e troppo vaga, quindi il match embedding e “abbastanza vicino”
Quando un modello vede solo pochi chunk puo sembrare sicuro pur essendo incompleto. L’esperienza utente tipica:
tono corretto
dettagli sbagliati
nessun modo per verificare
Il nostro takeaway: il retrieval non basta. Serve esplorazione.
Normalizzazione: collassa spazi, rimuovi query di tracking dai link.
Segmentazione:
record a livello pagina per navigazione e citazioni
record a livello chunk per retrieval
Annotazione:
locale
visibilita (pubblico, client-only, interno)
outline degli heading
link interni in uscita
La lezione principale: indicizza il sito renderizzato, non solo il repo sorgente, oppure perdi cio che l’utente vede davvero e finisci per citare contenuti che in produzione non esistono.
Retrieval: combinare lessicale e vettoriale prima di “chiedere al modello”
Non ci fidiamo di una sola strategia di retrieval.Facciamo:
ricerca lessicale per token esatti (ottima per identificatori, acronimi, codici errore)
ricerca vettoriale per match semantico (ottima per domande vaghe)
Poi uniamo i candidati e applichiamo sanity check basilari:
scarta pagine che non corrispondono al locale dell’utente (a meno che lo chieda esplicitamente)
preferisci pagine canoniche rispetto a tag page / listing duplicati
preferisci pagine con forte overlap tra heading e termini della query
Solo dopo lasciamo che l’assistente apra pagine e sintetizzi.
Design degli strumenti: cosa puo fare l’assistente (e cosa no)
Abbiamo implementato una superficie strumenti piccola e l’abbiamo resa rigorosa:Consentito:
elencare directory: ls/services
cercare path: find-name"billing"
leggere una pagina completa: cat/services/seo
cercare nel contenuto: grep-ri"canonical"/
Non consentito:
scrivere o modificare contenuti
fetch di URL arbitrari
chiamate di rete arbitrarie
Il vantaggio in sicurezza e enorme: rende la “prompt injection” soprattutto un problema di qualita del contenuto, non un problema di compromissione del sistema.
La lezione sulla latenza: filesystem reali sono troppo lenti per una chat interattiva
Se avvii un sandbox/container per sessione per fornire un filesystem reale:
il cold start diventa visibile
la chat sembra rotta
sei tentato di aggiungere complessita (pool caldi, ecc.)
Per un widget di supporto, l’utente sta fissando la UI. Servono strumenti istantanei.Quindi abbiamo virtualizzato le operazioni filesystem sopra il nostro indice:
ls e find risolvono dal path tree cacheato
cat ricompone la pagina dai chunk salvati (ordinati per chunk_index)
i risultati vengono cacheati per sessione (e in parte globalmente, quando e sicuro)
L’assistente ha l’illusione di una shell sopra dei file, ma non esistono file reali.
Cache di cio che si ripete: albero, pagine e “target di grep”
Cacheare “risposte” e fragile perche le domande variano.Cio che si ripete nelle conversazioni reali e:
listare le stesse sezioni
aprire le stesse 5-10 pagine importanti
fare grep sugli stessi token
Quindi abbiamo cacheato:
il path tree
pagine ricostruite complete
candidati recenti per grep
Questo ha contato piu di micro-ottimizzare gli embedding, perche ha aumentato la velocita nei follow-up e ridotto i loop “searching…”.
RBAC: il controllo accessi deve essere strutturale, non basato sul prompt
Se alcune docs non sono pubbliche (draft, note interne, docs client-only), non puoi affidarti al prompting.Abbiamo applicato RBAC prima che l’assistente esegua una singola tool call:
costruisci un path tree per utente
pruni tutto cio a cui l’utente non puo accedere
applica lo stesso filtro a ogni query e lettura pagina
Se l’assistente non puo fare ls su un file, non puo fare cat e non puo citarlo.E l’unico modello mentale che regge sotto pressione.
Guardrail: come abbiamo fermato risposte sbagliate ma “sicure”
Abbiamo aggiunto alcune regole che hanno ridotto drasticamente le risposte cattive:
Regola 1: niente evidenza, niente risposta
Se l’assistente non trova contenuto di supporto con gli strumenti, deve:
dire che non ha potuto verificare
mostrare cosa ha controllato (pagine o sezioni)
proporre un next step (contatto/supporto)
Regola 2: preferire citazioni a parafrasi per dettagli critici
Quando la risposta e sensibile alla formulazione esatta (requisiti, limitazioni, copy legale):
cita la/le frase/i rilevanti della pagina letta
mantieni la sintesi minimale
Regola 3: essere rigorosi su locale e pagine canoniche
Se l’utente e su una route di lingua, l’assistente deve:
preferire il contenuto di quel locale
evitare di mischiare lingue nella stessa risposta
fare fallback sul locale di default solo se la pagina in quel locale non esiste
Il “problema grep”: la feature killer richiede un piano in due fasi
Un grep ricorsivo naive e lento se legge tutto via rete.Abbiamo usato un approccio in due fasi:
filtro grossolano usando l’indice (quali pagine potrebbero contenere il token)
filtro fine in memoria sul testo pagina cacheato per estrarre match esatti + contesto
Questo ha fatto sembrare l’assistente capace di cercare come un developer, non di indovinare come un chatbot.
UX del widget: la UI fa o disfa la fiducia
Abbiamo rilasciato piu iterazioni UI prima che il widget risultasse affidabile.Cosa ha contato di piu:
streaming di token con layout stabile (evita reflow “saltellante”)
stati chiari:
“Cercando…”
“Leggendo la pagina…”
“Rispondendo…”
citazioni brevi inline:
“Da: /services/seo”
“Da: /legal/privacy”
fallback che non sembrano un fallimento:
“Ho controllato X e Y ma non ho potuto confermare; ecco come contattarci.”
Questo si combina bene col rendere visibile il “lavoro” dell’assistente. Gli utenti perdonano molto piu un bot che dice “non ho potuto confermare” rispetto a uno che inventa una risposta sicura.
Osservabilita: loggare le letture, non solo i token
Se vuoi migliorare la qualita, devi sapere cosa e successo.Abbiamo loggato:
tool call (path listati/letti/cercati)
quali pagine sono state usate come evidenza
lunghezza della risposta e bucket di latenza
tasso “non ho potuto verificare”
tasso di escalation (click su contatto)
Questo ci ha permesso di rispondere a domande pratiche:
quali pagine mancano di informazioni importanti?
quali domande non trovano mai evidenza?
quali pagine generano confusione e richiedono ristrutturazione?
Una checklist pratica di build (cosa rifaremmo)
Se stai costruendo un widget di supporto che chatta con i tuoi contenuti, partiremmmo da:
indicizzare il sito renderizzato e salvare record a livello pagina
combinare retrieval lessicale + vettoriale
aggiungere un layer di esplorazione (file + grep)
prunare i contenuti per RBAC prima che inizi la sessione
mantenere gli strumenti read-only di default
aggiungere la regola “niente evidenza, niente risposta”
rendere visibile in UI “cercando/leggendo/rispondendo”
loggare le tool call per poter debuggare i failure
Se vuoi che ti aiutiamo a implementare questo (widget + indicizzazione + architettura sicura):