Главная / Статьи

Десять способов уменьшить галлюцинации без финтюнинга

Десять исправлений на уровне системы, снижающих галлюцинации в больших языковых моделях в агентах электронной коммерции: улучшение контекста, поиска, графов, инструментов, проверки и оценки без необходимости финтюнинга.

Издатель: REACTAPP.TOP9 мин чтения2156 слов

Источники: 10 Ways to Reduce AI Hallucinations Without Fine-Tuning Your Model

Когда ассистент сообщает клиенту: «Ваш возврат средств обработан», но на самом деле возврата нет, причиной обычно не является сама модель. В системе существует пробел, который модель заполняет на основе предположений. Статус заказа отсутствует, запросы возвращают бесполезную информацию, контекст перегружен, вызовы инструментов не проверяются, а предыдущие успехи забываются.

Можно значительно сократить количество галлюцинаций, не меняя веса модели. Приведённый здесь пример — это ассистент в электронной коммерции, который проверяет заказы, осуществляет возвраты средств и обновляет информацию о доставке; мы будем укреплять его постепенно, слой за слоем.

Примитивная версия

Клиент пишет: «Где мой заказ №48291?» Первоначальная версия приложения просто пересылает этот вопрос дальше:

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

console.log(response.output_text);

Модель может ответить: «Ваш заказ отправлен в доставку и должен прибыть сегодня». Прекрасно, только она совершенно не знает, где находится заказ номер 48291. Она сгенерировала правдоподобное предложение на основе шаблонов из своих учебных данных. Это галлюцинация.

То, что вам нужно, выглядит скорее так:

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

Модель должна опираться на реальность, а не выдумывать её.

1. Передайте уже имеющийся у вас контекст

Первое правило: не заставляйте модель догадываться о том, что знает ваш бэкенд. Укажите, что бэкенд хранит следующую запись:

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

Предоставьте его вместе с вопросом:

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

Теперь от модели не требуется знать состояние заказа. Её задача сводится к тому, чтобы объяснить клиенту известный факт, что гораздо безопаснее. Сравните эти два варианта работы:

Bad:

Customer → LLM → Answer

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

Это суть инжиниринга контекста.

2. Сначала получите информацию, затем генерируйте

Другой клиент спрашивает о политике возврата электроники, купленной во время распродажи. Этот ответ отсутствует в модели; он хранится в вашей базе данных политик, документации к продуктам, правилах акций и региональных условиях. Поэтому сначала получите эту информацию, прежде чем что-либо генерировать. Основной алгоритм RAG:

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

И минимальная реализация:

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

Здесь качество важнее количества. Загрузка модели 30 документов не делает её умнее. Если важны три документа, остальные 27 — это лишний шум.

3. Устраните шум

Представьте, что ассистент получает эту кучу документов:

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

Теперь клиент спрашивает только: «Могу ли я вернуть эти наушники?» Модели не нужны последние пятнадцать разговоров, не связанные заказы, другие линейки продуктов, истекшие акции или старые правила доставки. Более эффективная система фильтрует информацию перед тем, как задавать вопросы:

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

В коде это будет выглядеть примерно так:

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

Вы не пытаетесь максимизировать количество токенов. Вы пытаетесь повысить соотношение сигнал/шум. Хорошее правило: дайте модели всё, что ей нужно, и ничего лишнего.

4. Используйте граф знаний для структурированных данных

Технология RAG подходит для документов. Однако многие вопросы в электронной коммерции вообще не связаны с документами. Они касаются взаимосвязей: какой склад обрабатывает этот заказ, какая оплата его оплатила, какие товары входят в акцию, какие заказы будут затронуты в случае отключения склада. Это проблемы графов. Упрощённый граф:

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

Когда пользователь спрашивает, какой склад обрабатывает его заказ, система может следовать этому пути:

Order #48291
      ↓
FULFILLED_BY
      ↓
Warehouse #17

со запросом к графу вида:

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

Результат становится надежным контекстом:

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

Разместите оба подхода рядом. Без структуры:

Without structured knowledge:

User → LLM
         ↓
      Guess

И с графом в цикле:

With Knowledge Graph:

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

Это особенно полезно, когда ассистенту необходимо рассуждать, опираясь на несколько систем.

5. Запрашивайте доказательства

Не просите только ответа. Запрашивайте ответ вместе с тем, что его подтверждает. Структурированный ответ может выглядеть так:

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

Вы можете закрепить эту привычку с помощью инструкций:

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.

Затем приложение может принять решение на основе уровня уверенности:

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

Это меняет простой порядок выполнения:

LLM → Answer

с проверками на каждом этапе:

LLM
 ↓
Answer
 ↓
Evidence
 ↓
Confidence
 ↓
Decision

6. Предоставьте агенту необходимые инструменты вместо того, чтобы заставлять его догадываться

