Ио — это бот на основе LLM, заточенный под работу в чатах, идеологически похож на Сучару, но более культурный. Функционал, наверное, поскромнее, но в чатах с ней всё равно весело. Ио умеет отвечать на запросы пользователя, распознавать картинки и войсы, учитывать текущий тред, историю чата и информацию о пользователе. Сейчас бот живёт в нескольких десятках чатов.
Расскажу про то, как пилил его кучу лет, сражался с памятью, пытался разобраться в векторных базах и в итоге получил что-то более-менее работающее.
Как появилась идея? Что вдохновило?
Много лет назад существовало околокосмическое комьюнити Alpha Centauri. У него был оффтоп-чат, а в нём жил чат-бот.. Никаких LLM тогда ещё не существовало, бот работал на регулярках, умел искать погоду, банить пользователей, запоминать информацию и прочие приколюхи. Чат вместе с ботом канул в Лету, а люди разбежались по другим чатам. И через какое-то время мне захотелось написать похожего бота.
Прототип из эпохи до LLM
Первая версия была написана летом 2020 года. В те времена только вышла GPT-3, в русский язык она не умела, да и до чатов не добралась, поэтому пришлось изобретать велосипеды. Я не знал регулярки, поэтому решил разбить текст на токены, выделить среди них смысловые и запустить нужную функцию.
Токенайзер работал так: удалял из текста ссылки, разбивал его по знакам препинания на предложения, затем предложения — на слова, переводил их в нижний регистр и применял стеммер.
Например, слова «расскажи» и «рассказать» превращаются в основу «расска», а «погода» и «погоду» — в «погод».
Таким образом, список искомых команд не разрастался до десятков вариантов написания одного и того же. В русском языке есть всякие склонения, падежи и прочие усложнения, поэтому работало это не идеально.

Среди первых функций были прогноз погоды, возможность банить пользователей, распознавание войсов и перевод текстов.
С погодой тоже были сложности. Пользователь мог писать имя города в разных вариантах, поэтому, чтобы определить город, я пользовался сервисом DaData. Сам прогноз брал и до сих пор беру из OpenWeather. Источник не лучший, зато бесплатный.
Ну а для перевода и распознавания войсов использовал сервисы Яндекса. Кружки тоже хотелось распознавать, поэтому пришлось затащить FFmpeg для извлечения аудио из видео.
В таком виде бот был заброшен на долгие три года. Мне, конечно же, хотелось затащить побольше фичей, но, во-первых, мешала прокрастинация, а во-вторых — сложность. Сделать что-то полезное и интересное на базе примитивного токенайзера было сложно.
Первые попытки с LLM
В 2023 году начал набирать популярность ChatGPT. Он уже умел в русский язык, имел API — кажется, всё сошлось.
Сначала я попробовал написать отдельного бота. В тот момент удобного способа вызывать функции через LLM ещё не было. Получился примитивный бот на базе GPT-3.5 Turbo. Контекст хранился в оперативной памяти, поэтому после перезапуска ничего не сохранялось. Работал он только внутри треда.

С полученным опытом я пошёл переписывать Ио. LLM вызывалась, если пользователь обращался к боту, но ни одна из обычных команд не срабатывала.
В таком виде бот прожил до 2024 года. После этого я добавил нормальную работу с контекстом. Все сообщения хранились в базе данных. Каждое сообщение ссылается на реплай, таким образом строится дерево диалога. Такой подход позволял, например, получить другой ответ на тот же запрос: достаточно было ответить на нужное сообщение в истории, и бот продолжал диалог именно с этой точки, не затрагивая остальные ветки переписки.
В это же время я перешёл на GPT-4o mini и добавил Tool Calling, чтобы модель сама выбирала и вызывала нужные функции. Соответственно все предыдущие костыли с токенайзером больше были не нужны и были выброшены. Заодно была выпилена функция банов пользователей, так как к тому моменту её никто уже не использовал.
Также я добавил поддержку изображений. Сначала бот просто передавал ссылку на картинку в каждый запрос к модели, но это оказалось слишком дорого. Поэтому обработку изображений я вынес в отдельный этап: когда пользователь отправляет фото, бот сразу прогоняет его через модель, получает текстовое описание и сохраняет его в базу. Позже, если картинка попадает в контекст диалога, используется уже готовое описание. В таком виде эта функция работает до сих пор.
Поскольку бота можно было свободно добавлять в любые чаты, обработка изображений сначала работала только в доверенных. Даже с этим ограничением бот съедал на моделях около $10–20 в месяц.
Учим бота запоминать информацию
Первая версия памяти
В 2025 году я начитался про RAG (Retrieval-Augmented Generation, метод работы с большим количеством данных), но был ещё не готов разбираться с векторными базами и прочими сложностями. Поэтому решил пойти самым простым путём. Добавил в базу поле metaInfo с JSON, в котором хранилась собранная информация о пользователе. Раз в 10 сообщений обновлял это поле вот таким запросом:

