← Все статьи Квиз-бот без единой нейросети: как я за 3 дня собрал бота, который сам собирает заявки
AI-команда

Квиз-бот без единой нейросети: как я за 3 дня собрал бота, который сам собирает заявки

Илья Черняк · 26 июля 2026 г.

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

Квиз-бот без единой нейросети: путь человека от кружка до заявки

Я не разработчик. Claude Code открыл впервые в феврале, до этого максимум лез в настройки роутера. Так что дальше — не гайд от программиста, а разбор от предпринимателя, который собрал живую штуку и теперь показывает, где сам напоролся.

Часть 1. Что болело до бота

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

И вот как у него был устроен набор в программу.

Руками. Каждому — лично в личку. Одно и то же объяснение по двадцатому разу. Ответы участников живут в переписке, вперемешку с «привет, как дела» и голосовыми про кофе.

Момент, после которого мне всё стало понятно, — его собственное голосовое. Сначала он сказал: данные собирать не нужно, я же знакомым рассылаю. А через одно сообщение сам себя развернул:

«Если я запомнил и ты запомнил — то куда это должно прилетать? Как я узнаю, чьи это ответы??»

Вот это и есть боль. Не «нет бота» — а ответы есть, а чьи они, непонятно.

Дальше — три вещи, которые превращают ручной набор в мучение:

  1. Часть людей отвечает голосом. Это его аудитория, они так привыкли. Значит, кто-то должен эти голосовые слушать и конспектировать. Этот кто-то — он сам, вечером.
  2. Программа требует интейка. Вопросы про запрос, про работу с психотерапевтом, про антидепрессанты. Задавать такое в лоб в личке неловко обеим сторонам.
  3. Он физически не масштабируется. Двенадцать человек — ещё ок. Поток с Instagram — уже нет.

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

Часть 2. Почему я отговорил клиента от ИИ

Вадим пришёл со словами «хочу умного бота». Это нормальное желание — все хотят умного. Я предложил ровно наоборот: кнопочный сценарный автомат, ноль нейросетей в рантайме.

Логика простая. Интейк — это не беседа. Это предсказуемая анкета, где важно, чтобы всем задали одни и те же вопросы в одном и том же порядке. LLM в этом месте — не преимущество, а риск: она начнёт вести диалог, переформулировать, сочувствовать невпопад и однажды выдумает то, чего в программе нет.

Чем расплачиваешься за «неумного» бота:

Умный на LLMСценарный автомат
Предсказуемость«как получится»шаг в шаг, всегда одинаково
Стоимостьтокены на каждого лиданоль
Что может выдуматьчто угодноничего, он не генерирует
Отладка«почему он так ответил?»видно в конфиге

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

Правило, которое я после этого записал себе: ИИ ставим не туда, где красиво звучит, а туда, где без него задача не решается вообще.

Часть 3. Как он устроен

Движок и клиент — разные вещи

Главное архитектурное решение: сценарий живёт в конфиге, а не в коде.

quizbot/
├── bot/engine/        ← общий движок, один на всех
└── clients/
    └── vector/        ← клиент
        ├── config.yaml   ← сценарий: кружки, вопросы, палитра, владелец
        ├── media/        ← видео-кружки
        └── .env          ← токен бота (права 600)

Движок читает config.yaml и собирает из него конечный автомат. Новый клиент — это новая папка и одна команда в systemd. Не форк, не копипаста, не «а давай подправим под нового».

Именно поэтому Вадим у меня в проекте называется не «клиент, для которого сделали бота», а первый клиент движка.

Чем платил за каждое решение

РешениеПочему так
aiogram 3.13живой асинхронный фреймворк под Telegram, всё нужное из коробки
SQLite, а не векторная базаразведка прямым текстом сказала: векторка тут — оверинжиниринг. Данных мало, запросы простые: кто, когда, что ответил
Своё хранилище состоянияштатное у aiogram живёт в памяти. Рестарт сервиса — и все, кто был в середине анкеты, теряют шаг. Своё, на SQLite, переживает рестарт
AssemblyAI для голосаpay-as-you-go, примерно 0,25 доллара на сотню заявок. Не подписка
Заявка = карточка в Telegram, не Excelиз-за голосовых. В карточке контакт сверху, ответы текстом, и сам голосовой файл рядом — можно послушать интонацию
Видео-кружки в сценарииэто лицо Вадима, а не текст от бота. Ни один конструктор квизов, который я нашёл, так не умеет
systemd-шаблон quizbot@.serviceодин юнит на всех клиентов. Запуск нового: systemctl enable quizbot@новый_клиент

