Strona główna / Artykuły

10 sposobów na ograniczenie halucynacji bez drobnej kalibracji

Te naprawy na poziomie systemu, które zmniejszają halucynacje modeli LLM w agentach e-commerce: lepszy kontekst, wyszukiwanie, grafy, narzędzia, walidacja i oceny, bez konieczności dopracowywania modeli.

Wydawca: REACTAPP.TOP9 min czytania2156 słów

Źródła: 10 Ways to Reduce AI Hallucinations Without Fine-Tuning Your Model

Gdy asystent mówi klientowi „Twoja zwrotka została przetworzona”, podczas gdy żadna zwrotka nie istnieje, zazwyczaj winowajcą nie jest model. System wokół niego pozostawił lukę, a model wypełnił tę lukę domysłem. Brakowało informacji o stanie zamówienia, pobrane dane były błędne, kontekst był zdezorganizowany, wywołania narzędzi nie były sprawdzane, a wcześniejsze sukcesy zostały zapomniane.

Można znacznie ograniczyć halucynacje bez ingerencji w wagi modelu. Przykładem jest tutaj asystent e-commerce, który sprawdza zamówienia, wypłaca zwroty i aktualizuje informacje o dostawach, a my wzmocnimy jego funkcjonowanie warstwa po warstwie.

Wersja naiwna

Klient pisze: „Gdzie jest moje zamówienie nr 48291?”. Wersja początkowa aplikacji po prostu przekazuje to pytanie dalej:

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

console.log(response.output_text);

Model może odpowiedzieć: „Państwa zamówienie jest w drodze i dotrze dzisiaj”. Pięknie, tylko że nie ma pojęcia, gdzie znajduje się zamówienie nr 48291. Wygenerował wiarygodne zdanie na podstawie wzorców z danych treningowych. To halucynacja.

To, czego szukacie, wygląda bardziej tak:

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

Model powinien odwoływać się do rzeczywistości, a nie ją wymyślać.

1. Przekaż kontekst, który już posiadasz

Reguła numer jeden: nie zmuszaj modelu do zgadywania tego, co wie twoja warstwa backendowa. Powiedz, że warstwa backendowa przechowuje ten rekord:

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

Przekaż to wraz z pytaniem:

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

Teraz od modelu nie wymaga się znajomości stanu zamówienia. Jego zadanie sprowadza się do wyjaśnienia klientowi już znanej informacji, co jest o wiele bezpieczniejsze. Porównaj oba procesy:

Bad:

Customer → LLM → Answer

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

To jest istota inżynierii kontekstu.

2. Najpierw pobrać, potem generować

Inny klient pyta o zasadę zwrotów dla urządzeń elektronicznych zakupionych podczas wyprzedaży świątecznej. Odpowiedź na to pytanie nie znajduje się w modelu – znajduje się w bazie danych zasad, dokumentacji produktów, zasadach promocji oraz warunkach regionalnych. Dlatego należy ją pobrać przed generowaniem odpowiedzi. Podstawowy proces RAG:

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

A prosty wariant implementacji:

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

Tutaj jakość przewyższa ilość. Podanie modelowi 30 dokumentów nie czyni go mądrzejszym. Jeśli trzy są istotne, pozostałe 27 to tylko zbędny hałas.

3. Usuń zbędne elementy

Wyobraź sobie, że asystent otrzymuje tę stertę:

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

Teraz klient pyta tylko: „Czy mogę zwrócić te słuchawki?”. Model nie potrzebuje ostatnich piętnastu rozmów, niepowiązanych zamówień, innych linii produktów, wygasłych promocji ani starych zasad wysyłki. Lepszy proces filtruje dane przed zadaniem pytania:

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

W kodzie coś w stylu:

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

Nie próbujesz maksymalizować liczby tokenów. Staramy się zwiększyć stosunek sygnału do szumu. Dobrą zasadą jest dostarczenie modelowi wszystkiego, czego potrzebuje, a niczego, czego nie potrzebuje.

4. Użyj grafu wiedzy do strukturyzowanych faktów

RAG nadaje się do dokumentów. Wiele pytań z zakresu e-commerce w ogóle nie dotyczy dokumentów – chodzi o relacje: który magazyn realizuje ten zamówienie, który płatność go opłaciła, które produkty biorą udział w promocji, które zamówienia ulegną przerwaniu w przypadku wyłączenia magazynu. To są problemy grafowe. Uproszczony graf:

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

Gdy użytkownik pyta, który magazyn zajmuje się jego zamówieniem, system może przejść tę ścieżkę:

Order #48291
      ↓
FULFILLED_BY
      ↓
Warehouse #17

za pomocą zapytania grafowego w stylu:

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

Wynik staje się wiarygodnym kontekstem:

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

Umieść obie metody obok siebie. Bez struktury:

Without structured knowledge:

User → LLM
         ↓
      Guess

A z wykresem w pętli:

With Knowledge Graph:

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

Największe korzyści przynosi to, gdy asystent musi rozumować w ramach kilku systemów.

5. Proś o dowody

Nie pytaj tylko o odpowiedź. Poproś o odpowiedź wraz z tym, co ją uzasadnia. Strukturyzowana odpowiedź może wyglądać tak:

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

Możesz wprowadzić tę nawyk poprzez instrukcje:

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.

Następnie aplikacja może podjąć działanie w oparciu o poziom pewności:

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

To zmienia prosty bieg wydarzeń:

LLM → Answer

łącząc to z kontrolami po drodze:

LLM
 ↓
Answer
 ↓
Evidence
 ↓
Confidence
 ↓
Decision

6. Daj agentowi narzędzia zamiast pozwalać mu zgadywać

