
Все мы следим за новостями тем или иным способом — без этого нынче никуда. Я привык это делать, подписываясь на RSS-ленты, и долгое время всё было прекрасно, пока лент не стало слишком много и чтение новостей не превратилось в тяжёлую работу на пару часов в день.
Не до конца понимая, как эту проблему решить, я перебрал кучу разных RSS-ридеров, ни один из которых мне не помог. Пришлось засучить рукава и, через пяток выкинутых прототипов, собрать собственное решение, которое работает.
В итоге получился Feeds Fun — читалка новостей, которая прозрачно для пользователя организует поток информации по её приоритету. Теперь я просматриваю 50-100 новостей в день, вместо 1000 (90% экономии времени!).
Работает Feeds Fun следующим образом:
Для каждой новости LLM проставляет теги, согласно её содержанию.
Пользователь задаёт правила оценки новостей. Например,
habr + cake => +100
илиmusk + politics => -100500
.Читалка суммирует рейтинг каждой новости и показывает их в порядке актуальности для пользователя.
Код, конечно, открыт: https://github.com/Tiendil/feeds.fun
Есть публичная версия: https://feeds.fun с поддержкой «краудсорсинга» разметки новостей тегами:
Вы можете ввести ключ API для OpenAI или Gemini, который будет использоваться для разметки только ваших новостей.
Если на одну ленту подписано несколько человек, то их ключи будут ротироваться. Чем больше подписчиков, тем дешевле разметка для каждого.
Инструкция по запуску, конфигурации и нюансам работы читалки есть в README репозитория, поэтому под катом я лучше расскажу про разные интересные нюансы в разработке web-based читалок.
Технологии
Так как в этот раз у меня стояла цель поэкспериментировать с бизнес фичами, а не технологиями, брал то, что знаю и что работает:
База данных: PostgreSQL
Backend: Python + FastAPI + Pydantic + Psycopg + yoyo-migrations
Frontend: TypeScript + Vue.js + TailwindCSS
Кэширования никакого пока даже не делал за ненадобностью. В какой-то момент потребуется, но это проблемы будущего меня :-)
Аутентификация работает с помощью Supertokens. Выбирал из нескольких open-source альтернатив, у меня в блоге есть их небольшое сравнение.
Прототипирование
Если вдруг вам интересна прямо вся история моих мытарств, то изначально я пытался сделать умную персональную базу знаний, потом урезанную версию этой базы с фокусом на новостях и только через года два понял, что мне нужна читалка новостей с мощной и прозрачной приоритезацией. Описание предыстории с «базой знаний» можно найти у меня в блоге, начиная с поста про экзокортекс 3.5.
Одновременно с этим удивительным инсайтом вышла ChatGPT 4 и в голове у меня щёлкнуло — «ага!», подумал я, «оно же может мапить тексты на любые теги, не требуя заранее заготовленной онтологии и других страшных слов». А раз не надо готовить какую-то собственную схему тегов для новостей и постоянно её тюнить, то пропадает самый большой геморрой, который я видел в организации данных тегами.
Надо было проверить концепцию на работоспособность и желательно так, чтобы не делать весь обвес читалки (получение новостей, интерфейс, etc.) — не хотелось выкидывать n-ый прототип и кучу времени.
К счастью, в open source нашлась одна (на тот момент, по-моему, одна-и-только-одна) читалка с поддержкой тегов, правил их оценки, ранжирования и API — Tiny Tiny RSS. Спасибо разработчикам!
Первая же реализация скрипта выставления тегов с помощью LLM показала отличные результаты. К сожалению, сам Tiny Tiny RSS оказался староват, нужной мне функциональности ему не хватало, патчи (по моим прикидкам) требовались слишком крупные, поэтому я решил всё-таки собирать свой велосипед вместо докручивания существующего.
Получение новостей через прокси
Многие крупные сайты очень не любят, когда на них ходят боты, даже если боты посещают только URL новостных лент. Проблемы эти были давно, но с бумом машинного обучения ещё сильнее обострились.
Логика ограничений очень разная и зависит от сочетания фантазий программистов, devops и продакт-менеджеров — не угадаешь. Поэтому пришлось купить доступ к прокси серверам, которые используются в случае ошибки при прямом доступе к ленте.
Борьба с дублированием новостей
Потоки новостей — концепция удобная, но наличие нескольких протоколов (RSS, ATOM, etc.) вкупе с несколькими протоколами коммуникации (HTTP, HTTPS), вкупе с юникодом, вкупе с фантазией разработчиков дают огромную возможность задать десяток разных URL на потоки с одинаковыми новостями.
Если бы их надо было только хранить, проблем не было бы. Но концепция читалки предполагает отправку каждой новости на анализ LLM (а то и нескольким), а это уже стоит денег. Поэтому пришлось побороться за определение уникальности новостей.
Проблем де-факто три:
Разные протоколы для передачи одних и тех же новостей (ATOM, RSS и ещё пара).
Разные URL для получения одного и того же потока новостей (по нескольким протоколам, с разным представлением юникода, etc.).
Стремление разработчиков сайтов делать конструкторы URL для фильтрации новостей по категориям, вроде https://example.org/feed/rss?category1+category2. Если у вас 10 категорий, то можно составить 1024 подобных URL и это без учёта перестановок.
Первую проблему я пока решил игнорировать (скорее всего в какой-то момент распространю на неё то же решение), а для остальных двух выстроил двухуровневую систему защиты от дублирования.
Уровень 1 — унификация URL
Нормализируем юникод.
Убираем экранирование символов.
Убираем протокол:
http://
,https://
превращаем в//
.Убираем порты — сложно представить случай, когда на разных портах одного домена будут разные новости.
Сортируем query параметры URL.
Убираем фрагментную часть URL (ту, которая после
#
).
Это гарантирует, что большинство дублирующих адресов будет сводиться к одному.
Так как информацию о протоколе мы убрали, то все потоки сначала пытаются загружаться по https://
, а если не получилось, то по http://
.
Уровень 2 — унификация источника новостей
В случае самого прямолинейного подхода, каждая новость должна принадлежать конкретному потоку новостей. Это работает для обычных читалок, но, как уже обсуждалось, не для Feeds Fun.
Поэтому я решил использовать эвристику и предположить, что новости принадлежат не конкретному потоку новостей, а его источнику — сайту, на котором поток размещён.
Это значит, для примера, что какие бы URL для получения новостей с того же Reddit мы ни использовали, мы сможем определить дубликаты по паре reddit.com
и id новости
. До тех пор, пока id новости уникален для портала.
Да, все протоколы передачи новостей требуют от провайдера новостей указывать уникальный идентификатор новости. Который должен быть уникальным «в каком-то контексте». Но разработчики настолько творчески подходят к формированию этих идентификаторов, что я не очень-то верю даже в уникальность всех идентификаторов в рамках одного сайта, поэтому в фоне думаю о какой-нибудь альтернативе вроде хэшсуммы содержимого новости по её абстракту от LLM :-D
Определение тегов
Расставлять теги с помощью LLM можно по-разному. Через несколько итераций я пришёл к следующей логике:
Просим LLM сгенерировать потенциальные сценарии, в которых пользователь может искать контент.
Для каждого сценария просим предположить несколько тегов, которые пользователь вводил бы в поиске базы знаний, организованной по тегам.
То есть мы создаём теги не для текста, а для пользователя, который ищет этот текст.
Кажется это одно и то же, но такое изменение точки зрения значительно сужает область решений для нейронки. Я подробно описал подход в блоге.
Только ли LLM используется для расстановки тегов?
Не только.
Архитектура построена так, что «процессоры тегов» можно подключать как плагины.
Например, сейчас есть процессоры, которые выставляют теги по доменам и по наличию заглавных буквы в заголовках новостей.
В будущем я планирую двигаться в сторону множества специализированных процессоров, а пока LLM используется как универсальная затычка.
Контроль качества тегов

