Кейс 01 · AI-assisted development · MCP Tutu · Tourvisor · VK
TurBot: подбор туров в VK и Telegram
Бот собирает заявку, ищет пакетные туры через Tourvisor, показывает варианты и передаёт менеджеру выбранное предложение. AI ускоряет разбор кода, сценариев и ошибок, а фактические цены берутся из внешних сервисов: Tourvisor и MCP Tutu.
MCP-клиент Tutu вместо тяжёлого SDK
на столько завышалась семейная заявка, пока дети шли как взрослые
диалоги, Tourvisor, webhook security, CRM и сетевые ошибки
Коротко · если читать некогда
Четыре строки вместо всей страницы
- Клиент получает варианты сам. VK-бот отправляет параметры в Tourvisor, показывает пакетные туры и сохраняет выбранное предложение для менеджера.
- Дети перестали быть взрослыми. Семейный расчёт завышался примерно на 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 отвечает за доли секунды вместо сорока пяти — можно открыть и засечь.
Публичная витрина работает в демо-режиме: бот предупреждает, что это демонстрация, и хранит телефон замаскированным — +7916***4567. Собирать чужие персональные данные от лица реального ИП ради витрины незачем. У VK-бота отдельный флаг, он боевой.
Стек
- Python
- Flask
- Telegram / VK
- MCP / JSON-RPC
- Tutu.ru
- SQLite
- CRM
- pytest
- Docker
- nginx / VPS
Живой код
Вот он. Не презентация.
Оба мессенджера, FSM, Tourvisor, MCP-клиент, CRM, защита webhook и деплой. Сейчас в проекте 202 теста; полный прогон опубликован в GitHub Actions.
- Открыть VK-бота — пройти клиентский сценарий и посмотреть самостоятельную подборку.
- bot.r0meo1.ru/health — состояние живого сервиса и ревизия реально запущенного кода.
- bot.r0meo1.ru/privacy — та самая политика, которую бот отдаёт по ссылке из согласия.
Этот запрос уходит из вашего браузера в момент чтения — значения не записаны в страницу. Если бот недоступен, блок честно об этом скажет.
Финал · что можно проверить
Не «я сделал бота», а два адреса и репозиторий
Основные утверждения можно проверить: клиентский путь — в VK, состояние сервиса — в /health, реализацию и тесты — в публичном репозитории. Старые решения не спрятаны: история Tutu/MCP показывает, как проект менялся вместе с задачей.