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
| Aspetto | LEGACY (v3.1.x) | DSL V2 (v3.5.0+) |
|---|---|---|
| Parser | Pipeline PDF unica | Auto-router MIME, Docling + Unstructured come peer |
| Chunking | 14 chunker domain-specific in codice | 5 chunker per ruolo + preset di dominio configurabili |
| Ruoli documento | Non modellati | TRUTH / FORMAT / RULES / OPERATIONAL / EXAMPLES |
| Cleanup | Re-embedding manuale | RecordManager con 4 modalita (UPSERT, FULL, INCREMENTAL, SCOPED) |
| Resume | No | Pipeline snapshot + checkpoint |
| Multi-sink | Solo Qdrant + Postgres | Qdrant + Postgres + OperationalData + RecordManager come nodi |
| Connettori | Solo upload + scrape | V2-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:
| Ruolo | Esempi tipici | Trattamento |
|---|---|---|
| TRUTH | Manuali, normative, policy, libri | Chunking semantico per paragrafi/articoli |
| FORMAT | Template, formulari, schemi | Estrazione struttura + placeholder |
| RULES | Decisioni, regolamenti, sentenze | Chunking per articolo/clausola con citazioni |
| OPERATIONAL | Listini, tabelle prezzi, dati strutturati | Estrazione per riga, sink OperationalData |
| EXAMPLES | Casi studio, scenari, FAQ | Chunking 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/2001non solo il titolo del documento. - Filtro automatico di vigenza: regole con
dateRepealed < oggisono 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
publicointernal. Le sentenze interne (es. arbitrati) possono essereconfidential.
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_operationalscrive le righe inOperationalDataRow(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
| Aspetto | Topic | Raccolte |
|---|---|---|
| Scopo | Tassonomia stabile (es. "Contratti", "HR", "Legale") | Aggregazioni temporanee o di lavoro (es. "Pratica Rossi 2026", "Audit Q1") |
| Cardinalita tipica | 3-15 per azienda | Decine o centinaia, possono nascere e morire |
| Vita | Lunga, raramente eliminata | Aperta -> Chiusa -> archiviata |
| Permessi | RBAC granulare per ruolo | Eredita ACL dai documenti contenuti |
| Customizzazione AI | Prompt di sistema dedicato | No prompt dedicato |
| Identificata in chat | Selettore Topic in alto | Filtro/Mention @raccolta nel campo input |
| Modello DB | Topic (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:
| Status | Significato | Effetto nella chat |
|---|---|---|
OPEN | Lavoro attivo | Inclusa nei suggerimenti @raccolta, retrieval prioritario quando filtrata |
CLOSED | Lavoro chiuso, archivio | Esclusa 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:
- Wizard Single (step Confirm) -- combobox "Raccolta" -- esistente o
+ Nuova. - 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).
- 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
@raccoltaper attivare un autocomplete delle RaccolteOPENaccessibili: 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
| Metodo | Endpoint | Scopo |
|---|---|---|
GET | /api/document-groups | Lista Raccolte (filtri per status, search, owner) |
POST | /api/document-groups | Crea Raccolta |
PATCH | /api/document-groups/:id | Rinomina, cambia status, Topic associato |
DELETE | /api/document-groups/:id | Elimina (i documenti restano, perdono solo il link alla Raccolta) |
POST | /api/document-groups/:id/documents | Aggiunge documenti |
DELETE | /api/document-groups/:id/documents/:docId | Rimuove un documento |
Feature flag
La sidebar mostra Raccolte solo se il tenant ha la feature attiva:
ENABLE_DOCUMENT_GROUPS=trueDisabilitando 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
- Una Raccolta = una pratica / progetto -- evita di usare Raccolte per fare ulteriore tassonomia (per quello ci sono i Topic).
- Chiudi le Raccolte concluse -- il working set di un utente attivo dovrebbe avere 5-20 Raccolte
OPEN, non centinaia. - Nominale parlanti --
Pratica Rossi 2026e meglio diR-2026-01. La chat usa il nome come hint per il planner. - 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-- aggiornaDocumentsu Postgres.sink_operational-- per role OPERATIONAL, scrive righe tabellari suOperationalDataRow.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:
| Modalita | Comportamento |
|---|---|
LEGACY | Pipeline vecchia (no role, parser unico). Default per company create prima della v3.5.0 |
SHADOW | LEGACY in primo piano, V2 in parallelo. Genera un report di confronto, nessun impatto utente |
V2 | Cutover completo al canvas DSL |
Migrazione consigliata
- Imposta
SHADOWsu un tenant pilota. - Verifica per ~1 settimana il
shadow_comparison_report(precisione, recall, latenza). - Passa a
V2quando i numeri sono pari o migliori. - 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:
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:
- Upload -- drag & drop o selezione file. Sorgente: locale, cloud, rete, URL.
- 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). - 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:
- 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).
- 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).
- Job in progress --
LiveJobProgressvia 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:
| Pattern | Override topic | Override role |
|---|---|---|
gdrive:/Legale/**/*.pdf | Contratti | TRUTH |
gdrive:/Legale/Sentenze/**/*.pdf | Giurisprudenza | RULES |
smb://share/Listini/**/*.xlsx | Listini | OPERATIONAL |
**/FAQ*.md | Help center | EXAMPLES |
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:
| Colonna | Editabile |
|---|---|
| Checkbox di selezione | -- |
| Filename | preview a click (Sheet laterale) |
| Stato | success / warning / error |
| Topic | inline (multi-select) |
| Raccolta | inline |
| Sector | inline |
| Visibilita | inline |
| AI confidence | progress 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-*.tsper stampigliare nuovi campi (sector, role, grade, aisummary) senza re-embedding.
Stato e monitoraggio
Ogni job di ingestione produce:
IngestionJobsu Postgres con stato (RUNNING, COMPLETED, FAILED, RESUMED).PipelineSnapshotper 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