Чтобы фильтрация приносила пользу, теги должны соответствовать содержанию новости, а лучше всего хорошо ему соответствовать.
Поначалу я оценивал качество тегов исходя из собственного чувства прекрасного. Все пользовались им, знаете какой это простой и замечательный инструмент.
К сожалению, в какой-то момент я обнаружил, что он сбоит, и пользоваться им становится всё сложнее. Легко оценить качество выставления тегов для пары текстов или даже пары источников текстов, но когда ты хочешь качественно тегать тысячи текстов из разных источников, нужно опираться на объективный и количественно измеримый критерий качества.
Я пришёл к собственной контрольной базе новостей. Каждая новость сопряжена с двумя списками тегов:
Теги, которые должны для неё определяться всегда и обязательно
Теги, которые должны определяться желательно.
Соответственно, контроль качества новых промптов происходит в два этапа:
Если промпт не создаёт обязательных тегов, то в промпте ошибка и надо исправлять.
Промты, прошедшие первый этап, сравниваются по проценту желательных тегов.
Заключение
Спасибо за внимание, надеюсь вам было интересно.
Я ищу пользователей и заинтересован в обратной связи от вас, поэтому пишите в комментариях что понравилось, что не понравилось, чего не хватает — все ваши идеи — буду очень благодарен.
На всякий случай повторю ссылки:
Репозиторий: https://github.com/Tiendil/feeds.fun
Сайт: https://feeds.fun
Читайте только хорошие интересные новости — берегите своё время и нервы :-)