Torna alle guide
Architettura·25 maggio 2026·Aggiornato il 23 settembre 2026·14 min di lettura

Guida all'implementazione RAG — generazione aumentata dal recupero in produzione con Claude e Gemini

Costruisci un sistema RAG di produzione che funzioni davvero, non solo una demo di 100 righe. Copre la strategia di suddivisione in chunk, la scelta del modello di embedding, il ranking del recupero, la struttura dei prompt, il controllo delle allucinazioni e come mantenere i costi mensili nell'ordine delle centinaia anche con 10.000 query al giorno.

Ultima revisione: .

La maggior parte delle demo RAG muore in produzione. Funziona sul set dimostrativo di 10 domande e si rompe nel momento in cui gli utenti chiedono qualcosa di nuovo. Questa guida illustra l'architettura e gli schemi che resistono: chunking che rispetta i confini semantici, recupero ibrido che intercetta query sia approssimate sia letterali, struttura del prompt che previene le allucinazioni e disciplina dei costi che mantiene stabile la fattura durante la scalabilità.

Le cinque decisioni che contano

  1. Strategia di chunking — influisce sulla qualità del recupero più del modello
  2. Modello di embedding — determina il limite superiore del recall
  3. Strategia di recupero — il vettoriale da solo non basta
  4. Struttura del prompt — determina se il modello ha allucinazioni
  5. Modello di generazione + caching — determina l'economia unitaria

1. Chunking — la semplicità batte l'ingegnosità

Non pensarci troppo. Splitter ricorsivo di caratteri con 1000-1500 caratteri per chunk e sovrapposizione di 100-200 caratteri. Tenta di interrompere prima sui confini dei paragrafi, poi sulle frasi e infine sulle parole. Stabile tra i domini:

chunk.py
# Recursive character splitter with overlap — the boring choice that wins
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_text(document)
# 1200 chars ≈ 300-400 tokens — fits 5+ chunks in a Sonnet context window
# with room for system prompt + question + answer

Le cose che sembrano più intelligenti ma raramente aiutano: modelli consapevoli delle frasi, splitter consapevoli del markdown e finestre scorrevoli con embedding. Non sono sbagliate: sono marginalmente migliori a fronte di uno sforzo enorme. Investi invece quello sforzo nel recupero.

2. Modello di embedding — text-embedding-3-large è l'impostazione predefinita

Gli embedding non sono forniti da Kunavo: chiama direttamente un provider di embedding per questo passaggio. /v1/embeddings è un formato wire implementato senza alcun modello abilitato dietro di esso, quindi una richiesta a questo endpoint fallisce; GET /v1/models è sempre l'autorità su ciò che è chiamabile. In pratica, ciò non costa nulla a una pipeline RAG oltre a una seconda chiave: la chiamata di embedding e quella di generazione sono comunque richieste separate, quindi crea gli embedding con OpenAI, Voyage o Cohere e genera con Kunavo.

text-embedding-3-large offre il miglior compromesso tra supporto multilingue e accuratezza per la maggior parte dei casi d'uso in produzione, alla tariffa pubblicata da OpenAI: controllala sulla loro pagina dei prezzi anziché qui, poiché non la rivendiamo e non dovremmo riportarne un importo. Ricrea gli embedding quando cambi la strategia di chunking o il modello stesso, non quando il contenuto si aggiorna (aggiungi semplicemente nuovi chunk).

Eccezioni in cui funzionano embedding più piccoli: recupero di brevi testi esclusivamente in inglese (FAQ, schede prodotto). Per questi casi text-embedding-3-small costa 4 volte meno con una perdita di qualità trascurabile. Per contenuti multilingue o tecnici, resta sul modello large.

3. Recupero — il vettoriale puro perde contro l'ibrido

La similarità vettoriale è ottima per la corrispondenza semantica approssimata ("come posso annullare" → "politica di annullamento dell'abbonamento"). È pessima con i termini letterali ("SKU-A92837" o "ordine n. 4729"). La soluzione è ibrida: ricerca per parole chiave BM25 in parallelo, poi reciprocal rank fusion:

retrieve.py
# Hybrid retrieval: vector similarity + BM25 keyword scoring
# Pure vector misses literal keywords (product SKUs, IDs, dates)
def retrieve(question: str, k: int = 5) -> list[dict]:
    q_embed = embed([question])[0]
    vector_hits = vector_db.similarity_search(q_embed, k=k * 2)
    keyword_hits = bm25_search(question, k=k * 2)

    # Reciprocal rank fusion — simple, robust
    scores: dict[str, float] = {}
    for rank, hit in enumerate(vector_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)
    for rank, hit in enumerate(keyword_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)

    top_ids = sorted(scores, key=scores.get, reverse=True)[:k]
    return [chunk_by_id[i] for i in top_ids]

Questo è il più grande incremento singolo della qualità dopo la scelta di un modello di embedding valido. Tieni traccia di recall@5 su una valutazione separata di 100 domande: se passa dal 70% al 90% dopo l'aggiunta di BM25, hai appena eliminato un terzo dei fallimenti "non lo so".

4. Struttura del prompt — tre schemi che prevengono le allucinazioni

answer.py
# Final RAG prompt structure — three sections, citations enforced
SYSTEM_PROMPT = """You answer based exclusively on the supplied Context.
- Cite the [doc_id] for each factual claim.
- If the context doesn't answer the question, say "I don't have that
  information" — do not extrapolate or use general knowledge.
- Be concise. No throat-clearing."""

