AI & MCP

RAG sulla documentazione: chunk, indice e risposte con fonti

Un RAG sulla documentazione taglia i testi in chunk, li indicizza con un embedding e risponde citando il file: la qualità si decide nel taglio, non nel modello.

In sintesi

  • 50 file markdown da 2.947.280 byte diventano 2.945 chunk da 1.200 caratteri con 200 di sovrapposizione.
  • L'indice vive in Postgres: pgvector 0.3.0 pesa 52.767 byte con 0 dipendenze e Node 22 come minimo.
  • Ogni risposta deve portare il file e l'indice del chunk, altrimenti non è verificabile.

Un RAG sulla documentazione è un indice di chunk con i loro embedding più un prompt che riceve solo i passaggi pertinenti: risponde citando il file, invece di indovinare. La qualità si decide nel taglio dei testi, prima del modello.

Il chunk è l'unità di lavoro

Un chunk troppo grande porta rumore nel prompt e costa token; uno troppo piccolo perde il soggetto della frase. Il taglio a 1.200 caratteri con 200 di sovrapposizione tiene insieme un paragrafo e permette di citare il file.

  • 1.200 caratteri: circa 300 token, quindi cinque chunk riempiono 1.500 token di contesto.
  • 200 caratteri di sovrapposizione: una frase a cavallo del taglio compare intera in almeno un chunk.
  • Un file, molti chunk: LIBRARIES.md da solo produce 2.470 chunk e va diviso per libreria o escluso.

L'indice in Postgres

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE doc_chunks (
  id bigserial PRIMARY KEY,
  source text NOT NULL,
  chunk_index int NOT NULL,
  content text NOT NULL,
  embedding vector(1536) NOT NULL
);

CREATE INDEX doc_chunks_embedding_idx
  ON doc_chunks USING hnsw (embedding vector_cosine_ops);

Lo schema sta in schema.sql e si applica con psql "$DATABASE_URL" -f schema.sql. La colonna vector(1536) ha la dimensione dell'embedding: se cambi modello cambi la colonna e ricostruisci l'indice. L'indice HNSW rende la ricerca approssimata, quindi veloce ma non esatta.

Il taglio, in codice

const SIZE = 1200;
const OVERLAP = 200;

export function chunk(text: string): string[] {
  const out: string[] = [];
  for (let i = 0; i = text.length) break;
  }
  return out;
}

La funzione è deterministica: lo stesso file produce gli stessi chunk, quindi puoi confrontare un indice vecchio con uno nuovo. Salva source e chunk_index a ogni riga: senza quei due campi la risposta non è verificabile.

La risposta cita la fonte

  • Cinque chunk, non venti: il prompt cresce in modo lineare e il costo pure.
  • Una soglia scelta da te, per esempio 0,3 di distanza coseno: sotto quella soglia il modello riceve «non lo so» invece del contesto.
  • Fonte in chiaro: ogni frase della risposta porta il file e l'indice del chunk.

La limitazione

L'indice invecchia: se aggiorni un file senza ricalcolare i suoi chunk, il RAG risponde con la versione vecchia e la cita con sicurezza. Serve un'impronta del file per riga e un job che ricostruisce solo i file cambiati; nel nostro caso il job non è ancora automatico. Il taglio a caratteri, inoltre, non coincide con i confini di frase.

Dove continuare

Per scegliere dove tenere i vettori, il confronto fra SQLite e Postgres in un progetto piccolo; il prompt che riceve i chunk è in prompt engineering per agenti; il protocollo per esporre la ricerca a un client è in che cos'è MCP. Le parti pronte per una chat con fonti sono in componenti chat, per esempio AI chat input di Jahed e chatgpt prompt input, dentro 111.985 componenti.

Domande frequenti

Quanti chunk servono per un corpus piccolo?

Nel nostro caso 50 file markdown da 2.947.280 byte diventano 2.945 chunk da 1.200 caratteri con 200 di sovrapposizione, misurati il 1 novembre 2026. Sotto i 10.000 chunk un indice HNSW in Postgres basta; sopra, misura la latenza prima di aggiungere un servizio.

Meglio pgvector o un servizio di vector database?

pgvector se i documenti stanno già in Postgres: una tabella, un indice e nessun servizio in più. Un servizio esterno conviene quando vuoi la ricerca gestita e accetti un'altra dipendenza; pgvector 0.3.0 pesa 52.767 byte con 0 dipendenze.

Come scelgo la dimensione del chunk?

Parti da 1.200 caratteri con 200 di sovrapposizione e misura: se le risposte citano mezze frasi, alza la sovrapposizione; se il prompt costa troppo, abbassa la dimensione. Il numero giusto dipende dal testo, non dal modello.

Il RAG elimina le allucinazioni?

No. Riduce lo spazio di invenzione perché il modello vede solo i chunk recuperati, ma può ancora unire due passaggi o ignorare la fonte. Una soglia di distanza e una risposta «non lo so» aiutano più di un prompt severo.

Come aggiorno l'indice quando cambia un documento?

Salva un'impronta del file a ogni chunk e ricalcola solo i file la cui impronta è cambiata. Senza questo passaggio l'indice risponde con la versione vecchia e la cita come attuale.