Перейти к основному содержимому
megapromotingДавайте обсудим

Экспертиза · Автоматический контент

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

Движок написан, полностью, и находится прямо в репозитории этого сайта: восемь этапов, 1.706 строк, системный таймер, который запускается ежедневно в 00:30 UTC. Важна не генерация, а ворота: пять локальных проверок, десять запрещённых выражений и дополнительная проверка, которая ищет имена клиентов без согласия, выдуманные цифры и награды, которых у нас нет. Если ворота не могут запуститься, ничего не публикуется.

Уже построено«delivered», потому что работа существует как файлы, которые вы можете открыть, а не как описание: `scripts/content-engine/` в репозитории megapromoting.com, 17 файлов, 1.706 строк TypeScript, unit systemd с таймером, восемь связанных этапов и журналы запусков. С оговоркой, которую говорим мы, а не узнаёте вы: **единственный полный запуск в журналах не опубликовал ни одной статьи.** Ворота остановили все три — два раза потому, что проверка вообще не смогла выполниться, в третий раз из-за отсутствующего поля в заголовке. `publish` остался пустым, IndexNow получил ноль адресов. Запуск был локальным, на рабочей станции, и длился два с половиной часа, то есть больше лимита в 60 минут, заданного в unit systemd, — значит, на сервере он был бы остановлен. Мы не можем подтвердить извне, что движок сегодня работает на сервере, и не утверждаем этого.

Автоматизация, которая сама пишет и публикует, — это место, где проще всего пообещать то, что нельзя сделать. Модель пишет тысячу убедительных слов о чём угодно, в том числе о том, чего не было, и делает это так же бегло. Если между моделью и кнопкой публикации ничего нет, вы построили машину для размещения ошибок в интернете, на двадцати языках, каждую ночь. Вся работа — в том, что находится между ними.

Наш поток состоит из восьми этапов. Он собирает темы из открытых источников, даёт им оценку от 0 до 100 за релевантность, отбрасывает всё ниже 50, выбирает первые N по правилу разнообразия, чтобы не получилось десять статей об одном и том же, пишет статью на румынском, переводит её на девятнадцать языков, пропускает через ворота и только потом публикует. Каждый этап записывает свои затраты и результат в журнал, чтобы запуск можно было восстановить позже.

У ворот два слоя. Первый — локальный и ничего не стоит: заголовок должен существовать и содержать title, description и keywords; текст должен быть длиннее 1.200 слов; в нём должно быть не менее трёх подзаголовков, не менее трёх внутренних ссылок и раздел вопросов минимум с тремя парами; и он не должен содержать ни одного из десяти маркетинговых выражений, которые мы внесли в чёрный список. Второй слой — это модель, которая ищет другое: имя клиента без доказательства согласия, выдуманную статистику, неверные утверждения о том, что можем мы, и награды, которых у нас нет — список реальных написан в проверке, а всё остальное помечается.

В цепочке есть человеческое утверждение, и мы говорим об этом как об аргументе, а не как об оправдании. Статьи, не прошедшие жёсткие проверки, отбрасываются, а не публикуются. Те, что прошли жёсткую проверку, но получили пометки от второй проверки, сохраняются с отметками, чтобы человек мог посмотреть. А если проверка не может выполниться — например, потому что служба моделей не отвечает, — статья считается не прошедшей. Fail-closed, а не fail-open. Так и произошло при единственном полном запуске в журналах, поэтому ничего не было опубликовано.

Что входит

Работа, по составляющим

Собирает темы из открытых источников параллельно, не падая, если одна молчит

Восемь сборщиков запускаются одновременно: Hacker News, Reddit, Product Hunt, GitHub trending по трём срезам, набор из четырнадцати RSS-лент и Indie Hackers. У каждого есть собственная обработка ошибок, поэтому упавший источник возвращает пустой список и не останавливает запуск. Результаты объединяются, дубликаты удаляются по идентификатору, после чего применяется потолок в восемь тем на источник, отсортированных по скорости распространения — чтобы шумный источник не затопил следующий этап.

Считывает и отбрасывает, прежде чем тратить на письмо

