Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи? Написать
Дайджесты новостей
Постерная схема: хаотичный AI-агент с вызовами инструментов проходит через строгий программный каркас, получает данные из базы и формирует ответ клиенту.

Харнес для AI-агента поддержки: детерминированный пайплайн вместо вызова тулов моделью

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

Однако стандартный подход к построению диалоговых агентов (концепция ReAct и механизм Tool Calling) в реальной эксплуатации сталкивается с серьезными сбоями. В классической агентской схеме большая языковая модель (LLM) самостоятельно принимает решение: ответить клиенту из общих соображений или вызвать внешнюю функцию (тул) для запроса в базу данных заказов.

На практике стохастическая природа нейросетей приводит к регулярным ошибкам:

  • В одном из десяти диалогов модель «забывает» запросить статус заказа и начинает вежливо успокаивать клиента общими фразами.
  • При получении частичных данных модель способна додумать (галлюцинировать) несуществующую дату доставки или фиктивный номер трек-кода.
  • В сложных ветках диалога агент входит в бесконечный цикл повторных вызовов одного и того же инструмента, пока не исчерпает лимит токенов.

Инженерный разбор архитектуры открытого проекта support-agent предлагает радикальное решение: полный отказ от свободного выбора тулов нейросетью в пользу жесткого программного каркаса (Deterministic Harness).

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

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

  1. Первое поколение: деревья решений на JSON-графах. Диалог строился по жестко прописанным сценариям с кнопками («Узнать статус», «Оформить возврат»). Система обладала стопроцентной предсказуемостью, но раздражала клиентов роботизированностью, не умела понимать свободную человеческую речь и требовала колоссальных трудозатрат аналитиков на прописывание каждой ветки.
  2. Второе поколение: свободные ReAct-агенты. Модели дали полный контекст истории, список инструментов и право самой оркестрировать процесс. Клиентский опыт стал естественным и дружелюбным, но надежность упала ниже допустимого порога. Чтобы модель реже сбоила, приходилось подключать самые дорогие флагманские модели (уровня Claude Opus или GPT-4), но даже они периодически теряли нить диалога.
  3. Третье поколение: детерминированный харнес. Архитектурный гибрид, объединяющий надежность кода и языковую гибкость нейросети. В этой схеме бизнес-логика и переходы между состояниями полностью контролируются классическим серверным кодом на TypeScript и NestJS. Нейросеть вызывается строго дважды: в начале для семантического распознавания намерения (интента) и извлечения параметров, а в конце — для генерации естественного текстового ответа на основе уже полученных из базы данных фактов.

Радикальная экономика: сокращение расходов в 60 раз

Помимо абсолютной надежности, архитектурный харнес фундаментально меняет финансовую модель инференса.

Слева разросшийся поток сообщений, документов и инструментов загружает красную модель и приводит к большой куче монет; справа компактный зелёный харнес последовательно передаёт JSON, проверенные данные и ответ с небольшой кучей монет.

Когда модель находится в режиме свободного агента, каждый шаг цикла требует передачи всей истории сообщений, системного промпта и подробных описаний всех доступных инструментов (JSON Schema). Контекстное окно стремительно раздувается. По расчетам команды проекта, обслуживание 1000 диалогов с использованием флагманской облачной модели обходится примерно в $30.

В жестком харнесе задача модели предельно упрощается. Ей не нужно держать в памяти сложную логику оркестрации: на первом шаге требуется вернуть лишь короткий JSON с категорией запроса, а на втором — пересказать готовые факты живым языком. С этой задачей безупречно справляются компактные открытые модели (например, Qwen 2.5 или 3.5 размером 9B параметров), запущенные локально через vLLM или Ollama. Стоимость обслуживания тех же 1000 диалогов падает до $0.48, что снижает инфраструктурные затраты более чем в 60 раз.

Организация чистой истории сообщений

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

В детерминированном харнесе контекст делится на два уровня:

  • Внутренний журнал трейсов: технический лог выполнения шагов, доступный операторам и аналитикам через веб-интерфейс на React.
  • Чистый клиентский контекст: для генерации ответа модели передается только прямая речь собеседников без программного мусора.

Пошаговая реализация контрактов и конвейера

Практическая реализация строится на монорепозитории, объединяющем серверную часть на NestJS 11, ORM Prisma 7 и фронтенд оператора на React 19.

В общем пакете контрактов объявляются строгие схемы валидации входящих аргументов с помощью библиотеки Zod:

// packages/contracts/src/order.ts
import { z } from 'zod';

// Схема валидации параметров запроса статуса заказа
export const GetOrderStatusArgs = z.object({
  orderNumber: z.string().trim().min(1, 'Номер заказа не может быть пустым'),
});

export type GetOrderStatusInput = z.infer<typeof GetOrderStatusArgs>;

// Структура гарантированного ответа для модели
export interface OrderStatusResult {
  orderNumber: string;
  status: 'PENDING' | 'IN_DELIVERY' | 'DELIVERED' | 'CANCELLED';
  deliveryDate: string | null;
  itemsCount: number;
}

На серверной стороне создается сервис безопасного исполнения инструментов. Код перехватывает аргументы, проводит валидацию, обращается к базе данных через Prisma и формирует детерминированный ответ:

// apps/backend/src/agent/tools.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { GetOrderStatusArgs, OrderStatusResult } from '@app/contracts';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class ToolsService {
  private readonly logger = new Logger(ToolsService.name);

  constructor(private readonly prisma: PrismaService) {}

  async executeGetOrderStatus(rawInput: unknown): Promise<OrderStatusResult | { error: string }> {
    // 1. Строгая проверка типов и формата номера заказа через Zod
    const parsed = GetOrderStatusArgs.safeParse(rawInput);
    if (!parsed.success) {
      this.logger.warn(`Некорректный аргумент заказа: ${JSON.stringify(rawInput)}`);
      return { error: 'Не удалось распознать корректный номер заказа' };
    }

    // 2. Детерминированный запрос к PostgreSQL без участия нейросети
    const order = await this.prisma.order.findUnique({
      where: { number: parsed.data.orderNumber },
      include: { items: true },
    });

    if (!order) {
      return { error: `Заказ ${parsed.data.orderNumber} не найден в системе` };
    }

    // 3. Возврат строго типизированного результата
    return {
      orderNumber: order.number,
      status: order.status,
      deliveryDate: order.estimatedDelivery ? order.estimatedDelivery.toISOString() : null,
      itemsCount: order.items.length,
    };
  }
}

Далее конвейер формирует финальный промпт для синтеза ответа. Модели подается исходный вопрос клиента и готовый объект с данными. Модели запрещено придумывать новые детали: ее единственная задача — вежливо изложить готовые факты.

Лимиты шагов и автоматическая эскалация оператору

Никакая автоматизация не должна ставить клиента в тупик при возникновении нестандартных ситуаций. В харнесе реализованы два предохранительных контура:

  1. Лимит внутри одного хода (Step Limit = 3). На один входящий вопрос система выполняет максимум три шага: классификацию, запрос в базу данных и генерацию ответа. Если произошла ошибка валидации, модели дается одна попытка уточнить данные у клиента. Зацикливание исключено на программном уровне.
  2. Лимит глубины сессии (Turn Limit = 15). Если общение длится дольше пятнадцати реплик, а проблема клиента не решена, диалог автоматически переводится на дежурного сотрудника поддержки с сохранением всей истории и трейсов.

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