Привет, Хабр! Меня зовут Никита Хробостов, я QA-инженер в Just AI. Тестирую диалоговые системы: чат-ботов и голосовых ботов, часть из них с обращениями в RAG.
Моя работа устроена так: беру схему диалога на Холсте, примеряю на себя роль клиента и последовательно прохожу сценарий. Задаю вопрос и проверяю ответ, соглашаюсь и смотрю, правильно ли сработал переход, отказываюсь — проверяю фолбэк. Так приходится проходить десятки сценариев, которые в крупном проекте быстро складываются в большой объем ручной работы.
Чтобы было понятно про объемы. На проекте голосового бота у нас 13 активных сценариев, на чат-боте один. Чтобы проверить один сценарий целиком, нужно от 40 до сотни с лишним звонков, а тест-кейсов при этом в два-четыре раза больше, зависит от дизайна. На полную проверку одного небольшого сценария уходит 5–6 часов.
Проблема не столько в количестве тестов, сколько в том, что человек, который прогоняет пятидесятый тест-кейс за день, начинает говорить как автомат: короткими шаблонными фразами, потому что держит в голове покрытие, а не естественность речи. А реальный пользователь говорит совсем иначе. Именно на естественной речи NLU-часть и разваливается, и именно эти баги проще всего пропустить.
Идея автоматизировать работу с помощью ИИ-агента появилась давно. Расскажу, как я подходил к ней дважды. Во второй раз собрал за один день работающий прототип, который уже находит баги. Материал получился максимально честным: с цифрами, с провалами и с местами, где решение до сих пор не доделано.
Попытка первая и блокер на первом шаге
Изначально я хотел сделать самое очевидное: взять схему диалога, сгенерировать по ней тест-кейсы и прогнать агента строго по ним. Другой вариант был еще проще — передать агенту сценарий бота в JSON и дать ему пройти его самостоятельно.
И тут же уперся в первый шаг, то есть в парсинг дизайна.
Схемы диалогов у нас лежат в визуальном редакторе — Holst. Выгрузить схему оттуда можно в PDF, JPG или SVG, то есть по сути скачивается обычная картинка. Для отрисовки в Confluence этого достаточно, для машинной обработки нет.

