Как Hermes готовит новости для aixai.ru и «Эй Ай думай».

Я поручил Hermes собирать и проверять кандидатов для aixai.ru и канала «Эй Ай думай». Агент готовит редакционный план, а выбор темы и публикация остаются за мной.

Аудиоверсия
Бумажные карточки на тёмном сортировочном конвейере проходят через синюю линзу к аккуратной папке, сомнительные остаются в красном лотке

У новостей об ИИ есть неприятное свойство: важная ссылка легко тонет среди вкладок, а заметка без первоисточника и цифр приносит больше работы, чем пользы. Для aixai.ru и телеграм-канала «Эй Ай думай» я не стал поручать агенту писать и размещать посты. Hermes получил более узкую задачу: собрать кандидатов, проверить, что за каждым стоит открываемый материал, и вернуть мне план, с которым можно принимать редакционное решение.

Такой выбор оказался важнее, чем ещё один бот с кнопкой «опубликовать». Я хочу понимать, откуда взялась тема, какие в ней есть числа и почему она не повторяет уже вышедший текст. Автоматизация убирает рутину вокруг этого решения, но не подменяет его.

Файлы задают агенту рамки

В статье оставляю оба исходных файла: бриф для Hermes и скрипт collect_candidates.py. Бриф объясняет, что считать готовым результатом, а скрипт делает повторяемую часть работы. Внутри брифа граница сформулирована без двусмысленности:

Главное правило: ты ничего не публикуешь. Ни постов, ни сообщений.
Результат: файл с планом и короткий отчёт.

Границы работы

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

Это не запрет на работу с темой. Это договорённость о последнем действии: Hermes может собрать аргументы для публикации, но не сделает её сам.

Сначала агент собирает не новости, а доказательства

В основе задачи лежит небольшой Python-скрипт collect_candidates.py. Он обходит восемь типов источников: RSS-ленты, Hacker News, arXiv, Reddit, Dev.to, Lobsters, GitHub Trending и YouTube. В RSS настроено пятнадцать лент, среди них OpenAI, Hugging Face, Google AI, Microsoft Research, PyTorch и NVIDIA. Это не редакционная подписка на все их сообщения. Скрипт лишь создаёт широкую воронку, из которой потом можно отобрать несколько тем.

Нижние пороги, свежесть и вес источников вынесены в начало файла. Их можно менять под редакционный ритм, не переписывая сборщики:

MIN_WORDS = {
    "youtube": 3000,
    "rss": 1000,
    "github": 1000,
    "reddit": 2000,
    "hn": 2000,
    "arxiv": 150,
    "devto": 800,
    "lobsters": 800,
}

FRESH_DAYS = 7
FRESH_DAYS_EXEMPT = {"arxiv", "github", "youtube"}

SOURCE_WEIGHT = {
    "arxiv": 3.0,
    "rss": 2.5,
    "github": 2.5,
    "hn": 2.0,
    "reddit": 1.5,
    "lobsters": 1.5,
    "devto": 1.0,
    "youtube": 2.0,
}

У источников разные правила. Hacker News попадает в выборку, если история набрала больше восьмидесяти баллов за последние семь дней. Для arXiv берутся свежие работы из cs.AI, cs.LG и cs.CL. Reddit читается через RSS, потому что JSON-эндпоинты часто отвечают ошибкой 403. YouTube требует yt-dlp, а без него источник честно пропускается и попадает в отчёт как недоступный.

На выходе у кандидата есть заголовок, URL, дата, тип источника и технические данные для оценки. Это ещё не заготовка поста. Одна громкая ссылка ничего не говорит о том, можно ли по ней написать материал для блога или канала.

Объём и свежесть нельзя угадать по заголовку

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

У каждого типа есть свой нижний порог. Для RSS и GitHub это тысяча слов, для материалов из Hacker News и Reddit, где важно перейти к исходной публикации, две тысячи. У arXiv достаточно короткого абстракта, поэтому порог ниже. Эти числа не измеряют качество текста. Они отвечают на более простой вопрос: есть ли в открытом материале хоть какая-то фактура, а не только анонс.

Параметр --deep берёт кандидатов с лучшей предварительной оценкой, скачивает страницу и считает слова в полном тексте. Если URL отдаёт ошибку, запрещает доступ или ведёт за пейволл, в результате остаётся deep_error. Hermes не должен заменить такую ошибку догадкой. Тема либо получает другой первоисточник, либо уходит в отклонённые.

Дедупликация проверяет не только ссылку

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

Проверка URL и пересечения слов остаётся короткой и читаемой. Её задача не решить за редактора, а остановить очевидные повторы:

def is_duplicate(entry, published, seen_urls: set, threshold=0.5) -> str | None:
    url = entry["url"].rstrip("/")
    if url in seen_urls:
        return "url уже использовался"

    for post in published:
        if any(url == link.rstrip("/") for link in post["links"]):
            return f"url в frontmatter поста {post['slug']}"

    cand = tokens(entry["title"])
    for post in published:
        overlap = len(cand & post["tokens"]) / max(len(cand), 1)
        if overlap >= threshold:
            return f"тема пересекается с {post['slug']} ({overlap:.0%})"

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

План важнее очереди на автопостинг

После сбора Hermes должен вернуть Markdown-план и JSON с теми же данными. Для каждой оставленной темы нужны рабочий заголовок, URL, конкретные цифры из открытого источника, угол подачи, приоритет и риск. В конце остаются отклонённые кандидаты с причиной: дубль, недостаточно материала, нет проверяемых цифр или недоступен источник. Отдельно видны ошибки по каждому источнику и число записей, отсеянных дедупликацией.

Так выглядит запуск, который оставляет после себя JSON для дальнейшей обработки и Markdown для чтения:

python3 collect_candidates.py \
  --sources rss,hn,arxiv,reddit,devto,lobsters,github,youtube \
  --blog-dir ~/blog/posts \
  --seen-file seen.json \
  --deep 10 \
  --limit 15 \
  --out plan.json --md plan.md

Именно здесь агент становится полезен как редакционный помощник. Я не трачу время на одинаковый обход лент, ручное сравнение URL и выяснение, почему очередной сервис ничего не вернул. Вместо набора вкладок появляется короткий документ с границами уверенности. Если в нём нет цифры или открываемой страницы, это причина не спешить с темой, а не повод написать её убедительнее.

Важное ограничение записано прямо в задаче Hermes: он ничего не публикует, не отправляет в мессенджеры и не коммитит в блог. При ответах 403 и 429 он не обходит блокировки и не ретраит источник в цикле. Агент должен сообщить, что ему не удалось получить, а не создать видимость полного мониторинга.

Последнее решение остаётся редакторским

После плана я открываю первоисточник, сверяю цифры и выбираю, подходит ли тема aixai.ru, каналу «Эй Ай думай» или обоим площадкам. Затем решаю, что именно в ней важно читателю: разбор релиза, практический эффект, сравнение подходов или повод ничего не писать. Только после этого появляется готовый текст и ручная публикация.

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

Источники

К списку статей