Startseite / Artikel

Zehn Möglichkeiten, Halluzinationen ohne Feinabstimmung zu reduzieren

Zehn systembezogene Korrekturen, die die Halluzinationen von LLMs in einem E-Commerce-Agenten verringern: besseres Kontextwissen, Datenabrufverfahren, Graphen, Tools, Validierung sowie Bewertungsmethoden – ohne Feinabstimmung.

Verlag: REACTAPP.TOP9 Min. Lesezeit2156 Wörter

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

Wenn ein Assistent einem Kunden mitteilt „Ihre Rückerstattung wurde bearbeitet“, obwohl es keine Rückerstattung gibt, liegt das Problem in der Regel nicht am Modell. Das zugrundeliegende System weist eine Lücke auf, und das Modell füllt diese Lücke mit einer Schätzung. Der Bestellstatus fehlte, die Abfrage lieferte Unsinn, der Kontext war unübersichtlich, die Tool-Aufrufe wurden nicht überprüft und frühere Erfolge wurden vergessen.

Man kann Halluzinationen erheblich reduzieren, ohne die Gewichte des Modells anzutasten. Das hier gezeigte Beispiel ist ein E-Commerce-Assistent, der Bestellungen überprüft, Rückerstattungen ausstellt und Lieferungen aktualisiert – wir werden ihn Schicht für Schicht sicherer machen.

Die naive Version

Ein Kunde schreibt: „Wo ist meine Bestellung #48291?“ Die erste Entwurfsversion der App leitet die Frage einfach weiter:

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

console.log(response.output_text);

Das Modell könnte mit dem Satz „Ihre Bestellung wird gerade ausgeliefert und wird heute ankommen“ antworten. Schön – außer dass es keine Ahnung hat, wo Bestellung 48291 ist. Es hat einen glaubwürdigen Satz anhand von Mustern in seinen Trainingsdaten erzeugt. Das ist eine Halluzination.

Was Sie möchten, sieht eher so aus:

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

Das Modell sollte die Realität abrufen, sie nicht erfinden.

1. Geben Sie den bereits vorhandenen Kontext weiter

Regel eins: Lassen Sie das Modell nicht raten, was Ihr Backend weiß. Sagen Sie, dass das Backend dieses Record enthält:

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