А дальше в каждый запрос добавлял эту информацию с такими приписками:

Память заработала, теперь Ио знала что-то о пользователе.





Но память работала не идеально. У одной девушки было гендерно-нейтральное имя, и Ио ни в какую не хотела запоминать, что она девушка. Я переписал метаинформацию и добавил фактам веса. Теперь модель оценивала важность каждого факта, а я увеличивал вес, если он повторялся. Но и это не помогло. Информация о гендере пользователя, по мнению модели, оказалась совсем не важной) В итоге проблема решилась простым ручным редактированием базы.

Эта же пользовательница очень сильно старалась заставить Ио начать писать что-нибудь неприличное. Это вылилось в то, что Ио начала отвечать неадекватно вообще на все её сообщения.

Пришлось добавить возможность очистить память.
Теперь всё-таки векторы
В таком виде бот просуществовал до 2026 года. К этому времени из-за развития агентов порог входа в подобные эксперименты сильно снизился, и мне наконец захотелось попробовать векторные базы. В моём представлении RAG строился просто: взять все сообщения, положить их в векторную базу, а при новом запросе найти похожие и добавить их в контекст. Всё — модель знает историю пользователя и учитывает её в ответах. Как же сильно я ошибался.
Очень быстро выяснилось, что поиск похожих сообщений почти бесполезен. Большинство найденных сообщений были просто кусками старых диалогов без какой-либо долгосрочной ценности. Модели гораздо полезнее знать устойчивые факты о пользователе, чем перечитывать его переписку. Поэтому я продолжил использовать два подхода: векторный поиск по сообщениям и отдельное извлечение фактов.
После этого я переписал сбор фактов. Теперь модель извлекает факт и оценивает его важность. Затем векторный поиск находит среди сохранённых фактов похожие, а модель решает, нужно ли увеличить вес существующего факта, обновить его или сохранить новый.
Такая сложность нужна потому, что один и тот же факт модель каждый раз может формулировать немного по-разному. Заодно появилась возможность актуализировать информацию, если она противоречит старым данным. При новом запросе факты пользователя ранжируются по дате и важности, после чего в контекст попадает топ-10.
Мелкие полезные фичи
Суммаризация войсов. Если войс длинный, бот делает саммари, а оригинальный текст прячет под спойлер. Также прогоняю расшифровку через LLM, чтобы расставить знаки препинания и убрать повторы.

Guest mode. Возможность призвать бота в любой чат и пообщаться с ним там. Это похоже на inline mode, только диалог не ограничивается одним запросом. Скажем спасибо за это террористу Дурову.
Поиск по Википедии. Позволяет модели хоть иногда не косячить в фактах.
Явная память. Возможность напрямую попросить бота что-нибудь запомнить. Это решило проблему, когда для исправления фактов приходилось руками лезть в базу.
Монетизация
Недавно запилил монетизацию. В бесплатной версии есть дневные лимиты на сообщения, распознавание картинок и войсов. Ограничение на сообщения мягкое: после достижения лимита бот переходит на более дешёвую модель. Можно купить подписку на неделю, месяц, три месяца или год. В зависимости от срока увеличивается ежедневный лимит. В качестве бонуса немного увеличиваются лимиты всем участникам чата, один из пользователей которого оформил подписку.

