Accueil / Articles

Dix façons de réduire les hallucinations sans retouche fine

Dix correctifs au niveau du système qui réduisent les hallucinations des LLM dans un agent de commerce électronique : un meilleur contexte, des mécanismes de récupération améliorés, des graphes, des outils, une validation et des évaluations plus efficaces, sans nécessiter de fine-tuning.

Éditeur : REACTAPP.TOP9 min de lecture2156 mots

Sources: 10 Ways to Reduce AI Hallucinations Without Fine-Tuning Your Model

Lorsqu’un assistant dit à un client « votre remboursement a été traité » alors qu’aucun remboursement n’existe, le modèle n’est généralement pas la cause. Le système qui l’entoure présente une faille, et le modèle comble cette faille par une supposition. L’état de la commande fait défaut, la récupération des données fournit des informations erronées, le contexte est encombré, les appels aux outils ne sont pas vérifiés, et les succès antérieurs sont oubliés.

On peut réduire considérablement les hallucinations sans toucher aux poids du modèle. L’exemple présenté ici concerne un assistant de commerce électronique qui vérifie les commandes, procède aux remboursements et met à jour les livraisons, et nous le renforcerons étape par étape.

La version naïve

Un client écrit : « Où se trouve ma commande n°48291 ? » La première version de l’application se contente de transmettre la question :

const response = await client.responses.create({
  model: "gpt-5",
  input: `
    Where is order #48291?
  `
});

console.log(response.output_text);

Le modèle pourrait répondre par « Votre commande est en cours de livraison et arrivera aujourd’hui ». C’est gentil, sauf qu’il ignore complètement où se trouve la commande 48291. Il a généré une phrase crédible à partir des modèles de ses données d’entraînement. Il s’agit d’une hallucination.

Ce que vous souhaitez ressemble plutôt à ceci :

Customer
   ↓
AI Agent
   ↓
Order System
   ↓
Actual Order State
   ↓
AI Agent
   ↓
Response

Le modèle doit se référer à la réalité, et non la inventer.

1. Fournissez le contexte que vous possédez déjà

Règle numéro un : ne laissez pas le modèle deviner ce que votre backend sait. Indiquez que le backend contient cet enregistrement :

{
  "orderId": "48291",
  "status": "SHIPPED",
  "carrier": "FedEx",
  "trackingNumber": "784512963",
  "estimatedDelivery": "2026-08-25"
}

Soumettez-le avec la question :

const context = {
  order: {
    id: "48291",
    status: "SHIPPED",
    carrier: "FedEx",
    trackingNumber: "784512963",
    estimatedDelivery: "2026-08-25"
  }
};

const response = await client.responses.create({
  model: "gpt-5",
  input: `
    Answer the customer using only the supplied order information.
    Context:
    ${JSON.stringify(context, null, 2)}
    Customer:
    Where is my order #48291?
  `
});

Désormais, le modèle n’a plus besoin de connaître l’état de la commande. Sa tâche se réduit à expliquer un fait connu au client, ce qui est bien plus sûr. Comparez les deux processus :

Bad:

Customer → LLM → Answer

Better:
Customer
   ↓
Retrieve state
   ↓
Build context
   ↓
LLM
   ↓
Answer

C’est là l’essence de l’ingénierie de contexte.

2. Récupérer d’abord, générer ensuite

Un autre client demande des informations sur la politique de retour pour des appareils électroniques achetés lors d’une vente de vacances. Cette réponse n’existe pas dans le modèle ; elle se trouve dans votre base de données des politiques, les documents produits, les règles de promotion et les conditions régionales. Il faut donc la récupérer avant de générer une réponse. Un flux RAG de base :

Customer Question
       ↓
    Retrieve
       ↓
     Rank
       ↓
Build Context
       ↓
      LLM
       ↓
    Answer

Et une implémentation minimale :

const results = await vectorStore.search({
  query: customerQuestion,
  topK: 10
});

const relevant = results
  .filter(item => item.score > 0.8)
  .slice(0, 5);
const context = relevant
  .map(item => item.content)
  .join("\n\n");
const answer = await generateAnswer(
  customerQuestion,
  context
);

Ici, la qualité prime sur la quantité. Fournir 30 documents au modèle ne le rend pas plus intelligent. S’il y a trois documents importants, les 27 autres ne font que créer du bruit.

3. Éliminer le bruit