Übergeben Sie es zusammen mit der Frage:

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?
  `
});

Jetzt muss das Modell den Bestellstatus nicht kennen. Seine Aufgabe beschränkt sich darauf, dem Kunden eine bekannte Tatsache zu erklären, was viel sicherer ist. Vergleichen Sie die beiden Abläufe:

Bad:

Customer → LLM → Answer

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

Das ist das Herzstück des Kontextingenieurwesens.

2. Zuerst abrufen, dann erst erzeugen

Ein weiterer Kunde fragt nach der Rückgabepolitik für Elektronikartikel, die bei einem Sonderverkauf gekauft wurden. Diese Antwort befindet sich nicht im Modell – sie liegt in Ihrer Richtliniendatenbank, den Produktdokumenten, den Werberegeln sowie den regionalen Bedingungen. Holen Sie sie daher vor der Erstellung ab. Ein grundlegender RAG-Ablauf:

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

Und eine minimalistische Implementierung:

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
);

Qualität ist hier wichtiger als Quantität. Das Modell durch 30 Dokumente zu füttern macht es nicht intelligenter. Wenn drei wichtig sind, sind die anderen 27 nur Ballast.

3. Reduzieren Sie den Lärm

Stellen Sie sich vor, der Assistent erhält diesen Haufen:

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

Jetzt fragt der Kunde nur noch: „Kann ich diese Kopfhörer zurückgeben?“ Das Modell benötigt die letzten fünfzehn Gespräche, nicht verwandte Bestellungen, andere Produktlinien, abgelaufene Aktionen oder alte Versandregeln nicht. Ein besseres System filtert vor dem Abfragen bereits aus:

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

In Code sieht es etwa so aus:

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);

Man versucht nicht, die Anzahl der Tokens zu maximieren. Man versucht vielmehr, das Signal-Rausch-Verhältnis zu verbessern. Eine gute Regel: Geben Sie dem Modell alles, was es benötigt, und nichts, was es nicht braucht.

4. Verwenden Sie ein Wissensgraph für strukturierte Fakten

RAG eignet sich für Dokumente. Viele Fragen im E-Commerce beziehen sich überhaupt nicht auf Dokumente, sondern auf Beziehungen: Welches Lager bearbeitet diese Bestellung, welche Zahlung hat sie beglichen, zu welchen Produkten gehört eine Aktion, welche Bestellungen sind betroffen, wenn ein Lager offline geht. Das sind Graphenprobleme. Ein vereinfachter Graph:

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

Wenn ein Benutzer fragt, welches Lager seine Bestellung bearbeitet, kann das System diesen Weg folgen:

Order #48291
      ↓
FULFILLED_BY
      ↓
Warehouse #17

mit einer Graphenabfrage wie:

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

Das Ergebnis wird zum vertrauenswürdigen Kontext:

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

Stellen Sie die beiden Ansätze nebeneinander dar. Ohne Struktur:

Without structured knowledge:

User → LLM
         ↓
      Guess

Und mit einem Graphen im Loop:

With Knowledge Graph:

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

Es lohnt sich am meisten, wenn der Assistent über mehrere Systeme hinweg schlussfolgern muss.

5. Fordern Sie Beweise an

Fordern Sie nicht nur die Antwort, sondern auch die Gründe, die sie stützen. Eine strukturierte Antwort könnte so aussehen:

{
  "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"
    }
  ]
}

Man kann die Gewohnheit durch Anweisungen durchsetzen:

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.

Dann kann die Anwendung auf der Grundlage des Vertrauensns warten:

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

Das ändert den einfachen Ablauf:

LLM → Answer

in Einklang mit Kontrollen unterwegs:

LLM
 ↓
Answer
 ↓
Evidence
 ↓
Confidence
 ↓
Decision

6. Geben Sie dem Agenten Werkzeuge anstelle dessen, dass er raten muss

Manchmal ist die beste Prävention, dem LLM überhaupt keine Antwort zu ermöglichen. Wenn ein Kunde fragt, ob eine Rückerstattung erfolgt ist, zeigen Sie eine Abfrage an:

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

Die Ausführung verläuft dann wie folgt:

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

Anstelle der vagen Aussage „Ihre Rückerstattung sollte in 5 bis 7 Tagen eintreffen“, kann der Agent mitteilen, dass die Rückerstattung bereits gestern eingeleitet wurde und noch aussteht, weil das tatsächlich der Fall ist. Wenn die Realität eine Antwort geben kann, fragen Sie die Realität.

7. Überprüfen Sie die Eingaben und Ausgaben der Tools

Allein das Aufrufen eines Tools macht einen Agenten noch nicht zuverlässig. Das Modell kann weiterhin fehlerhafte Anfragen erzeugen. Ein Kunde bittet um eine Rückerstattung von 100 Dollar für Bestellung 48291, und das Modell gibt folgendes aus:

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

Aber Ihre Zahlungs-API verlangt:

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

Fügen Sie eine Schemavergleichung hinzu, beispielsweise mit 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
);

Verweilen Sie nicht nur bei den Typen. Überprüfen Sie auch die Geschäftsregeln:

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

Der Pipeline wird dann wie folgt:

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

Das ist besonders wichtig für Agenten, die tatsächliche Aktionen ausführen. Das LLM sollte niemals das letzte Wort über den ausgeführten Daten haben.

8. Trennen Sie Fakten von Schlussfolgerungen

Ein subtiler, aber wirksamer Trick besteht darin, das, was das System weiß, getrennt von dem zu speichern, was das Modell geschlossen hat:

{
  "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."
}

Sie weisen unterschiedliche Gewissheit auf. Die ersten Aussagen stammten von Systemen, während die letzte eine Schlussfolgerung ist. Wenn sich die endgültige Antwort als falsch erweist, kann man eine klare Frage stellen:

Was the fact wrong?

OR
Was the reasoning wrong?

Das ist weitaus einfacher zu debuggen. Man kann sogar die interne Aufteilung sichtbar machen:

FACT
→ Order shipped

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

Eine kleine Trennung ermöglicht eine hohe Nachvollziehbarkeit.

9. Lernen aus den Abläufen, nicht aus dem Training

Lassen Sie den Agenten sich verbessern, ohne feine Anpassungen vorzunehmen. Ein Kunde bittet um die Stornierung der Bestellung 48291. Der Agent überprüft die Bestellung:

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

Dann ruft an:

cancel_order(48291)

und erhält zurück:

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

Dieses Ergebnis ist es wert, beibehalten zu werden. Speichern Sie den erfolgreichen Ablauf:

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

Wenn später eine ähnliche Anfrage auftaucht, suchen Sie zunächst nach dem entsprechenden früheren Erfolg:

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

Aus Fehlern lernt man ebenfalls. Ein missglückter Versuch hinterlässt eine Spur:

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

Diese Spuren werden zu Wissen, und der Rückkopplungsschleifenverlauf sieht so aus:

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

Nennen Sie es Laufzeit-Lernen. Es ist kein Neutraining erforderlich.

10. Jede Änderung bewerten

Letztendlich muss geprüft werden, ob etwas geholfen hat. Nehmen wir an, Sie haben den Abrufansatz geändert. „Die Antworten wirken besser“ ist kein Beweis. Erstellen Sie ein Bewertungsset:

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

Dann überwachen Sie Metriken wie diese:

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

Eine Tabelle mit vorher/nachher-Szenario macht den Nutzen konkret:

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

Jetzt behaupten Sie nicht, dass der neue Prompt besser funktioniert, Sie zeigen es einfach. Hier beginnt die KI-Entwicklung, der herkömmlichen Softwareentwicklung zu ähneln:

Change
  ↓
Test
  ↓
Measure
  ↓
Compare
  ↓
Improve

Alle zehn Schichten zusammen

Legen Sie die zehn Schichten um den LLM an, dann sieht die Architektur so aus:

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

Achten Sie darauf, was nicht passiert ist: Das Modell wurde nicht feinabgestimmt. Das umgebende System hat sich verbessert.

Fragen, die gestellt werden sollten, bevor man das Modell beschuldigt

Der erste Impuls bei einer Halluzination ist oft ein besseres Modell. Manchmal stimmt das. Prüfen Sie jedoch zunächst eine Checkliste.

  1. Wurde der richtige Kontext bereitgestellt?
  2. Wurden die richtigen Informationen abgerufen?
  3. Wurde irrelevanter Material entfernt?
  4. Könnte strukturiertes Wissen die Frage deterministisch beantworten?
  5. Könnte ein Tool den tatsächlichen Systemzustand lesen?
  6. Wurde die Ausgabe des Modells überprüft?
  7. Wurden Fakten von der Argumentation getrennt gehalten?
  8. Hat das System aus früheren Ausführungen gelernt?
  9. Wurden Beweise geliefert?
  10. Wurden die Ergebnisse gemessen?

Diese Überprüfungen führen in der Regel zu einer kostengünstigeren, sichereren und leichter kontrollierbaren Lösung als die Feinabstimmung des Modells.

Das Modell ist nur ein Bestandteil

Ein nützliches Bild:

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

Die große Sprachmodelle erzeugen Möglichkeiten. Alles um sie herum liefert Fakten, Kontext, Einschränkungen, Werkzeuge, Validierungen, Erinnerungen und Rückmeldungen – und das macht das Ganze zuverlässig.

Halluzinationen sind oft eher ein Problem des Systemdesigns als eines des Modells. Besserer Kontext, besseres Wissen, bessere Werkzeuge, validierte Ausgaben sowie Lernen aus dem, was tatsächlich geschehen ist, bringen weiter als ein neuer Trainingslauf. Die praktische Frage verschiebt sich von der Wahl des geeigneten Modells hin zu dem, was das Modell wissen sollte, was es tun kann, und wie man feststellt, ob es die Dinge richtig verstanden hat.