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

Когда модель находится в режиме свободного агента, каждый шаг цикла требует передачи всей истории сообщений, системного промпта и подробных описаний всех доступных инструментов (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,
};
}
}
Далее конвейер формирует финальный промпт для синтеза ответа. Модели подается исходный вопрос клиента и готовый объект с данными. Модели запрещено придумывать новые детали: ее единственная задача — вежливо изложить готовые факты.
Лимиты шагов и автоматическая эскалация оператору
Никакая автоматизация не должна ставить клиента в тупик при возникновении нестандартных ситуаций. В харнесе реализованы два предохранительных контура:
- Лимит внутри одного хода (Step Limit = 3). На один входящий вопрос система выполняет максимум три шага: классификацию, запрос в базу данных и генерацию ответа. Если произошла ошибка валидации, модели дается одна попытка уточнить данные у клиента. Зацикливание исключено на программном уровне.
- Лимит глубины сессии (Turn Limit = 15). Если общение длится дольше пятнадцати реплик, а проблема клиента не решена, диалог автоматически переводится на дежурного сотрудника поддержки с сохранением всей истории и трейсов.
Такая организация устраняет юридические и финансовые риски: робот никогда не подтвердит ошибочную отмену заказа, не пообещает клиенту несогласованную компенсацию и сэкономит компании тысячи долларов на инфраструктуре.