Как это выглядит для человека

Двенадцать шагов, и в них зашита вся драматургия:

кружок №1 (привет, ты в «Векторе»)

телефон одной кнопкой + согласие на обработку данных

кружок №2 (сейчас задам вопросы, отвечай честно)

рамка доверия: «дальше личные вопросы, видит только Вадим»

6 вопросов (текстом или голосом, счётчик «Вопрос 3 из 6»)

кружок №3 (спасибо, до встречи)

карточка с заявкой падает Вадиму в Telegram

Обратите внимание на два места, которых не было в первой версии.

Телефон просим сразу после первого кружка, а не в конце. Потому что человек, который бросит анкету на пятом вопросе, всё равно останется контактом — а не растворится.

Рамка доверия перед личными вопросами — отдельный пассивный шаг, который просто показывает текст и идёт дальше. Его подсказал агент, который смотрел на бота глазами UX. Я сначала эту правку пропустил, вернулся и добавил. Без неё вопрос про антидепрессанты прилетает человеку в лоб.

Часть 4. Грабли

Тут самое ценное. Девять штук, честно, по порядку.

1. Аудит четырьмя агентами нашёл то, чего не видел я

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

Трое сошлись независимо друг от друга на одном месте. В коде отправки заявки стояло вот это:

try:
    await bot.send_message(owner_id, card)
except:
    pass          # ← вот здесь молча умирали заявки

Два сценария, при которых заявка исчезала бесследно. Первый: Telegram отвечает 403, потому что владелец не нажал /start у своего бота (об этом ниже, это отдельная боль). Второй: карточка длиннее 4096 символов — человек ответил развёрнуто, и всё сообщение не ушло.

Клиент бы никогда не сказал «у вас баг». Он бы сказал «ваш бот не работает» — и ушёл.

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

Урок, который стоил бы мне клиента: except: pass — это не обработка ошибки. Это решение никогда о ней не узнать.

2. Ключ от сервиса лежал прямо в исходнике

Тот же аудит нашёл ключ AssemblyAI, вписанный прямо в код. Вынес в .env с правами 600, код теперь падает при старте, если ключа нет, — лучше не запуститься, чем работать наполовину.

Продолжение истории смешнее: при выкатке на сервер чуть не уехал файл stt.py.bak — со старым ключом внутри. Бэкап, который я сам сделал часом раньше. Вычистил.

3. Гонка на семи секундах

Расшифровка голосового занимает около семи секунд. Если человек за это время дописывал что-то ещё — анкета проскакивала шаг вперёд и путалась.

Лечится замком на пользователя: пока обрабатываем один ответ, второй получает вежливое «Секунду…».

4. Скрипт создания бота чуть не стёр все ключи

У меня был скрипт, который создаёт бота через BotFather и записывает токен в .env. Записывает — перезаписывая весь файл целиком. Вместе с ключом AssemblyAI, который там уже лежал.

Поймал до запуска. Новый скрипт меняет только строку с токеном, остальное не трогает, плюс делает бэкап.

5. Бот вешался на стикер

Человек в середине анкеты присылает фото, стикер или кружок — и бот встаёт. Он ждал текст или голос, а пришло другое.

Лечится хендлером-ловушкой, который ловит всё остальное и мягко переспрашивает. Но есть нюанс: регистрировать его надо последним. Зарегистрируешь раньше — он перехватит вообще всё, и бот перестанет слышать нормальные ответы.

6. Сироты в базе

Повторный /start создавал новую заявку, а старая, недозаполненная, оставалась висеть. База обрастала мусором.

Теперь при новом старте предыдущая незавершённая заявка того же человека помечается как брошенная. Завершённые и чужие — не трогаем.

7. Спор про личные вопросы, который решил не я

Двое агентов — тот, что смотрел UX, и тот, что смотрел постановку — независимо сказали одно: вопросы про терапию и антидепрессанты в воронке выглядят грубо.

Правильный ответ оказался не техническим. Аудитория бота — не холодный трафик, а люди, уже пришедшие в программу. Это не воронка, это интейк перед годовой работой, и там такие вопросы нормальны. Развилку закрыл человек, а не код.