Каждая тема получает оценку от 0 до 100 за то, насколько она подходит к тому, что мы делаем, плюс предложенный угол, целевую отрасль и целевую услугу. Всё, что ниже 50, отбрасывается. Этап работает на дешёвой модели, с четырьмя потоками параллельно, именно потому, что это этап, который решает, на что дальше тратиться. Из финального выбора извлекается ещё один слой: первый проход берёт по одной теме на отрасль, чтобы не получилось десять вариаций на одну и ту же нить.

Пишите статью с заданной структурой, а не свободно

Генератор получает жёсткую структуру: 1.500–2.200 слов на румынском, YAML-заголовок с заголовком не более 60 символов и описанием в 155, один H1 и четыре-шесть H2, между тремя и пятью внутренними ссылками, выбранными из списка адресов, которые существуют на сайте, выделенную цитату, раздел вопросов, совместимый со схемой FAQPage, и финальный призыв к действию. Работает с тремя потоками параллельно, с бюджетом в пять минут на статью и лимитом в 5.000 токенов на выходе.

Переводит на девятнадцать языков, с глоссарием, который не переводится

Источник — румынский; цели — девятнадцать, от английского, русского и украинского до арабского, иврита, хинди, японского и упрощённого китайского — включая две письменности справа налево. Правила прямо заданы в промпте: заголовок сохраняет структуру, адреса внутренних ссылок не переписываются и не локализуются, числа, единицы и даты сохраняются, а брендовые термины из глоссария остаются нетронутыми. Перевод идёт только с двумя потоками параллельно, намеренно — шлюз моделей ограничивает пропускную способность при большей нагрузке.

Жёсткий шлюз: пять условий и десять запрещённых выражений

Локально, без какого-либо платного вызова: заголовок должен существовать и содержать заголовок, описание и ключевые слова; текст должен превышать 1.200 слов, посчитанных после удаления блоков кода и тегов; минимум три подзаголовка второго уровня; минимум три внутренних ссылки на разделы, которые существуют на сайте; раздел вопросов как минимум с тремя парами. Плюс чёрный список: „game-changing”, „cutting-edge”, „world-class”, „revolutionize”, „synergy” и ещё пять. Одно появление — и статья не проходит.

Второй шлюз ищет именно то, чего не может увидеть счётчик

Вторая модель, с низкой температурой и ответом в JSON, получает статью и ищет пять вещей: имя клиента без доказательства, что он дал согласие, галлюцинированные статистики типа процентной доступности без измерения, неточные утверждения о том, что мы можем делать, рекламный язык, и утверждения о сторонних инструментах, которые создают впечатление, будто мы ими владеем, когда мы лишь их интегрируем. У неё есть и список наград, которые у нас действительно есть — любая награда вне списка помечается.

Когда проверка не может запуститься, статья считается проваленной

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

Публикация имеет одно условие и три шага

Публикация даже не вызывается, если ноль статей прошли шлюз. Когда вызывается: добавляет только директорию контента, останавливается, если ему нечего коммитить, делает commit с датой, количеством статей и количеством языков, и пытается push. Если push не удаётся, commit остаётся локальным, а запуск продолжается — работа не теряется и не утверждается, что она опубликована. Уведомление поисковых систем происходит только если push удался.

Каждый запуск оставляет свой счёт

В конце пишется дневной отчёт: сколько тем было собрано, сколько прошло по оценке, сколько было выбрано, сколько было написано, сколько переводов получилось, сколько прошло шлюз, что было закоммичено и что было запушено, что ответили поисковые системы, сколько это заняло времени, и разбивка стоимости по модели с количеством вызовов и входных и выходных токенов. Отчёт — это файл, а не панель: его можно читать, архивировать и сравнивать.

Как это выглядит

Путь, шаг за шагом.

01

Определяем, что ей можно писать, а что нельзя

Перед любым кодом: список тем, которые мы охватываем, список тех, которых не касаемся, тон и чёрный список выражений. Здесь же пишется и список утверждений, которые движок не имеет права делать о вас — клиенты, цифры, награды, сравнения. Поставляем: правила в письменном виде, в той форме, в которой они сразу входят в проверку, а не как отдельный документ, о котором забывают.