Какой технологический стек вы использовали? Почему?
Изначально стек был таким: Node.js, TypeScript, Telegraf.js, OpenAI.
Позже с Telegraf.js я перешёл на grammY. У него удобный API, хорошая типизация, быстрая поддержка новых версий Telegram Bot API и много полезных расширений.
В качестве векторной базы изначально взял Qdrant. Для моих масштабов это оказалось оверкилом: незачем поддерживать отдельную БД, когда всё спокойно помещается в PostgreSQL. Поэтому недавно я перешёл на расширение pgvector.
Для работы с LLM сначала перешёл на LangChain, но он оказался слишком сложным. Поэтому в итоге мигрировал на AI SDK от Vercel.
В качестве провайдера сначала использовал OpenAI, затем перешёл на OpenRouter, а потом на RouterAI из-за проблем с оплатой.
Промпты и трейсы запросов храню в Langfuse. Для моих задач это оказалось не лучшим решением, потому что промпт у меня динамически изменяется, а возможностей шаблонов не хватает. В итоге сейчас у меня статическая часть хранится в Langfuse, а динамическая подставляется одним куском. Трейсинг я почти не использую: пока не возникало задач, для которых хотелось бы регулярно туда смотреть.
Основная модель сейчас — Gemini 3.6 Flash, бюджетная — Gemini 2.5 Flash Lite. Для работы с фактами, суммаризации и распознавания картинок использую Nex-N2-mini.
Векторы изначально генерировал через OpenRouter, но с переходом на RouterAI генерация эмбеддингов стала занимать слишком много времени — от 10 до 60 секунд. Бот векторизует текущее сообщение при каждом запросе, чтобы найти подходящий контекст, что создаёт ощущение, что бот тормозит. Поэтому я поднял text-embeddings-inference с multilingual-e5-small. Качество пока не сравнивал, зато эмбеддинги генерируются быстро даже на небольшом сервере.
Отдельная история — инфраструктура. Сначала всё работало через Docker Compose, но при каждом деплое нужно было руками заходить на сервер, стягивать изменения и пересобирать бота. Это, конечно, можно было автоматизировать, но мне было лень. Поэтому потом бот переехал в Coolify. Там появились автоматическая сборка, развёртывание и бэкапы. Но сборка сильно нагружала сервер и могла вообще положить его на пару минут. Плюс возникали проблемы с обновлением базы данных.
Поэтому решил перелезть на k3s. Теперь GitHub Actions собирает образ бота, после чего он деплоится в k3s. Бэкапы отправляются в S3-хранилище на домашнем NAS. В планах дома поднять ещё один k3s и развернуть там мониторинг, чтобы было красиво. Тем более что с агентами это делать стало очень просто и быстро (хотя есть риск, что очередной агент когда-нибудь снесёт мне и базу, и бэкапы).
Как вы запускались и искали первых пользователей?
Добавил бота в чаты, в которых был админом. В чатах, где я не был админом, призывал его через Guest mode и показывал, что он умеет. Правда, помогало это не всегда. Часть пользователей, увидев, как работает бот, добавили его в другие чаты. Так он постепенно и расползся по нескольким десяткам чатов.
С какими самыми неожиданными трудностями пришлось столкнуться?
Спам чата. Когда приносишь новую фичу или бот как-то особенно смешно начинает отвечать, все начинают его задолбывать. В целом проблема не особо решаемая. Через пару дней всем надоедает, и мясные снова начинают больше общаться друг с другом.
Сколько потратили и заработали? Есть идеи как это можно монетизировать?
Расходы $10 на сервер и $10–30 на LLM. Сумма сильно зависит от моих косяков, активности пользователей и того, насколько дешёвую модель удаётся использовать. Ну и моё время — бесценно.
По доходам всё грустно. Монетизацию только прикрутил, пока есть всего одна покупка на $4.
На самом деле подписку я делал не столько ради заработка, сколько чтобы перестать ежемесячно оплачивать чужие бесконечные эксперименты с ботом. И да, с таймингами очень угадал, с текущими новостями есть шансы, что монетизацией из РФ будет вообще нельзя пользоваться.
Какие планы на будущее?
Запилить мониторинг. Починить баги. Реализовать веб-поиск, ибо сейчас есть только поиск по Википедии. Доработать инфраструктуру: сейчас бот во время деплоя несколько минут недоступен.
Нужны ли какие-то советы или помощь Клуба?
Предложения новых фичей не помешали бы. Ну и бот в теории опенсорсный, так что если кому интересно, заходите жамкнуть звёздочку на GitHub. Правда, предупреждаю: там лютый говнокод вперемешку с вайбкодом.
Какой совет вы бы сами могли дать идущим по вашим стопам?
Не лениться — это главный ограничивающий фактор) Ну и помнить, что векторные базы — не золотая пуля.
Ио до сих пор остаётся для меня большим полигоном для экспериментов. Многие новые идеи из мира LLM и инфраструктуры я сначала пробую именно на нём. Пока проект продолжает приносить удовольствие — значит, всё было не зря.
