Avanzato 12 minAvanzato

Agentic RAG in locale: quando vale la peine

L'agentic RAG affida a un modello la guida della ricerca: decide cosa cercare, legge ciò che ha trovato, poi rilancia una query se gli manca qualcosa. Questo ciclo ha un prezzo, soprattutto su una macchina locale, dove ogni giro si paga in tempo di generazione. Questa guida spiega cosa cambia rispetto a un RAG classico e in quali casi questo costo aggiuntivo è giustificato, e mostra un esempio completo con un modello servito da Ollama e LangGraph.

Di Samir K.·Agg. 2026-11-02·Testato su Windows, macOS e Linux

#L'agentic RAG in breve

Un RAG classico segue un percorso fisso: la domanda avvia una ricerca, i passaggi trovati finiscono nel prompt, il modello risponde. L'agentic RAG sostituisce questo percorso con un ciclo. Il modello dispone di strumenti di ricerca e sceglie da solo se chiamarli, con quale query e quante volte, finché non ritiene di poter rispondere o di dover rinunciare.

Il corso AI Agents for Beginners di Microsoft lo descrive come un funzionamento in cui il modello pianifica i passaggi successivi mentre recupera informazioni da fonti esterne, con un ciclo di tipo «maker-checker»: produrre, verificare, correggere. Lo stesso corso ricorda il limite di questa autonomia: si esercita all'interno del compito e degli strumenti che hai fornito.

La conclusione pratica sta in una frase. Se il tuo RAG fallisce perché una sola ricerca non riesce a raccogliere tutta la risposta, un loop può aiutare. Se fallisce perché l'ingestione o il chunking sono fatti male, non farà che ripetere il problema, più lentamente.

i
Cosa non afferma questa guida
Qui non viene annunciato alcun guadagno di precisione, in mancanza di misurazioni sui tuoi dati. Per questa guida non è stato eseguito alcun benchmark: gli ordini di grandezza sono calcoli illustrativi, segnalati come tali.

#Agentic RAG o RAG in un solo passaggio: la differenza

Il kit completo per creare il tuo RAG locale.

Approfitta della nostra esperienza e di un percorso guidato per costruire il tuo RAG locale e lavorare con i tuoi documenti.

  • Spazio online a vita
  • PDF + file
  • Garanzia di rimborso di 30 giorni

I componenti di base restano gli stessi: embedding, database vettoriale, modello di generazione. Se non ti sono familiari, inizia dall'introduzione al RAG locale. Ciò che cambia è chi decide la sequenza. In un RAG classico è il tuo codice; in un RAG agentico è il modello, entro i limiti che il codice gli impone.

Query
RAG classico: la domanda dell'utente, talvolta riscritta una volta. Agentico: il modello formula le proprie query e può scomporre una domanda in più domande.
Numero di ricerche
RAG classico: una, nota in anticipo. Agentico: variabile da una domanda all'altra, entro un limite che fissi tu.
Fonti
RAG classico: in genere un solo indice. Agentico: più strumenti possibili (indice vettoriale, ricerca per parole chiave, database SQL), tra cui il modello sceglie.
Controllo del risultato
RAG classico: nessuno, il modello scrive con ciò che ha ricevuto. Agentico: il modello può giudicare insufficienti i passaggi e rilanciare la ricerca, il che non è una garanzia: può anche sbagliare questo giudizio.
Requisito per il modello
RAG classico: qualsiasi modello in grado di scrivere a partire da estratti. Agentico: un modello che gestisce correttamente la chiamata di strumenti.
Prevedibilità
RAG classico: tempo di risposta abbastanza regolare. Agentico: tempo variabile e percorso da controllare nelle tracce.

