Salta ai contenuti

Scrivi qualcosa per cercare nel manuale

    RAG: dare all'IA i tuoi documenti

    ~ min di lettura

    Riepilogo in 30 secondi
    • RAG (Retrieval-Augmented Generation) vuol dire: prima di chiedere al modello, recupera dai tuoi documenti i pochi pezzi rilevanti alla domanda e metti solo quelli nel prompt.
    • Serve perché il modello non conosce i tuoi documenti privati, interni o recenti, e non puoi infilarli tutti nel contesto: non ci stanno e li paghi a ogni chiamata.
    • La pipeline ha due tempi. Una volta: spezzi i documenti in pezzi e ne calcoli l’embedding, un vettore che ne cattura il significato. A ogni domanda: embeddi la domanda e cerchi i pezzi più vicini.
    • Il collo di bottiglia è il recupero: se recuperi i pezzi sbagliati, il modello risponde male anche su dati giusti. La qualità del RAG è la qualità del recupero.
    • Se tutti i documenti rilevanti stanno comodi nella finestra di contesto, il RAG non ti serve: passali e basta. Il RAG è per quando non ci stanno o cambiano spesso.

    Il modello non conosce i tuoi documenti. Non ha visto il tuo manuale interno, i ticket dell’anno scorso, il contratto che devi analizzare: il suo addestramento si ferma a una certa data e a dati pubblici. Puoi incollare un documento nel prompt, ma quando i documenti sono tanti o cambiano spesso non ci stanno nella finestra e li paghi tutti a ogni chiamata. Il RAG risolve questo.

    RAG sta per Retrieval-Augmented Generation: generazione aumentata dal recupero. L’idea in una riga: invece di dare al modello tutti i documenti, ne recuperi i pochi pezzi rilevanti alla domanda e metti solo quelli nel prompt. È la differenza tra studiare a memoria un’enciclopedia e dare un esame a libro aperto, dove cerchi la pagina giusta al momento giusto.

    Il RAG ha due momenti. Uno lo fai una volta, o quando i documenti cambiano; l’altro a ogni domanda.

    L’indicizzazione, una volta. Spezzi i documenti in pezzi (chunk) di poche centinaia di parole. Per ogni pezzo calcoli un embedding: un vettore, cioè una lista di numeri, che cattura il significato del testo. Testi simili nel significato hanno vettori vicini. Salvi i vettori in un archivio (un vector store, o anche solo un file se sono pochi).

    Il recupero, a ogni domanda. Calcoli l’embedding della domanda con lo stesso modello, poi cerchi i pezzi il cui vettore è più vicino a quello della domanda. «Vicino» si misura di solito con la similarità del coseno. Prendi i primi k pezzi, i più vicini, e li metti nel prompt come contesto, chiedendo al modello di rispondere basandosi su quelli.

    L’embedding lo produce un modello dedicato (OpenAI, Voyage, o uno aperto), diverso dal modello che genera la risposta. E non è una ricerca per parole chiave: due frasi che dicono la stessa cosa con parole diverse hanno vettori vicini, quindi il recupero trova il senso, non la stringa esatta.

    Il cuore del recupero è più semplice di quanto sembri. La parte che conta: dati gli embedding dei pezzi e quello della domanda, la similarità del coseno e la scelta dei primi k. In produzione useresti un vector store vero; qui la logica nuda.

    # embed(testo) -> vettore: lo fornisce un modello di embedding
    # (es. OpenAI text-embedding-3-small, Voyage, o un modello aperto)
    def coseno(a, b):
    prod = sum(x * y for x, y in zip(a, b))
    na = sum(x * x for x in a) ** 0.5
    nb = sum(y * y for y in b) ** 0.5
    return prod / (na * nb)
    # indicizzazione (una volta)
    pezzi = ["Il rimborso si richiede entro 30 giorni.",
    "La spedizione standard arriva in 3-5 giorni.",
    "La garanzia copre i difetti per 24 mesi."]
    indice = [(p, embed(p)) for p in pezzi]
    # recupero (a ogni domanda)
    domanda = "Quanto tempo ho per farmi rimborsare?"
    qv = embed(domanda)
    migliori = sorted(indice, key=lambda item: coseno(qv, item[1]), reverse=True)[:2]
    contesto = "\n".join(p for p, _ in migliori)
    # generazione: passi SOLO i pezzi recuperati
    message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=512,
    system="Rispondi solo in base al contesto fornito. Se non c'è, dillo.",
    messages=[{"role": "user",
    "content": f"Contesto:\n{contesto}\n\nDomanda: {domanda}"}],
    )
    print(message.content[0].text)

    Il modello vede solo i due pezzi più vicini alla domanda, non tutti i documenti. Il system prompt («rispondi solo in base al contesto») lo ancora ai dati recuperati e gli dice di ammettere quando la risposta non c’è, invece di inventarla.

    Il RAG non è gratis: aggiunge un modello di embedding, un archivio, e un pezzo di codice da mantenere. Prima di montarlo, chiediti se ti serve davvero.

    • Ti serve quando i documenti sono troppi per stare nella finestra, cambiano spesso, o sono privati e vuoi risposte ancorate a fonti tue e citabili.
    • Non ti serve se tutti i documenti rilevanti stanno comodi nel contesto: in quel caso passali direttamente (long context). È più semplice e non rischi di recuperare il pezzo sbagliato. La finestra grande qui è un vantaggio, come hai visto in Il contesto in token.
    • C’è una via gestita, senza codice: i Progetti di Claude, i Custom GPT e i Gem fanno una versione di questo per te, ed è il tema di Riusare contesto con i progetti. Il RAG che monti a mano serve quando vuoi controllo, scala, o integrazione nel tuo sistema.

    Il RAG dà al modello i dati giusti al momento giusto. Il passo successivo è lasciargli decidere da solo quali strumenti usare e in che ordine per portare a termine un compito in più passi. È il tema di Agenti.