Skip to content

Ingestion DSL

A partire dalla v3.5.0 anche l'ingestione documentale e diventata canvas-native. La pipeline che porta un PDF caricato dall'utente fino ai chunk vettorizzati in Qdrant non e piu un servizio TypeScript imperativo: e un canvas con purpose = INGESTION eseguito dal motore DSL.

Questo abilita personalizzazioni avanzate per tenant, ingestione role-aware e una migrazione graduale dalle pipeline legacy.

Cosa cambia

AspettoLEGACY (v3.1.x)DSL V2 (v3.5.0+)
ParserPipeline PDF unicaAuto-router MIME, Docling + Unstructured come peer
Chunking14 chunker domain-specific in codice5 chunker per ruolo + preset di dominio configurabili
Ruoli documentoNon modellatiTRUTH / FORMAT / RULES / OPERATIONAL / EXAMPLES
CleanupRe-embedding manualeRecordManager con 4 modalita (UPSERT, FULL, INCREMENTAL, SCOPED)
ResumeNoPipeline snapshot + checkpoint
Multi-sinkSolo Qdrant + PostgresQdrant + Postgres + OperationalData + RecordManager come nodi
ConnettoriSolo upload + scrapeV2-ready per email, cloud storage, SharePoint

Ruoli documento

Ogni documento ingestato viene classificato in uno o piu ruoli, che determinano come viene trattato dalla pipeline:

RuoloEsempi tipiciTrattamento
TRUTHManuali, normative, policy, libriChunking semantico per paragrafi/articoli
FORMATTemplate, formulari, schemiEstrazione struttura + placeholder
RULESDecisioni, regolamenti, sentenzeChunking per articolo/clausola con citazioni
OPERATIONALListini, tabelle prezzi, dati strutturatiEstrazione per riga, sink OperationalData
EXAMPLESCasi studio, scenari, FAQChunking per coppia Q&A o blocco-scenario

La classificazione e automatica (nodo role_classifier) ma puo essere forzata dall'admin in fase di upload o tramite override per topic. Un documento puo avere piu ruoli (es. una sentenza e TRUTH + RULES).

Per la guida operativa al ruolo nelle ricerche vedi docs/ingestion-dsl/DOCUMENT_ROLES_GUIDE.md nel repository interno.

TRUTH

Contenuto autorevole, narrativo, di riferimento. E' il ruolo "default" per la maggior parte dei documenti aziendali.

  • Esempi: manuali operativi, normative interpretate, policy aziendali, white paper, libri.
  • Chunker: chunker_truth -- segmentazione semantica per paragrafi e sezioni, allineamento ai confini naturali, overlap 15%.
  • Metadata estratti: titolo, sezioni, lingua, livello di confidenza, eventuale riassunto.
  • In retrieval: peso base alto, citato come [N] con preview del paragrafo originale.

FORMAT

Documenti che mostrano una struttura da riprodurre piu che contenere informazioni autorevoli.

  • Esempi: template di contratti, modulistica vuota, formulari, schemi grafici, layout di documenti standard.
  • Chunker: chunker_format -- estrae la struttura (campi, sezioni, placeholder) e la indicizza separatamente dal "testo riempitivo".
  • Metadata estratti: lista dei campi/placeholder, tipologia di documento, lingua del template.
  • In retrieval: usati principalmente da Generazione Documenti per popolare nuovi documenti. Citati raramente nella chat (a meno che la domanda non sia esplicitamente "come si compila il modulo X?").

RULES