Czasami najlepszą formą zapobiegania jest w ogóle nie pozwolenie modelowi LLM na udzielenie odpowiedzi. Jeśli klient pyta, czy zwrot pieniędzy został przetworzony, ujawnij możliwość wyszukiwania:

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

Wykonanie przebiega w następujący sposób:

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

Zamiast niejasnego stwierdzenia „zwrot pieniędzy powinien nadejść w ciągu 5 do 7 dni”, agent może powiedzieć, że zwrot został rozpoczęty wczoraj i wciąż jest w trakcie przetwarzania, ponieważ tak faktycznie wygląda sytuacja. Jeśli rzeczywisty świat potrafi udzielić odpowiedzi, należy zapytać go o to.

7. Walidacja danych wejściowych i wyjściowych narzędzia

Samo wywołanie narzędzia nie czyni agenta niezawodnym. Model nadal może wygenerować błędnie sformatowaną prośbę. Klient prosi o zwrot 100 dolarów z zamówienia 48291, a model wysyła:

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

ale twoja API płatności wymaga:

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

Dodaj walidację schematu, na przykład za pomocą 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
);

Nie zatrzymuj się tylko na typach. Sprawdź również zasady biznesowe:

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

Wtedy łańcuch przekształca się w:

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

To ma największe znaczenie dla agentów, które podejmują rzeczywiste działania. LLM nigdy nie powinno mieć ostatecznego słowa w kwestii danych, które są wykonywane.

8. Trzymaj fakty oddzielnie od rozumowania

Subtelnym, ale skutecznym sposobem jest przechowywanie tego, co system wie, oddzielnie od tego, do czego doszedł model:

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

One niosą ze sobą różną pewność. Pierwsze stwierdzenia pochodziły z systemów, natomiast ostatnie jest wnioskiem. Gdy ostateczna odpowiedź okazuje się błędna, można zadać jedno proste pytanie:

Was the fact wrong?

OR
Was the reasoning wrong?

To o wiele łatwiej debugować. Można nawet ujawnić rozdzielanie wewnątrz aplikacji:

FACT
→ Order shipped

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

Niewielka separacja zapewnia dużą możliwość śledzenia.

9. Ucz się na podstawie wykonywanych zadań, a nie treningu

Zróbmy tak, aby agent się doskonalił bez konieczności drobnej kalibracji. Klient prosi o anulowanie zamówienia 48291. Agent sprawdza zamówienie:

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

Następnie wywołuje:

cancel_order(48291)

i otrzymuje w odpowiedzi:

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

Taki wynik warto zachować. Zapisz udany przebieg:

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

Gdy później pojawi się podobna prośba, najpierw poszukaj odpowiedniego wcześniejszego sukcesu:

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

Porażki również czegoś uczą. Nieudana próba pozostawia ślad:

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

Te ślady stają się wiedzą, a pętla sprzężenia zwrotnego wygląda w ten sposób:

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

Nazwijmy to uczeniem w czasie wykonywania. Nie wymaga to ponownego szkolenia.

10. Ocena każdej zmiany

Na koniec sprawdź, czy cokolwiek przyniosło efekt. Załóżmy, że zmieniłeś metodę wyszukiwania. Stwierdzenie „Odpowiedzi są lepsze” nie stanowi dowodu. Stwórz zbiór do oceny:

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

Następnie śledź takie metryki:

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

Tabela przed i po sprawia, że korzyści stają się konkretnymi:

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

Teraz nie twierdzisz, że nowy prompt działa lepiej, ale to pokazujesz. W tym momencie inżynieria AI zaczyna przypominać zwykłą inżynierię oprogramowania:

Change
  ↓
Test
  ↓
Measure
  ↓
Compare
  ↓
Improve

Wszystkie dziesięć warstw razem

Ułożone obok siebie wokół LLM, architektura wygląda w ten sposób:

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

Zwróć uwagę na to, czego nie wydarzyło się: model nie został dostosowany. Poprawił się natomiast system wokół niego.

Pytania, które należy zadać przed obwinianiem modelu

Pierwszą reakcją na halucynację jest często potrzeba lepszego modelu. Czasami jest to słuszne. Najpierw jednak sprawdź listę kontrolną.

  1. Czy dostarczono odpowiedni kontekst?
  2. Czy pobraano właściwe informacje?
  3. Czy usunięto materiał nieistotny?
  4. Czy ustrukturyzowana wiedza mogłaby na to odpowiedzieć w sposób deterministyczny?
  5. Czy narzędzie może odczytać rzeczywisty stan systemu?
  6. Czy wynik działania modelu został zweryfikowany?
  7. Czy fakty były oddzielone od rozumowań?
  8. Czy system nauczył się na poprzednich próbach?
  9. Czy dostarczono dowody?
  10. Czy wyniki są mierzalne?

Te sprawdzenia zazwyczaj prowadzą do rozwiązań tańszych, bezpieczniejszych i łatwiejszych w kontrolowaniu niż dostosowywanie modelu.

Model to tylko jeden element

Korzystny obrazek:

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

LLM generuje możliwości. Wszystko wokół niego dostarcza faktów, kontekstu, ograniczeń, narzędzi, weryfikacji, pamięci oraz informacji zwrotnych, i to właśnie sprawia, że całość jest niezawodna.

Halucynacje są często problemem projektu systemu, a nie problemem modelu. Lepszy kontekst, lepsza wiedza, lepsze narzędzia, zweryfikowane wyniki oraz uczenie się na podstawie tego, co naprawdę się wydarzyło, przyniosą lepsze rezultaty niż kolejne treningi. Pytanie praktyczne przesuwa się od tego, jaki model użyć, ku temu, co model powinien wiedzieć, co może robić oraz jak sprawdzić, czy postąpił prawidłowo.

Literatura pokrewna