Sources hallucinées : quand les agents inventent des sources

Les sources hallucinées apparaissent lorsqu’un agent cite des liens, documents ou faits inexistants. Pourquoi cela arrive et comment le détecter.
Sur cette page
  1. Le problème
  2. Pourquoi ça arrive
  3. Pannes les plus fréquentes
  4. Citations URL non vérifiées (Unfetched URL citations)
  5. Snippet au lieu de preuve (Search-as-evidence)
  6. Dérive des citations entre étapes (Citation drift)
  7. Pseudo-citations sans couverture des claims (Claim-source mismatch)
  8. Comment détecter ces problèmes
  9. Comment distinguer les sources hallucinées d'une réponse simplement imprécise
  10. Comment stopper ces pannes
  11. Où c'est implémenté dans l'architecture
  12. Auto-vérification
  13. FAQ
  14. Pages liées

Le problème

La demande paraît standard : produire un court résumé des changements de policy et ajouter les sources.

Dans les traces, c'est différent : sur un run, l'agent a renvoyé 7 citations, mais la vérification a montré que 3 sources n'ont jamais été fetch et que 2 mènent à 404. Pour l'utilisateur, la réponse semble fiable, mais elle n'est pas reproductible.

Le système ne tombe pas en panne.

Il renvoie simplement des citations plausibles sans preuve réelle.

Analogie : imagine un auditeur qui met dans son rapport des références vers "des dossiers en archive", mais personne n'a vu ces dossiers. Le document paraît professionnel jusqu'à ce que quelqu'un vérifie les sources. Les sources hallucinées dans les systèmes d'agents fonctionnent exactement comme ça.

Pourquoi ça arrive

Les sources hallucinées n'apparaissent généralement pas à cause d'une seule erreur de modèle, mais à cause d'un manque de contrôle strict des citations dans le runtime.

LLM a un fort biais vers des réponses "complètes", donc sans vérification stricte le modèle préfère inventer une citation plutôt que renvoyer une réponse sans source.

En production, c'est souvent ce schéma :

  1. l'agent génère des citations comme partie d'une réponse "complète" ;
  2. les snippets de recherche sont pris comme evidence alors que les pages n'ont pas été ouvertes ;
  3. les source_id ne sont pas liés aux snapshots d'evidence ;
  4. sans vérification des citations, le runtime laisse passer des sources unfetched ou invalides ;
  5. si fail-closed n'est pas activé, les sources inventées arrivent jusqu'à l'utilisateur.

Dans la trace, on voit une hausse de citations_count en parallèle d'une baisse de citation_validity_rate.

Le problème n'est pas une seule URL ratée.

Le runtime ne bloque pas les citations non vérifiées avant la réponse finale.

Pannes les plus fréquentes

En production, on observe surtout quatre patterns récurrents de sources hallucinées.

Citations URL non vérifiées (Unfetched URL citations)

L'agent cite une URL qui n'est jamais passée par http.get ou kb.read dans ce run.

Cause typique : les citations ne sont pas restreintes aux source_id de l'evidence store.

Snippet au lieu de preuve (Search-as-evidence)

La réponse contient des "sources" issues de la recherche, mais l'agent n'a aucune confirmation du contenu réel des pages.

Cause typique : les search results sont mélangés avec la couche evidence.

Dérive des citations entre étapes (Citation drift)

À une étape précoce la source était valide, mais après retry ou truncation la réponse finale pointe vers un autre document.

Cause typique : absence de lien stable claim -> source_id -> snapshot hash.

Pseudo-citations sans couverture des claims (Claim-source mismatch)

La réponse contient un bloc de citations, mais les claims clés n'ont pas de source correspondante.

Cause typique : la validation vérifie seulement "présence de liens", pas la couverture des claims.

Comment détecter ces problèmes

Les sources hallucinées se voient bien via la combinaison de métriques citation et retrieval.

MétriqueSignal de sources hallucinéesAction
citation_validity_ratela part de citations validées baisseactiver une vérification fail-closed par source_id
unfetched_source_ratebeaucoup d'URL unfetched dans les réponsesinterdire les citations URL sans evidence snapshot
source_404_rateune partie des sources n'est pas accessiblevérifier le statut et l'URL canonique pendant le fetch
claim_without_citation_rateclaims sans lien vers une sourceajouter un claim-level coverage check
citation_stop_reason_ratecitations:invalid fréquent dans le runtimevérifier la qualité du retrieval et la policy des tools

Comment distinguer les sources hallucinées d'une réponse simplement imprécise

Toute imprécision de texte ne signifie pas source inventée. La question clé : peut-on reproduire techniquement la source pour chaque claim critique.

Normal si :

  • chaque citation pointe vers un source_id existant dans l'evidence store ;
  • les métadonnées snapshot existent (URL, timestamp, hash) ;
  • la vérification des claims montre que les sources couvrent les conclusions clés.