02

Запускаем только сбор и оценку, ничего не пишем

Работает в сухом режиме: собираются темы, они отмечаются, выбираются первые, пишется отчет, и выход осуществляется до генерации. Это недорого — при таком прогоне из наших журналов было 24 вызова, все на малой модели, с измеренной стоимостью примерно в один цент всего. Видно точно, что бы выбрал движок, день за днем, без какого-либо текста. Мы поставляем: отчеты сухого прогона и согласование по тому, что выбирается.

03

Мы пишем и проверяем, но не публикуем

Запускаются генерация и ворота, при остановленной публикации. Здесь настраиваются пороги: если все статьи проваливаются по длине, это требует одной настройки, а если проваливаются по внутренним ссылкам — другой. Здесь видно и то, что отмечает вторая проверка, которая является самой поучительной частью. Мы поставляем: сгенерированные статьи, полные вердикты с причиной каждого отклонения и скорректированные пороги.

04

Открываем публикацию, с воротами на месте

Запускаются системный таймер и публикация. Время выбирается вне пика, со случайной задержкой, чтобы прогоны не накладывались друг на друга; у нас — 00:30 UTC с задержкой до 30 минут, и с восстановлением, если сервер был остановлен в это время. Устанавливается лимит времени на один прогон. Мы поставляем: service unit и таймер, журналы и процедуру остановки.

05

Добавляем языки, один за другим

Перевод — этап с наибольшим расходом и самый легко выходящий из-под контроля. Языки добавляются по приоритетным полосам, а не все сразу, и проверяется, что получается в каждом — особенно в нелатинских письменностях, где правило такое: брендовым терминам надлежит оставаться в латинском алфавите. Мы поставляем: глоссарий, запущенные языки и измеренную стоимость на язык из журнала прогона.

Opt etape1adunare din optculegători paraleli,fiecare cu prindereproprie de eroare2notare 0–100, aruncăsub 503alegere cudiversitate peindustrie4scriere în română,1.500–2.200 decuvinte5traducere în 19 limbicu glosar neatins6poarta dură, cincicondiții și zeceexpresii interzise7poarta a doua, carecaută clienți, cifreși premii inventate8publicare, doar dacăa trecut măcar unulCând poarta nu poate rula, nu se publică.
Opt etape

Данные

К чему прикасаемся, где оно лежит и сколько остаётся

Вопросы, которые задаёт всякий, у кого есть ответственный за защиту данных, — заданы здесь раньше, чем их задаст он.

Что входит
Заголовки, адреса и короткие фрагменты из публичных источников — открытые API и RSS-потоки. Персональные данные не собираются, приватные аккаунты не читаются, доступ после авторизации не осуществляется. Каждая тема сохраняет источник, канал и момент обнаружения, чтобы можно было отследить, откуда вышла статья.
Где хранятся тексты
В виде файлов MDX в репозитории кода сайта, по пути `content/daily/{limbă}/{dată}/{slug}.mdx`. Не в отдельной базе данных. Следствие — полная история это история git: видно, кто что написал и когда, и можно откатиться командой. У каждой статьи есть собственный заголовок, из которого публичная страница формирует структурированные данные.
Где хранятся ключи
В файле среды, читаемом systemd unit при запуске, вне репозитория кода. Загрузчик среды не перезаписывает переменную, уже определенную в процессе, поэтому конфигурацию запуска можно заменить, не трогая файл. Обращения к моделям идут через наш внутренний шлюз, адресованный локально на сервере.
Журналы и срок хранения
Один JSON-файл на день в каталоге журналов движка, плюс стандартный вывод и вывод ошибок, записываемые отдельно в `/var/log` через unit systemd. Ежедневный журнал содержит вердикты ворот по каждой статье, с точной причиной отклонения и подсчитанным числом слов. Он не содержит текста статей — они находятся в репозитории.
Чего это не касается
Это не затрагивает ваши аккаунты в соцсетях, не отправляет email и не пишет в CRM. Публикация означает commit в репозитории сайта и уведомление двум поисковым системам. Если вы хотите публикацию по другим каналам, это обсуждается отдельно, с тем же правилом: ворота перед, а не после.

