Рефлексивный бот с долгой памятью: почему универсальный LLM-чат тут не работает, и как я переписал онбординг по данным. ai.. ai. Claude.. ai. Claude. provider.. ai. Claude. provider. python.. ai. Claude. provider. python. reasoning.. ai. Claude. provider. python. reasoning. reflect.. ai. Claude. provider. python. reasoning. reflect. reflection.. ai. Claude. provider. python. reasoning. reflect. reflection. telegram.. ai. Claude. provider. python. reasoning. reflect. reflection. telegram. Анализ и проектирование систем.. ai. Claude. provider. python. reasoning. reflect. reflection. telegram. Анализ и проектирование систем. Голосовые интерфейсы.. ai. Claude. provider. python. reasoning. reflect. reflection. telegram. Анализ и проектирование систем. Голосовые интерфейсы. Занимательные задачки.. ai. Claude. provider. python. reasoning. reflect. reflection. telegram. Анализ и проектирование систем. Голосовые интерфейсы. Занимательные задачки. здоровье.

Я какое-то время использовал ChatGPT и Claude как собеседника для рефлексии — выгрузить, что в голове, посмотреть на себя со стороны. С самим разговором у них всё отлично. Проблема в другом: они со временем теряют память в целом управлять этим не сильно удобно из-за раздутого контекста.

Для разовой задачи это норм. Но рефлексия — это процесс во времени: ценность не в одном разговоре, а в том, что собеседник помнит, к чему ты возвращаешься из недели в неделю, и замечает паттерны, которые тебе самому не видны. Универсальный чат каждый раз начинает с чистого листа. Контекст можно вставлять руками, но это ручная работа, которую никто не делает.

Так я начал делать Telegram-бота, у которого память — не фича, а суть. И в процессе наступил на показательные грабли с онбордингом. Об этом и статья — без маркетинга, только инженерия и то, что я понял.

Часть 1. Память — это весь продукт

Центральная сущность — memory card: JSON на пользователя, который накапливается из разговоров. Не лог сообщений (он тоже есть), а извлечённая модель человека: повторяющиеся напряжения, паттерны поведения и мышления, ограничивающие убеждения, «зона роста», цель, пол/возраст.

Наполняется в два темпа:

  • Фоновая экстракция на старте (пол/возраст из первых сообщений).

  • Еженедельный разбор (по воскресеньям) и мид-вик обновление, когда накопилось достаточно записей: отдельный промпт прогоняет недельный контекст и обновляет карту.

Каждый ответ бот собирает richCtx — язык, профиль, точку А (старт), цель, последние записи, недавние саммари — и кладёт в системный промпт. Это и даёт continuity: бот не «вспоминает», он держит модель тебя в контексте постоянно.

Важный замер: richCtx у активных юзеров — медиана ~113 токенов, максимум ~1.5к. То есть «память» дёшева; дорогая часть — извлечение карты (читает неделю записей), но оно офлайновое.

Часть 2. Онбординг: главная ошибка

Первая версия онбординга была «по учебнику»:

  1. Выбери режим: Цель или Дневник.

  2. Расскажи, где ты сейчас (точка А).

  3. Расскажи, к чему хочешь прийти (точка Б).

Структурно, аккуратно — и текло ровно здесь. Я просил незнакомого человека сформулировать цель жизни за 30 секунд. Это просьба о структуре до того, как он что-либо почувствовал. Люди замерзают.

Воронка подтвердила: главная утечка — не в глубине, а на первом экране. За неделю 4 из 5 новых застряли на «выбери режим / назови цель». При этом свободный разговор составлял ~47% всей активности — люди приходили говорить, а я встречал их анкетой. Продукт (рефлексивный диалог) воевал с онбордингом (форма сбора данных).

Переписал на chat-first — это тёплый вопрос «как ты сейчас? что не отпускает?». Всё остальное собирается из разговора:

  • Точка А = первое честное сообщение, молча, без вопроса «сформулируй».

  • Режим переформулирован из абстрактного «Цель vs Дневник» в конкретную пользу: «хочешь, буду присылать короткий вопрос утром или вечером?» — и спрашивается в конце прогрева, после нескольких реплик.

  • Цель (точка Б) спрашивается явно только если человек выбрал режим с напоминаниями, и вплетённо.