Il ciclo è composto da tre meccanismi. La pianificazione: scomporre la domanda in sotto-domande. L'uso degli strumenti: tradurre ogni sotto-domanda in una chiamata. Il rilancio: leggere il risultato e decidere se riformulare o concludere. Per l'architettura generale di un agente (memoria, misure di sicurezza, supervisione), consulta la guida dedicata: qui ci interessa solo il ciclo di ricerca.

Esiste anche una via di mezzo che spesso si dimentica: la pipeline a fasi fisse. Una riscrittura della query, una ricerca ibrida e poi un reranker vengono eseguiti sempre nello stesso ordine, senza decisioni del modello. Non è agentico, ed è spesso la prima cosa da provare.

#Quando vale la pena usare l'agentic RAG

Il criterio giusto riguarda la forma delle tue domande, non il livello di sofisticazione desiderato. Guarda i fallimenti reali del tuo RAG attuale, poi classificali.

Domande multi-hop
La risposta a una prima ricerca determina la seconda: trovare il fornitore di un contratto, poi la clausola che si applica a quel fornitore.
Confronti tra documenti
«Confronta il preavviso dei contratti A e B» richiede due ricerche distinte. Una sola query può restituire cinque passaggi dello stesso contratto.
Più fonti di natura diversa
Un dato numerico in un database SQL e una spiegazione nei PDF. Il modello sceglie lo strumento in base alla domanda, come descrive il corso di Microsoft per le interazioni complesse con un database.
Tolleranza all'attesa
Ricerca documentale, preparazione di un dossier, elaborazione in background: qualche decina di secondi in più non disturba nessuno se il risultato viene riletto.
Correttezza prioritaria rispetto alla velocità
Quando una risposta incompleta costa cara, una fase di verifica è difendibile, a condizione di mantenere una revisione umana sulle decisioni impegnative.

Al contrario, resta su un RAG semplice in queste situazioni:

FAQ e domande con una sola fonte
La risposta si trova in un passaggio: basta una ricerca, un ciclo aggiunge solo attesa.
Assistente conversazionale su hardware modesto
L'utente si aspetta una risposta rapida e il modello gira già vicino ai suoi limiti.
Ricerca che non trova i passaggi giusti
Correggi prima la suddivisione, gli embedding e la ricerca ibrida. Rilanciare una ricerca mal configurata produce altri risultati scadenti.
Calcolo esatto su dati strutturati
Una query SQL scritta e testata è più affidabile di un agente che ne improvvisa una.
Collegamenti multipli tra entità su tutto il corpus
Se l'esigenza è esplorare relazioni su larga scala, un approccio basato su grafi risponde a un altro problema: vedi la guida GraphRAG.

#Il costo in latenza e in modello

Nel cloud, ogni giro del ciclo si vede in fattura. In locale, si vede sull'orologio e nella memoria. Contano tre voci.

#La latenza si moltiplica

Per N ricerche, il modello genera N + 1 volte: una volta prima di ogni ricerca, un'ultima per la risposta. Un RAG a passaggio singolo genera una volta sola. Poiché il prompt si allunga a ogni giro, ogni generazione rilegge più testo, tranne ciò che la cache di contesto del runtime permette di riutilizzare. Il tempo totale dipende dal modello, dalla quantizzazione e dalla macchina; misuralo sul tuo sistema invece di fidarti di un dato generico.

#Il contesto si riempie in fretta

Calcolo illustrativo: 4 passaggi da 1 200 caratteri rappresentano circa 1 400 token, considerando da 3 a 4 caratteri per token. Cinque ricerche aggiungono quindi circa 7 000 token, senza contare la domanda, le istruzioni e i ragionamenti del modello. Con num_ctx a 8192, il margine è quasi scomparso. Ollama tronca allora il contesto, e i primi risultati, talvolta le istruzioni, possono uscire dalla finestra senza che l'applicazione lo segnali chiaramente.

Aumentare num_ctx consuma più memoria e rallenta i giri successivi. Spesso conviene restituire meno passaggi, o riassumerne il contenuto prima di reinserirlo.

