Бот @futur_e_news_bot появился как ответ на три типичные проблемы новостных лент: одно событие прилетает из пяти каналов с разными заголовками, 90% повестки нерелевантны конкретному читателю, а поток негатива утром стал нормой. Разработчик потратил несколько выходных и получил работающий продукт, который сейчас хранит около 1500 новостей в базе и обслуживает десятки активных пользователей.

Центральная идея — фильтр тональности. Каждая входящая новость оценивается языковой моделью по шкале от 0 до 3: ноль означает нейтральный или позитивный материал, тройка — тяжёлая трагедия. По умолчанию пользователь видит только уровни 0 и условно нейтральное, остальное подключается вручную через четыре ступени в настройках. Персонализация работает через реакции: нажатие 🔥, ❤️ или 😢 сдвигает «вектор вкуса» пользователя, и следующая выдача ранжируется с учётом этого сигнала. Начальные интересы вытягиваются из профиля Telegram, дальнейшая настройка — через реакции или текстовую команду вроде «больше про космос, меньше про политику».

КомпонентПамятьСтоимость, $/мес
Бот (app + swap)512 МБ + 512 МБ~3.2
RSSHub (приватный)256 МБ~1.9
Том SQLite1 ГБ~0.15
OpenRouter (LLM)0–1
Итого~5–6

Дедупликация реализована через векторные эмбеддинги. Для каждой новости при инжесте вычисляется эмбеддинг (384-мерный вектор через fastembed с ONNX-моделью локально, без внешнего API), затем выполняется KNN-запрос к базе. Если ближайший сосед оказывается ближе заданного порога косинусного расстояния — новость считается дублём и прикрепляется к существующей карточке как дополнительный источник. В итоге пять материалов об одном событии превращаются в одну карточку с пометкой «📰 5 источников». Количество источников само по себе учитывается в ранжировании: мультиисточные события поднимаются выше.

Пять изданий об одном событии превращаются в одну карточку с перечнем источников — дедупликация через векторные эмбеддинги.

Ключевое инфраструктурное решение — отказ от Postgres в пользу sqlite-vec. Расширение добавляет в SQLite виртуальные таблицы vec0 с поддержкой косинусной, L2 и Hamming-метрик и KNN-поиском прямо через SQL. Это функциональный аналог pgvector, но встраиваемый: никакой отдельной машины, никаких миграций и бэкапов базы данных. На Fly.io минимальный инстанс под Postgres стоит от $5 в месяц — sqlite-vec убирает эту статью расходов полностью. Единственное ограничение: SQLite не поддерживает параллельную запись от нескольких процессов, но для архитектуры с одним воркером это несущественно — режим WAL и параметр busy_timeout решают редкие конфликты.

Для задач, требующих языковой модели, используется OpenRouter — маршрутизатор с единым API-ключом и доступом к десяткам моделей, включая полностью бесплатные (с ограничением по частоте запросов). Один промпт на новость закрывает сразу несколько задач: определение категории и тегов, оценку важности и срочности, тональность, краткое резюме на 1–2 предложения и перевод RU↔EN. Платные модели подключаются как запасной вариант при исчерпании бесплатных лимитов.

Итоговая инфраструктура состоит из двух машин Fly.io: основная на 512 МБ RAM запускает aiogram с long-polling, APScheduler с четырьмя джобами (пайплайн каждые 15 минут, доставка каждые 20 минут, проверка срочных новостей каждую минуту, дневной дайджест) и SQLite на томе 1 ГБ. Вторая машина на 256 МБ — приватный инстанс RSSHub, доступный только по внутренней сети Fly и превращающий Telegram-каналы в RSS-ленты. Никакого публичного HTTP, балансировщиков и Redis. При текущей нагрузке основная машина использует около 167 МБ из 459 МБ доступной RAM, CPU практически простаивает — запас до сотен пользователей без апгрейда железа есть.