Un modello linguistico non conosce i vostri listini, le vostre procedure o le vostre condizioni di garanzia. Se glielo chiedete, risponde comunque — e questa è la prima cosa da capire prima di decidere se un assistente conversazionale sia adatto al vostro caso.

Non è un difetto da correggere: è come funziona. Un modello è addestrato a produrre la continuazione più plausibile di un testo, e «non lo so» raramente è la continuazione più plausibile. Tutto il lavoro di un assistente aziendale serio consiste nel vincolarlo ai vostri contenuti e nel definire con precisione cosa può e cosa non può fare.

L'architettura#

i cinque passi
   documenti aziendali   (FAQ, schede, manuali, condizioni)   ┌────────▼─────────┐   │ 1. INDICIZZAZIONE│  divisi in blocchi → vettori (embedding)   │                  │  salvati in un indice, UNA VOLTA   └────────┬─────────┘        (indice)   domanda ─┼──▶ ┌──────────────┐                 │ 2. RECUPERO  │  la domanda diventa un vettore,                 │              │  si prendono i blocchi più simili                 └──────┬───────┘                        │  i 5-8 passaggi più pertinenti                 ┌──────▼───────────┐                 │ 3. GENERAZIONE   │  il modello risponde USANDO SOLO                 │    VINCOLATA     │  quei passaggi, e cita la fonte                 └──────┬───────────┘              ┌─────────┴──────────┐              ▼                    ▼     4. AZIONE (prenota,    5. PASSAGGIO A UN        apre ticket)           OPERATORE UMANOIl nome della tecnica viene dal passo 2 + 3: RAG, generazioneaumentata dal recupero.

Dove si gioca la qualità#

recupero.jsJavaScript
// Il punto che sorprende chi si aspetta che basti "un modello migliore":// se il blocco che contiene la risposta NON viene recuperato, nessun// modello può rispondere correttamente. Ha in mano i passaggi sbagliati.//// La qualità si gioca quasi tutta qui, e dipende da quattro scelte:// 1. DIMENSIONE DEI BLOCCHI//    Troppo grandi → la risposta è diluita in mezzo ad altro, e il//                    modello perde il segnale.//    Troppo piccoli → si perde il contesto ("questo" e "il prodotto"//                    si riferiscono a qualcosa scritto prima).//    Punto di partenza ragionevole: 300-600 token per blocco.// 2. SOVRAPPOSIZIONE fra blocchi contigui (~15%)//    Senza, una frase spezzata a metà fra due blocchi non è//    recuperabile da nessuno dei due.// 3. METADATI, per filtrare prima di cercareconst blocchi = [{  testo: '...',  vettore: [...],  meta: { prodotto: 'X200', lingua: 'it', versione: '3.4',          aggiornato: '2026-08-01', fonte: 'manuale-x200.pdf#p12' }}];// Filtrare per prodotto PRIMA della ricerca semantica elimina in// un colpo la categoria di errore più comune: la risposta giusta// per il prodotto sbagliato.// 4. RIFORMULAZIONE della domanda//    "e per quello più piccolo?" non contiene abbastanza informazione//    per una ricerca. Va riscritta usando i messaggi precedenti://    → "qual è la garanzia del modello X100?"

È il punto che sorprende chi si aspetta che il problema si risolva scegliendo «un modello migliore». Se il blocco che contiene la risposta non viene recuperato, il modello — qualunque modello — ha in mano solo passaggi sbagliati. Nel migliore dei casi dice di non saperlo; nel peggiore risponde a partire da quelli.

Il caso in cui la ricerca semantica sbaglia#

ibrido.jsJavaScript
// La ricerca puramente semantica sbaglia su una cosa specifica:// i codici, i numeri di parte, i nomi propri esatti. "X200-B" e// "X200-C" sono vicinissimi nello spazio vettoriale.//// La correzione è combinare due ricerche: semantica + testuale.const semantici = await indice.cercaVettori(vettore(domanda), 20);const testuali  = await indice.cercaTesto(domanda, 20);       // BM25// Fusione per rango reciproco: un risultato che compare in ENTRAMBE// le liste sale, anche se in nessuna delle due era primo.function fondi(liste, k = 60) {  const punti = new Map();  for (const lista of liste) {    lista.forEach((doc, i) => {      punti.set(doc.id, (punti.get(doc.id) || 0) + 1 / (k + i + 1));    });  }  return [...punti.entries()].sort((a, b) => b[1] - a[1]);}