#Il modello deve reggere il ciclo

Il modello deve produrre chiamate agli strumenti ben formate, poi giudicare se ciò che ha letto è sufficiente. Verifica il supporto con ollama show: la sezione Capabilities deve menzionare tools. Questa verifica dice che il formato è gestito, non che il modello prende buone decisioni.

RTX 3060 12 GB o RTX 4070 12 GB
Un modello da 7 a 9B in Q4_K_M (riferimento del sito: 7B ≈ 5 GB) lascia spazio per il contesto e l'embedding. È il punto di partenza dell'esempio qui sotto, con qwen3.5:9b.
RTX 4080 16 GB
Un 14B in Q4_K_M (≈ 9 GB) ci sta con un margine più comodo per un contesto ampliato.
RTX 4090 24 GB o M4 Pro da 24 a 48 GB
Diventa possibile considerare un modello più grande, come qwen3.8:27b (circa 18 GB). Con 24 GB, tieni d'occhio quanta memoria resta dopo aver ampliato il contesto.
!
Il continuo passaggio tra due modelli
Il ciclo alterna embedding e generazione. Se i due modelli non stanno insieme in memoria, Ollama può scaricarne uno per caricare l'altro a ogni giro, e questo tempo si somma a tutti gli altri. Controlla con ollama ps durante un'esecuzione e regola OLLAMA_MAX_LOADED_MODELS se necessario.

#Scegliere un orchestratore

L'orchestratore esegue il loop: trasmette le chiamate agli strumenti, restituisce i risultati al modello e applica i limiti. Esistono tre strade, tutte compatibili con un modello locale.

Ciclo scritto a mano
Una ventina di righe attorno all'API di chat di Ollama e al suo parametro tools. Adatto per capire il meccanismo o per un caso molto semplice. I limiti e le tracce dovrai scriverli tu.
LangGraph
Il ciclo è un grafo esplicito di nodi e archi, con stato, limite di ricorsione e streaming dei passaggi. Una buona scelta se vuoi controllare ogni transizione. È quella del nostro esempio.
LlamaIndex
Interessante se la tua ingestione e i tuoi indici sono già lì: i suoi agenti riutilizzano i tuoi motori di query come strumenti.

Non scegliere in base alla popolarità. Se la tua pipeline funziona già con un framework, restaci. In ogni caso, blocca le versioni: queste librerie evolvono rapidamente e le loro funzioni predefinite cambiano. Il nostro esempio scrive quindi il grafo esplicitamente, il che rende anche leggibile il ciclo.

#Prerequisiti

Ollama
Installato e in ascolto su http://localhost:11434.
Python
Un ambiente virtuale recente. Verifica la versione richiesta dai pacchetti al momento dell'installazione.
Documenti
Alcuni file Markdown o di testo in una cartella mes_documents. Per i PDF, riprendi l'ingestione del tutorial ChromaDB.
Memoria
Circa 6 GB liberi per un modello da 9B e il suo contesto, di più se aumenti num_ctx.

#Esempio di agentic RAG con LangGraph e Ollama

L'esempio costruisce un agente che dispone di un solo strumento di ricerca, basato su un indice ChromaDB locale. Resta volutamente minimale perché il ciclo sia visibile.

!
Codice non eseguito per questa guida
Questo codice segue le API documentate di queste librerie, ma non è stato eseguito durante la stesura e nessun output è riprodotto qui. Testalo su un piccolo corpus e consulta la documentazione della versione che installi.

#Passaggio 1: installare i modelli e i pacchetti

Terminale
ollama pull qwen3.5:9b
ollama pull nomic-embed-text
ollama show qwen3.5:9b

python -m venv .venv
source .venv/bin/activate
pip install langgraph langchain-ollama chromadb ollama
pip freeze > requirements.txt

#Passaggio 2: indicizzare i documenti

