Vai al contenuto
megapromotingParliamone

Expertise · Basi di conoscenza e ricerca semantica

Costruiamo il corpus da cui un assistente ha il permesso di rispondere, con regole su cosa entra, come si divide, come si cerca e cosa succede quando la sorgente cambia.

Un assistente che risponde ai clienti è esattamente buono quanto il corpus da cui legge. Ci occupiamo del corpus: estrazione da documenti e sito, suddivisione in frammenti, indicizzazione, reperimento, gate di approvazione umana e aggiornamento. Include la risposta alla domanda quando NON conviene la ricerca semantica.

Già costruitoEsistono implementazioni proprie per ogni fase, ma il loro stato differisce e va detto chiaramente. Ciò che gira con vettori propri: la memoria di un board interno usato quotidianamente, con colonna `vector(1536)`, indice `ivfflat` sulla distanza coseno, soglia di similarità 0,35 e un ciclo che reindicizza ogni 30 minuti solo ciò che è cambiato. Ciò che gira con vettori del fornitore: la base di conoscenza degli agenti vocali, sincronizzata verso il motore di recupero del fornitore con frammenti di 500 e sovrapposizione di 100. Ciò che gira in produzione **senza** vettori: la base di conoscenza della piattaforma di assistenti testuali — estrazione, suddivisione, configurazione per assistente e recupero ibrido sono scritti e funzionano, ma il percorso di indicizzazione scrive la colonna dei vettori con `null`, quindi il punteggio ibrido degrada a lessicale, e il codice lo indica da sé con l’etichetta `keyword-only`. Ciò che è scritto e ancora non è avviato in produzione: il pipeline proprio su `pgvector` della piattaforma vocale, la cui migrazione porta nell’intestazione la menzione «LOCAL ONLY — do not push to prod». Un servizio si vende con questa mappa sul tavolo, non senza di essa.

Un assistente che parla con i clienti non «sa» nulla. A ogni domanda, qualcuno cerca in anticipo alcuni frammenti di testo e glieli mette davanti, e lui formula la risposta a partire da quelli. La qualità della risposta si decide quasi integralmente in quella fase di ricerca, non nella scelta del modello. Per questo il servizio qui riguarda il corpus e il retrieval, non il modello.

Il lavoro ha quattro parti che si guastano indipendentemente l’una dall’altra. L’estrazione: da quali file e quali pagine ricaviamo testo utilizzabile, e cosa facciamo quando non possiamo. La suddivisione: quanto sono grandi i frammenti, quanto si sovrappongono, dove si taglia una frase. Il retrieval: come si scelgono i frammenti per una domanda specifica, quanti, e secondo quale criterio. L’aggiornamento: cosa succede quando il documento sorgente viene modificato. Un sistema può essere impeccabile in tre di queste e inutile a causa della quarta.

Esiste una fase in più, che molti saltano e che cambia completamente il risultato: la qualification gate. Un corpus che cresce da solo, dalle conversazioni, finisce presto per contenere risposte che nessuno ha validato. Il ciclo che abbiamo costruito va al contrario: quando l’agente dice „non ho questa informazione”, la domanda viene registrata con lo stato „in attesa”, un umano riceve una notifica e risponde, e la sua risposta passa allo stato „approvato” e solo allora entra nel corpus. La conoscenza cresce da mancanze reali, con un umano in catena.

E la parte impopolare: la ricerca semantica non è sempre la scelta corretta. Per una piattaforma pubblica propria l’abbiamo rifiutata deliberatamente. Il corpus è il testo stesso della piattaforma, suddiviso in passaggi brevi che riportano ciascuno la pagina da cui provengono, segmentati tramite sovrapposizione di parole. Niente vettori, niente archivio vettoriale, niente richiesta supplementare in rete — e, cosa più importante, nessuna fonte esterna al deposito. L’assistente non può fare altro che ripetere ciò che è già scritto sul sito, e ogni risposta viene verificata in una pagina scritta da un umano. Questo è l’intero meccanismo anti-invenzione.

Cosa comprende

Il lavoro, per componenti

Estrarre il testo e dire chiaramente quando non possiamo