Gli embedding catturano il significato, ed è il loro pregio. Ma «X200-B» e «X200-C» significano quasi la stessa cosa, e nello spazio vettoriale sono a distanza minima: una ricerca puramente semantica li confonde con regolarità. Lo stesso vale per numeri di parte, codici fiscali, riferimenti normativi.

La correzione è affiancare una ricerca testuale classica e fondere le due liste. Un risultato che compare in entrambe sale, anche se in nessuna delle due era al primo posto — che è esattamente il comportamento desiderato quando la domanda contiene sia un concetto sia un codice.

Generazione vincolata#

prompt.jsJavaScript
// La generazione vincolata. Le istruzioni che fanno la differenza// sono le ultime due, e vanno messe in modo esplicito.const istruzioni = `Rispondi ESCLUSIVAMENTE usando i passaggi forniti qui sotto.- Cita la fonte di ogni affermazione nel formato [fonte: nome-file].- Se i passaggi non contengono la risposta, scrivi esattamente:  "Non ho questa informazione nei documenti a mia disposizione.   Vuoi che ti metta in contatto con un operatore?"- Non usare conoscenze generali. Non dedurre. Non stimare.- Non fare promesse su tempi, prezzi o condizioni che non siano  scritte testualmente nei passaggi.PASSAGGI:${passaggi.map((p, i) => \`[\${i+1}] fonte: \${p.meta.fonte}\n\${p.testo}\`).join('\n\n')}`;// "Se non lo sai, dillo" NON è ovvio per un modello linguistico:// è addestrato a produrre testo plausibile, e una risposta inventata// è testo plausibile. L'istruzione va data, e va VERIFICATA.

Le due istruzioni che contano sono quella sulla citazione della fonte e quella sul rifiuto. La citazione ha un doppio effetto: rende l'affermazione verificabile dall'utente, e nella pratica riduce le invenzioni — un'affermazione che deve essere ancorata a un passaggio specifico è più difficile da fabbricare.

La difesa che sta prima del prompt#

soglia.jsJavaScript
// La difesa più efficace contro le risposte inventate non sta nel// prompt: sta PRIMA. Se il recupero non trova niente di abbastanza// pertinente, il modello non viene nemmeno interpellato.const SOGLIA = 0.72;   // da tarare sui propri dati, non copiareconst passaggi = await recupera(domanda);if (!passaggi.length || passaggi[0].similarita < SOGLIA) {  return {    testo: 'Non ho questa informazione. Ti metto in contatto con un operatore?',    azione: 'proponi_operatore'  };}// Una risposta mancata costa molto meno di una risposta sbagliata.// La prima è un piccolo attrito; la seconda è un impegno preso// dall'azienda che qualcuno dovrà onorare o smentire.

È l'accorgimento più efficace e il meno implementato: se il recupero non trova nulla di sufficientemente pertinente, il modello non viene nemmeno interpellato. Non c'è modo di inventare una risposta se non si è mai stati chiamati a darne una.

Le azioni#

azioni.jsJavaScript
// Dove serve, il modello non risponde: richiama una funzione.// La regola non negoziabile è che i parametri vengono VALIDATI dal// codice prima di eseguire — il modello propone, il codice dispone.const strumenti = [{  name: 'prenota_appuntamento',  description: 'Prenota un appuntamento in una fascia disponibile',  input_schema: {    type: 'object',    properties: {      data: { type: 'string', description: 'formato AAAA-MM-GG' },      fascia: { type: 'string', enum: ['mattina', 'pomeriggio'] },      email: { type: 'string' }    },    required: ['data', 'fascia', 'email']  }}];// Lato server, prima di eseguire:const p = SchemaPrenotazione.parse(chiamata.input);   // valida davveroif (!await fasciaDisponibile(p.data, p.fascia)) {  return { errore: 'fascia non disponibile', alternative: await proponi(p.data) };}// E per le azioni irreversibili o con effetti economici: conferma// esplicita dell'utente prima di eseguire, sempre.

Quando l'assistente può fare cose — prenotare, aprire un ticket, registrare un contatto — la regola non negoziabile è che il modello propone e il codice dispone. I parametri passano per una validazione vera prima dell'esecuzione, e le azioni irreversibili o con effetti economici richiedono una conferma esplicita dell'utente.

