ГлавнаяБлогИИ-агент поддержки: честность как свойство архитектуры
AI / Нейросети

ИИ-агент поддержки: честность как свойство архитектуры

Как построить ИИ-агента поддержки, который не галлюцинирует: ограниченные интенты, типизированные действия, отказ как норма. Читайте и внедряйте!

Al
Редакция Algolitalgolit.ru
8 мин чтения11 августа 2026 г.

Почему ИИ-агенты поддержки врут и как это исправить

Большинство демо «ИИ-клиентского сервиса» проваливаются одинаково: вы задаёте вопрос, на который модель не может ответить из данных, и вместо остановки она выдаёт правдоподобный ответ. В чат-игрушке это курьёз, а в постпродажном обслуживании — обещание, которое компания обязана выполнить: возврат, который никто не одобрял, или дата доставки, которой никогда не было.

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

Ошибка: модель как источник истины

Наивный дизайн — одна модель, один большой промпт и куча документов в векторной базе. Спрашиваете «где мой заказ 41822?» — слой поиска возвращает три чанка, похожих на вопрос. Ни один не содержит заказ 41822, потому что это живая строка в БД, а не документ. Модель получает контекст, полный текстов про заказы, и всё равно отвечает. Ответ неверный.

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

Ограниченные действия вместо свободной генерации

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

Каждый интент сопоставляется с типизированным действием и явным контрактом:

from dataclasses import dataclass
from enum import Enum
from typing import Optional

class Reason(Enum):
    NOT_FOUND = 'not_found'
    OUT_OF_SCOPE = 'out_of_scope'
    POLICY_UNCLEAR = 'policy_unclear'
    LOW_CONFIDENCE = 'low_confidence'
    HUMAN_REQUESTED = 'human_requested'
    EMOTIONAL = 'emotional'

@dataclass(frozen=True)
class OrderStatus:
    """Читает систему записи заказов. Никогда не генерирует статус."""
    order_ref: str

    def resolve(self, ctx) -> 'Resolution':
        order = ctx.commerce.get_order(self.order_ref)  # API Shopify/WooCommerce
        if order is None:
            return Resolution.escalate(
                reason=Reason.NOT_FOUND,
                say="Я не могу найти номер заказа на этом аккаунте."
            )
        return Resolution.answer(
            template="order_status",
            facts=order.public_facts(),  # только белый список полей
        )

Две детали делают основную работу. Первая: facts — это белый список. public_facts() возвращает перевозчика, трек-номер, время отправки и текущий статус. Он не возвращает маржу, внутренние заметки, другие заказы клиента или скоринг фрода. Модель не может утечь поле, которое ей никогда не передавали.

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

Отказ как полноценный результат

Resolution.escalate — это не путь ошибки. Это ожидаемый результат со своей планкой качества, и он самый важный.

class Resolution:
    @staticmethod
    def escalate(reason: Reason, say: str) -> 'Resolution':
        return {'type': 'escalate', 'reason': reason, 'say': say}

    @staticmethod
    def answer(template: str, facts: dict) -> 'Resolution':
        return {'type': 'answer', 'template': template, 'facts': facts}

Каждая причина даёт разную передачу: конкретное сообщение клиенту, приоритет в очереди людей и сводку для тикета, чтобы тот, кто поднимет, не начинал с нуля.

Ветка EMOTIONAL важнее, чем кажется. Разгневанный клиент — это не сбой поиска; система может владеть всеми фактами. Всё равно передаём человеку, потому что «технически решаемо» и «должно обрабатываться машиной» — разные вопросы. Ошибка здесь — как автоматизация теряет доверие, которое должна была заслужить.

LOW_CONFIDENCE требует реального порога, откалиброванного под каждый магазин, а не жёстко заданного 0.7. Порог должен быть асимметричным: неверный возврат стоит намного дороже лишней эскалации.

Запись отделена от чтения

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

actions:
  order_status:
    mode: auto
  return_label:
    mode: auto
    max_value_eur: 80
  refund:
    mode: propose   # агент создаёт черновик, человек нажимает «отправить»
  cancel_order:
    mode: propose
  address_change:
    mode: auto
    before_dispatch_only: true

Режим propose — это то, что делает первый месяц внедрения жизнеспособным. Агент выполняет работу и создаёт черновик; человек одобряет; вы наблюдаете за процентом одобрений. Когда действие проходит без правок достаточно часто, эта история оправдывает переключение на auto: решение, заработанное на своих данных, а не обещание вендора.

Почему размещение — это архитектурное решение

Я запускаю каждый магазин на отдельном инстансе, во Франции, на французской модели (Mistral). Это звучит как маркетинг, поэтому вот инженерия за этим.

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

Выделенный инстанс также делает обратимость реальной, а не контрактной. Когда мерчант уходит, экспорт — это дамп базы данных и конфиг-файл, передаваемые без единого тикета в поддержку.

Обязательство прозрачности в EU AI Act (статья 50) указывает в ту же сторону: клиент должен знать, что говорит с машиной. Это легче гарантировать, когда раскрытие живёт в вашем собственном слое компоновки сообщений, чем когда это тумблер в чужой панели управления.

Что это вам стоит

Компромисс реален: такая схема решает меньше разговоров, чем модель со свободными руками. Закрытый набор интентов не покроет длинный хвост, ограниченные действия не могут импровизировать, а режим propose держит человека в цикле неделями.

Я бы пошёл на этот компромисс каждый раз. Разрешительный дизайн имеет худший режим отказа. Он даёт не чуть худший ответ, а уверенное ложное утверждение, на которое действует реальный человек, в канале, где юридически ответственным является магазин.

Агент, который говорит «давайте подключу человека», — это лёгкое разочарование. Агент, который выдумывает политику возврата, — это инцидент, который магазину потом приходится разгребать.

Практический вывод

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

#ИИ-агенты#архитектура#поддержка клиентов#безопасность
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →