В 3 ночи мой бот уверенно перепутал «колики» и «голод». Я всё равно его выпустил — и вот почему
Я сделал Telegram-бота, который по короткому фрагменту детского плача (в MVP беру 5 секунд) пытается угадать причину из 6 классов и быстро вернуть ответ. Это не диагностика: бот может ошибаться, а шум и контекст ломают предсказание сильнее, чем хочется.
Что именно предсказываем
Сейчас бот возвращает один наиболее вероятный класс:
- Боль в животике
- Колики
- Дискомфорт
- Усталость
- Голод
- Страх
Честная оговорка: в реальности причины могут накладываться, а некоторые классы пересекаются по смыслу (например, «колики» vs «боль в животике»), поэтому это классификация по разметке, а не “понимание ребёнка”.
Архитектура без воркера: очередь в Postgres + cron
Я не держу постоянно работающий воркер. Задачи на обработку копятся в Postgres‑очереди, а по расписанию их разгребает cron прямо в базе: достаёт пачку задач, отправляет их в Flask-инференс, сохраняет результат и помечает задачу выполненной.
Supabase Cron работает на расширении pg_cron: расписание хранится в cron.job, а история прогонов — в cron.job_run_details. Очередь — это pgmq: чтение сообщений задаёт visibility timeout (пока задача “в работе” она невидима), а после успеха сообщение нужно удалить (pgmq.delete) или архивировать (pgmq.archive), иначе оно вернётся в очередь после истечения окна.
Как я разгребаю очередь (вот что реально важно)
Суть тут не в “красивом SQL”, а в трёх правилах, без которых ты быстро утонешь:
- Пачка: cron‑задача читает ограниченное число сообщений через pgmq.read(queue, vt, qty) и обрабатывает их за один запуск, чтобы не упираться в таймауты и не выстрелить себе в ногу.
- Повторы: если инференс/сеть упали и вы не сделали delete/archive, сообщение снова станет видимым после vt — значит, дубликаты будут.
- Идемпотентность: результат пишется по уникальному request_id (UPSERT/“записать один раз”), иначе повторная доставка начнёт плодить мусор и ломать статистику.
Если в системе есть только одна вещь “как у взрослых” — пусть это будет идемпотентность.
Где Supabase, а где модель
Supabase у меня — это Postgres и два расширения вокруг фоновых задач: очередь (pgmq) и планировщик (pg_cron). Модель живёт отдельно в Flask: сервис принимает ссылку на голосовое, скачивает её, делает препроцессинг и отдаёт вероятности по 6 классам, а я сохраняю результат в базу и отвечаю пользователю.
ML: baseline mel + CNN (почему так)
Я сделал максимально прямолинейный baseline, чтобы быстро выйти в “работает end‑to‑end”:
- Аудио привожу к SAMPLE_RATE = 22050, беру фиксированное окно DURATION = 5 секунд (короткое дополняю нулями).
- Строю mel‑спектрограмму (N_MELS=128), перевожу в dB через librosa.power_to_db(..., ref=np.max), и привожу временную ось к MAX_TIME=200.
- Дальше CNN: три блока Conv+Pool (32/64/128) → Dense(128) + Dropout(0.5) → Softmax.
5 секунд я выбрал как компромисс между UX и стабильностью входа: проще стандартизировать форму тензора и быстрее отвечать пользователю.
Данные и ответственность
Сейчас я не храню аудио пользователей: файл нужен только на время обработки и ответа. Дальше я хочу улучшать модель, но сбор аудио для дообучения возможен только как отдельный opt‑in с понятными сроками хранения и удалением по запросу — иначе доверия не будет.
И ещё: если у ребёнка есть тревожные симптомы (температура, вялость, отказ от еды и т.п.), бот не должен быть точкой принятия решения — это надо проговаривать в тексте прямо, иначе вы выглядите безответственно.
Где это ломается (и что буду чинить первым)
- Шум/эхо и агрессивная обработка микрофоном телефона меняют спектрограмму сильнее, чем кажется.
- В реальности “причина” может быть не одна, но модель всегда выбирает один класс.
- Пересечение классов делает ошибки неизбежными — поэтому нужен режим “не уверен” и предложение перезаписать в тишине.
Вопрос к читателям (выберите A/B/C)
Мне нужен совет по двум решениям — выберите вариант в комментариях:
- Дообучение и данные:
- A) Только opt‑in + пользователь выбирает “что было на самом деле” (дороже по UX, но лучшая разметка).
- B) Только opt‑in + кнопки “угадал/не угадал” (хуже разметка, выше конверсия).
- C) Не собирать аудио вообще, улучшать только препроцессинг и правила отказа.
- Что первым даст прирост качества:
- A) VAD/проверка качества входа (шум, клиппинг, доля плача) + “не уверен”.
- B) Аугментации под шум/микрофоны.
- C) Менять модель (CRNN/Transformer) раньше, чем чинить данные.
Если вы делали аудио‑ML в проде: где я гарантированно наступлю на грабли?
Если хотите смотреть, как это всё развивается (и где оно снова развалится), я веду Telegram‑канал про разработку/продукт и этот проект: t.me/debug_leg. Туда же кидаю мелкие апдейты