Munshi: 4B читает WhatsApp, Python решает, Temporal держит номер

Три режима на одно сообщение гостя: компактная модель вытаскивает поля брони, код считает аванс и срок удержания номера, оркестратор переживает рестарт процесса. Соло-разработчику с агентными цепочками в IDE важен список решений, которые модели запрещено принимать. Разбор стека Munshi: лонгрид автора arqamwd.
Munshi, open-source WhatsApp-«клерк» для небольших отелей: гости пишут на Roman Urdu (латиницей или урду-письмом), часто ночью, с датами, числом гостей и вопросами про тариф и аванс. Материал подан как submission на Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle, прикладной OSS-кейс.
Четыре роли ИИ и кода на одно входящее
Автор описывает обработку как четыре последовательных слоя.
- Чтение: дообученная Qwen3.5-4B (LoRA на Tinker) превращает текст в строгий JSON: даты, ночи, гости, комнаты, тип, бюджет. Не хватает даты или числа гостей: модель задаёт уточняющий вопрос и ждёт ответа.
- Риск и аванс: TabPFN оценивает вероятность отмены. Низкий риск: аванс 20%, удержание номера 12 часов. Высокий: 50% и 2 часа.
- Ответ гостю: Gemma 4 пишет короткий текст на языке гостя только из разобранных полей; функция
check_draftв Python проверяет каждое число до показа владельцу. - Удержание: workflow Temporal (
RoomHoldWorkflow) ждёт перевод; частичные платежи JazzCash суммируются; при неуплате номер освобождается и гостю уходит вежливый отказ.
Правило в репозитории сформулировано жёстко:
models read and write, code decides: даты, счётчики, аванс, длительность hold и цифры в ответе считаются или проверяются в Python (
munshi/dates.py,parser.apply_rules,policy.py,reply.check_draft).
Для vibe coding это прямой аналог skills/rules в проекте: модель не выбирает цену, процент аванса и таймер hold. При сбое парсера нет fallback на большую модель, владелец отвечает вручную.
Fine-tune на 4B против «учителя» и перенос арифметики в код
Заголовок поста совпадает с архитектурой: сюжет лонгрида про сравнение дообученной 4B с Gemma 4 как «учителем» и перенос календарной арифметики из модели в код.
На 80 размеченных вручную сообщениях автор фиксирует:
| Подход | Точность полей брони |
|---|---|
| Только LoRA, без code rules | 75,0% (95% CI 63,7–82,5) |
| Gemma 4 + rulebook на 532 токена | 87,5% (80,0–93,8) |
LoRA + dates.py и counting rules |
до 95,0% на том же наборе |
Промпт для fine-tune: 68 токенов против 532 у rulebook-подхода. Списочная стоимость inference на Tinker: $0,084 за 1k сообщений для fine-tune против $0,27 у untuned + rulebook; обучение LoRA (rank 32, 678 строк, 126 steps) обошлось в $0,29 за run.
После заморозки правил автор прогнал 30 новых сообщений один раз: Qwen3.5-4B + Munshi LoRA дала 30/30 верных полей брони, Gemma 4 + rulebook: 29/30. Автор отдельно предупреждает: третий прогон с counting rules подбирался по ошибкам на тесте, а выборка из 30 сообщений не заменяет production-бенчмарк (нижняя граница 95% CI при n=30: 88%).
Вывод для агентного стека: маленькая модель на чтении плюс детерминированный код на всём, что ломает деньги и календарь, обгоняет схему «большая модель + толстый rulebook» по точности и по стоимости инференса.
Temporal: hold, сигналы и хаос-дриллы
Temporal здесь не декоративный «оркестратор ради оркестратора»: один workflow на inquiry (parse → score → draft → ожидание аванса с таймаутом по risk band). Сигналы принимают депозиты и ответы гостя; частичные переводы копятся до суммы аванса.
В eval/temporal_drills.py автор гоняет отказоустойчивость:
- SIGKILL worker посреди hold: сигнал депозита сохранён, новый worker подтвердил бронь (6 activities, 0 повторных запусков).
- SIGKILL worker и сервера Temporal с простоем 15 с после дедлайна: таймер сработал через 0,1 с после возврата.
- Chaos drill: 5/5 holds при намеренных 50% fail первой попытки activity.
Для разработчика, который привык к «агенту в чате», долгоживущее состояние (удержание номера, частичная оплата) должно жить вне контекста LLM. Иначе любой рестарт процесса превращается в потерянную бронь.
Отдельный инженерный урок из поста: первый cold start TabPFN в activity занял ~107 с (timeout 90 с); после warmup throwaway predict в том же сценарии около 1,5 с.
Приватность, трассировка и честные пробелы
Сырое сообщение гостя не уходит в Gemma (Google): в большую модель попадают только JSON полей и котировка. Текст видит только дообученная Qwen3.5-4B после strip имён, телефонов и email. В Sentry один trace на inquiry через activities; в span: модели, токены, стоимость, verdict checker, без текста гостя и телефона (tests/test_tracing.py).
Что автор не продаёт как готовый продукт:
- Реального владельца отеля в Murree пока нет («I don't have one yet»).
- TabPFN обучен на hotel-bookings dataset (CC BY 4.0, португальские отели 2015–2017), не на пакистанском рынке из сценария.
- Munshi не видит свободные номера: UI просит владельца проверить наличие вручную.
- Публичный git URL в тексте поста не приведён (есть относительные пути
munshi/…,eval/…и демо 57 с вdocs/demo.mp4); лицензия проекта MIT.
Если вы переносите похожий паттерн в свой соло-workflow с агентами, заранее зафиксируйте те же запреты: модель не трогает цену и политику hold, checker на каждое число в черновике ответа, оркестратор на всём, что должно пережить ночь и рестарт воркера.
Источники
- Основной материал: Munshi: a WhatsApp front desk for small hotels (Dev.to, @arqamwd), дата доступа при обогащении: 2026-10-18 (UTC).
- Метаданные публикации (дата 2026-10-18, время чтения 19 мин, теги ai / opensource): карточка поста на Dev.to.