Архитектурная развилка: как не утонуть в собственном боте

В первом посте я рассказывал, как за несколько дней собрал пилот бота и быстро нарастил функциональность: Марафон, Лента, консультант, MCP-хранилища, многоязычность. Всё работает, первые пользователи тестируют, сделал концептуальное описание бота. Но сейчас я упёрся в стену.

Планов громадье, хочу добавить новые возможности:
1. Заметочник — отправлять себе заметки прямо из бота в Aisystant, Obsidian или Notion
2. Задачник — решать простенькие задачи по системному мышлению в крошках времени
3. Тест ступени ученика — диагностика на входе, чтобы понять, с какого уровня начинать
4. FPFkids — помощник взрослым, которые обучают детей первым принципам.
5. Экзокортекс-помощник — консультант, который помогает настроить личный экзокортекс
6. Полноценная многоязычность — сейчас локализация в одном файле, это не масштабируется

И тут я понял: текущая архитектура не выдержит.
Марафон живёт в bot.py (монолит на 3000+ строк), Лента — в отдельной папке engines/feed/, консультант размазан по коду. Каждая новая фича будет увеличивать хаос. Добавить Задачник? Куда — в bot.py? В engines/? Создать engines/practice/? А как он будет связан с консультантом?

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

Три варианта, которые я рассматриваю

Вариант 1: Модульная архитектура (Plugin-based)
Идея простая: каждая фича — отдельный модуль в своей папке. Модули независимы, подключаются через registry.

modules/
├── marathon/
├── feed/
├── notes/
├── practice/
├── exocortex/
└── fpfkids/

Плюсы: легко добавлять новое, один человек = один модуль. Минусы: модули начнут дублировать код, непонятно как связывать общие вещи (цифровой двойник, экономика токенов, ORY идентификация пользователя).

Вариант 2: Domain-Driven Design (DDD)
Классический подход для сложных систем: выделяем «домены» (области знания), каждый домен имеет свои сущности, сервисы, репозитории.

src/
├── domain/
│ ├── learning/ # Всё про обучение
│ ├── notes/ # Заметки
│ ├── assessment/ # Диагностика
│ └── economy/ # Токены, подписки
├── application/ # Use cases
└── infrastructure/ # Telegram, БД, API

Плюсы: чёткие границы, хорошо масштабируется на команду. Минусы: высокий порог входа, нужно заранее продумать все домены, избыточно для текущего размера.

Вариант 3: Гибрид — то, на чём я остановился.
Взял из обоих подходов:
∙ Модули по типу взаимодействия (не по домену)
∙ Общее ядро для сквозных вещей
∙ Feature flags для безопасной миграции

Ключевое решение: три типа сущностей

Долго не мог понять, как назвать и сгруппировать фичи. «Режим»? «Модуль»? «Функция»?
Пробовал разные классификации, пока не нащупал критерий — характер взаимодействия с пользователем:
— Мастерские (workshops/). Строгая структура, заранее известный путь, пользователь идёт по шагам

Мастерская Что изготавливается
Марафон Мастерство ученика за 14 дней
Экзокортекс Настроенный личный экзокортекс
FPFkids Система обучения ребёнка
Задачник Навык через программу задач

Ключевое: есть программа, есть шаги, есть результат-артефакт.

— Консультанты (consultants/). Гибкая структура, по запросу, без жёсткого плана

Консультант Как работает
Общий (?) Любой вопрос в любой момент
Лента Push-контент по выбранным темам
Тест ступени Диагностика по запросу

Ключевое: нет строгой последовательности, пользователь сам решает когда и что.

— Утилиты (utilities/). Одно действие — один результат

Утилита Действие
Заметочник /. текст → сохранено
Экспорт /export → файл в Obsidian

Ключевое: атомарное действие, никакого состояния.

Важное архитектурное решение: единый консультант

Изначально думал сделать отдельных консультантов: «Консультант по экзокортексу», «Консультант по детям», «Консультант по задачам».

Но это, по моему, неправильно. Консультант должен быть один — как умный помощник, который знает про всё.

Решение: консультант маршрутизирует вопросы к базам знаний мастерских:

Пользователь: "? Как организовать заметки в Obsidian?"
                    │
                    ▼
            [Консультант]
                    │
        Определяет тему → "экзокортекс"
                    │
        Ищет сначала в: workshops/exocortex/knowledge/
        Потом в: core/knowledge/base/
                    │
                    ▼
              [Ответ с контекстом]

Пользователь не думает «к какому консультанту обратиться». Он просто спрашивает — система сама находит релевантную базу знаний.

** Целевая структура репо**

aist_bot/
├── bot.py                      # Только точка входа
│
├── workshops/                   # 🔨 Мастерские
│   ├── marathon/
│   ├── exocortex/
│   │   └── methodology/        # Таблицы, практики
│   ├── fpfkids/
│   │   └── scenarios/          # Сценарии обучения
│   └── practice/
│       └── problem_bank/       # Банк задач
│
├── consultants/                 # 💬 Консультанты  
│   ├── main/                   # Общий (?)
│   │   └── router.py           # Маршрутизация по базам
│   ├── feed/
│   └── assessment/             # Тест ступени
│
├── utilities/                   # 🔧 Утилиты
│   ├── notes/
│   └── export/
│       └── adapters/           # Obsidian, Notion, Markdown
│
├── core/
│   └── knowledge/              # Загрузка и поиск по базам
│
├── i18n/                        # Локализация
│   ├── ru/
│   ├── en/
│   └── es/
│
└── config/
    └── features.yaml           # Feature flags

Как мигрировать без поломок

Главный страх: сломать работающего бота во время рефакторинга.

Решение — Strangler Fig Pattern + Feature Flags.

Принцип: новый код растёт рядом со старым. Тестирую локально с flag=true. В проде — false. Когда готово — переключаю. Если что-то пошло не так — откатываю флагом.

План на ближайшие недели (с учетом того, что не смогу выделять много времени на развитие бота).

Неделя Что делаю
1 Создаю структуру папок, базовые классы, feature flags
2 Выношу Марафон из bot.pyworkshops/marathon/
3 Выношу Ленту → consultants/feed/
4 Переделываю локализацию → i18n/ с YAML-файлами
5 Создаю Заметочник → utilities/notes/
6 Создаю Тест ступени → consultants/assessment/
7+ Новые мастерские: Экзокортекс, FPFkids, Задачник

Что это даст

После миграции смогу быстро добавлять новые возможности:

Заметочник — написал /note мысль про экзокортекс → заметка сохранилась, можно экспортировать в Obsidian.

Тест ступени — на входе бот определяет уровень: «случайный ученик», «практикующий», «систематический». Контент адаптируется.

Задачник — в крошках времени решаешь задачи по системному мышлению. Есть готовые банки задач + генерация персональных через Claude.

Экзокортекс-мастерская — пошаговая программа: аудит текущего состояния → выбор инструментов → настройка → практика. Не просто «советы», а изготовление результата.

FPFkids — для родителей и наставников: как объяснить ребёнку концепцию, какое упражнение дать, как проверить понимание.

Полная многоязычность — интерфейс и контент на русском, английском, испанском (и легко добавить другие).


Рефлексия

Это типичная ситуация в разработке: быстрый MVP создаёт технический долг. Можно игнорировать и продолжать наращивать функциональность с риском закопаться в хаосе. Можно остановиться и переписать с нуля (убьёт мотивацию).

Я выбрал третий путь: пока бот будет тестироваться на Марафоне для новичков, а я буду делать инкрементальную миграцию с сохранением работоспособности. Дольше, но надёжнее.

Интересно, что в процессе проектирования архитектуры я лучше понял сам продукт и его ценность. Разделение на «Мастерские / Консультанты / Утилиты» — это не просто техническое решение. Это модель того, как люди взаимодействуют с инструментом развития:

  • Иногда нужна программа с шагами (Мастерская)
  • Иногда нужен ответ на вопрос (Консультант)
  • Иногда нужно быстро что-то сделать (Утилита)

Хорошая архитектура отражает реальность использования. Так что сейчас это мой главный объект внимания.

P.S. на 17:00 того же дня
После публикации наш архитектор Андрей Смирнов предложил другой подход — State Machine.
Идея: один Python-файл = одно состояние. Пользователь всегда находится в конкретном стейте (marathon_question, consultant, notes). Состояние хранится в базе данных. Все переходы описаны в одной таблице:

marathon_question:
correct: marathon_task # Верный ответ → задание
incorrect: marathon_question # Неверный → повторяем
skip: marathon_task

Что это даёт:
∙ Явная модель — вся логика переходов в одном файле
∙ Всегда понятно, где пользователь сейчас (user.current_state)
∙ Легко добавлять новые сценарии — создал стейт, добавил строку в таблицу
∙ Готовность к ЦД и Ory — состояние уже централизовано, заменить хранилище на Цифровой двойник — одна точка изменения
Разница в сроках небольшая (+1-2 недели на создание движка), но архитектура получается чище и масштабируемее.
Решил идти этим путём. Подготовил с Claude обновлённый план миграции на 7-9 недель — MIGRATION_PLAN_V2.md.​​​​​​​​​​​​​​​​

4 лайка

Церен, спасибо за то, что делитесь прогрессом!

Читаю и завидую, потому что занимаюсь этой областью с 2022 года, имею кучу идей по полезным ботам, но буксую на этапе реализации (и по техническим, и по организационным причинам).

А вы на глазах у изумлённой публики (по крайней мере, в лице меня) быстро и успешно реализуете такую довольно сложную систему.

Это мотивирует образовываться дальше и не бросать правильно приоритизировать мои собственные начинания.

Это фундаментальный вопрос построения таких систем, и у него нет единственно правильного ответа.

Сейчас по работе занимаюсь похожим проектом и пришёл к тому, что в реактивном режиме (когда пользователь инициирует общение) на входе всё же должен быть некий маршрутизатор, отправляющий запросы пользователя в правильный сценарий обработки, который может быть представлен субагентом или даже в значительной части запрограммирован “хардкодом” (но всё равно с использованием LLM, естественно).