La suddivisione per caratteri serve solo a mantenere breve l'esempio. Su un corpus reale, usa una suddivisione che rispetti la struttura e un modello di embedding validato sui tuoi testi in francese.

index.py
from pathlib import Path

import chromadb
import ollama

EMBED_MODEL = "nomic-embed-text"

client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection(
    "docs", metadata={"hnsw:space": "cosine"}
)


def decouper(texte, taille=1200, recouvrement=150):
    pas = taille - recouvrement
    return [texte[i:i + taille] for i in range(0, len(texte), pas)]


for fichier in Path("mes_documents").glob("*.md"):
    morceaux = decouper(fichier.read_text(encoding="utf-8"))
    if not morceaux:
        continue
    vecteurs = ollama.embed(model=EMBED_MODEL, input=morceaux)["embeddings"]
    collection.upsert(
        ids=[f"{fichier.name}-{i}" for i in range(len(morceaux))],
        documents=morceaux,
        embeddings=vecteurs,
        metadatas=[{"source": fichier.name} for _ in morceaux],
    )

print(collection.count(), "passages indexés")

#Passo 3: scrivere lo strumento e il ciclo

La descrizione dello strumento conta: è quella che il modello legge per decidere se chiamarlo. L'istruzione di sistema impone di cercare prima di rispondere, di separare le sotto-domande e di ammettere l'assenza di informazioni.

agent.py
import chromadb
import ollama
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from langgraph.graph import START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition

EMBED_MODEL = "nomic-embed-text"
collection = chromadb.PersistentClient(path="./chroma_db").get_collection("docs")


@tool
def chercher_documents(requete: str) -> str:
    """Cherche dans les documents internes. Utiliser une requête courte
    portant sur un seul sujet. Renvoie des passages précédés de leur source."""
    vecteur = ollama.embed(model=EMBED_MODEL, input=requete)["embeddings"][0]
    res = collection.query(query_embeddings=[vecteur], n_results=4)
    passages = zip(res["documents"][0], res["metadatas"][0])
    return "\n\n".join(f"[{m['source']}] {d}" for d, m in passages) or "Aucun passage."


outils = [chercher_documents]
llm = ChatOllama(model="qwen3.5:9b", temperature=0, num_ctx=8192).bind_tools(outils)

CONSIGNE = SystemMessage(content=(
    "Tu réponds uniquement à partir des résultats de chercher_documents. "
    "Si la question porte sur plusieurs sujets, fais une recherche par sujet. "
    "Si les passages ne suffisent pas, reformule une fois puis dis clairement "
    "ce que tu n'as pas trouvé. Cite la source entre crochets."
))


def agent(state: MessagesState):
    return {"messages": [llm.invoke([CONSIGNE] + state["messages"])]}


graphe = StateGraph(MessagesState)
graphe.add_node("agent", agent)
graphe.add_node("tools", ToolNode(outils))
graphe.add_edge(START, "agent")
graphe.add_conditional_edges("agent", tools_condition)
graphe.add_edge("tools", "agent")
app = graphe.compile()

if __name__ == "__main__":
    question = "Compare la durée de préavis du contrat A et du contrat B."
    entree = {"messages": [HumanMessage(content=question)]}
    for etape in app.stream(entree, {"recursion_limit": 12}, stream_mode="values"):
        etape["messages"][-1].pretty_print()

#Passo 4: leggere la traccia

Il vantaggio di stream_mode="values" è che mostra ogni messaggio. Leggi la traccia prima della risposta finale, aiutandoti con questa griglia:

  1. 01
    Il modello chiama lo strumento?
    Se risponde direttamente, non sta cercando: rafforza l'istruzione o cambia modello.
  2. 02
    Le query hanno senso?
    Per il confronto, l'ideale è vedere una query per contratto. Una sola query che riprende tutta la domanda indica che la scomposizione non sta avvenendo.
  3. 03
    I passaggi rispondono alla domanda?
    Se gli estratti sono fuori tema, il problema sta nell'indice, non nell'agente.
  4. 04
    La risposta si basa sugli estratti?
    Verifica ogni citazione tra parentesi quadre nel passaggio corrispondente.

