Привет, Хабр! Меня зовут Никита Хробостов, я QA-инженер в Just AI. Тестирую диалоговые системы: чат-ботов и голосовых ботов, часть из них с обращениями в RAG.

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

Чтобы было понятно про объемы. На проекте голосового бота у нас 13 активных сценариев, на чат-боте один. Чтобы проверить один сценарий целиком, нужно от 40 до сотни с лишним звонков, а тест-кейсов при этом в два-четыре раза больше, зависит от дизайна. На полную проверку одного небольшого сценария уходит 5–6 часов.

Проблема не столько в количестве тестов, сколько в том, что человек, который прогоняет пятидесятый тест-кейс за день, начинает говорить как автомат: короткими шаблонными фразами, потому что держит в голове покрытие, а не естественность речи. А реальный пользователь говорит совсем иначе. Именно на естественной речи NLU-часть и разваливается, и именно эти баги проще всего пропустить.

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

Попытка первая и блокер на первом шаге

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

И тут же уперся в первый шаг, то есть в парсинг дизайна.

Схемы диалогов у нас лежат в визуальном редакторе — Holst. Выгрузить схему оттуда можно в PDF, JPG или SVG, то есть по сути скачивается обычная картинка. Для отрисовки в Confluence этого достаточно, для машинной обработки нет.

Пример схемы диалогов в визуальном редакторе, намеренно заблюренного из-за NDA
Пример схемы диалогов в визуальном редакторе, намеренно заблюренного из-за NDA

Почему OCR не подошел. Мне нужен не приблизительный текст со схемы, а точная структура: какие есть шаги в сценарии (стейты), какие между ними переходы и при каком условии каждый переход срабатывает. OCR давал слишком много ошибок распознавания, а главное, регулярно путал связи между узлами и переходами. Стрелка, приписанная не тому стейту, это тест-кейс, который проверяет не то, что нужно. Каждая такая ошибка на входе превращается в ложнопадающий тест на выходе, и разбирать их вручную дороже, чем прогнать сценарий руками.

Лучший вариант, до которого я дошел, это цепочка конвертаций:

  1. Выгружаем дизайн из Холста в PDF с разделением на фреймы.

  2. Конвертируем PDF в PPTX, т.к. в презентации схема разбирается на отдельные элементы, то есть на текстовые блоки, фигуры и стрелки-переходы, а не остается плоским изображением.

  3. Отдаем PPTX в LLM с промптом и примером желаемого JSON-вывода.

  4. На выходе получаем структуру, довольно похожую на реальный дизайн.

По моим субъективным оценкам такая цепочка дает около 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" }
  ]
}

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

Агент-тестировщик и его четыре режима

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

  1. Клиент, который соглашается со всеми предложениями и доходит до логического конца сценария. 

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

  3. Шум. Клиент в случайный момент начинает говорить нелогичный текст или задает неожиданный вопрос. Это имитация разговоров на фоне и плохого распознавания речи, то есть проверка CatchAll-обработки.

  4. Прогон тест-кейсов. Здесь агент идет по заранее сгенерированным кейсам, а не импровизирует.

Третий режим оказался самым полезным. Ровно такие входные данные и приходят от реальных пользователей: обрывки фраз, посторонняя речь, вопрос не по теме посреди оформления заявки.

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

BASE_SYSTEM_PROMPT = """Ты тестирующий агент для голосового бота.
Веди себя как один и тот же реальный клиент на протяжении всей сессии.
Отвечай коротко, естественно, по-русски.
Не объясняй правила, не пиши markdown, не добавляй комментарии вне JSON.
Верни строго JSON:
{"query":"текст следующего ответа клиентом","reason":"кратко почему выбран этот ответ"}"""

MODES = {
    "negative": "Иди по ветке отказа максимально естественно "
                "и старайся завершить диалог корректным негативным ответом.",
    # ...
}

Параметр, о котором я сначала не подумал

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

Если в дизайне нет явного окончания диалога, например это бот с обращениями в RAG, где можно спрашивать что угодно, пока сам не попрощаешься, то агент-тестировщик и бот вежливо беседуют, пока их кто-нибудь не остановит. А каждая такая реплика стоит денег!

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

Агент-судья