Целей тут три:

  1. Не смешивать разные режимы работы (или эпизоды общения) для пользователя.
  2. Поддерживать архитектуру системы в состоянии минимальной связанности между разными “доменами”, допускающую дальнейшее развитие системы.
  3. Правильный context engineering для обращения к LLM, чтобы она фокусировалась на единственно важном в данный момент, а также быстро и дёшево отвечала.

Технически это может быть реализовано через отдельных субагентов основного агента, который выступает неким оркестратором диалога, или механизмом состояний (state machine) в запрограммированном виде или в виде допустмых переходов по графу состояний Langchain / Pydantic agents.

2 лайка

При работе с агентами по разным темам всегда делаю разные проекты/папки. Почему? Конечно можно задать разные системные промпты, сохраняется/ограничивается контекст и т.д. Но это чисто технические моменты

А с точки зрения пользовательского интерфейса, для меня важно иметь информацию по теме в одном месте, иметь возможность перечитать диалог, вспомнить о чем говорили раньше, и продолжить в рамках той же темы

И меня не прельщает возможность в одном чате с одним лицом общаться на разные темы..

Вполне может оказаться, что в итоге технически вы правильно разделите систему на модули, а вот, с точки зрения UX получится спорно

2 лайка

У нас сейчас в проде реализуют как раз общий бот для пользователей обычной платформы, в ней вроде встроен маршрутизатор, который сам понимает по какой теме идти (документация/API/Terraform/пользовательские заявки)

и возможность добавить вкладки, чтобы можно было переключаться между разными темами - это следующий спринт.

Так что это не взаимоисключающие себя опции.
У нас реализуют по модели ChatGPT, когда у тебя будет возможность использовать много вкладок.

2 лайка

Дмитрий, спасибо за вашу высокую оценку! Я абсолютно уверен, что сейчас время созидателей. Это люди, которые имеют определенное видение будущего и хотят его реализовать. Ранее это было сделать сложно, даже если у тебя были способности и терпение. Но сейчас ИИ и системное мышление позволяют значительно проще, дешевле и быстрее создавать системы. Тем самым тестировать свое видение.

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

Впереди еще много интересного. Но пока такой активный режим разработки бота окончен, далее посмотрю заинтересованность в нем.

1 лайк

Да, мне многие разработчики говорят про разделение на разных агентов. Но вот мне целый парк разных ботов совсем не прельщает. Я хотел бы иметь одного и, конечно, с хорошим UX/UI.

Забавно, что в посте ни разу не упомянуто слово sota, которого довольно много в руководствах :slight_smile:

В общем нет такого понятия, как хороший UI/UX. Стоит принять что любой или, как минимум, какой-то UI/UX всегда будет плохим. И учитывать это в разработке.

1 лайк

Ну да, хотя можно сказать, что сейчас удовлетворительный, нужен хороший, а вот идеального не будет).

Может быть, для этих целей оптимальным вариантом будет не просто бот, а «бот + телеграм-приложение»?

1 лайк

Да, конечно, это тоже рассматриваем.

в конечно итоге все может прийти к варианту консольного ввода - где в качестве интерфейса будет какой-нибудь небольшой ИИ который будет уметь собирать статистику по пользователю и формировать подсказки (индивидуальный предиктивный ввод, ака т9 или как в строке поиска).

Все есть текст. Текст пока не смогли забороть никакими интерфейсами. Мессенджер хорошо можно будет состыковать с таким интефейсом.

и стоит учитывать, что текстовая консоль - это 1 способ взаимодействовать с миллионами приложений.
можно работать и на пк и на мобильных устройствах, и потом прикрутить голосовой ввод (голос2текст).
Будущее кмк там.

Если пилить гуи под приложение то начнется история с фрагментацией - разные устройства, разные ос. вариант с браузером в качестве гуи - не готов говорить, тут нужен фронтэедер. но и там много подводных камней, которые будут забирать ресурсы на разработку и последующее сопровождение. если проект не коммерческий, то это может быть критично.

Это порочная практика. В реальности просто веб-приложение будет лучше почти всегда. Боты и телеграм-приложение лишь дань моде и работа на чужие интересны (в данном случае, телеграма)

Я имел в виду немного другое – вообще сделать хороший или идеальный (в данном случае одно и то же, т.к. недостижимо или достижимо только в отдельные моменты времени) интерфейс или продукт - неверная цель, если говорит про ИТ.

А (более) верная цель – сделать UI/UX/продукт легко поддерживаемым (или просто поддерживаемым). Поддерживаемость означает возможность внесения изменений в общем (и развития в частности).

По-моему опыту это даже в индустрии разработки для многих контринтуитивно, чего говорить о людях из других индустрий.

Поясняю, какой бы ни был интерфейс, скорее всего его придется менять (не придется только если продукт будет заброшен). Это 100%. Даже если у вас такого желания не будет.

Легкость внесения изменений это и есть то, насколько он хороший объективно.

1 лайк