Иногда лучшим способом предотвращения проблем является вообще не позволять большой языковой модели отвечать. Если клиент спрашивает, была ли произведена возврат средств, покажите соответствующую информацию при поиске:

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

Затем выполнение происходит следующим образом:

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

Вместо расплывчатой фразы «ваш возврат должен поступить через 5–7 дней» агент может сказать, что процедура возврата была начата вчера и до сих пор находится в процессе обработки, если это действительно так. Если есть возможность получить точный ответ, обратитесь к источнику информации.

7. Проверка входных и выходных данных инструмента

Одного лишь вызова инструмента недостаточно для того, чтобы считать агента надежным. Модель всё ещё может сгенерировать некорректный запрос. Клиент просит вернуть 100 долларов с заказа 48291, и модель выдает:

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

но ваш API оплаты требует:

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

Добавьте проверку схемы, например, с помощью 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
);

Не ограничивайтесь типами. Проверьте также бизнес-правила:

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

Затем конвейер выглядит так:

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

Это особенно важно для агентов, которые принимают реальные действия. ЯЗЫК никогда не должен иметь окончательное слово по отношению к данным, которые выполняются.

8. Разделяйте факты и логику

Тонкий, но эффективный прием — хранить то, что знает система, отдельно от того, к чему пришел к выводу модель:

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

Они несут в себе разную степень уверенности. Первые утверждения исходили от систем, тогда как последнее — это вывод. Когда окончательный ответ оказывается неверным, можно задать один простой вопрос:

Was the fact wrong?

OR
Was the reasoning wrong?

Так гораздо проще отлаживать. Вы даже можете выявить место разделения внутри кода:

FACT
→ Order shipped

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

Небольшое разделение позволяет обеспечить высокий уровень отслеживаемости.

9. Учитесь на реальных задачах, а не на тренировках

Давайте заставим агента совершенствоваться без доработки его настроек. Клиент просит отменить заказ 48291. Агент проверяет заказ:

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

Затем вызов:

cancel_order(48291)

и получает в ответ:

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

Такой результат стоит сохранить. Сохраните успешную попытку:

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

Когда позже появится похожий запрос, сначала найдите соответствующий прошлый успешный случай:

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

Неудачи тоже учат. Неудавшаяся попытка оставляет след:

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

Эти следы превращаются в знания, и цикл обратной связи выглядит так:

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

Назовите это обучением во время выполнения. Переобучение не требуется.

10. Оценка каждого изменения

В конце концов необходимо определить, помогло ли что-либо. Допустим, вы изменили способ поиска информации. Фраза «Ответы стали лучше» не является доказательством. Составьте набор для оценки:

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

Затем отслеживайте такие показатели:

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

Таблица с сравнением до и после делает полученный результат более наглядным:

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

Теперь вы не утверждаете, что новый промпт работает лучше, вы это демонстрируете. Именно здесь инженерия ИИ начинает напоминать обычную инженерию программного обеспечения:

Change
  ↓
Test
  ↓
Measure
  ↓
Compare
  ↓
Improve

Все десять слоев вместе

Сложив десять слоев вокруг LLM, архитектура будет выглядеть следующим образом:

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

Обратите внимание на то, чего не произошло: модель не подвергалась доработке. Улучшилась система, в которой она работает.

Вопросы, которые следует задать перед тем, как винить модель

Первым порывом при возникновении галлюцинаций часто бывает желание использовать лучшую модель. Иногда это верно. Однако сначала стоит пройтись по списку проверок.

  1. Был ли предоставлен правильный контекст?
  2. Была ли получена правильная информация?
  3. Был ли удалён нерелевантный материал?
  4. Может ли структурированные знания дать однозначный ответ?
  5. Может ли инструмент считывать фактическое состояние системы?
  6. Был ли результат работы модели проверен?
  7. Были ли факты отделены от логических рассуждений?
  8. Научилась ли система на основе предыдущих запусков?
  9. Были ли предоставлены доказательства?
  10. Измеряются ли результаты?

Эти проверки обычно приводят к решениям, которые дешевле, безопаснее и проще в контроле, чем доработка модели.

Модель — это лишь один компонент

Полезная картинка:

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

ГЛМ генерирует возможные варианты. Всё вокруг него предоставляет факты, контекст, ограничения, инструменты, средства проверки, память и обратную связь, и именно это делает всю систему надежной.

Галлюцинации чаще являются проблемой проектирования системы, чем проблемой самой модели. Лучший контекст, лучшие знания, лучшие инструменты, проверенный результат и извлечение уроков из того, что действительно произошло, принесут больший эффект, чем просто еще одна процедура обучения. Практический вопрос смещается от выбора модели к определению того, что должна знать модель, что она может делать, и как определить, правильно ли она справилась с задачей.