Da testo semplice, markdown, log, CSV, JSON, DOCX e PDF. Il DOCX viene aperto come archivio e si legge il documento XML al suo interno; il PDF passa attraverso l’estrattore standard con mantenimento dell’impaginazione, e se questo manca esiste un fallback che legge direttamente dal file gli operatori di testo. Il caso importante è quello in cui non funziona: un PDF scansionato, che è un’immagine, non contiene testo selezionabile, e il sistema restituisce un messaggio esplicito — caricate DOCX o testo, oppure sottoponete il file a riconoscimento ottico prima. Il riconoscimento ottico non è implementato e non fingiamo di averlo.

Suddividiamo in frammenti con sovrapposizione, tagliando tra le frasi, non attraverso di esse

I valori predefiniti che usiamo: 500 unità lessicali con 50 di sovrapposizione per il pipeline vocale, 1.200 caratteri con 150 di sovrapposizione per quello testuale; sul lato fornitore vocale, 500 con 100. La sovrapposizione esiste perché una frase tagliata a metà tra due frammenti diventa inutilizzabile per entrambi. Il separatore di frasi è scritto per le lingue con cui lavoriamo: riconosce la fine di frase seguita da una maiuscola latina **o cirillica**, comprese le diacritiche romene. Un separatore scritto per l’inglese taglia male in romeno e ancora peggio in russo.

Indicizziamo con vettori dove l’abbiamo avviato, e diciamo dove no

Il modello predefinito di rappresentazione è `text-embedding-3-small`, con 1.536 dimensioni, richiesto tramite la nostra gateway dei modelli, non direttamente al fornitore — così il fornitore può essere cambiato senza toccare il codice della base di conoscenza. Nella base, la colonna è di tipo vector con indice `ivfflat` sulla distanza coseno. Esiste una strategia di riserva scritta nella migrazione: se l’estensione vettoriale non è disponibile, lo schema si crea comunque con indice trigram sul testo, e il retrieval passa alla similarità lessicale. Non è ipotetico — è stato scritto proprio perché i diritti sull’estensione non sono garantiti in ogni installazione.

Recupero ibrido, con i pesi in chiaro

Il punteggio finale è 0,7 della similarità vettoriale più 0,3 della corrispondenza delle parole, con la parte lessicale normalizzata. Si valuta un insieme di candidati e si conservano i primi frammenti, mentre il contesto inviato al modello taglia ogni frammento a una lunghezza massima, così che un singolo documento lungo non occupi tutto lo spazio. La configurazione è per assistente, di base: recupero attivato o disattivato, numero massimo di frammenti per domanda (predefinito 5, limitato tra 1 e 20), soglia di similarità, tipo di ricerca — semantica, lessicale o ibrida — e ponderazione della parte lessicale.

Qualification gate: la conoscenza cresce dalle lacune, con un umano nel ciclo

Quando l’agente risponde che non ha l’informazione, la domanda viene scritta in una tabella con lo stato «in attesa», marcata con il canale da cui è arrivata — voce o widget. Un umano riceve la notifica e risponde direttamente al messaggio; un webhook preleva la risposta, porta la voce nello stato «approvato», e da lì entra nel contesto dell’agente. Gli stati possibili sono quattro: in attesa, approvato, ignorato, duplicato — e i duplicati si collegano alla domanda originale tramite un riferimento, così da non accumulare dieci formulazioni della stessa mancanza. La consegna all’agente avviene una sola volta per voce approvata.

Aggiornamento: cosa accade quando la fonte cambia

All’aggiunta si calcola l’impronta SHA-256 del contenuto; se il testo è identico a qualcosa già indicizzato, viene riutilizzato e non si paga una nuova rappresentazione vettoriale. Quando il documento si modifica, i suoi frammenti vengono eliminati integralmente e riscritti — sostituzione completa per fonte, non aggiornamento parziale, perché un aggiornamento parziale lascia dietro di sé frammenti orfani della vecchia versione, e sono quelli che producono risposte in contraddizione con il sito. La fonte ha un ciclo di stati visibile: in attesa, in elaborazione, in rielaborazione, pronto o fallito, e la reindicizzazione può essere richiesta puntualmente, per fonte.