Documenti prescrittivi: stabiliscono cosa si puo o non si puo fare, definiscono obblighi, sanzioni, scadenze, regolamenti applicabili. Il sistema li tratta in modo speciale perche la precisione del riferimento normativo e critica.

  • Esempi: sentenze (Cassazione, Corti d'appello, TAR), decreti, regolamenti UE, articoli di legge, ordinanze, decisioni amministrative, circolari Agenzia Entrate, regolamenti interni aziendali con effetto vincolante.
  • Chunker: chunker_rules -- riconosce la struttura articolo - comma - lettera - numero tipica della legislazione italiana e dei regolamenti UE. Mantiene ogni articolo come unita coerente, preserva i riferimenti incrociati (es. "ai sensi dell'art. 12 c. 3 lett. b") e indicizza separatamente massime e dispositivi nelle sentenze.
  • Tipo chunk assegnato: ARTICLE, CLAUSE, SECTION, MASSIMA, DISPOSITIVO -- granularita piu fine rispetto a TRUTH.
  • Metadata estratti:
    • documentType -- es. SENTENZA, DECRETO_LEGISLATIVO, REGOLAMENTO_UE, CIRCOLARE_AE.
    • articleNumber, paragraphNumber, letter -- identificatori strutturati per citazione precisa.
    • dateInForce, dateRepealed -- vigenza temporale (se rilevata).
    • issuingAuthority -- es. "Agenzia delle Entrate", "Corte di Cassazione Sez. I", "Parlamento UE".
    • citedNorms[] -- altre norme citate nel testo (link a chunk RULES correlati).
  • In retrieval:
    • Priorita esplicita rispetto a TRUTH quando la domanda riguarda obblighi, sanzioni, conformita.
    • Citazione precisa: la risposta cita art. 5 c. 2 DLgs 231/2001 non solo il titolo del documento.
    • Filtro automatico di vigenza: regole con dateRepealed < oggi sono escluse di default (il filtro si puo disattivare per ricerche storiche).
    • Compatibilita con sorgenti esterne: una norma italiana ingestata internamente puo essere collegata alla versione in Legal Sources (Normattiva).
  • Confidentiality: tipicamente public o internal. Le sentenze interne (es. arbitrati) possono essere confidential.

Esempio pratico

Domanda: "Posso licenziare un dipendente in malattia?" -> Il sistema preferisce documenti RULES (Statuto dei Lavoratori art. 2110, sentenze Cass. Sez. Lav. correlate) rispetto a una circolare HR interna marcata TRUTH. La risposta cita art. 2110 c.c. e Cass. n. 12345/2023 con dispositivo testuale, non un riassunto.

Multi-ruolo TRUTH + RULES

Una sentenza ha due aspetti: la motivazione (TRUTH -- ragionamento giuridico, contesto) e il dispositivo + massima (RULES -- regola stabilita). Il classifier assegna entrambi i ruoli e i due chunker producono chunk distinti che coesistono nello stesso documento.

OPERATIONAL

Documenti tabulari o strutturati con dati che hanno senso "per riga" piu che "per testo libero".

  • Esempi: listini prezzi, anagrafiche fornitori/clienti, schedari prodotto, bilanci in formato tabellare, riconciliazioni, time sheet, KPI dashboard.
  • Chunker: chunker_operational -- una riga = un chunk. Mantiene la chiave-valore della riga (es. prodotto=Alpha, prezzo=120.00, disponibilita=in stock).
  • Sink dedicato: sink_operational scrive le righe in OperationalDataRow (Postgres) abilitando query strutturate (filtra, somma, raggruppa) accanto al retrieval testuale.
  • In retrieval: il planner riconosce intent aggregativi ("qual e il fatturato totale?", "quanti prodotti sotto 100 euro?") e li instrada verso una query SQL sui dati operational invece del LLM.

EXAMPLES

Documenti dimostrativi che illustrano come applicare un concetto, una procedura, una regola.

  • Esempi: casi studio, scenari di applicazione, FAQ aziendali, esercizi risolti, knowledge base di supporto.
  • Chunker: chunker_examples -- una coppia Q&A o uno scenario completo = un chunk. Preserva l'integrita del singolo caso esempio.
  • In retrieval: usati per arricchire la risposta con un esempio concreto quando la domanda lo permette (es. "ho un caso simile a..."). Citati con sourceType dedicato.

Raccolte (Document Groups)

Le Raccolte (DocumentGroup) sono insiemi nominati di documenti che condividono uno scopo comune di breve-medio termine -- una pratica, un progetto, un cliente, un dossier, un audit, una migrazione. Sono pensate per organizzare l'ingestione e la consultazione trasversalmente ai Topic, senza dover creare nuovi Topic per ogni iniziativa.

Differenza con i Topic

AspettoTopicRaccolte
ScopoTassonomia stabile (es. "Contratti", "HR", "Legale")Aggregazioni temporanee o di lavoro (es. "Pratica Rossi 2026", "Audit Q1")
Cardinalita tipica3-15 per aziendaDecine o centinaia, possono nascere e morire
VitaLunga, raramente eliminataAperta -> Chiusa -> archiviata
PermessiRBAC granulare per ruoloEredita ACL dai documenti contenuti
Customizzazione AIPrompt di sistema dedicatoNo prompt dedicato
Identificata in chatSelettore Topic in altoFiltro/Mention @raccolta nel campo input
Modello DBTopic (con ragPresetId, systemPrompt)DocumentGroup (con topicId, status)

In pratica: un documento appartiene a uno o piu Topic (categoria stabile) e opzionalmente a una o piu Raccolte (aggregazione di lavoro). Topic risponde a "che tipo di documento e?", Raccolta risponde a "per quale pratica/progetto e stato caricato?".

Stato della Raccolta

Una Raccolta ha un campo status con due valori:

StatusSignificatoEffetto nella chat
OPENLavoro attivoInclusa nei suggerimenti @raccolta, retrieval prioritario quando filtrata
CLOSEDLavoro chiuso, archivioEsclusa dai suggerimenti, accessibile solo se richiamata esplicitamente

Chiudere una Raccolta non cancella i documenti: li lascia indicizzati e ricercabili, ma esce dal "working set" dell'utente.

Pagina Raccolte (/raccolte)

L'admin trova in Ingestione > Raccolte una pagina CRUD dedicata:

  • Tabella delle Raccolte: nome, Topic associato, numero documenti, status, owner, ultima modifica, azioni.
  • Toolbar: search per nome, filtro per azienda (solo SYSTEM_ADMIN), pulsante + Nuova raccolta.
  • Detail Sheet (al click su una riga): header con metadati + tabella annidata dei documenti contenuti (filename, topic, status, azione "Rimuovi dalla raccolta") + footer "Aggiungi documenti" con multi-select picker.

Azioni del menu (per riga): Apri, Rinomina, Cambia status (OPEN/CLOSED), Elimina.

Quando assegnare un documento a una Raccolta

Tre punti di ingresso:

  1. Wizard Single (step Confirm) -- combobox "Raccolta" -- esistente o + Nuova.
  2. Bulk profilo (step 2) -- stessa combobox, applicata a tutti i file della run; gli override Path-rules non toccano la Raccolta (e' un unico valore per job).
  3. Review Queue post-bulk -- colonna inline modificabile, oppure bulk-edit bar per assegnare tante righe insieme.

Un documento puo appartenere a piu Raccolte contemporaneamente (rel. molti-a-molti). E' frequente per documenti riusati (es. un contratto quadro usato sia in "Pratica Rossi 2026" sia in "Pratica Bianchi 2026").

Utilizzo nella chat

L'utente in chat puo:

  • Scrivere @raccolta per attivare un autocomplete delle Raccolte OPEN accessibili: la conversazione viene filtrata a quei documenti.
  • Combinare Topic + Raccolta: es. Topic = Contratti, Raccolta = Pratica Rossi 2026 -> retrieval ristretto a contratti di quella pratica.
  • Chiedere riassunti per Raccolta: es. "riassumi lo stato della pratica Rossi 2026" -> il sistema costruisce una sintesi sui soli documenti della Raccolta.

Lifecycle tipico

Apertura pratica
   |
   v
+ Nuova raccolta "Pratica Rossi 2026"
   |
   v
Bulk ingest 200 file (cliente + corrispondenza + atti)
   |   profilo Bulk -> Raccolta = "Pratica Rossi 2026"
   v
Review Queue: correggo topic per ~10 file
   |
   v
Lavoro attivo: chat filtrata @raccolta-pratica-rossi
   |
   v
Chiusura pratica
   |
   v
status -> CLOSED (storico ricercabile, fuori dal working set)

Endpoint API

MetodoEndpointScopo
GET/api/document-groupsLista Raccolte (filtri per status, search, owner)
POST/api/document-groupsCrea Raccolta
PATCH/api/document-groups/:idRinomina, cambia status, Topic associato
DELETE/api/document-groups/:idElimina (i documenti restano, perdono solo il link alla Raccolta)
POST/api/document-groups/:id/documentsAggiunge documenti
DELETE/api/document-groups/:id/documents/:docIdRimuove un documento

Feature flag

La sidebar mostra Raccolte solo se il tenant ha la feature attiva:

bash
ENABLE_DOCUMENT_GROUPS=true

Disabilitando il flag, l'API resta funzionante (i dati esistenti non vanno persi) ma scompaiono le voci di navigazione e i campi Raccolta nei wizard / review.

Best practice

  1. Una Raccolta = una pratica / progetto -- evita di usare Raccolte per fare ulteriore tassonomia (per quello ci sono i Topic).
  2. Chiudi le Raccolte concluse -- il working set di un utente attivo dovrebbe avere 5-20 Raccolte OPEN, non centinaia.
  3. Nominale parlanti -- Pratica Rossi 2026 e meglio di R-2026-01. La chat usa il nome come hint per il planner.
  4. Raccolte non sostituiscono i Topic -- una Raccolta che non e in un Topic e' un orfano: assegna sempre il Topic appropriato in fase di ingestione.

Anatomia di una pipeline INGESTION

┌──────────┐    ┌─────────┐    ┌──────────────┐    ┌────────────┐    ┌──────────┐
│  source  │ -> │ parser  │ -> │role_classify │ -> │  chunker   │ -> │ embedder │
└──────────┘    └─────────┘    └──────────────┘    └────────────┘    └──────────┘

                                                                            v
                                              ┌──────────┬───────────┬──────────────┐
                                              │ qdrant   │ postgres  │ operational  │
                                              │ chunks   │ document  │ data         │
                                              └──────────┴───────────┴──────────────┘

                                                              v
                                                    ┌──────────────────┐
                                                    │  record manager  │
                                                    │  (cleanup state) │
                                                    └──────────────────┘

Nodi sorgente

  • source_upload -- da upload utente (form web, drag&drop, API).
  • source_inchat -- documenti allegati alla chat (temporanei).
  • source_scrape -- pagine web da URL.
  • source_ocr -- immagini con OCR (GLM-OCR via vLLM 8004).
  • (V2) source_email, source_gdrive, source_sharepoint -- struttura pronta, implementazione futura.

Parser

  • parser_auto -- router automatico per MIME type.
  • parser_docling -- per PDF/DOCX/PPTX/XLSX/HTML/immagini. Estrae sezioni, tabelle, ordine lettura.
  • parser_unstructured -- per EML/MSG/EPUB/ODT/RTF, audio (in V2 transcript).

Entrambi delegano al microservizio ingestion-bridge (FastAPI Python su Docker).

Classifier e chunker

  • role_classifier -- LLM-based, output uno o piu ruoli + confidence.
  • chunker_truth, chunker_format, chunker_rules, chunker_operational, chunker_examples -- ciascuno con preset di dominio (legal, medical, financial, HR, ...).

Metadata extractors

Nodi LLM specializzati: extract_dates, extract_entities, extract_summary, extract_topics, extract_grade, extract_confidentiality. Eseguiti in parallelo dove possibile.

Sink

  • sink_qdrant -- vettorizza con bge-m3 e scrive chunks.
  • sink_metadata -- aggiorna Document su Postgres.
  • sink_operational -- per role OPERATIONAL, scrive righe tabellari su OperationalDataRow.
  • sink_record_manager -- traccia hash di contenuto e identita per il cleanup incrementale.

Modalita di ingestione

Per gestire la migrazione e l'A/B testing, ogni azienda ha un campo Company.ingestionMode:

ModalitaComportamento
LEGACYPipeline vecchia (no role, parser unico). Default per company create prima della v3.5.0
SHADOWLEGACY in primo piano, V2 in parallelo. Genera un report di confronto, nessun impatto utente
V2Cutover completo al canvas DSL

Migrazione consigliata

  1. Imposta SHADOW su un tenant pilota.
  2. Verifica per ~1 settimana il shadow_comparison_report (precisione, recall, latenza).
  3. Passa a V2 quando i numeri sono pari o migliori.
  4. Ricorda: cambiare modalita non ri-vettorizza i documenti esistenti. Per applicare i nuovi chunker ai vecchi documenti serve un reingest esplicito.

Cambio modalita via SQL:

sql
UPDATE "Company" SET "ingestionMode" = 'V2' WHERE id = '<companyId>';

Oppure da UI: Impostazioni azienda > Ingestione > Modalita.

Wizard, Bulk e Path-rules

L'Hub Ingestione (/documents/upload) e il punto di partenza per caricare documenti in Queria. Esistono due intent modes, entrambe appoggiate alla stessa pipeline DSL: cambia solo il flusso UX.

Modalita Single -- il Wizard AI in 3 step

Pensata per 1-10 file caricati manualmente. La pagina monta il UploadWizardPage (3 step) con classificazione assistita dall'AI:

  1. Upload -- drag & drop o selezione file. Sorgente: locale, cloud, rete, URL.
  2. Classify -- per ogni file il sistema propone una card "ruolo suggerito" (es. RULES — score 0.87) con topic, sector e lingua predetti. L'utente conferma o corregge. Multi-ruolo: si possono selezionare piu ruoli quando il documento e ibrido (es. policy + checklist -> TRUTH + RULES).
  3. Confirm -- riepilogo, scelta della visibilita, avvio dell'ingestion job.

Il wizard e l'unica modalita che mostra esplicitamente lo score di confidenza del classifier prima del lancio. E' utile quando i singoli documenti sono critici (legale, sanitario) e la review pre-lancio e accettabile.

Quando usarla

Da 1 a circa 10 file. Tutte le aziende con piccolo volume per default partono qui. Oltre i 50 file il sistema propone automaticamente la modalita Bulk.

Modalita Bulk -- per cloud, rete, URL list

Pensata per ingerire decine o migliaia di file da una sorgente comune. Tre step:

  1. Source selection -- scegli una delle quattro card: File (drag&drop massivo), Cloud (Drive / S3 / Azure / OneDrive), Network (SMB / NFS), URL list (CSV con N url).
  2. Profilo di ingestione -- form unico con i default che si applicano a tutti i file selezionati:
    • Topic (multi-select, obbligatorio se la Visibilita e "Solo topic assegnati").
    • Raccolta (combobox -- esistente o "+ Nuova").
    • Document role -- TRUTH / FORMAT / RULES / OPERATIONAL / EXAMPLES / Auto AI (default).
    • Sector -- preset list o Auto AI.
    • Lingua -- auto-detect o ISO.
    • Visibilita -- Tutti / Solo topic assegnati / Solo admin.
    • Path-rules (avanzate, accordion chiuso di default — vedi sotto).
  3. Job in progress -- LiveJobProgress via SSE: stato per file (queued / parsed / vectorized / failed). Appena un file e completato puoi gia entrare in Review Queue senza aspettare la fine del job.

Quando usarla

Quando importi da una sorgente intera (es. tutta una cartella Drive, un mount SMB, un dump JSONL). Da 50 file in su il sistema preferisce questa modalita.

Path-rules (regole per percorso)

Le Path-rules sono override per-file definiti dentro il profilo di ingestione bulk. Ogni regola e una riga { glob_pattern, override_topic, override_role }.

Esempi:

PatternOverride topicOverride role
gdrive:/Legale/**/*.pdfContrattiTRUTH
gdrive:/Legale/Sentenze/**/*.pdfGiurisprudenzaRULES
smb://share/Listini/**/*.xlsxListiniOPERATIONAL
**/FAQ*.mdHelp centerEXAMPLES

Le regole vengono valutate top-down per ogni file, al momento dell'esecuzione del job. La prima che fa match vince; se nessuna combacia, si applicano i default del profilo (incluso Auto AI per topic/role/sector).

Quando usarle:

  • Sorgenti cloud o di rete con una struttura a cartelle che gia codifica la tassonomia aziendale.
  • Migrazioni one-shot dove vuoi mantenere mapping path -> topic senza correggere file per file in Review.
  • Sorgenti miste dove diversi sotto-percorsi dovrebbero finire in topic/ruolo diversi.

Buone pratiche:

  • Ordina le regole dal piu specifico al piu generico (la prima vince).
  • Documenta le regole nel nome della Raccolta o nel changelog del topic: chi le legge dopo deve capirne la ratio.
  • Per pochi file isolati, non scrivere regole: meglio editare a posteriori in Review Queue (vedi sotto).

Review Queue (post-bulk)

Dopo un job bulk, la Review Queue (/documents/upload?mode=bulk&job=<id>) mostra tutti i file della run in una DataTable:

ColonnaEditabile
Checkbox di selezione--
Filenamepreview a click (Sheet laterale)
Statosuccess / warning / error
Topicinline (multi-select)
Raccoltainline
Sectorinline
Visibilitainline
AI confidenceprogress 0-100 (filtrabile)

Toolbar veloce:

  • Search-by-filename (debounce 300ms).
  • 3 chip preset: Sector = ALTRO, AI confidence < 70%, Topic mancante -- per pescare subito i file critici.
  • Export CSV della selezione.

Bulk-edit bar: appare quando selezioni >= 1 riga. Permette di applicare topic / sector / visibilita / raccolta a tutti i file selezionati in una sola PATCH (/api/documents/bulk-update). UI ottimistica con toast Sonner di errore in caso di fallimento.

Il job di ingestione non aspetta la review: i file scorrono in pipeline man mano che vengono caricati. La Review Queue serve a correggere ex-post topic/ruolo/sector dei file con bassa confidence o quelli che hanno richiesto fallback. I cambi su questi metadati non richiedono reingest (sono payload Qdrant + Postgres update).

Reingest

Per applicare cambi di pipeline a documenti gia vettorizzati:

  • Singolo documento: pagina Documenti > [doc] > Reingest.
  • Massivo: script src/scripts/reingest-company-onebyone.ts (template disponibile).
  • Backfill payload (no reingest, solo metadati): script idempotenti backfill-qdrant-*.ts per stampigliare nuovi campi (sector, role, grade, aisummary) senza re-embedding.

Stato e monitoraggio

Ogni job di ingestione produce:

  • IngestionJob su Postgres con stato (RUNNING, COMPLETED, FAILED, RESUMED).
  • PipelineSnapshot per resume in caso di crash.
  • IngestionRecord (RecordManager) per cleanup deterministico.

Dashboard admin: Ingestione > Dashboard live mostra job correnti, code, errori, latenza per nodo.

Composabilita per tenant

Un admin puo:

  • Aggiungere extractor custom -- es. un nodo che estrae il codice ATECO per documenti aziendali.
  • Disabilitare classifier -- forzando tutto come TRUTH per casi semplici.
  • Cambiare embedder -- es. per multilingua avanzato (V2).
  • Aggiungere sink custom -- es. mirror su un data lake esterno.

Tutto senza modificare il backend: basta duplicare il canvas di ingestione e modificarlo.

Migrazione dei preset

I preset legacy avevano sezioni chunking, retrieval, reranker, ecc. Oggi conservano solo chunking (chunkSize, overlapPercentage). Tutto il resto e nel canvas. Vedi Preset RAG (slim) per i dettagli del nuovo schema preset.

Limiti V1

  • Parser audio/video deferred a V2.
  • Connettori email/SharePoint/GDrive non ancora attivi nei canvas (architettura pronta, implementazione successiva).
  • Graph ingestion (entity extraction + community detection) escluso permanentemente in V1 per costi LLM elevati.
  • OCR per scansioni di bassa qualita resta meno preciso del parser nativo Docling: per archivi pesantemente scansionati valutare miglioramento OCR a monte.

Queria v3.5.0 -- Ingestion DSL canvas-native

Queria - Document Intelligence con Cog-RAG