Dangereux si :

  • la réponse contient des URL absentes de l'étape fetch ;
  • les citations existent "pour la forme" mais ne couvrent pas les claims principaux ;
  • les réponses ne sont pas reproductibles au niveau run (run_id -> source_id -> snapshot).

Comment stopper ces pannes

En pratique, cela ressemble à ceci :

  1. toutes les sources passent par l'evidence store (snapshot + hash + timestamp) ;
  2. le modèle renvoie les citations uniquement en source_id, pas en URL arbitraires ;
  3. le citation verifier vérifie que tous les source_id existent, ont été fetch et sont autorisés par la policy ;
  4. si la vérification échoue, le runtime renvoie stop reason et safe fallback.

Guard minimal pour valider les citations :

PYTHON
from dataclasses import dataclass
import hashlib
import time


@dataclass(frozen=True)
class EvidenceMeta:
    source_id: str
    url: str
    fetched_at: float
    text_sha256: str


class EvidenceStore:
    def __init__(self):
        self.items: dict[str, EvidenceMeta] = {}

    def add_snapshot(self, source_id: str, url: str, text: str) -> None:
        self.items[source_id] = EvidenceMeta(
            source_id=source_id,
            url=url,
            fetched_at=time.time(),
            text_sha256=hashlib.sha256(text.encode("utf-8")).hexdigest(),
        )

    def has(self, source_id: str) -> bool:
        return source_id in self.items


def verify_citations(cited_source_ids: list[str], store: EvidenceStore) -> str | None:
    # cited_source_ids are expected to come from structured output
    if not cited_source_ids:
        return "citations:missing"

    unknown = [sid for sid in cited_source_ids if not store.has(sid)]
    if unknown:
        return "citations:unknown_source_id"

    return None

C'est un guard de base. En production, on l'étend généralement avec claim-level coverage check, allowlist des citation tools et stop reason séparé pour les URL unfetched. verify_citations(...) est appelé avant le rendu final, pour que l'utilisateur ne voie pas de sources invalides.

Où c'est implémenté dans l'architecture

En production, le contrôle des sources hallucinées est presque toujours réparti entre trois couches du système.

Tool Execution Layer gère le fetch d'evidence : statut de réponse, normalisation URL, snapshots et hash. Si cette couche ne stocke pas de preuve, les citations ne peuvent pas être vérifiées de façon fiable.

Agent Runtime contrôle structured output, citation verification, stop reasons et fallback fail-closed. C'est ici que la décision finale est prise avant affichage à l'utilisateur.

Memory Layer maintient le lien run-evidence : run_id, source_id, retention et reproductibilité. Sans cette couche, l'équipe ne peut pas faire un audit d'incident propre.

Auto-vérification

Vérification rapide avant release. Coche les points et regarde le statut ci-dessous.
C'est un sanity-check court, pas un audit formel.

Progression: 0/8

⚠ Il y a des signaux de risque

Il manque des contrôles de base. Fermez les points clés de la checklist avant release.

FAQ

Q : Peut-on juste demander au modèle "d'ajouter des sources" ?
R : Oui, mais ce n'est pas suffisant. Sans vérification runtime des citations, c'est du format, pas de la preuve.

Q : Les search results peuvent-ils servir de preuve ?
R : En général non. Search donne des candidats. La preuve est seulement ce qui a été fetch et stocké comme snapshot.

Q : Faut-il stocker le texte complet de la source ?
R : Pas toujours. Le minimum pour audit : URL, timestamp, hash et source_id stable. Le texte complet est ajouté quand replay ou citations exactes sont nécessaires.

Q : Que montrer à l'utilisateur quand les citations sont invalides ?
R : Une stop reason explicite, ce qui est déjà vérifié, et une étape sûre : réponse partial sans sources non vérifiées ou nouveau run avec vérification.


Un incident de sources hallucinées ressemble rarement à un crash bruyant. C'est une perte de confiance silencieuse, visible le plus souvent seulement après vérification des sources. Les agents de production ont donc besoin non seulement de bonnes réponses, mais aussi d'une discipline stricte sur les citations.

Pages liées

Si ce problème apparaît en production, ces pages sont aussi utiles :

⏱️ 8 min de lectureMis à jour 12 mars 2026Difficulté: ★★☆
Implémenter dans OnceOnly
Guardrails for loops, retries, and spend escalation.
Utiliser dans OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Kill switch & arrêt incident
  • Audit logs & traçabilité
  • Idempotence & déduplication
  • Permissions outils (allowlist / blocklist)
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.
Exemple de policy (concept)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Auteur

Nick — ingénieur qui construit une infrastructure pour des agents IA en production.

Focus : patterns d’agents, modes de défaillance, contrôle du runtime et fiabilité des systèmes.

🔗 GitHub: https://github.com/mykolademyanov


Note éditoriale

Cette documentation est assistée par l’IA, avec une responsabilité éditoriale humaine pour l’exactitude, la clarté et la pertinence en production.

Le contenu s’appuie sur des défaillances réelles, des post-mortems et des incidents opérationnels dans des systèmes d’agents IA déployés.