Non reindicizziamo ciò che non è cambiato

In un ciclo che gira ogni trenta minuti su un elenco che cresce soltanto, il ricalcolo della rappresentazione per un elemento intatto è un pagamento ricorrente per un vettore che non aveva modo di muoversi. Il nostro ciclo confronta il momento dell’ultimo intervento con il momento dell’ultima indicizzazione e salta ciò che non è modificato. Le rappresentazioni si richiedono in batch, non una per una. Sono dettagli di costo, ma il costo mensile di una base di conoscenza si decide quasi interamente qui.

Un catalogo connesso non è un elenco caricato

La differenza non è di formato, è di momento. Un elenco caricato è vero al momento del caricamento: il prezzo, lo stock e i nuovi prodotti restano congelati finché qualcuno si ricorda di ricaricare — e di solito si scopre che nessuno se ne ricorda, quando un cliente chiede un prezzo vecchio di tre mesi. Un catalogo connesso si legge al momento della domanda, dalla fonte che tiene il registro. Ciò che abbiamo costruito in questa direzione: un estrattore che riconosce le collezioni di prodotti in un JSON o CSV dalle chiavi abituali e, quando la struttura è insolita, chiede aiuto a un modello con l’obbligo di rispondere strettamente in una forma data; e un analizzatore che legge il prezzo dalla pagina viva del negozio. La strada migliore resta l’interfaccia strutturata propria del negozio, dove esiste — ma quella si verifica per negozio, non si presume.

Scansione di un sito: prima la mappa, poi l’esplorazione

Si cerca prima la mappa del sito, nelle varianti abituali di denominazione, e si espandono le mappe nidificate su due livelli. Solo se non esiste alcuna mappa si passa all’esplorazione in ampiezza, strettamente sulla stessa origine, con un limite di pagine configurabile (predefinito 50, plafonato a 200). La strategia effettivamente usata viene riportata indietro — mappa o esplorazione — perché da essa si legge se un risultato povero deriva dal sito o dal metodo. Si rispetta il file di esclusione del sito.

Come si presenta

Il percorso, passo dopo passo.

01

Stabiliamo che cosa è autorizzato a sapere l’assistente, prima di qualsiasi importazione

L’elenco delle fonti ammesse e, altrettanto importante, l’elenco di quelle respinte, con il motivo. Un file interno di prezzi di acquisto, un export di contatti, un archivio di e-mail — sono esattamente le cose che finiscono nel corpus «così ha contesto» e poi diventano una risposta a un cliente. Consegniamo l’inventario delle fonti con decisione e motivo per ciascuna, più la regola su ciò che non deve mai entrare.

02

Importiamo, dividiamo e verifichiamo ciò che è uscito leggendo effettivamente i frammenti

Estrazione, divisione con sovrapposizione, indicizzazione. La fase di verifica non si salta: si leggono frammenti a caso dal corpus risultante. Lì compaiono tabelle trasformate in stringhe di parole incollate, i PDF scansiti che hanno prodotto zero testo e le pagine di menu di navigazione entrate come documenti a sé stanti. Consegniamo il corpus più il rapporto su ciò che non si è potuto estrarre e perché.

03

Regoliamo il recupero su domande reali, non su domande comode

Si costruisce un insieme di domande da ciò che chiedono effettivamente i clienti, incluse le formulazioni scritte male e quelle in due lingue mescolate, e si regola il numero di frammenti, la soglia e la ponderazione della parte lessicale. L’obiettivo non è che ogni domanda riceva una risposta, ma che le domande senza copertura nel corpus ricevano «non ho questa informazione». Consegniamo l’insieme delle domande con i risultati e la configurazione finale.

04

Mettiamo il gate di approvazione e il ciclo di crescita

Registrazione delle domande senza risposta, notifica alla persona che approva, percorso di risposta, inserimento nel corpus e collegamento dei duplicati. Forniamo il ciclo funzionale e l’istruzione scritta per chi approva — incluso ciò che non va copiato dalla domanda del cliente.

05

Consegniamo la procedura di aggiornamento, con il nome di chi la esegue

