Перейти к содержимому

ZenAi: динамические воронки, качество бота и message debounce

Эта статья — результат рабочей сессии с AI-агентом. Агент фиксирует процесс, я редактирую и дополняю.

ZenAi: динамические воронки, качество бота и message debounce

Date: 2026-02-28 — 2026-03-01 Project: ZenAi — ИИ-менеджер для Telegram (C:\Tools\ZenAi) Duration context: два дня интенсивной работы (архитектурная переделка + 15+ фиксов + новые фичи)

Что было сделано

Динамические воронки — замена хардкодного FSM

Продажная воронка v2

Circle-back механизм — без допроса

Качество бота — 10+ фиксов промптов и поведения

Typing emulation и уведомления

Fallback LLM

Message debounce buffer

Вкладка «Лиды» + фильтры

Потеря сообщений при деплое

UX админки

Проблемы и решения

Проблема: бот зацикливается — допрашивает клиента

Симптомы: бот спрашивал «как к вам обращаться?» по 3 раза, блокируя продвижение по воронке.

Решение: Двойной удар — поле name стало is_required=false, промпт переписан: если клиент игнорирует вопрос, бот отвечает на то что спросили и двигается дальше. Позже реализован полноценный circle-back: бот собирает пропущенное перед финальным шагом, не блокируясь ни на одном обязательном поле.

Проблема: structured JSON от LLM — boolean false = “не заполнено”

Симптомы: бот бесконечно возвращался к вопросу о противопоказаниях, хотя клиент ответил «нет».

Решение: collected.get(field.name) возвращает False для boolean полей — falsy в Python. Заменено на is not None в трёх методах: check_step_complete(), compute_completion_pct(), get_missing_required_fields().

Проблема: LLM генерирует em dashes и «кавычки»

Симптомы: в сообщениях бота появляются , «, » — нетипично для переписки в Telegram.

Решение: post-processing в _post_process_message(): -, «»"". Также вычищены все em dashes из промптов, KB-документов и docs.

Проблема: KB reindex через ARQ не работает

Симптомы: publish endpoint пушил задачу как raw JSON в redis.lpush("arq:queue", ...), но ARQ использует msgpack и слушает очередь "default". 21 stale job, worker никогда не подбирал задачу.

Решение: reindex запускается синхронно через await _run_reindex() прямо в publish endpoint. Просто и надёжно.

Проблема: 502 Bad Gateway после пересборки контейнеров

Симптомы: nginx в admin-контейнере кешировал IP API-контейнера. После docker compose build IP менялся → 502.

Решение: resolver 127.0.0.11 valid=5s + переменная $api_upstream для динамического DNS в nginx.conf.

Ключевые решения

  1. Data-driven воронки вместо enum FSM — шаги и поля в БД, конфигурируются через админку. LLM управляет извлечением полей и переходами, а не хардкод. Позволяет создавать новые воронки без кода.
  2. Structured JSON output от LLM{"message", "extracted_fields", "step_complete"} вместо свободного текста. Надёжный парсинг полей, детерминированные переходы.
  3. Circle-back вместо жёсткого гейта — бот не блокирует клиента на обязательных полях. Клиентский опыт важнее полноты данных. Всё пропущенное доспрашивается перед финальным шагом.
  4. Message debounce в Redis — буферизация с таймером тишины решает проблему серий сообщений (клиент шлёт 3-5 коротких подряд). Один LLM-вызов вместо пяти — экономия токенов и когерентный ответ.
  5. Short TTL lock вместо полного TTL — idempotency-ключ с коротким TTL (5 мин) предотвращает потерю сообщений при kill контейнера. Лок автоматически истекает, catchup подхватывает.

Источники и ссылки

Из сессии

Дополнительный контекст

Что дальше

Все записи