Но замечание не пропало зря: добавили рамку доверия перед блоком и возможность пропустить — достаточно написать «пропущу», и в карточке будет честное «(пропущено)».

8. Правил в одном месте, бой жил в другом

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

Записал себе отдельным правилом: «active» в systemd не значит «моя правка применилась». Проверять надо не статус сервиса, а сам файл на сервере.

9. Последняя миля оказалась физической

Бот собран. Задеплоен. Сервис зелёный. Тестовые прогоны прошли.

И первая настоящая заявка не долетает до Вадима.

Причина: Telegram не разрешает боту написать человеку первым. Вадим — владелец бота, но он сам ни разу не нажал у него /start. Ни строчкой кода это не решается. Только человек и один клик.

Я теперь закладываю это в план как отдельный пункт: «клиент нажимает /start у своего бота». Десять секунд, разово — и без них вся система бесполезна.

Часть 5. Сколько это заняло

КогдаЧто
День 0бриф клиенту: премиум-PDF на пять страниц, разбор его голосовых
День 2ответы на два вопроса клиента: как собираем контакт, как быть с голосовыми
День 3, утроклиент прислал контент: три видео-кружка и шесть вопросов
День 3, деньрабочая версия собрана и проверена вживую. Аудит четырьмя агентами. Правки
День 3, вечербоевой бот, деплой на сервер
День 3, 16:21первый живой незнакомец проходит анкету
День 4правки клиента выкачены, тёплый отзыв, подтверждён следующий этап

От брифа до боевого бота — три дня. От контента клиента до работающего бота — один день.

И вот это, пожалуй, главная цифра всей статьи: 90% времени я ждал не код, а контент. Кружки и вопросы. Сборка — день. Ожидание материала — всё остальное.

Что живёт сейчас: сервис крутится на сервере, десять заявок, все со статусом «завершена». То есть люди доходят до конца анкеты, а не отваливаются на середине — включая тех, кто отвечал голосом.

Часть 6. Чем закончилось

Отзыв клиента, голосовым, дословно:

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

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

Это, по-моему, лучший способ продавать следующий этап — сделать предыдущий так, чтобы клиент сам увидел, что дальше.

Его роадмап, в его же порядке приоритета: ветвление по ответам → рассылка всем, кто хоть раз нажал /start → самостоятельная правка вопросов без меня → выгрузка в таблицу.

Что я вынес

  1. «Без ИИ» — это фича, а не компромисс. Клиент просил умного бота, я отговорил, и оказался прав: для анкеты предсказуемый автомат лучше нейросети. Дешевле, стабильнее и ничего не выдумывает.
  2. ИИ ставим точечно. Единственное место, где он тут нужен, — расшифровка голоса. Он не ведёт диалог, он снимает с клиента ручную работу по прослушиванию.
  3. Аудит перед запуском окупается один раз и навсегда. Четыре агента с разными ролями нашли except: pass, который бы молча съедал заявки. Одна эта находка стоит всего времени на аудит.
  4. Разные роли важнее количества. Ценность дал не «ещё один проверяющий», а то, что четверо смотрели с разных сторон: код, слом, путь человека, сама постановка задачи. Два самых важных замечания пришли не от кода.
  5. Сразу стройте движок, а не проект. Разница между «бот для Вадима» и «движок, у которого Вадим — первый клиент» — это одна папка с конфигом. Но она решает, будет ли следующий клиент за день или за неделю.
  6. Данные сохраняем до отправки, а не после. Любая доставка может упасть. Заявка, которая уже в базе, переживёт и 403, и падение сервиса, и мой кривой деплой.
  7. Последняя миля почти всегда физическая. Код готов, сервис зелёный, а система не работает, потому что человек не нажал одну кнопку. Планируйте и это тоже.
  8. Узкое место — не разработка. Три дня проекта, из них один — сборка. Остальное — ожидание контента. Если хотите быстрее, начинайте выбивать материал раньше, чем откроете редактор.

Хочешь такого же бота под свою программу, курс или услугу — пиши в личку @magic4e, движок уже готов, вопрос только в твоём сценарии. Подписывайся на @mdkguru — там я по горячим следам показываю, как собираю команду AI-агентов и во что это превращается.

Спасибо Вадиму Прощенко за разрешение рассказать про его кейс и процитировать голосовые. Сделано одним человеком и небольшим флотом агентов. Все баги мои.

Понравилась статья?

Подписывайся на канал - там больше кейсов и практики

@mdkguru в Telegram →