Зачем понадобился отдельный биологический дайджест
Откройте любой день недели — и вы увидите, что биологические статьи выходят тоннами в нескольких тысячах рецензируемых журналов. Данных генерируется столько, что «Big Data» перестаёт быть метафорой: часть работ публикуется, часть оседает в препринтах, а разобраться во всём этом без профильного образования и пары свободных часов уже не выходит.
Парадокс в том, что при таком объёме удобной ленты нет. Автор проекта kublya перерыл и русскоязычные, и англоязычные источники — и не нашёл ни одного места, где можно за три минуты за утренним кофе прочитать несколько коротких свежих новостей. Вместо этого предлагают одно из трёх: длинные профессиональные разборы, которые вовсе не обязательно про новые открытия; подборки, где биология перемешана с космосом и IT-трендами; либо материалы с щедрой порцией кликбейта и гомеопатии в духе «учёные опять победили рак».
Именно из этого разрыва вырос Bio_news. Показательный пример того, что хотелось бы видеть в ленте, — новость про фермент EC1.1.1.1 и метаболизм гипертермофильных архей: узкая тема, но за ней стоит живая наука, а не очередная сенсация. Дать читателю с любым уровнем подготовки хотя бы одну такую заметку за выпуск — уже задача; для этого под каждой новостью оставлен первоисточник, куда можно нырнуть за деталями, если трёхминутного формата не хватило.
Четыре этапа пайплайна: от скриптов до интерфейса Telegram
Автор разбил конвейер на четыре шага, и границы между ними проведены не случайно: каждый этап отвечает за свой тип решений.
Этап 1 — сбор. Фоновые скрипты прочёсывают источники каждые два часа и складывают свежие биологические материалы в общий пул. Источников сейчас 13, преимущественно англоязычные агрегаторы — крупные издательства неохотно пускают к себе небольшой парсер. Есть и точечные каналы вроде RSS Американского общества микробиологов. Отдельная лазейка — ручная: через телеграм-бота можно подкинуть ссылку или уже готовый текст, и такая новость получит наивысший вес. Модель потом выправит орфографию и пунктуацию, но содержание и порядок мыслей не тронет.
Этап 2 — фильтрация и ранжирование. Здесь происходит основная чистка. Скрипты ищут дубли, отбрасывают лишнее и присваивают каждой новости вес: он зависит от авторитетности источника и от того, сколько материала лежит в пуле без движения. Чем дольше висит — тем ниже вес. Неделя в пуле — и новость удаляется, но за сутки до этого у неё есть последний шанс попасть к модели вне обычных ограничений. Работает это на большом наборе правил. Одно из них: если источник начинает заваливать пул сильнее прочих, ему временно снижают вес штрафом, который постепенно тает.
Этап 3 — LLM. Каждые 12 часов пачка из 21 новости уходит к Gemini 3.6 Flash или Claude Sonnet 5. Модель выбирает 7 самых интересных, переписывает их по-русски в едином стиле, придумывает завершающую шутку и добавляет рубрики. Невыбранные материалы либо возвращаются в пул, либо отбрасываются насовсем. Свободы у модели немного: жёсткая фильтрация прошла ещё на втором этапе, так что косячить ей особо негде.
Этап 4 — публикация. Готовый дайджест приходит в приватный телеграм-чат, где вы нажимаете кнопку на панели бота и выпускаете его в публичный канал. При желании можно попросить бота убрать отдельную новость, если она кажется неподходящей. Автоматическую отправку автор отключил: бот «ещё маленький и неразумный», поэтому включён режим human-in-the-loop.
Пул новостей, веса и правило недели
Что происходит с новостью между попаданием в пул и отправкой к модели? Судьба у материалов разная, и определяется она весом. Наивысший приоритет — у «ручных» новостей, которые вы добавляете сами через телеграм-бота: они обходят конкурентов по умолчанию. Остальные получают вес от двух факторов — авторитетности источника (её вы задаёте вручную) и времени, проведённому в ожидании. Здесь работает принцип песочных часов: чем дольше материал лежит без внимания, тем ниже его вес. Скидок за терпение не предусмотрено.
Точка невозврата наступает через неделю. Новости, провисевшие в пуле больше семи дней, удаляются безвозвратно. Но за день до удаления у каждой есть последний шанс: она гарантированно попадает на рассмотрение к модели, в обход всех ограничений по темам и источникам. Это страховка от ситуации, когда перспективный материал так и не добрался до выпуска ни разу.
Дальше в работу включаются модели. Каждые 12 часов к ним уходит пачка из 21 новости — это Gemini 3.6 Flash или Claude Sonnet 5. Задача одна: выбрать 7 самых интересных. Невыбранное либо возвращается в пул, либо выбрасывается навсегда — если модель решила, что материал вообще никуда не годится. Так пул постепенно самоочищается, а на вход следующего цикла приходят только те новости, которые ещё не получили свой вердикт.
Граница медицины и биологии
Провести границу между «пациент не выжил» и «вот изящный механизм, который его убил» сложнее, чем кажется на старте. Автор решает эту задачу в два прохода — и каждый инструмент делает свою часть работы.
Скрипты работают грубо, но рано: на втором этапе, ещё до знакомства с языковой моделью, они выкидывают пограничные материалы по набору жёстких правил. Медицина не доходит до выпуска на этом уровне, и это сознательное решение, а не побочный эффект.
У LLM оставшаяся зона ответственности другая — она получает неотфильтрованные, спорные случаи, которые скрипты пропустили. Здесь работает сдвиг акцента: модель не пересказывает, а разворачивает материал в сторону чистой биологии. Незавидная судьба пациента её не интересует — а вот красота механизма болезни да. Один и тот же текст превращается из клинической сводки в биологический сюжет.
Почему граница проведена именно так, а не мягче? Причина двойная. Первая — простая: канал не должен превратиться в медицинскую ленту. Вторая — честная: автор сам признаётся, что в медицине не разбирается, и не хочет отвечать за материал, который не может оценить профессионально. Фильтрация здесь — не снобизм, а способ держать планку качества на том поле, где у него есть компетенция.
Зарегулированная LLM: правила тона и границы сарказма
Автор проекта изначально дал языковым моделям куда больше свободы — и получил то, чего и следовало ожидать. Причина не только в том, что используются не самые сильные модели вроде Gemini 3.6 Flash или Claude Sonnet 5. Главное в другом: биологические темы не прощают импровизаций, к которым нейросети так склонны. Модель обещает «проверю факт, а не буду гадать», а затем делает ровно наоборот. Поэтому свободу пришлось урезать жёстко, как выражается сам автор — «по‑государственному».
Один из показательных запретов касается иронии. Обесценивать науку фразами в духе «и так все знали» нельзя — это подрывает доверие к материалу и превращает новость в пустой звук. Исключение сделано ровно одно, и оно узкое: если исследование провели в Великобритании, а его результат действительно самоочевиден, саркастическое «британские учёные выяснили» разрешено. Классический пример — подтверждение того, что голодные люди хотят есть.
Границу здесь важно не размывать. Расширять этот сарказм на другие страны запрещено: только Британия получает право быть смешной таким образом. Стоит один раз применить ту же шутку к исследованию из другой страны — и правило теряет смысл, а вместе с ним рушится вся логика ограничений. Так что рецепт простой: чем тоньше тема, тем меньше в ней места для личной импровизации — и для модели, и для редактора.
Панчлайн и дополнительные рубрики выпуска
Семь новостей из двадцати одной — это отбор, а не генерация. Модель получает пачку кандидатов раз в 12 часов, выбирает семь, переписывает каждую по-русски и добавляет завершающую фразу в шутливой форме. Автор сам называет эти шутки «шутками из склепа» и признаётся, что пока стыдится половины из них. Отсюда и осторожность в логике: если удачная концовка не складывается органично, лучше оставить новость без неё.
Механика панчлайна трёхступенчатая. Сначала модель пробует придумать шутку с нуля, отталкиваясь от сути находки. Если не выходит — идёт в банк заготовок с крылатыми фразами, идиомами и отсылками к кино и интернет-культуре, подбирая подходящую по смыслу. Здесь отдельное требование: устойчивое выражение должно сохранить исходное значение, а не быть притянутым за уши. Если и банк не даёт варианта — панчлайна нет вовсе. Принцип простой: плохая шутка хуже отсутствия шутки.
Тот же «человеческий» подход работает и в трёх дополнительных рубриках, которые модели добавляют к семи новостям.
Статья недели. Источник — bioRxiv, портал биологических и биомедицинских препринтов. У материалов там два атрибута — BIO и MED, и берётся только BIO. Выбирается самая читаемая статья за отрезок в одну неделю; если её уже показывали в прошлом выпуске, скрипт берёт следующую по просмотрам. Это не обязательно свежий текст — упор именно на читаемость за неделю, хотя обычно эти вещи совпадают.
День в истории. Данные приходят из открытого API Википедии «в этот день» — список событий для текущей даты. Алгоритм отбирает из них биологические по ключевым словам и оставляет только то, что старше 50 лет. После такого фильтра событий остаётся мало: за всё время рубрика выходила один раз.
Персона дня. Здесь пришлось собирать логику вручную. Google Scholar недолюбливает ботов, а у Semantic Scholar нет метки научной области у самого автора — только у статей. Поэтому скрипт ищет статьи по случайной теме с меткой «Biology», собирает их авторов и оставляет тех, у кого индекс Хирша ≥ 30. Одного этого мало: автор мог быть просто соавтором в чужой работе, так что среди самых цитируемых статей учёного ищется та, где он указан последним автором — в биологии это обычно руководитель работы, а значит, тема статьи и есть его основное направление.
Итог каждого цикла — дайджест из семи главных новостей плюс статья недели, день в истории и персона дня.
Инженерия и технические детали бота
Весь бот написан на Python — и это, пожалуй, единственное решение, которое не пришлось объяснять самому себе. Всё остальное — набор библиотек, где каждая закрывает свою задачу. Начнём с той, что держит всю конструкцию: python-telegram-bot. Именно здесь сосредоточено управление жизненным циклом бота — приём команд и JobQueue для периодических задач: обхода RSS, выбора «статьи недели» и проверки окна публикации дайджеста. Внутри JobQueue крутится APScheduler: его видно в логах (apscheduler.scheduler, apscheduler.executors), но отдельно он не импортируется и не устанавливается — это транзитивная зависимость python-telegram-bot.
Для работы с моделями выбран официальный путь. Claude вызывается через SDK anthropic — импорт вида from anthropic import Anthropic, без самодельных обёрток над HTTP. А вот для Gemini используется сырой REST — официального SDK под эту задачу в проекте нет, поэтому запросы уходят напрямую. Такая асимметрия выглядит нелогично ровно до того момента, пока не вспомнишь: разные модели, разные экосистемы, единого удобного слоя над обеими всё равно не получилось бы.
Остальной HTTP закрывает requests. Через него идут DOI/CrossRef в url_resolver.py, Semantic Scholar в person_of_the_day.py, Wikipedia onthisday в history_... — то есть все запросы, кроме тех, что уходят в Telegram. Сбор RSS-лент держится на feedparser, а разбор HTML-страниц — на beautifulsoup4. Отдельная головная боль — ссылки Google News: они приходят в редиректном виде, и для их декодирования в настоящий адрес статьи подключён googlenewsdecoder — в url_resolver.py он вызывается как from googlenewsdecoder import new_decoderv1.
Есть две детали, которые легко пропустить при чтении кода, но которые стоит держать в голове. Первая — принудительный IPv4. В bot.py через httpx настроен транспорт httpx.AsyncHTTPTransport(local_address="0.0.0.0"): это обход проблемы с подключением к Telegram по IPv6. Вторая — порядок инициализации окружения. load_dotenv(override=True) вызывается в bot.py и digest_builder.py, причём до импорта config.py — с явным комментарием в коде. Если поменять местами, переменные из .env просто не успеют подхватиться, и вы получите пустые значения вместо токенов.
Логирование осталось штатным: logging.basicConfig без ротации. Файл bot_stderr.log растёт бесконечно, пока его не почистить руками — за день он раздувается до состояния, когда открывать его без -Tail уже не хочется.
Human-in-the-loop, публикация и что дальше
Три вещи в этой системе держатся не на коде, а на решениях, которые код лишь исполняет.
Первый вывод — человек остаётся обязательным звеном, пока бот не заменён. Свёрстанный дайджест приходит в приватный чат, публикация запускается кнопкой на панели бота, а не автоматически, и у вас есть возможность убрать отдельную новость, если она кажется кривой или неподходящей. Автор испробовал обратный вариант — отправку сразу в канал — и вернулся к тумблеру «human-in-the-loop». Причина проста: по мере настройки фильтров ручных правок становится всё меньше, но они не исчезают.
Второй вывод — устойчивость публикации обеспечивается развязкой каналов. После выхода выпуска в Telegram его подхватывает синхробот Дзена. Ограничение одной площадки тогда не обрывает распространение: резервный маршрут уже работает.
Третий вывод — логи без ротации растут бесконечно. Логирование здесь настроено штатным logging.basicConfig, файл bot_stderr.log увеличивается, пока его не почистят руками, и за сутки дорос до размера, при котором открывать его без -Tail уже страшно.
Практический совет: перед включением автоматической публикации поставьте один обязательный шаг проверки — превью в личном чате с кнопкой выпуска, — а для логов добавьте RotatingFileHandler с лимитом размера и числом бэкапов, чтобы чистка перестала быть ручной операцией.