Судья это отдельная модель, которая читает залогированный диалог и оценивает адекватность ответов бота.

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

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

Другое дело — голосовой бот

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

Для транскрибации я использую локальный faster-whisper — библиотеку для перевода аудио в текст. Скорость обработки приятно удивила, качество тоже. Несмотря на то, что аудио у нас хорошее, транскрипт обычно очень близок к оригиналу, хотя артефакты все равно появляются.

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

Как я собирал решение

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

Интерфейс запуска агентов
Интерфейс запуска агентов

Под капотом там нет никакого агентного фреймворка: это самописный цикл на Python с прямыми вызовами API. Для такой задачи этого достаточно.

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

Отрывок из Agents.md
Отрывок из 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, заточенному под конкретные боты, над которыми я работаю. Планы по порядку приоритета:

  1. Довести парсинг дизайна. Это по-прежнему проблема. Пока точность не вырастет, полноценный прогон шаг в шаг по дизайну будет упираться в ложные падения.

  2. Перестроить сам процесс тестирования. Сейчас агент дополняет мою работу, а должен часть ее заменить. В идеале сценарий сразу пишется в машиночитаемом виде, и тогда вся костыльная цепочка с переводом дизайна в JSON просто не нужна. Но такие перестановки не делаются с разбега: сначала нужны убедительные результаты личного использования и бета-тест внутри команды.

  3. Сделать запуск бесшовным. Сейчас пайплайн запускается по частям, а хочется свести все к одной кнопке.

  4. Превратить проект в подобие фреймворка. Идеальный вариант — универсальный интерфейс, в котором для нового проекта достаточно заменить эндпоинт и API-ключи, после чего агент сможет тестировать другого бота. Сейчас проект слишком привязан к конкретным ботам, хотя логика внутри у него общая.

Выводы

Есть две вещи, которые я бы сказал себе год назад.

Не пытайтесь заставить LLM делать то, в чем она слабее человека. Обход графа по строгим правилам это не ее сильная сторона. Естественная речь как раз ее.

Основная работа лежит не в промпте, а в данных на входе. Мой блокер был не в модели и не в архитектуре агента, а в том, что схема диалога выгружается картинкой. Полдня, потраченные на ручной проход API в Postman и на подробный AGENTS.md, сэкономили несколько дней отладки.

Технический стек проекта сейчас такой: Python, faster-whisper (STT, локально), Codex (сборка проекта), Caila (LLM по API).

Открытый вопрос, который я так и не закрыл, это машинный парсинг схем диалога. Если вы решали похожую задачу и у вас есть подход точнее, чем цепочка из PDF, PPTX и LLM, расскажите в комментариях. И если у вас есть свои режимы поведения тестового пользователя, которых нет у меня, тоже рассказывайте.

Если захочется показать код или разобрать конкретный кейс, а не писать это в комментарии, заходите в наш чат для разработчиков.

Комментарии (5)


  1. kokanov
    26.08.2026 14:03

    Один умный человек в своё время сказал: нельзя автоматизировать хаос. Автоматизация хаоса приводит к ещё большему хаосу. (Цитата не точная).

    Вы лучше разберитесь с процессами, чтобы не картинки исовать, и затем парсить, а изначально текстовые скрипты диалогов делать, которые можно и в автоматизации использовать и на их основе потом картинки генерировать.


    1. just_ai Автор
      26.08.2026 14:03

      Здравствуйте! Согласны, автоматизировать хаос — такая себе затея :)

      Мы как раз об этом и пишем в статье, что нужно перестраивать процессы, чтобы автоматизация была без костылей. В большой компании нельзя просто взять и по щелчку перестроить процесс. Сначала нужно показать, что автоматизация работает и действительно упрощает жизнь команде — а уже потом менять весь процесс.

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

      Собственно, мы и хотели поделиться честным кейсом о том, как сотрудники сами ищут способы упростить себе работу. А такая автоматизация помогла увидеть, что и сам процесс можно сделать удобнее. Сейчас уже начались обсуждения в команде QA


  1. predpremi
    26.08.2026 14:03

    Никит, позови меня в Just AI в продажи. Не знаю как пробиться к вам. Второй год с*учусь.

    Контакт Руководителя Отдела Продажможешь дать?