Misurare, invece di avere impressioni#

valutazione
# Senza un insieme di prova, ogni modifica e' un'impressione.# Cinquanta domande con la risposta attesa, rieseguite a ogni# cambiamento: e' il minimo che rende il lavoro misurabile.cat prove.jsonl | head -2{"domanda":"Qual e' la garanzia dell'X200?","attesa":"24 mesi","fonte":"manuale-x200.pdf"}{"domanda":"Spedite in Svizzera?","attesa":"NON_SO","fonte":null}node valuta.js  Recupero (il passaggio giusto e' fra i primi 5)   43/50   86%  Risposta corretta                                 39/50   78%  Rifiuti corretti (domande senza risposta)          8/8   100%   ← il piu' importante  Risposte INVENTATE                                  2/50    4%   ← il piu' grave# Il secondo numero da guardare non e' l'accuratezza media:# e' il tasso di risposte inventate, che deve tendere a zero# anche a costo di piu' rifiuti.
Il numero da guardare non è l'accuratezza media: è il tasso di risposte inventate, che deve tendere a zero anche al prezzo di più rifiuti.

Cinquanta domande con la risposta attesa, comprese domande a cui non esiste risposta nei documenti — che sono le più importanti, perché verificano che l'assistente sappia dire di no. Riesecuzione a ogni modifica: al prompt, alla dimensione dei blocchi, alla soglia, al modello. Senza, ogni intervento è un'impressione, e le impressioni su un sistema probabilistico sono inaffidabili per costruzione.

I costi#

quattro voci
Le quattro voci di costo, e quale sorprende.  1. COSTO PER CONVERSAZIONE     Proporzionale al traffico, a differenza di una pagina web.     Una conversazione di 6 scambi con 8 passaggi recuperati muove     nell'ordine delle decine di migliaia di token. Va moltiplicato     per il volume atteso PRIMA di decidere, non dopo.  2. MANUTENZIONE DELL'INDICE                    ← la voce dimenticata     Quando i contenuti cambiano, l'indice va rigenerato. Se non     accade, l'assistente risponde con informazioni superate — con     la sicurezza di chi le crede attuali. È il fallimento peggiore,     perché è invisibile: nessun errore, nessun log, solo risposte     sbagliate dette bene.  3. LATENZA     Recupero + generazione, con un servizio esterno nel mezzo:     tipicamente 1-4 secondi alla prima parola. Lo streaming della     risposta cambia molto la percezione, ma non il tempo.  4. RESPONSABILITÀ     Ciò che l'assistente dice è un'affermazione DELL'AZIENDA, non     del fornitore del modello. Se promette una consegna in 24 ore     o una garanzia estesa, qualcuno dovrà decidere se onorarla.

La seconda voce è quella che manca in tutti i preventivi e che determina se il progetto continuerà a funzionare dopo sei mesi. Un indice che non viene rigenerato quando i contenuti cambiano produce il fallimento peggiore possibile: risposte superate ma sicure, senza alcun errore, senza alcun log, indistinguibili da quelle giuste finché un cliente non si presenta con una condizione che non esiste più.

Quando non conviene#

SituazioneUn assistente RAGAlternativa
Poche decine di domande al mesenoFAQ curate e un modulo ben fatto
Documentazione ampia e stabile, molte domande ripetute
Serve un dato in tempo reale che nessun sistema esponenoil problema è l'integrazione, non il chatbot
Risposta sbagliata = conseguenze contrattuali o legalinoricerca guidata deterministica
Filtrare e instradare richieste verso il reparto giusto
Sostituire l'assistenza umananoaffiancarla, con passaggio sempre disponibile
Contenuti che cambiano ogni settimanasì, se l'indice si rigenera da soloautomatizzare prima l'aggiornamento

Le due righe più importanti sono la prima e la quarta. Con volumi bassi, il tempo speso a curare i contenuti e a controllare le risposte supera quello risparmiato: un buon insieme di FAQ e un modulo che raccoglie bene la richiesta rendono di più, e costano una frazione. E dove una risposta sbagliata ha conseguenze contrattuali, serve un sistema deterministico — una ricerca guidata che mostra il documento, non un modello che lo riassume.