Quale sorgente si aggiorna, con quale frequenza, chi preme e come si vede che è avvenuto. Qui dichiariamo apertamente il limite: nelle piattaforme che operiamo, la reindicizzazione è un’azione richiesta, non una programmata automaticamente. Una procedura senza un nome di persona accanto è una procedura che non viene eseguita.

DocumenteConversazioniFontiSintesiAzioneTeamContestualizzaFonti autorizzate. Azioni revisionate.
Il cammino di una domanda nel sistema: le fonti approvate entrano in estrazione e suddivisione, i frammenti arrivano nell'indice; alla domanda si valutano i candidati — vettoriale e lessicale — e solo alcuni frammenti arrivano nel contesto; e quando non si trova nulla, la domanda esce sul ramo di approvazione umana e torna nel corpus.

I dati

Cosa tocchiamo, dove risiedono e quanto rimangono

Le domande che pone chiunque abbia un responsabile della protezione dei dati — poste qui prima che le ponga lui.

Che cosa finisce nel corpus
Solo ciò che è approvato esplicitamente: documenti caricati, pagine selezionate, coppie domanda-risposta, testi scritti a mano. Le sorgenti sono tipizzate — testo, documento, sito, coppie domanda-risposta — e hanno uno scopo dichiarato: prodotti, documentazione, domande frequenti o altro. Ogni sorgente ha il proprio interruttore, e l’interruttore viene verificato a ogni interrogazione di recupero, non solo nell’interfaccia — una sorgente spenta non può arrivare nel contesto nemmeno per errore.
Dove si trovano i frammenti e le loro rappresentazioni
Nel database della rispettiva piattaforma, insieme al resto dei dati del cliente, non in un servizio esterno separato di ricerca. Per le installazioni su PostgreSQL, in tabelle proprie con l’estensione vettoriale attivata esplicitamente; per la piattaforma di assistenti testuali, in MySQL, con la rappresentazione in una colonna JSON e il calcolo della distanza eseguito nell’applicazione. Il secondo modello funziona su piccola scala e non scala fino a centinaia di migliaia di frammenti — è un limite di architettura, dichiarato come tale.
Dati personali nelle conversazioni
Il corpus non dovrebbe contenere dati personali, ma il ciclo di apprendimento può portarli dentro, perché la domanda di un cliente è testo scritto dal cliente. Per questo l’ingresso nel corpus passa attraverso l’approvazione di una persona: lì si filtra. La regola operativa che accompagna questo punto: chi approva le risposte riceve l’istruzione esplicita di non copiare nel corpus nomi, numeri di telefono o dettagli dell’ordine dalla domanda originale.
Che cosa si conserva sulle interrogazioni
Esiste una tabella di log delle interrogazioni di recupero, con i punteggi ottenuti, il tempo di recupero e il numero di frammenti restituiti. È lo strumento con cui si risponde al «perché ha detto questo» — senza di esso, la regolazione delle soglie diventa un’indovinello. Il log si conserva finché è utile per la taratura e rientra nei termini di retention della piattaforma, non in un regime separato.
Che cosa non esiste ed è dichiarato come tale
Non esiste l’applicazione di un termine di scadenza sui documenti: il campo è dichiarato nello schema, ma nulla lo legge, quindi un documento non scompare da solo dal corpus. Non esiste reindicizzazione automatica programmata nelle piattaforme del cliente — l’aggiornamento va richiesto esplicitamente, per sorgente. Non esiste riordinamento con un modello dedicato né punteggio lessicale di tipo BM25; ciò che chiamiamo «ibrido» è una somma ponderata tra corrispondenza di parole e distanza vettoriale, e questa formulazione è quella corretta.

Un caso

Una base di conoscenza cresciuta dalle domande a cui l’agente non sapeva rispondere

La situazione

Una catena di ristoranti con consegna aveva un voice agent al telefono e un assistente nella pagina web. Il corpus iniziale era stato caricato una volta, al lancio. Il problema non era che l’agente sbagliasse, ma che dicesse spesso «non ho questa informazione» — a domande perfettamente ragionevoli che nessuno aveva previsto quando il corpus fu scritto. Ogni risposta del genere era una conversazione persa, e l’elenco di queste non esisteva da nessuna parte.