#Passo 5: limitare il ciclo

Ogni ricerca consuma due passaggi del grafo (agente, poi strumento). Un limite di 12 consente quindi cinque ricerche, poi la risposta finale. Oltre questo limite, LangGraph solleva un errore GraphRecursionError (modulo langgraph.errors): intercettalo nella tua applicazione e restituisci un messaggio esplicito, invece di lasciare l'utente davanti a uno stack trace.

#Insidie e risoluzione dei problemi

Il modello non chiama mai lo strumento
Verifica tools in ollama show, riformula la descrizione dello strumento e prova un altro modello. Non compensare con un'istruzione sempre più lunga.
Ripete la stessa ricerca
Il modello gira a vuoto. Abbassa il limite e, nello strumento, restituisci un messaggio chiaro quando la query è già stata eseguita.
La risposta dimentica i primi risultati
Probabile segno di contesto troncato. Riduci n_results, suddividi in modo più fine o aumenta num_ctx se la memoria lo consente.
Le risposte diventano molto lente
Controlla ollama ps: forse una parte del modello è sul processore, oppure i modelli si alternano in memoria. Conta anche il numero di turni nella traccia.
Inventa anche se non ha trovato nulla
Il ciclo non impedisce le invenzioni. Controlla le citazioni tramite codice: ogni fonte citata deve comparire nei risultati degli strumenti della conversazione.
Uno strumento troppo potente
Un agente che legge documenti ne riceve anche il contenuto, eventualmente ostile. Dagli strumenti in sola lettura e prevedi una convalida umana per qualsiasi azione che modifichi qualcosa.
→
Prova prima senza agente
Prima di aggiungere un ciclo, prova una scomposizione fissa: una generazione che divide la domanda in sotto-domande, una ricerca per ogni sotto-domanda, poi una risposta. Se questo risolve i tuoi confronti, mantieni un tempo di risposta prevedibile e un codice più semplice.

#Decidere in base alle tue domande

I dati pubblicati altrove riguardano altri corpus, altri modelli e altre impostazioni. Solo un confronto sui tuoi dati permette di decidere, e non richiede molto hardware.

  1. 01
    Creare un set di domande
    Prendi domande reali di cui conosci la risposta e i passaggi di origine, mescolando domande semplici e domande a più salti. Tieni traccia del tipo di ogni domanda.
  2. 02
    Stabilire un riferimento
    Falle passare nel tuo RAG attuale, senza agente. Annota se è stato ritrovato il passaggio giusto, se la risposta è corretta e il tempo totale.
  3. 03
    Cambiare una cosa sola
    Esegui lo stesso set con l'agente, sullo stesso indice e con lo stesso modello, sulla stessa macchina.
  4. 04
    Leggi per tipo di domanda
    Guarda il risultato separatamente per le domande semplici e per quelle complesse, e annota il numero di ricerche per domanda. Una media complessiva può nascondere un guadagno sulle une e una perdita di tempo sulle altre.
  5. 05
    Tenere ciò che è giustificato
    Se ne traggono vantaggio solo alcune domande, può bastare un routing: RAG diretto come impostazione predefinita, ciclo per le domande rilevate come complesse.

Per automatizzare la parte di qualità di questo confronto, la guida dedicata a Ragas presenta metriche adatte. Annota anche la versione dei pacchetti, il modello, la quantizzazione e num_ctx: senza questi dati, il tuo risultato non sarà confrontabile il mese successivo.


#Per approfondire

L'agentic RAG si appoggia a un RAG che funziona già. Queste guide coprono le basi da stabilizzare prima, gli approcci affini e l'architettura dell'agente.

Questa guida ti è stata utile?

Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.