Imaginez l’assistant qui reçoit cette pile de documents :

20 retrieved documents
+
15 previous messages
+
10 previous tool responses
+
customer profile
+
product catalog
+
order history
+
promotion metadata

Désormais, le client ne demande plus que : « Puis-je retourner ces écouteurs ? » Le modèle n’a pas besoin des quinze dernières conversations, des commandes non liées, d’autres gammes de produits, des promotions expirées ou des anciennes règles d’expédition. Un meilleur système de traitement filtre les informations avant de poser des questions :

Available Information
        ↓
Relevance Filtering
        ↓
Metadata Filtering
        ↓
Ranking
        ↓
Deduplication
        ↓
Context Compression
        ↓
LLM

Dans le code, quelque chose comme :

const context = results
  .filter(x => x.score >= 0.82)
  .filter(x => x.metadata.category === "returns")
  .filter(x => x.metadata.region === customer.region)
  .sort((a, b) => b.score - a.score)
  .slice(0, 5)
  .map(x => x.content);

Vous ne cherchez pas à maximiser le nombre de tokens. Vous cherchez à améliorer le rapport signal/bruit. Une bonne règle : donner au modèle tout ce dont il a besoin et rien de ce dont il n’a pas besoin.

4. Utiliser un graphe de connaissances pour les faits structurés

RAG convient aux documents. De nombreuses questions de e-commerce ne concernent pas du tout des documents. Elles portent sur des relations : quel entrepôt traite cette commande, quel paiement l’a financée, quels produits font partie d’une promotion, quelles commandes sont affectées en cas de panne d’un entrepôt. Ce sont des problèmes de graphe. Un graphe simplifié :

Customer
   │
   └── PLACED → Order
                  │
                  ├── CONTAINS → Product
                  │
                  ├── PAID_BY → Payment
                  │
                  ├── FULFILLED_BY → Warehouse
                  │
                  └── SHIPPED_BY → Carrier

Lorsqu’un utilisateur demande quel entrepôt gère sa commande, le système peut suivre ce chemin :

Order #48291
      ↓
FULFILLED_BY
      ↓
Warehouse #17

avec une requête graphique comme :

MATCH (o:Order {id: "48291"})
      -[:FULFILLED_BY]->
      (w:Warehouse)
RETURN w.id, w.name, w.location;

Le résultat devient un contexte fiable :

{
  "orderId": "48291",
  "warehouse": {
    "id": "WH-17",
    "name": "Delhi Fulfillment Center",
    "location": "Delhi"
  }
}

Placez les deux approches côte à côte. Sans structure :

Without structured knowledge:

User → LLM
         ↓
      Guess

Et avec un graphique dans la boucle :

With Knowledge Graph:

User
 ↓
Entity Identification
 ↓
Graph Traversal
 ↓
Verified Relationship
 ↓
Context
 ↓
LLM

Cela est particulièrement utile lorsque l’assistant doit raisonner à travers plusieurs systèmes.

5. Demandez des preuves

Ne demandez pas seulement la réponse. Demandez la réponse ainsi que ce qui la justifie. Une réponse structurée pourrait ressembler à ceci :

{
  "answer": "Your order is being fulfilled by Warehouse WH-17.",
  "confidence": 0.98,
  "evidence": [
    {
      "type": "order_record",
      "source": "orders_db",
      "orderId": "48291"
    },
    {
      "type": "warehouse_relationship",
      "source": "knowledge_graph",
      "warehouseId": "WH-17"
    }
  ]
}

Vous pouvez instaurer cette habitude grâce à des instructions :

For every factual claim:
1. Identify supporting evidence.
2. Use only available evidence.
3. Never invent a source.
4. If evidence is unavailable, say so.
5. Clearly distinguish facts from inference.

Alors l’application peut agir en fonction du degré de confiance :

if (result.confidence < 0.7) {
  return escalateToHuman(result);
}

Cela change complètement le flux :

LLM → Answer

en intégrant des vérifications au fur et à mesure :

LLM
 ↓
Answer
 ↓
Evidence
 ↓
Confidence
 ↓
Decision

6. Fournir aux agents des outils plutôt que de les laisser deviner

Parfois, la meilleure prévention consiste à ne pas laisser du tout le LLM répondre. Si un client demande si un remboursement a été effectué, afficher une recherche correspondante :