Cosa abbiamo costruito

Abbiamo trasformato la mancanza in un input di sistema. Quando l’agente risponde che non ha l’informazione, la domanda viene scritta in una tabella con lo stato «in attesa» e con il canale da cui è arrivata — voce o widget. La persona responsabile dal lato cliente riceve una notifica sul canale di messaggistica che usa comunque, con l’istruzione di rispondere direttamente al messaggio. Un webhook preleva la risposta, porta l’input nello stato «approvato», e da lì la risposta entra nel contesto dell’agente. Le formulazioni diverse della stessa mancanza si collegano all’input originale tramite un riferimento, e la consegna all’agente avviene una sola volta per input, così che una risposta approvata non venga reiniettata a ogni ciclo.

Cosa è emerso

Il corpus ha smesso di essere un file caricato al lancio ed è diventato un elenco di mancanze reali, ordinato dai clienti, non dalle nostre ipotesi. Chi approva vede esattamente cosa è stato chiesto e in quale canale, quindi scrive la risposta nella forma in cui verrà usata. Gli stati sono quattro — in attesa, approvato, ignorato, duplicato — perché «ignorato» è un esito legittimo: alcune domande non devono mai ricevere una risposta automatica.

Cosa non dice il caso

La bucla funziona esattamente quanto funziona l’essere umano dentro di essa. Se nessuno risponde alle notifiche, gli elementi “in attesa” si accumulano e l’agente continua a non sapere — il meccanismo rende visibile la mancanza, non la risolve da solo. La seconda limitazione, altrettanto importante: l’implementazione è stata cablata per il flusso di questo cliente, con la tabella, la notifica e l’iniezione scritte per lui. Come funzione generica, disponibile dall’interfaccia per qualsiasi account, non esiste ancora.

Domande

Cosa ci chiedono le persone prima di chiamare

Perché l’assistente risponde in modo errato se l’informazione è nei documenti caricati?

Quasi sempre perché il frammento corretto non è stato ritrovato, non perché il modello sarebbe “stupido”. Le cause frequenti, nell’ordine in cui le verifichiamo: frammenti troppo grandi, in cui l’informazione utile si perde tra le altre; taglio a metà della frase, senza sovrapposizione; troppo pochi frammenti portati nel contesto; oppure un corpus in cui la stessa informazione appare in tre versioni diverse e quella vecchia ottiene un punteggio migliore. Il registro delle interrogazioni, con i punteggi ottenuti, mostra quale sia il caso — senza di esso si va a intuito.

Usate davvero la ricerca semantica ovunque?

No, ed è più onesto dire esattamente dove. Con vettori propri: la memoria di un board interno usato quotidianamente, su colonna vettoriale con indice sulla distanza coseno e soglia di similarità 0,35. Con vettori del fornitore: la base di conoscenza degli agenti vocali, sincronizzata verso il motore di recupero del fornitore. **Senza** vettori, oggi, in produzione: la base di conoscenza della piattaforma di assistenti testuali — il percorso di indicizzazione scrive la colonna vettoriale vuota, quindi il recupero gira in modo lessicale, e il codice etichetta da solo il modulo come `keyword-only`. Esiste anche una pipeline propria completamente scritta, con la migrazione marcata “da non pubblicare in produzione” finché non vengono confermati i diritti sull’estensione vettoriale. Questa è la mappa reale.

Cosa succede quando modifichiamo un documento già indicizzato?

I frammenti della sorgente vengono eliminati integralmente e riscritti dalla nuova versione. Abbiamo scelto la sostituzione completa al posto dell’aggiornamento parziale perché la parte difficile non è aggiungere, ma eliminare: i frammenti rimasti dalla vecchia versione continuano a essere ritrovati e producono risposte che il cliente contraddice con la pagina davanti. In fase di aggiunta esiste anche un controllo di impronta del contenuto — se il testo è identico, non si ricalcola nulla e non si paga inutilmente.

Si aggiorna da solo quando cambia il sito o il catalogo?