Случай

Ночь, когда движок написал три статьи и не опубликовал ни одну

Ситуация

Полный запуск, со всеми включёнными этапами: сбор из открытых источников, оценка, отбор, написание на румынском, перевод, ворота, публикация. Цель была намеренно маленькой — три статьи, чтобы было видно всю цепочку целиком, без больших затрат.

Что мы построили

Сбор после удаления дублей и ограничения по каждому источнику дал 24 уникальные темы. Оценка сохранила 18 и отбросила 6 ниже порога 50. Правило разнообразия выбрало 3 из разных отраслей. Генератор написал 3 статьи на румынском. Переводчик создал 57 файлов. Затем сработали ворота.

Что получилось

Ни одна статья не прошла. Две провалились, потому что вторая проверка вообще не смогла выполниться — сервис моделей не ответил в пределах бюджета 90 секунд, и ошибка была обработана как отрицательный вердикт, а не проигнорирована. Третья провалилась по одному условию: у неё не хватало поля ключевых слов в заголовке, хотя в ней было 2.478 слов, то есть в два раза больше минимального порога. Поскольку ни одна статья не прошла, публикация даже не вызывалась: commit остался пустым, push не был выполнен, поисковые системы получили ноль адресов. 57 переводов остались на диске, неиспользованные.

Чего этот случай не говорит

Запуск был на рабочей станции, а не на сервере — это видно по путям в журнале — и длился два с половиной часа, что превышает лимит 60 минут, заданный в unit systemd. Значит, он одновременно доказывает две вещи: что ворота работают, и что ежедневный пакет слишком велик для настроенного лимита времени. И то и другое реально, и об этом стоит сказать.

Вопросы

О чём нас спрашивают перед тем, как позвонить

Публикуется ли что-то без того, чтобы человек увидел текст?

Да, если вы так выберете — и нет, если не выберете. Движок может работать полностью автоматически, и в этом режиме статья, прошедшая оба слоя ворот, уходит в публикацию без того, чтобы кто-то её читал. Но ворота не декоративные: они могут отклонить статью за отсутствие поля в заголовке, за 1.199 слов вместо 1.200 или за одно выражение из чёрного списка. А если вы предпочитаете, чтобы каждая статья проходила через пару глаз, автоматическая публикация останавливается, и всё остаётся до этапа проверки, со всеми сигналами на виду. Наша рекомендация на старте — второй вариант, пока сигналы не станут скучными.

Что происходит, когда модель ошибается в факте?

Это зависит от того, о каком факте идёт речь, и честно стоит сказать, что не всё ловится. Ловится: имя клиента, указанное без доказанного согласия; выдуманная статистика вроде процента доступности без измерения; утверждение, которое создало бы впечатление, что у нас есть инструмент, который мы лишь интегрируем; премия, которой нет в списке реальных. Не ловится: неверная календарная дата из внешней новости или техническое утверждение о стороннем продукте, которое звучит правдоподобно. Для таких случаев нет другого стража, кроме человека, и поэтому каждая статья сохраняет в заголовке источники, из которых она исходила — проверка остаётся возможной после публикации, а исправление делается через commit.

Как останавливается поток?

Тремя способами, в порядке скорости. Остановка системного таймера — завтра ничего не запускается. Очистка переменной, которая держит ключ публикации — движок пишет и проверяет, но уведомление поисковых систем пропускается с предупреждением в журнале. И запуск в сухом режиме, который останавливается после выбора тем и ничего не генерирует. Кроме того, у отдельного запуска есть лимит времени в unit службы, сначала мягкий сигнал, потом принудительная остановка.

Движок сейчас работает на сервере?