const tools = [{
  name: "get_refund_status",
  description: "Retrieve the current refund status for an order",
  parameters: {
    type: "object",
    properties: {
      orderId: {
        type: "string"
      }
    },
    required: ["orderId"]
  }
}];

L’exécution se déroule alors de la manière suivante :

Customer
   ↓
LLM
   ↓
get_refund_status()
   ↓
Payment System
   ↓
Actual Refund State
   ↓
LLM
   ↓
Customer

Plutôt que d’indiquer vaguement « votre remboursement devrait arriver en 5 à 7 jours », l’agent peut préciser que le remboursement a été initié hier et est toujours en attente, car c’est bien ce qui s’est réellement passé. Si le monde réel peut donner une réponse, demandez-lui directement.

7. Valider les entrées et les sorties de l’outil

Le simple fait d’appeler un outil ne rend pas un agent fiable. Le modèle peut encore générer une demande mal formatée. Un client demande le remboursement de 100 $ pour la commande 48291, et le modèle produit :

{
  "orderId": "48291",
  "amount": "one hundred",
  "currency": "dollars"
}

mais votre API de paiement exige :

{
  "orderId": "48291",
  "amount": 100,
  "currency": "USD"
}

Ajoutez une validation de schéma, par exemple avec Zod :

import { z } from "zod";

const RefundRequest = z.object({
  orderId: z.string(),
  amount: z.number().positive(),
  currency: z.enum(["USD", "EUR", "GBP", "INR"])
});
const refundRequest = RefundRequest.parse(
  modelOutput
);

Ne vous arrêtez pas aux types. Vérifiez également les règles métier :

if (refundRequest.amount > order.total) {
  throw new Error(
    "Refund amount exceeds order total"
  );
}

Le pipeline devient alors :

LLM
 ↓
Schema Validation
 ↓
Business Validation
 ↓
Permission Check
 ↓
Execute
 ↓
Response Validation
 ↓
Accept / Retry / Escalate

Cela est particulièrement important pour les agents qui prennent des actions concrètes. Le LLM ne doit jamais avoir le dernier mot sur les données qui sont mises en œuvre.

8. Séparer les faits de la réflexion

Un astuce subtile mais efficace consiste à stocker ce que le système sait séparément de ce à quoi le modèle est parvenu :

{
  "facts": [
    "Order 48291 is shipped",
    "Order 48291 is fulfilled by Warehouse WH-17",
    "Warehouse WH-17 is currently operating"
  ],
"reasoning": [
    "The order is likely to remain on schedule"
  ],
  "conclusion": "The order is currently expected to arrive on time."
}

Ils comportent un degré de certitude différent. Les premières affirmations provenaient de systèmes, tandis que la dernière est une inférence. Lorsqu’une réponse finale s’avère fausse, on peut poser une question simple :

Was the fact wrong?

OR
Was the reasoning wrong?

C’est bien plus facile à déboguer. Vous pouvez même afficher la séparation en interne :

FACT
→ Order shipped

FACT
→ Estimated delivery: Aug 25
INFERENCE
→ Delivery is currently expected on schedule

Une petite séparation vous offre une grande traçabilité.

9. Apprenez des exécutions, pas de l’entraînement

Essayons de faire en sorte que l’agent s’améliore sans ajustement fin. Un client demande l’annulation de la commande 48291. L’agent vérifie la commande :

{
  "orderId": "48291",
  "status": "PROCESSING",
  "cancelable": true
}

Puis appels :

cancel_order(48291)

et reçoit en retour :

{
  "success": true,
  "cancellationId": "CAN-83921"
}

Ce résultat mérite d’être conservé. Enregistrez l’exécution réussie :

{
  "task": "Cancel order",
  "orderState": "PROCESSING",
  "action": "cancel_order",
  "result": "SUCCESS",
  "cancellationId": "CAN-83921"
}

Lorsqu’une demande similaire apparaît plus tard, cherchez d’abord le succès antérieur correspondant :

New Request
    ↓
Find Similar Successful Execution
    ↓
Retrieve Relevant Pattern
    ↓
Check Current Order State
    ↓
Generate Action
    ↓
Validate
    ↓
Execute

Les échecs enseignent aussi. Une tentative qui a échoué laisse une trace :

Attempt 1
    ↓
cancel_order()
    ↓
Rejected: Order already shipped
    ↓
Agent retrieves shipping information
    ↓
Explains cancellation is unavailable

Ces traces deviennent des connaissances, et le cycle de rétroaction se présente comme ceci :