Технически это конечный автомат на стейтах в conversation_state + флаг onb_optin в memory_card, который помечает «юзер ещё в интро». Главный принцип, выстраданный на данных: один вопрос за раз, никогда не складывать два «ответь мне» подряд (первая версия умудрялась в одном ответе и отразить, и спросить цель — человек терял нить).

Пара нюансов, которые всплыли:

  • Язык определяю по Telegram. Англоязычные отваливались сильнее — добавил одноэкранный пикер языка для неуверенного случая (ловит тех, у кого телефон на английском, а говорить хочется по-русски).

  • «Призраки» — получили приветствие и притихли. Их нельзя через неделю догонять текстом «давно не писал» (они не начинали). Отдельная ветка в крон-свипе: мягкое повторение опенера через несколько часов, пока бот в памяти.

Часть 3. Инженерные решения

Абстракция провайдера. Весь LLM-трафик идёт через одну функцию askClaude(systemPrompt, userMessage, ctx, opts). Внутри — рантайм-свитч: по умолчанию проксирует в Gemini-бэкенд, AI_PROVIDER=anthropic возвращает на Claude. Один контракт, бэкенды взаимозаменяемы — переключение провайдера это одна переменная окружения, а не рефакторинг 20 call-site’ов.

Тиры моделей под задачу. Не одна модель на всё:

  • Живой /chat — на быстрой gemini-3.5-flash. Юзер ждёт каждый ответ в реальном времени, тут важнее скорость и живость.

  • Офлайн-синтез (инсайты, недельные саммари, извлечение memory card) — на gemini-2.5-pro с полным thinking-бюджетом. Никто не ждёт, можно думать глубоко.

Урок про латентность: я сначала посадил чат на 2.5-pro ради глубины — и получил лаг, потому что это thinking-модель: на каждый ответ она сначала генерит тысячи токенов «размышлений», и только потом печатает. Для интерактивного чата это смерть. Вернул на flash — стало и быстрее, и ~в 4 раза дешевле, а глубину оставил там, где её никто не ждёт.

Почему не локальная модель. Считал: VPS без GPU → 7B на CPU = 15–40 сек на ответ (хуже, чем API). GPU-сервер = сотни долларов в месяц против ~$7 за весь Gemini. Самохост окупается на миллионах вызовов или когда приватность становится требованием продукта — у меня пока ни того, ни другого.

Часть 4. Что поймал ESLint

Отдельный сюжет. Я завёл ESLint (в pre-push и CI) — и он сразу нашёл четыре молча сломанные фичи: отсутствующие импорты, из-за которых ветки падали с ReferenceError, а grammy глушил ошибку, так что фича просто молча не работала. Среди них — команда /insights, которая падала у всех (это объясняло ноль её использований в аналитике). Мораль банальна, но я её проживал: на динамическом языке статический анализ ловит то, что тесты не покрывают, и это окупается с первого прогона.

Уроки, которые усвоил

  1. Если строишь поверх LLM что-то про время и отношения с пользователем — память важнее модели. Универсальный чат проигрывает не качеством ответа, а отсутствием continuity.

  2. Не проси структуру до того, как человек что-то почувствовал. Сначала разговор — структуру достанешь из него.

  3. Разные тиры модели под разные задачи. Быстрая на интерактив, тяжёлая на офлайн. Thinking-модель на real-time chat — антипаттерн.

  4. Абстрагируй провайдера за одной функцией с первого дня. Рынок моделей меняется каждый месяц.

  5. ESLint на JS-проекте — не формальность. Ловит молча сломанное.

Выборка пока маленькая, трубить о победе рано. Но направление однозначное: кто заходит в разговор — проходит без заморозки на бланке. И главное, что я вынес: онбординг — это не сбор данных, это первое касание. Сначала дай человеку почувствовать, что его слышат, — аналитику достанешь потом. Если желаете глянуть что вышло и прожарить найдите в тг@Wiseinsights_bot

Автор: metamagic

Источник