Мы не можем подтвердить это извне и не будем этого утверждать. Что мы можем показать — это код, unit службы, таймер, процедуру установки и журналы тех запусков, которые у нас есть. Единственный полный запуск в журналах был на рабочей станции, а не на сервере — это видно по путям файлов — и длился два с половиной часа, то есть больше лимита 60 минут в unit systemd. Если вас интересует сегодняшнее состояние нашей установки, спросите, и мы проверим вместе. Мы предпочитаем такой вопрос, а не «да», которое окажется ложным.

Сколько статей он уже опубликовал?

Из имеющихся у нас журналов: ноль. Полный запуск собрал 24 темы, сохранил 18 после оценки, выбрал 3, написал 3 статьи и 57 переводов — и ворота остановили все три. Две — потому что вторая проверка вообще не смогла запуститься, одну — из-за отсутствующего поля в заголовке, хотя в ней было 2.478 слов, то есть она с большим запасом прошла порог длины. Публикация не вызывалась, поисковые системы не получили ни одного адреса. Мы могли бы вам этого не говорить. Но говорим, потому что это как раз та демонстрация, которая нужна сервису: ворота действительно останавливают.

Разве не «AI-контент» портит позиции в поиске?

Правильный вопрос не в том, кто написал текст, а в том, отвечает ли он на то, что кто-то действительно ищет, и можно ли это проверить. Поэтому пороги в воротах поставлены на вещи, которые коррелируют с полезностью, а не со стилем: минимальная длина, структура с подзаголовками, внутренние ссылки на существующие страницы, раздел вопросов. И поэтому чёрный список содержит именно те выражения, которые сигнализируют о тексте, написанном, чтобы заполнить пространство. Чего мы вам не обещаем — это конкретного места в результатах: это зависит от множества вещей, которые не у нас.

Сколько стоит один запуск?

Стоимость измеряется, а не оценивается: каждый вызов к модели учитывает входные и выходные токены, а ежедневный отчет дает итог с разбивкой по моделям, с числом вызовов. При прогонке по сухому из наших журналов: 24 вызова на дешевой модели. При полном прогоне с тремя статьями и 57 переводами: 32 вызова, из них 3 на дорогой модели — и именно они дали большую часть стоимости. Модель, используемая на каждом этапе, задается переменной среды, так что этап можно опустить на более дешевую модель, не трогая код. Сумма в деньгах зависит от выбранных моделей и их тарифов на момент запуска.

На каких языках он пишет и что происходит с терминами брендов?

Источник — румынский, целевых языков — девятнадцать: английский, русский, украинский, польский, немецкий, итальянский, французский, испанский, нидерландский, арабский, турецкий, иврит, хинди, японский, корейский, индонезийский, упрощенный китайский, бразильский португальский и чешский. Для каждого языка в конфигурации записана причина, почему он там есть. Термины брендов и технические термины хранятся в глоссарии и остаются непереведенными, в том числе на арабском, иврите и в азиатских письменностях, где правило явно указано: они сохраняются в латинском алфавите. Адреса внутренних ссылок никогда не локализуются.

Что не готово из того, что вы мне описали?

Три вещи, сказанные сейчас, чтобы вы не обнаружили их сами. Собственная документация местами опередила код — комментарий в начале этапа разметки упоминает модель, которой уже не вызывают, а реестр источников описывает набор RSS-потоков больше, чем в списке. Второе: два из источников, внесенных в реестр, помечены как активные, но их сборщик не вызывается в функции, которая собирает все, так что сегодня они ничего не дают. Третье: полный прогон из журналов превысил лимит времени в сервисной единице, а значит на сервере, с текущими настройками, он бы был остановлен на середине — лимит нужно поднять или суточный пакет уменьшить до запуска автоматически.

На чём основаны утверждения выше (26 источников)

26 из них — код и файлы из наших репозиториев. Мы не публикуем их название и строку: вместе, на одной странице, они слишком точно описали бы, как устроены системы, которые принадлежат не только нам. Мы разбираем их вместе с тобой, в репозитории, по запросу — проверка остаётся возможной, просто она проходит в разговоре.

Что вы хотели бы наладить?

Расскажите о своём процессе. Вместе определим, что стоит построить, что можно подключить и как проверим результат.

Давайте обсудим