Execute
   ↓
Observe
   ↓
Evaluate
   ↓
Store Experience
   ↓
Improve Future Context

Appelons cela un apprentissage en temps de exécution. Aucun reconditionnement n’est nécessaire.

10. Évaluer chaque changement

Finalement, déterminez si quelque chose a été utile. Supposons que vous ayez modifié la méthode de récupération : « Les réponses semblent meilleures » n’est pas une preuve suffisante. Créez un ensemble d’évaluation :

[
  {
    "question": "Where is order 48291?",
    "expected": "SHIPPED"
  },
  {
    "question": "Which warehouse fulfills order 48291?",
    "expected": "WH-17"
  },
  {
    "question": "Can order 48291 be cancelled?",
    "expected": false
  }
]

Ensuite, suivez des indicateurs tels que ceux-ci :

Answer Accuracy
Groundedness
Retrieval Precision
Tool Selection Accuracy
Tool Success Rate
Task Success Rate
Recovery Rate
Hallucination Rate
Latency
Cost

Un tableau avant/après rend les améliorations concrètes :

                      Before    After
Answer Accuracy        72%      95%
Groundedness           69%      97%
Tool Success           81%      98%
Hallucination Rate     17%       3%

Vous ne prétendez pas que le nouveau prompt fonctionne mieux, vous le démontrez. C’est là que l’ingénierie des IA commence à ressembler à l’ingénierie logicielle ordinaire :

Change
  ↓
Test
  ↓
Measure
  ↓
Compare
  ↓
Improve

Tous les dix couches ensemble

En empilant les dix couches autour du LLM, l’architecture ressemble à ceci :

                         ┌──────────────────┐
                         │ Knowledge Graph  │
                         └────────┬─────────┘
                                  │
                         ┌────────▼─────────┐
                         │   RAG / Search   │
                         └────────┬─────────┘
                                  │
       Customer → Intent → Context Engine → LLM
                     ↑             │
                     │             ▼
                   Memory      Tool Selection
                     ↑             │
                     │             ▼
                     │          Validation
                     │             │
                     │             ▼
                     │        Real Systems
                     │             │
                     │             ▼
                     └─────── Feedback
                                   │
                                   ▼
                               Evaluation

Remarquez ce qui n’a pas eu lieu : le modèle n’a pas été affiné. Le système qui l’entoure s’est amélioré.

Questions à se poser avant de blâmer le modèle

La première réaction face à une hallucination est souvent de penser à un meilleur modèle. C’est parfois vrai. Mais avant tout, passez en revue une liste de contrôle.

  1. Le contexte adéquat a-t-il été fourni ?
  2. Les informations correctes ont-elles été récupérées ?
  3. Le matériel irrelevant a-t-il été supprimé ?
  4. Une connaissance structurée peut-elle y répondre de manière déterministe ?
  5. Un outil peut-il lire l’état réel du système ?
  6. La sortie du modèle a-t-elle été validée ?
  7. Les faits ont-ils été séparés du raisonnement ?
  8. Le système a-t-il appris des exécutions précédentes ?
  9. Des preuves ont-elles été fournies ?
  10. Les résultats sont-ils mesurés ?

Ces vérifications mènent généralement à des solutions moins coûteuses, plus sûres et plus faciles à contrôler que l’affinage du modèle.

Le modèle n’est qu’un composant

Une image utile :

              ┌───────────────────┐
              │ Knowledge + RAG   │
              └─────────┬─────────┘
                        ↓
Customer → Context → LLM → Tools → Real World
            ↑         ↓      ↓
          Memory   Reasoning Validation
            ↑         ↓
            └──── Feedback
                    ↓
                Evaluation

Le LLM génère des possibilités. Tout ce qui l’entoure fournit des faits, un contexte, des contraintes, des outils, une validation, de la mémoire et des retours d’information, et c’est ce qui rend l’ensemble fiable.

Les hallucinations sont souvent un problème de conception du système plutôt qu’un problème lié au modèle. Un meilleur contexte, de meilleures connaissances, de meilleurs outils, des résultats validés et l’apprentissage à partir de ce qui s’est réellement produit vous mèneront plus loin qu’un simple nouvel entraînement. La question pratique passe de savoir quel modèle utiliser à déterminer ce que le modèle doit savoir, ce qu’il peut faire, et comment savoir s’il a eu raison.