Кейс 01 · AI-assisted development · MCP Tutu · Tourvisor · VK
TurBot: подбор туров в VK и Telegram
Бот собирает параметры поездки и передаёт менеджеру структурированную заявку. Интеграция с Tourvisor Search API реализована и работала при активной подписке: бот запрашивал пакетные туры, показывал варианты и сохранял выбранное предложение. Код и тесты интеграции сохранены как часть проекта.
MCP-клиент Tutu вместо тяжёлого SDK
на столько завышалась семейная заявка, пока дети шли как взрослые
диалоги, Tourvisor, webhook security, CRM и сетевые ошибки
Коротко · если читать некогда
Четыре строки вместо всей страницы
- Интеграция Tourvisor работала в полном цикле. VK-бот отправлял параметры в Search API, показывал пакетные туры и сохранял выбранное предложение для менеджера. Сейчас сценарий зависит от активной подписки провайдера.
- Дети перестали быть взрослыми. Семейный расчёт завышался примерно на 28 000 ₽. Нашёл живой менеджер, а не 74 теста.
- AI помогает быстрее, но не считает цены. Им разбираю код, сценарии и ошибки; при активной подписке фактическая стоимость приходила из Tourvisor и MCP Tutu.
- Два транспорта работают по ситуации. VK принимает события через Callback API, Telegram использует polling там, где провайдер режет webhook.
Дальше — то же самое, но с числами и адресами, которые можно открыть и перепроверить. Если верите на слово, дальше можно не читать; но тогда непонятно, зачем вам инженер.
Контекст · почему всё горело
Свободная переписка — враг нормальной заявки
Клиент пишет как умеет: «хотим в Египет где-то в мае ну или июне человек 3–4 бюджет так». Менеджеру нужны поля и телефон — внятно, а не «где-то в чате». Ручной перевод «из чата в таблицу» — олимпийский вид спорта с высоким шансом потерять детали.
Бот не заменяет менеджера (пока) — он собирает и проверяет данные, пока заявка не превратилась в квест «кто, куда и на сколько человек». А вот на вопрос «сколько будет стоить» первая версия отвечала текстом от языковой модели: складно, уверенно и мимо. Дальше — как эти цифры заменили на настоящие и что по дороге сломалось.
Путь заявки (без лирики)
Решение · что сделал
Пошаговый сценарий вместо «напишите всё одним сообщением»
Стек простой и злой: Python + Flask. Бот ведёт клиента по шагам, проверяет телефон, даты, число туристов и бюджет — и только потом сохраняет заявку. Свободный полёт мысли оставили для чата с друзьями.
- FSM и навигация. Откуда вылет, направление, даты, состав туристов по возрастным группам, бюджет, телефон. Есть «Назад» и «Отмена» — потому что люди передумают, это норма.
- Хранение и админка. SQLite: сессии и заявки. Админ получает уведомления, а не медитирует в чате.
- МоиДокументы.Туризм (CRM). Заявка уходит в фоновом режиме: внешний API не держит пользователя в заложниках «секундочку…».
- Контактные данные. Согласие перед телефоном, /privacy,
/delete— данные для заявки, без «навсегда в чате». Политику отдаёт сам бот, ссылка в тексте согласия подставляется автоматически.
Один сценарий — Telegram и VK. CRM получает одинаковые поля, а не два диалекта «как получилось».
Цены · интеграция по MCP
Бот перестал выдумывать рейсы
Теперь на вопрос о стоимости бот идёт в MCP-сервер Tutu (mcp.tutu.ru) и приносит настоящие рейсы: цена, перевозчик, время вылета. Четыре вещи, которые пришлось решить по дороге.
- Официальный SDK не пригодился. Сервер оказался stateless JSON-RPC поверх обычного POST: ни авторизации, ни хендшейка. SDK притащил бы anyio, httpx и pydantic ради одного запроса. Клиент — 74 строки в
shared/tutu.pyповерх уже подключённогоrequests. Проверяется за десять секунд: в requirements.txt лежат Flask, requests, groq, gunicorn, python-dotenv — и всё. - Страна — это не город. Воронка собирает страны, а Tutu ищет по городам и резолвит страну в столицу. Египет превращался в Каир: 36 152 ₽ против 24 539 ₽ до Хургады на тех же датах. Завёл карту «страна → курорт».
- Дубли по тарифным группам. Tutu возвращает по строке на каждую тарифную группу, и один и тот же рейс приходил трижды с одинаковой ценой. Схлопываю по цене, перевозчику и времени вылета.
- Ссылку «купить» клиент не получает. Только ориентир по цене и поиск. Кнопка оплаты увела бы продажу мимо агентства — checkout остаётся менеджеру.
Самым сложным в этой интеграции оказался не протокол, а решение не давать клиенту кнопку «купить».
Расчёт · нашёл человек, а не тесты
Дошкольникам считали взрослый тариф
Воронка спрашивала «сколько человек поедет» и отправляла это число в Tutu как количество взрослых. Семье с двумя детьми и младенцем выходило пять взрослых билетов. Ни один тест этого не поймал — они проверяли, что запрос уходит и ответ разбирается, а не то, что в запросе правильные люди. Нашёл живой менеджер, сверив выдачу с сайтом.
Замер на живом сервере, Москва → Анталья, одни и те же даты:
- 2 взрослых — 58 694 ₽
- 2 взрослых, 2 ребёнка и младенец — 118 933 ₽
- те же пятеро, посчитанные как взрослые — около 146 700 ₽
Разница — примерно 28 000 ₽ на заявку, и всегда в сторону завышения. Теперь групп три: взрослые от 12 лет, дети 2–11 и младенцы до 2. На скриншоте выше видно, как бот спрашивает про них отдельно и объясняет зачем.
Побочная находка. Пока проверял пересчёт, семейный запрос «туда-обратно» занял 22.8 секунды при таймауте в 12. То есть именно семейные поиски — самые дорогие и самые важные для агентства — молча отваливались, и клиент вместо цен видел заглушку. Таймаут поднят до 30 секунд.
Сеть · вебхук, которого не было
Сервис здоров, а Telegram до него не доходит
После переезда бот замолчал. Симптом обманчивый: /health отдаёт 200, процесс живой, сертификат валидный, конфиг nginx правильный. При этом getWebhookInfo показывает Connection timed out и растущую очередь необработанных апдейтов.
Решающая деталь нашлась в логах nginx: от Telegram не пришло ни одного запроса. Не 403, не 500 — вообще ничего. Значит дело не в приложении и не в сертификате: пакеты не доезжают до сервера.
Проверка по адресам подтвердила: один адрес из пула Telegram закрыт, другой открыт. Хостинг фильтрует диапазон в обе стороны.
Ни один конфиг на моей стороне не чинит маршрут, которым владеет провайдер. Чинится сменой транспорта.
Отсюда BOT_MODE=polling: бот сам ходит за апдейтами, и достаточно исходящего канала. Вебхук остался значением по умолчанию — оба транспорта сходятся в один dispatch_update(), так что логика не раздвоилась. Какой режим работает сейчас, видно в /health: поле bot_mode.
Мелочи, которые дорого стоят
Три бага не там, где болело
- Токен в системном журнале.
urllib3при обрыве соединения пишет в лог полный URL, а Telegram держит токен прямо в пути. Один сетевой сбой — и credential лежит в journald, откуда переезжает в вырезки логов и тикеты. Фильтр на корневых хендлерах режет секрет до записи. - Пустая переменная убивала процесс.
int(os.getenv("X", "0"))берёт значение по умолчанию, только если переменной нет. Если она есть и пустая,int("")бросает исключение прямо на импорте, и наружу торчит голыйexit 3без единой строчки диагностики. Поправлено в 17 местах; отдельный тест грепает оба модуля, чтобы шаблон не вернулся. - Два лида с одного клиента. Двойной тап по кнопке подтверждения создавал две заявки — и менеджер звонил человеку дважды. Гонка закрыта.
Инфраструктура · переезд и данные
С Render на свой сервер в Москве
На бесплатном Render было три проблемы, и ни одна не про удобство. Холодный старт 45+ секунд, замерено дважды: клиент писал боту и ждал почти минуту. Эфемерный диск стирал базу при каждом деплое. Данные лежали в США — для российского ИП, собирающего телефоны клиентов, это отдельный разговор с 152-ФЗ.
Сейчас: свой VPS в Москве, nginx с сертификатом Let’s Encrypt, ночной бэкап базы. /health отвечает за доли секунды вместо сорока пяти — можно открыть и засечь.
Рабочий Telegram-бот использует основной backend; демонстрационный режим в текущем развёртывании выключен. Отправка менеджеру требует отдельного подтверждения. Для проверки интерфейса без реальной заявки можно пройти анкету, проверить параметры и отменить её.
Стек
- Python
- Flask
- Telegram / VK
- MCP / JSON-RPC
- Tutu.ru
- SQLite
- CRM
- pytest
- Docker
- nginx / VPS
Telegram Serverless · архив прототипа
Анкета в Telegram без отдельного сервера для демо
В октябре 2026 года отдельно проверена JavaScript-версия сценария для Telegram Serverless: обработчик сообщения или кнопки, временное хранение ответов в SQLite, проверка и редактирование анкеты. Ниже — результат того прогона и ссылка на зафиксированную версию кода.
Это исторический пример, а не действующий публичный демо-бот. Сейчас @apreltour_bot — рабочий бот турагентства с отдельным подтверждением отправки менеджеру. Для знакомства остановитесь на проверке параметров или отмените анкету; не отправляйте тестовую заявку.
Что проверили 10 октября 2026 года
- Анкета: направление, город вылета, даты, ночи, состав туристов, бюджет и тестовый контакт.
- Редактирование: число ночей изменено с 7 на 10; дата возвращения пересчитана, остальные ответы сохранены.
- Завершение: прототип подтвердил, что анкета никуда не отправлена; проверена очистка данных командой
/delete.
- Проверка данных: календарные даты, допустимое число ночей, возраст детей и бюджет. После изменения ночей пересчитывается дата возвращения.
- Защита состояния: повторные обновления и старые кнопки не должны повторно менять анкету; предусмотрено восстановление после ошибки отправки.
- Приватность: телефон маскируется до записи в базу.
/deleteочищает анкету; черновик истекает через 24 часа бездействия, просроченные записи удаляются при следующем обращении к боту. - Проверки: 16 автоматических тестов, проверка SDK в облаке и полный сценарий в Telegram 10 октября 2026 года: валидация, редактирование, завершение и очистка.
В архивном прототипе использовались вымышленные данные, без отправки менеджеру или в CRM, бронирования и оплаты. Отдельно проверялся запрос рейсов Tutu: тогда сервис не вернул цены, и прототип показал недоступность поиска. Этот результат не подтверждает текущую доступность тарифов.
Serverless-прототип сохранён в коде отдельно от рабочего Telegram/VK-бота и Mini App. Проверка VPS ниже относится к основному backend.
Код, тесты и инструкция развёртывания ↗ · Изменения в PR #317 ↗
Проверка пользовательских сценариев · октябрь 2026
От параметров поездки до результатов поиска
В Telegram Desktop проверены бот и Mini App: точный бюджет 270 123 ₽, минимальный бюджет «от», возрасты детей, возврат, редактирование и отмена. Поиск проверен на реальных предложениях без оформления заявки, бронирования или оплаты.
- Данные: бюджет на всю поездку, даты, ночи и возрасты детей сохраняются при переходе из Mini App в чат.
- Сбои: ошибка поиска отделена от завершённого поиска без подходящих туров; повтор сохранения проверен после прерывания соединения.
- Границы проверки: живые сценарии на физических мобильных устройствах и финальная доставка реальной заявки менеджеру этим прогоном не подтверждены.
Живой код
Вот он. Не презентация.
Оба мессенджера, FSM, Tourvisor, MCP-клиент, CRM, защита webhook и деплой. Регресс от 16 октября 2026 года: 1 720 тестов прошли, 6 пропущены. Результат прогона в GitHub Actions ↗.
- Открыть VK-бота — пройти доступный клиентский сценарий; автоматическая подборка Tourvisor требует активной подписки API.
- bot.r0meo1.ru/health — состояние живого сервиса и ревизия реально запущенного кода.
- bot.r0meo1.ru/privacy — та самая политика, которую бот отдаёт по ссылке из согласия.
Этот запрос уходит из вашего браузера в момент чтения — значения не записаны в страницу. Если бот недоступен, блок честно об этом скажет.
Финал · что можно проверить
Не «я сделал бота», а два адреса и репозиторий
Основные утверждения можно проверить: клиентский путь — в VK, состояние сервиса — в /health, реализацию и тесты — в публичном репозитории. Старые решения не спрятаны: история Tutu/MCP показывает, как проект менялся вместе с задачей.