Почему OCR не подошел. Мне нужен не приблизительный текст со схемы, а точная структура: какие есть шаги в сценарии (стейты), какие между ними переходы и при каком условии каждый переход срабатывает. OCR давал слишком много ошибок распознавания, а главное, регулярно путал связи между узлами и переходами. Стрелка, приписанная не тому стейту, это тест-кейс, который проверяет не то, что нужно. Каждая такая ошибка на входе превращается в ложнопадающий тест на выходе, и разбирать их вручную дороже, чем прогнать сценарий руками.
Лучший вариант, до которого я дошел, это цепочка конвертаций:
Выгружаем дизайн из Холста в PDF с разделением на фреймы.
Конвертируем PDF в PPTX, т.к. в презентации схема разбирается на отдельные элементы, то есть на текстовые блоки, фигуры и стрелки-переходы, а не остается плоским изображением.
Отдаем PPTX в LLM с промптом и примером желаемого JSON-вывода.
На выходе получаем структуру, довольно похожую на реальный дизайн.
По моим субъективным оценкам такая цепочка дает около 80% точности парсинга схемы. Для генерации черновиков тест-кейсов это рабочий вариант. А вот для строгого автоматического прогона по схеме — нет: оставшиеся 20% выливаются в те же ложные падения.
Есть и еще один важный момент. Схемы диалогов это внутренняя информация о логике продакшен-бота, иногда с текстовками, номерами и деталями бизнес-процессов клиента. Поэтому и конвертеры, и LLM для этого шага я использую только внутренние или локальные, чтобы данные не уходили сторонним системам. Про LLM-часть подробнее расскажу ниже.
На этом первая попытка и закончилась. Задача сделать нормальный парсинг дизайна оказалась больше, чем задача сделать агента, и я отложил идею.
Попытка вторая
Через несколько месяцев я вернулся к теме с другой стороны — было решено отойти от прогона по дизайну и написать агентскую систему из трех ролей:
Агент-генератор. Получает общий контекст по проекту для генерации тест-кейсов. Сейчас работает на уже отлаженном способе получения JSON из PDF-дизайна и пишет только тест-кейсы по этому JSON.
Агент-тестировщик. Собственно ведет диалог с ботом, изображая пользователя.
Агент-судья. Оценивает получившиеся диалоги.
Дальше расскажу про каждую роль по отдельности, потому что все интересное спрятано в деталях.
Что генерирует первый агент
Тест-кейсы складываются отдельным JSON. Вот пример в обрезанном виде:
{ "test_id": "TC_A26_001", "name": "Happy path - greeting agreed", "description": "Успешный сценарий: клиент соглашается разговаривать", "initial_variables": {}, "steps": [ { "actor": "bot", "expected_state": "greeting" }, { "actor": "user", "intent": "Говорите сейчас || Да || Слушаю || Оки || Давайте", "user_text": "Да, давайте" }, { "actor": "bot", "expected_state": "askProducts" } ] }
Логика такая: у шага бота есть ожидаемый стейт, у шага пользователя есть набор допустимых формулировок и конкретная фраза, которую агент произнесет. Ожидаемые стейты берутся из распарсенного дизайна, и именно поэтому качество парсинга так важно.
Агент-тестировщик и его четыре режима
Одного сценария поведения пользователя недостаточно, потому что он проверит только одну траекторию диалога. Поэтому у тестировщика четыре режима поведения:
Клиент, который соглашается со всеми предложениями и доходит до логического конца сценария.
Негативный клиент. Клиент негативит разными способами: просит больше не звонить, ругается, принудительно заканчивает диалог.
Шум. Клиент в случайный момент начинает говорить нелогичный текст или задает неожиданный вопрос. Это имитация разговоров на фоне и плохого распознавания речи, то есть проверка CatchAll-обработки.
Прогон тест-кейсов. Здесь агент идет по заранее сгенерированным кейсам, а не импровизирует.
Третий режим оказался самым полезным. Ровно такие входные данные и приходят от реальных пользователей: обрывки фраз, посторонняя речь, вопрос не по теме посреди оформления заявки.
Устроено это как базовый системный промпт плюс короткая инструкция режима, а в запрос дополнительно передаются примеры паттернов. Вот основа:
BASE_SYSTEM_PROMPT = """Ты тестирующий агент для голосового бота. Веди себя как один и тот же реальный клиент на протяжении всей сессии. Отвечай коротко, естественно, по-русски. Не объясняй правила, не пиши markdown, не добавляй комментарии вне JSON. Верни строго JSON: {"query":"текст следующего ответа клиентом","reason":"кратко почему выбран этот ответ"}""" MODES = { "negative": "Иди по ветке отказа максимально естественно " "и старайся завершить диалог корректным негативным ответом.", # ... }
Параметр, о котором я сначала не подумал
Первый же прогон вскрыл забавную проблему: два бота могут разговаривать друг с другом бесконечно.
Если в дизайне нет явного окончания диалога, например это бот с обращениями в RAG, где можно спрашивать что угодно, пока сам не попрощаешься, то агент-тестировщик и бот вежливо беседуют, пока их кто-нибудь не остановит. А каждая такая реплика стоит денег!
Поэтому в контекст агента пришлось добавить два параметра: минимальное и максимальное число шагов и способ окончания диалога. Агент знает, сколько вопросов или ответов ему осталось сделать до конца теста, и может либо аккуратно попрощаться, либо резко оборвать разговор. Кстати, обрыв на середине это тоже отдельный тестовый случай.
Агент-судья
Судья это отдельная модель, которая читает залогированный диалог и оценивает адекватность ответов бота.
Запускать его имеет смысл при большом количестве прогонов и только на первых трех режимах. В режиме прогона тест-кейсов судья не нужен, потому что там уже есть ожидаемое поведение, с которым можно сравнить результат напрямую.
Смысл судьи в том, что он позволяет не читать глазами сто диалогов, а посмотреть только те, которые он пометил как проблемные.
Другое дело — голосовой бот
С чат-ботом все относительно просто: отправил текст, получил текст. С голосовым ботом, у которого предзаписанные реплики, в ответе приходит ссылка на аудио. Поэтому в цикл тестирования пришлось добавить еще несколько шагов: скачать аудио, транскрибировать его, положить текст в контекст модели, получить следующую реплику и отправить ее боту.
Для транскрибации я использую локальный faster-whisper — библиотеку для перевода аудио в текст. Скорость обработки приятно удивила, качество тоже. Несмотря на то, что аудио у нас хорошее, транскрипт обычно очень близок к оригиналу, хотя артефакты все равно появляются.
И вот что здесь важно отметить: для общего тестирования, то есть для проверки того, прошел ли диалог, не упал ли бот и туда ли ушла ветка, этого достаточно с запасом. Модель понимает смысл реплики и может продолжать диалог. Но для строгого прогона по дизайну со сверкой текстовок слово в слово — это очередной источник ложных падений. Например, если транскрибатор иначе распознал название продукта или бренда, тест упадет не потому, что бот ответил неправильно.
Как я собирал решение
Собрать работающий драфт удалось с первого раза, быстро и почти бесплатно. Первая итерация сборки проекта уместилась в бесплатный дневной лимит Codex. Остальные минорные правки и интерфейс запуска я дописывал уже сам.

Под капотом там нет никакого агентного фреймворка: это самописный цикл на Python с прямыми вызовами API. Для такой задачи этого достаточно.
Главным фактором оказался не выбор модели, а подготовка контекста. Перед тем как просить агента писать код, я прошел весь путь тестирования через Chat API бота вручную в Postman. И положил в AGENTS.md реальные примеры запросов и ответов, которые получил сам, а не придумал, четко расписанную логику работы будущего агента и пожелания по используемым библиотекам. Вот как выглядит часть про стек:

Это кажется очевидным, но сильно сокращает пространство для фантазии модели. У нее уже есть реальные примеры API-контракта, а не описание того, как он примерно должен работать. В итоге первый тестовый прогон сразу нашел баги, что для проекта, собранного за день, довольно неплохой результат.
Запускается все это пока с моего личного компьютера, логи диалогов складываются в файлы в локальной папке.
Где здесь LLM и почему через Caila
Языковые модели в этой схеме используются в четырех местах: разбор PPTX в JSON, генерация тест-кейсов, сам агент-тестировщик и судья. Требования к этому слою у меня были такие:
Данные не должны утекать в сторонние сервисы. Через модель проходят схемы диалогов и текстовки продакшен-ботов.
Модель нужно менять без переписывания кода. Для роли клиента и роли судьи подходят разные модели, и подбирать их приходится экспериментально.
Расходы должны быть видны. Иначе стоимость такого тестирования становится непредсказуемой.
Поэтому все обращения к LLM в проекте идут через Caila, один из наших продуктов от Just AI, которая работает как единая точка доступа к моделям, облачным и открытым.
Для моего сценария это удобно прежде всего технически:
Один ключ и один base_url на все. В коде обычный OpenAI-совместимый клиент, у которого подменены базовый URL и ключ. Ничего специфического писать не пришлось, никаких отдельных интеграций под каждого вендора.
Модель меняется одной строкой. Когда нужно проверить, справится ли с ролью клиента модель попроще и подешевле, я меняю идентификатор модели, а не переписываю обвязку.
Видимость расходов. Стоимость прогонов я считаю по расходу на своем ключе, в разбивке по моделям и запросам. Без этого разговор о том, сколько стоит автотест на агенте, остается разговором про ощущения, что мы в компании не любим.
Изолированный контур. Caila разворачивается и в изолированном контуре, а часть моделей вообще не выходит за пределы РФ. Для задачи, где через модель проходят внутренние схемы диалогов, это как раз тот случай, когда важно не то, какая модель умнее, а то, куда физически уходит запрос.
При этом Caila не решает за меня, какую модель лучше выбрать для судьи или для разбора PPTX. Это по-прежнему приходится проверять самому. Но сравнивать варианты проще: один и тот же запрос можно отправить разным моделям через один и тот же код.
Сколько это стоит
Стоимость одного прогона, то есть одного диалога от начала до конца, сильно зависит от длины ответов тестируемого бота и от длительности разговора:
Для скриптового бота один тест-кейс обходится примерно в 20–30 копеек.
Бот с объемными ответами и ссылками дает самый дорогой диалог, примерно 80–90 копеек.
То есть полный прогон из ста тест-кейсов стоит от 20 до примерно 90 рублей. Транскрибация в эту сумму не входит вообще, потому что faster-whisper запускается локально на компьютере и не использует внешний сервис.
Когда я первый раз посмотрел на расход после прогона, то решил, что где-то сломался биллинг.
Что агент находит хорошо, а что нет
Это, пожалуй, самый полезный раздел, потому что тут проще всего обмануться.
Находит хорошо:
NLU-баги. Это как раз основная задача проекта. Агент говорит естественнее, чем тестировщик, который стремится охватить объем проекта и невольно переходит на шаблонные формулировки. В результате легче обнаруживаются случаи, когда паттерн не смэтчился, диалог ушел не в ту ветку или бот неправильно понял реплику.
Критичные падения. Если бот упал, ветка не пошла или сценарий сломался, это обычно видно уже на первом прогоне.
Проблемы с шумом. Агент может специально задавать неожиданные вопросы и говорить нелогичные вещи, поэтому хорошо проверяет поведение CatchAll.
Находит плохо:
Ошибки в текстовках. Сверять формулировки слово в слово с дизайном агент не может: мешают и артефакты транскрибации, и те же 20% погрешности парсинга схемы.
Минорные правки дизайна. Мелкие изменения и специфические кейсы по-прежнему проверяются руками, и, честно говоря, я не жду, что это изменится.
И общая оговорка: все прогоны пока требуют ручной перепроверки. Я запускаю прогон фоном во время созвона, а потом смотрю результат и понимаю, где искать. Параллельно проверяю руками то, что агенту не отдашь. Экономия времени получается за счет распараллеливания, а не за счет замены тестировщика.
Что дальше
Называть это готовым продуктом было бы преувеличением. Сейчас проект ближе к PoC, заточенному под конкретные боты, над которыми я работаю. Планы по порядку приоритета:
Довести парсинг дизайна. Это по-прежнему проблема. Пока точность не вырастет, полноценный прогон шаг в шаг по дизайну будет упираться в ложные падения.
Перестроить сам процесс тестирования. Сейчас агент дополняет мою работу, а должен часть ее заменить. В идеале сценарий сразу пишется в машиночитаемом виде, и тогда вся костыльная цепочка с переводом дизайна в JSON просто не нужна. Но такие перестановки не делаются с разбега: сначала нужны убедительные результаты личного использования и бета-тест внутри команды.
Сделать запуск бесшовным. Сейчас пайплайн запускается по частям, а хочется свести все к одной кнопке.
Превратить проект в подобие фреймворка. Идеальный вариант — универсальный интерфейс, в котором для нового проекта достаточно заменить эндпоинт и API-ключи, после чего агент сможет тестировать другого бота. Сейчас проект слишком привязан к конкретным ботам, хотя логика внутри у него общая.
Выводы
Есть две вещи, которые я бы сказал себе год назад.
Не пытайтесь заставить LLM делать то, в чем она слабее человека. Обход графа по строгим правилам это не ее сильная сторона. Естественная речь как раз ее.
Основная работа лежит не в промпте, а в данных на входе. Мой блокер был не в модели и не в архитектуре агента, а в том, что схема диалога выгружается картинкой. Полдня, потраченные на ручной проход API в Postman и на подробный AGENTS.md, сэкономили несколько дней отладки.
Технический стек проекта сейчас такой: Python, faster-whisper (STT, локально), Codex (сборка проекта), Caila (LLM по API).
Открытый вопрос, который я так и не закрыл, это машинный парсинг схем диалога. Если вы решали похожую задачу и у вас есть подход точнее, чем цепочка из PDF, PPTX и LLM, расскажите в комментариях. И если у вас есть свои режимы поведения тестового пользователя, которых нет у меня, тоже рассказывайте.
Если захочется показать код или разобрать конкретный кейс, а не писать это в комментарии, заходите в наш чат для разработчиков.
Комментарии (5)

predpremi
26.08.2026 14:03Никит, позови меня в Just AI в продажи. Не знаю как пробиться к вам. Второй год с*учусь.
Контакт Руководителя Отдела Продажможешь дать?
kokanov
Один умный человек в своё время сказал: нельзя автоматизировать хаос. Автоматизация хаоса приводит к ещё большему хаосу. (Цитата не точная).
Вы лучше разберитесь с процессами, чтобы не картинки исовать, и затем парсить, а изначально текстовые скрипты диалогов делать, которые можно и в автоматизации использовать и на их основе потом картинки генерировать.
just_ai Автор
Здравствуйте! Согласны, автоматизировать хаос — такая себе затея :)
Мы как раз об этом и пишем в статье, что нужно перестраивать процессы, чтобы автоматизация была без костылей. В большой компании нельзя просто взять и по щелчку перестроить процесс. Сначала нужно показать, что автоматизация работает и действительно упрощает жизнь команде — а уже потом менять весь процесс.
Визуальный формат у нас появился давно и не случайно: его удобно редактировать и валидировать вместе с заказчиком, а бизнесу все-таки проще работать с визуальной схемой, чем с JSON. К тому же дублирование дизайна в текстовом формате требует дополнительных трудозатрат от дизайнера.
Собственно, мы и хотели поделиться честным кейсом о том, как сотрудники сами ищут способы упростить себе работу. А такая автоматизация помогла увидеть, что и сам процесс можно сделать удобнее. Сейчас уже начались обсуждения в команде QA