Perché gli agenti di ricerca autonomi allucinano — e come un ciclo di critica risolve il problema
Gli agenti planner-executor falliscono sulla verificabilità: un critic con accesso diretto alle fonti è la correzione che regge alle query avversarie.
Perché conta
Un agente di ricerca autonomo che allucina può essere peggio di nessun agente. Produce output che sembrano autorevoli, includono citazioni plausibili e possono essere parzialmente o completamente sbagliati. Chi riceve l'output non ha un modo efficiente per verificarlo: è proprio per questo che ha delegato la ricerca a un agente.
Ho costruito agenti di ricerca che hanno fallito in questo modo. Il planner generava sotto-domande sensate, l'executor recuperava documenti reali, poi la sintesi provava a costruire un report coerente da risultati incompleti. In quel passaggio il modello riempiva i vuoti con affermazioni plausibili ma prive di fonte. Non erano nei documenti recuperati: arrivavano dai dati di addestramento, con l'aspetto di una ricerca documentata.
Per me questo è un fallimento architetturale, non un problema da risolvere solo con prompt migliori. Una coppia planner-executor, senza verifica, non distingue in modo affidabile tra «l'ho trovato in una fonte» e «il modello ne è sicuro». Per questo aggiungo un critic con accesso indipendente alle fonti. È il controllo strutturale che uso per rendere verificabile il passaggio dalla ricerca al report.
1. Il pattern planner-executor e i suoi limiti
Uso la coppia planner-executor come base per la ricerca autonoma: il planner scompone un tema in sotto-domande, l'executor recupera e riassume le risposte, poi il planner sintetizza gli output in un report finale.
Il pattern è utile quando il corpus è ben delimitato, le sotto-domande sono indipendenti e le fonti contengono informazioni sufficienti. Diventa fragile quando il corpus è scarso, le domande sono ambigue o la sintesi deve riconciliare fonti parziali e contraddittorie.
Il punto critico è la sintesi. Il planner riceve output come «La fonte A dice X» e «La fonte B è ambigua su Y» e deve trasformarli in un testo coerente. Con token limitati e molte fonti, il modello può completare le parti ambigue con ciò che si aspetta di vedere. Il risultato suona ordinato, ma alcune affermazioni non sono verificabili.
Non uso il loop del critic per eliminare le cause radice, come corpora incompleti o domande mal formulate. Lo uso per esporre il problema. Il critic segnala le affermazioni che non riesce a collegare alle fonti, distingue «trovato nei documenti» da «inferito dai dati di addestramento» e dà al planner un segnale operativo: rilanciare la query o dichiarare l'incertezza.
2. Cosa fa davvero un critic agent
Tratto il critic come un agente di verifica, non come un secondo sintetizzatore. Gli assegno due elementi: accesso agli stessi documenti fonte recuperati dall'executor e un vincolo rigoroso di grounding.
Il suo compito è semplice da descrivere: dati il riassunto dell'executor e i documenti fonte, marca ogni affermazione come grounded (trovata testualmente o parafrasata in una fonte), inferred (deducibile dalle fonti ma non dichiarata direttamente) oppure unsupported (assente dalle fonti recuperate).
from pydantic import BaseModel
from typing import Literal
class ClaimVerification(BaseModel):
claim: str
status: Literal["grounded", "inferred", "unsupported"]
source_url: str | None # required when status == "grounded"
confidence: float # 0.0 to 1.0
class CriticOutput(BaseModel):
verified_claims: list[ClaimVerification]
overall_groundedness: float # fraction of claims that are "grounded"
flags: list[str] # specific issues for the planner to act on
Nel mio schema questo output strutturato torna al planner, non all'executor e non come testo libero. Il planner legge overall_groundedness e flags per decidere il passo successivo. Se la groundedness supera una soglia, per esempio 0.8, approvo il report. Se resta sotto, rimetto in coda le sotto-domande segnalate e chiedo fonti per le affermazioni unsupported.
Aggiungo sempre una guardia di max-iterations. Se la groundedness non migliora dopo due cicli di re-query, il planner deve marcare nel report finale le affermazioni a bassa confidenza, invece di lasciarle passare in silenzio.
3. Accesso indipendente alle fonti per il critic
Il requisito centrale è questo: il critic deve vedere i documenti fonte, non solo il riassunto dell'executor.
Se vede solo il riassunto, può controllare la coerenza interna, per esempio individuare affermazioni in conflitto. Non può però verificare il grounding esterno: non sa se una frase è davvero presente nella fonte. Un riassunto può essere coerente e comunque sbagliato.
# Wrong: critic sees only the summary
critic_input = {
"summary": executor_output.summary,
"task": "Verify the claims in this summary."
}
# Critic can check consistency but not source grounding
# Right: critic sees summary and original documents
critic_input = {
"summary": executor_output.summary,
"source_documents": executor_output.retrieved_docs, # original text, not summaries
"task": "For each claim in the summary, verify it against the source documents."
}
# Critic can map claims to specific passages
In pratica, la context window del critic deve contenere il riassunto e gli estratti rilevanti delle fonti. Per task long-form con molte fonti, scelgo tra due opzioni: verificare una sotto-domanda alla volta oppure usare un modello con contesto ampio solo per il passaggio del critic.
La verifica per sotto-domanda tende a costare meno nel mio setup, ma può perdere affermazioni multi-fonte, come «Le fonti A e B confermano entrambe che...», se non passo tutto il contesto necessario. La verifica sull'intero corpus tende a costare di più, ma intercetta meglio le contraddizioni tra fonti perché il critic vede più versioni della stessa affermazione.
4. Recursive summarization e tracciamento delle citazioni
Gli agenti di ricerca autonomi spesso recuperano più contenuto di quanto entri in una singola context window. La risposta usuale è la recursive summarization: riassumere il documento A, riassumere il documento B, poi sintetizzare i riassunti.
Uso la recursive summarization per comprimere il contesto, ma non la considero sufficiente per il tracciamento delle citazioni. Quando un documento viene condensato, alcuni passaggi specifici spariscono. A quel punto il critic può capire il senso generale, ma non sempre può mappare un'affermazione al testo originale. La catena probatoria si indebolisce.
Il design che uso per preservarla è diretto: conservo il documento originale insieme al riassunto e passo entrambi al critic.
class ExecutorResult(BaseModel):
sub_question: str
summary: str # compressed, used for planning
source_docs: list[str] # original text, used for critic verification
source_urls: list[str]
Il critic legge il riassunto per orientarsi, poi cerca nell'originale il passaggio specifico. Questo aumenta i dati archiviati per ogni risultato dell'executor. Nel mio caso è accettabile quando il corpus si misura in migliaia di token. Se la dimensione lo rende impraticabile, creo embedding degli originali ed eseguo la ricerca delle citazioni al momento della verifica, invece di passare tutto inline.
5. Map-reduce per sotto-domande indipendenti
Quando il task si scompone in sotto-domande indipendenti — trend di mercato, panorama competitivo, contesto normativo — il planner può inviare gli executor in parallelo (map) e poi aggregare i risultati verificati (reduce).
Nella fase map, il planner invia le sotto-domande simultaneamente. Nella fase reduce, sintetizza gli output verificati in un report finale. In questo schema faccio girare il critic sulla sintesi finale rispetto all'intero corpus, non su ciascun singolo risultato dell'executor.
Nel mio setup questo può ridurre la latenza rispetto all'esecuzione sequenziale e mantiene la verificabilità: nella fase reduce passo tutti i documenti fonte al critic, così la sintesi finale viene controllata contro tutto ciò che è stato recuperato.
Il rischio da evitare è la fusione impropria di risultati incompatibili. Se una sotto-domanda riporta «La market size è $5B» e un'altra «La market size è $3B», il planner non deve mediare i valori. Deve segnalarli come conflitto. La lista flags del critic serve proprio a far emergere questi casi.
6. Gestire esplicitamente le lacune di copertura
Un agente di ricerca non dovrebbe produrre una risposta sicura quando le fonti non bastano. Nel mio design il loop del critic serve anche a riconoscere questo caso.
Il segnale è operativo: se il critic continua a marcare affermazioni come unsupported dopo due cicli di re-query con termini diversi, considero probabile un problema di copertura del corpus, non solo una query debole. In quel caso il report deve dichiarare la lacuna, invece di generare una risposta plausibile.
class ResearchReport(BaseModel):
findings: list[ClaimVerification]
coverage_gaps: list[str] # topics where sources were insufficient
confidence: float # overall groundedness across all findings
generated_at: str # ISO timestamp
Rendo obbligatorio il campo coverage_gaps: il planner deve compilarlo, anche con una lista vuota. Se lo schema del report non include un campo obbligatorio per le lacune di copertura, il sistema può ometterle senza segnalarlo.
Preferisco una lacuna esplicita a una risposta sicura ma sbagliata. Se il report dice «Lacuna di copertura: quadro normativo in UE dopo il 2023 — fonti disponibili solo fino al Q2 2023», chi legge sa quale ricerca aggiuntiva serve. Una risposta allucinata, invece, non offre alcun segnale fino a quando una validazione downstream non fallisce.
Il quadro di categoria
Quando progetto agenti di ricerca autonomi che devono produrre output verificabili, uso tre componenti: un planner che scompone e sintetizza, un executor che recupera e riassume preservando le fonti, e un critic che verifica il grounding sui documenti originali.
Il loop del critic non elimina l'allucinazione: i modelli mantengono prior dai dati di addestramento. Lo uso per renderla visibile. Il critic distingue grounded, inferred e unsupported, e il planner conserva questa distinzione nel report invece di trasformarla in falsa confidenza.
La garanzia strutturale è limitata ma utile: ogni affermazione nel report finale dovrebbe essere tracciata a un URL della fonte, marcata come inferred oppure esposta come lacuna di copertura. In pratica, riduco le categorie operative a tre e rendo più difficile far passare un'affermazione non verificata come se fosse fondata.
Se stai costruendo automazione della ricerca che deve rispettare uno standard di verificabilità — compliance, legal, financial — scrivimi. È qui che, nel mio setup, questo pattern può giustificare il suo costo di complessità.
FAQ
Perché un planner-executor può allucinare nella sintesi?
Perché la sintesi è il punto in cui il modello deve trasformare fonti parziali in un testo coerente. Con token limitati, ambiguità o contraddizioni, può completare i vuoti con informazioni attese dai dati di addestramento. Il testo resta plausibile, ma alcune affermazioni non sono verificabili nelle fonti recuperate.
Che cosa deve verificare un critic agent in un research agent?
Deve verificare il grounding delle affermazioni. Nel mio schema marca ogni claim come grounded, inferred o unsupported, usando sia il riassunto dell'executor sia i documenti fonte. Il planner usa overall_groundedness e flags per approvare il report, rilanciare query o dichiarare incertezza.
Perché il critic deve vedere i documenti originali?
Perché un riassunto permette solo controlli di coerenza interna. Per verificare se un'affermazione è davvero supportata, il critic deve poterla mappare a un passaggio specifico della fonte. Per questo gli passo sia il riassunto sia i documenti originali.
Come gestire le affermazioni unsupported dopo più re-query?
Uso una guardia di max-iterations. Se la groundedness non migliora dopo due cicli di re-query, il planner deve marcare nel report finale le affermazioni a bassa confidenza. Se le stesse aree restano unsupported con termini di ricerca diversi, le tratto come lacune di copertura e le rendo esplicite.
Quando conviene verificare per sotto-domanda o sull'intero corpus?
Verificare per sotto-domanda tende a costare meno nel mio setup, ma può perdere affermazioni multi-fonte se manca il contesto. Verificare l'intero corpus tende a costare di più, però intercetta meglio contraddizioni tra fonti e affermazioni che attraversano più risultati dell'executor.
Articoli correlati
RAG in produzione: correggi chunking e re-ranking prima di toccare gli embedding
Le pipeline RAG falliscono su chunking o re-ranking prima che sugli embedding: un metodo diagnostico per trovare il collo di bottiglia giusto.
20 dic 202411 min di lettura#RAG#Retrieval#LLM#ProductionCinque pattern di function calling che hanno retto in produzione
L’uso degli strumenti è dove i sistemi LLM falliscono più spesso: cinque pattern che hanno retto in produzione e gli anti-pattern che hanno sostituito.
25 nov 202412 min di lettura#LLM#Function Calling#OpenAI#ProductionLo stato è l'API: LangGraph dopo tre riscritture
Lo schema di stato è la decisione più importante in LangGraph. Tre iterazioni su come modellarlo, e perché i canali con reducer sono la primitiva giusta.
8 gen 202512 min di lettura#LangGraph#LLM#Multi-Agent#Orchestration