def answer(question: str) -> dict:
    chunks = retrieve(question, k=5)
    context = "\n\n---\n\n".join(
        f"[doc:{c['id']}] {c['text']}" for c in chunks
    )
    resp = client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=[
            {"role": "system", "content": [{
                "type": "text",
                "text": SYSTEM_PROMPT,
                "cache_control": {"type": "ephemeral"},
            }]},
            {"role": "user", "content": f"# Context\n{context}\n\n# Question\n{question}"},
        ],
        max_tokens=600,
    )
    return {
        "text": resp.choices[0].message.content,
        "sources": [c["id"] for c in chunks],
    }

Tre elementi imprescindibili nel prompt di sistema:

  • Cita le fonti — fai allegare al modello [doc:42] a ogni affermazione. Se l'ID citato non è reale, hai intercettato un'allucinazione
  • Rifiuta esplicitamente — "se il contesto non risponde, dì che non hai quell'informazione". Senza questa istruzione il modello completa usando la conoscenza del mondo
  • Output conciso — le risposte brevi correlano con risposte accurate. Un massimo di 600 token è una buona impostazione predefinita per la produzione

5. Modello di generazione + caching — il livello dei costi

Claude Sonnet 4.6 è l'impostazione predefinita. Claude Haiku 4.5 è l'alternativa economica se hai vincoli di costo. Avvolgi sempre il prompt di sistema in cache_control: il prompt di sistema è uguale a ogni chiamata, quindi il caching riduce quella porzione al 10% della tariffa di input.

Costo realistico per query su scala di produzione (5K di contesto, 500 di output, caching attivo):

ModelloCosto per queryCon 10.000 query/giornoQualità rispetto a Sonnet
claude-sonnet-4-6~$0,007~$70/giornoBase di riferimento
claude-haiku-4-5~$0,001~$10/giorno~85%, 4 volte più economico

Con 10.000 query/giorno → $70/giorno su Sonnet, $10/giorno su Haiku. Scegli Sonnet per casi d'uso ad alta criticità (rivolti ai clienti, legali, medici), Haiku per l'elaborazione interna o in blocco.

Cosa si rompe in produzione (e come intercettarlo)

  • Deriva della distribuzione: il corpus di addestramento smette di corrispondere alle domande reali degli utenti. Campiona 100 query/settimana e controlla manualmente recall@5
  • Embedding obsoleti: i documenti sorgente vengono aggiornati ma gli embedding non vengono rinnovati. Monitora settimanalmente le dimensioni dell'indice rispetto a quelle della fonte
  • Citazioni false: il modello inventa ID di documenti apparentemente reali. Valida ogni [doc:N] rispetto all'elenco effettivo degli ID recuperati prima del rendering
  • Picchi di latenza: il database vettoriale collassa oltre un milione di vettori. Usa l'indicizzazione HNSW e, in caso di multi-tenancy, partiziona per tenant

Roadmap di produzione in 4 settimane

  1. Settimana 1: prototipo con 100 documenti, 10 domande di test, valutazione manuale
  2. Settimana 2: scala all'intero corpus, crea il recupero ibrido e scrivi un set di valutazione di 100 domande
  3. Settimana 3: ottimizza chunking + prompt di sistema iterando sulla valutazione e distribuisci agli utenti interni
  4. Settimana 4: monitoraggio, budget dei costi, rilascio pubblico con interfaccia per citare le fonti

Domande frequenti

Quale dimensione dei chunk dovrebbe usare un sistema RAG in produzione?

1.000–1.500 caratteri per chunk con una sovrapposizione di 100–200 caratteri, suddivisi con uno splitter ricorsivo di caratteri che interrompe prima sui confini dei paragrafi, poi sulle frasi e infine sulle parole. I modelli consapevoli delle frasi, gli splitter consapevoli del markdown e le finestre scorrevoli sono marginalmente migliori a fronte di uno sforzo molto maggiore: quello sforzo viene ripagato meglio se investito nel recupero.

Quale modello di embedding dovrebbe usare una pipeline RAG?

text-embedding-3-large è l'impostazione predefinita per contenuti multilingue e tecnici e, per il recupero di brevi testi esclusivamente in inglese come FAQ e schede prodotto, text-embedding-3-small costa circa 4 volte meno con una perdita di qualità trascurabile. Nota che gli embedding non sono forniti da Kunavo: /v1/embeddings è un formato wire implementato senza alcun modello abilitato dietro di esso, quindi il passaggio di embedding chiama direttamente OpenAI, Voyage o Cohere, mentre il passaggio di generazione chiama Kunavo. In ogni caso, ciò non costa nulla alla pipeline oltre a una seconda chiave, poiché le due operazioni sono comunque richieste separate.

La ricerca vettoriale da sola è sufficiente per il recupero RAG?

No. La similarità vettoriale gestisce bene la corrispondenza semantica approssimata, ma fallisce con termini letterali come uno SKU o un numero d'ordine. Eseguire in parallelo una ricerca per parole chiave BM25 e combinare i risultati con la reciprocal rank fusion porta tipicamente recall@5 da circa il 70% a circa il 90%: il più grande incremento singolo della qualità dopo la scelta di un modello di embedding valido.

Come si impedisce a un sistema RAG di avere allucinazioni?

Tre schemi nel prompt di sistema: richiedere una citazione [doc:N] per ogni affermazione fattuale, istruire il modello a dire di non avere l'informazione quando il contesto non fornisce una risposta e mantenere brevi le risposte: un massimo di 600 token di output è una buona impostazione predefinita per la produzione. Poi valida ogni id citato rispetto all'insieme recuperato prima del rendering: un id inesistente è un'allucinazione intercettata.

Quanto costa una query RAG in produzione?

Con 5K token di contesto e 500 token di output, con il caching del prompt attivo: circa $0,007 per query su Claude Sonnet 4.6 e circa $0,001 su Claude Haiku 4.5 (4 volte più economico, con circa l'85% della qualità). Con 10.000 query al giorno, sono circa $70/giorno su Sonnet e circa $10/giorno su Haiku.