Non automaticamente, nelle piattaforme che operiamo oggi. La reindicizzazione va richiesta esplicitamente, per sorgente, tramite un’azione dedicata. Esiste una sola eccezione, in un nostro sistema interno, dove un ciclo gira ogni trenta minuti e reindicizza solo ciò che è cambiato. La programmazione automatica si può costruire ed è una fase separata; ciò che non facciamo è dare l’impressione che esista già. Esiste anche un campo di scadenza dichiarato nello schema che nessuno legge — quindi un documento non esce da solo dal corpus.

Qual è la differenza tra un catalogo collegato e un elenco di prodotti caricato?

Il momento della verità. L’elenco caricato è corretto nel giorno del caricamento; dal giorno dopo, prezzo, stock e nuovi prodotti divergono in silenzio, e la divergenza si scopre quando un cliente chiede un prezzo che non esiste più. Il catalogo collegato si legge nel momento della domanda, dal sistema che tiene la registrazione, quindi non può restare indietro. Il costo è un altro: compare una dipendenza dalla disponibilità di quel sistema e bisogna decidere cosa dice l’assistente quando la sorgente non risponde — la risposta corretta essendo “non posso confermare il prezzo adesso”, non l’ultimo valore conosciuto.

Potete indicizzare PDF scannerizzati?

Non direttamente. Un PDF scannerizzato è un’immagine in un involucro PDF e non contiene testo selezionabile; il nostro estrattore lo rileva e restituisce un messaggio esplicito invece di indicizzare un documento vuoto — il peggior risultato possibile essendo una sorgente marcata “pronta” che non contiene nulla. La soluzione è il riconoscimento ottico prima dell’importazione, oppure la fornitura del documento in DOCX o testo. Il riconoscimento ottico non è implementato nella catena attuale.

Funziona in romeno e russo?

Sì, e c’è un dettaglio che conta più di quanto sembri. La suddivisione in frammenti taglia tra le frasi, e la nostra regola riconosce la fine di frase seguita da una maiuscola latina **o** cirillica, comprese le diacritiche romene. Un separatore scritto per l’inglese non riconosce l’inizio di una frase in cirillico e produce frammenti incollati, con effetto diretto sulla qualità del recupero. Per il lato vocale, il modello di rappresentazione usato dal fornitore è multilingue.

Quando NON conviene la ricerca semantica?

Quando il corpus è piccolo, stabile e scritto da voi — per esempio il contenuto del vostro sito. In una piattaforma pubblica propria abbiamo scelto deliberatamente il recupero tramite sovrapposizione di parole sul testo della piattaforma stessa, suddiviso in passaggi brevi che portano ciascuno la pagina di origine. Senza vettori, senza archivio vettoriale, senza una richiesta di rete aggiuntiva e, cosa essenziale, senza alcuna fonte esterna all’archivio. L’assistente può solo ripetere ciò che dice già il sito, e ogni risposta si può verificare in una pagina scritta da una persona. Quando la posta in gioco principale è non inventare nulla, questo batte la ricerca semantica.

Ci tenete prigionieri nella vostra piattaforma?

Le fonti sono vostre e restano nella forma originale; i frammenti e le rappresentazioni stanno nel database della vostra installazione, non in un servizio esterno di ricerca a cui solo noi abbiamo accesso. Il modello di rappresentazione viene richiesto tramite un gate proprio dei modelli, quindi il cambio di fornitore non tocca il codice della base di conoscenza. Le rappresentazioni vettoriali sono però legate al modello che le ha prodotte: cambiare modello richiede la reindicizzazione dell’intero corpus. È un’operazione da calcolare, non gratuita, ed è meglio saperlo all’inizio che scoprirlo a metà.

Su cosa si basano le affermazioni sopra (29 fonti)

29 di esse sono codice e file dei nostri repository. Non ne pubblichiamo il nome né la riga: insieme, in un'unica pagina, descriverebbero con troppa precisione come sono costruiti sistemi che non sono solo nostri. Le esaminiamo con te, nel repository, su richiesta — la verifica resta possibile, solo che avviene in una discussione.

Cosa vorresti che funzionasse meglio?

Raccontaci il tuo processo. Insieme stabiliamo cosa vale la pena costruire, cosa possiamo collegare e come verifichiamo il risultato.

Parliamone