В последнее время на ленту Хабра стало больно смотреть, ощущение песка в глазах от статей, которые узнаются с первого абзаца: гладкий, грамматически безупречный текст, который при этом физически неприятно дочитывать до конца. Особенно это касается корпоративных блогов, где материал выходит по редакционному календарю, а авторы явно прогоняют тему через модель и публикуют результат, даже толком не прочитав его перед публикацией. Кнопки “скрыть все статьи этого блога от греха подальше” или фильтра по признаку качества у Хабра до сих пор по понятным причинам нет, а модерация тут явно не ставит себе такую задачу как поддержание качества публикаций.
Хочу сразу уточнить: для меня сам факт использования нейросетей авторами при подготовке статьи не является негативным моментом. Желание авторов пользоваться таким плодом прогресса как современные большие языковые модели вполне объяснимо, ведь они значительно ускоряют создание черновика с правильной структурой повествования или стилем после перевода. Проблема начинается в тот момент, когда автор публикует этот черновик без единой правки. Разница между текстом подготовленным с помощью ИИ и неотредактированным ИИ-слопом видна невооруженным глазом.
Сети слишком хорошо пишут текст, настолько хорошо что у человека возникает Ощущение (с большой буквы О), что тут что-то не так. Это ощущение трудно сформулировать точнее, чем “песок в глазах”. Текст вроде бы написан гладко, факты на месте, да и структура логичная, а к третьему абзацу читать физически тяжело и хочется закрыть вкладку. Чаще всего это симптом не плохого содержания, а слишком узнаваемой формы, так называемого ИИ-акцента, на который наш мозг реагирует усталостью быстрее, чем можно было бы объяснить объемом материала. ИИ-акцент является определенным стилем написания текста и как любой стиль его можно описать как совокупность двух составляющих: лексической и структурной.
Лексическая составляющая заключается в том что у моделей есть набор слов, которые они используют статистически чаще людей. Для английского языка это подробно задокументировано: анализ более 15 миллионов рефератов из PubMed показал резкий скачок частотности таких слов, как delve, underscore, boast, meticulous, commendable, showcase, intricate и tapestry именно после массового распространения ChatGPT. При этом избыточная частотность у некоторых терминов достигала статистически колоссальных значений 1. Причем эффект не остался внутри текстов: исследователи из Института Макса Планка обнаружили, что эти же слова стали заметно чаще звучать в живой устной речи, подкастах и научно-популярных лекциях, т.е. язык моделей, обученных на человеческих текстах, начал в обратную сторону влиять на то, как говорят люди 2. В русскоязычном ИИ-слопе похожая картина, можно выделить четкие группы слов-маркеров:
1. Псевдо-искренность и навязывание доверия
Честный [разбор / обзор / взгляд](“Ниже — честный разбор нового смартфона”)Давайте будем честны(“Давайте будем честны: никто не любит заполнять отчеты”)Без купюр / Без воды(“Рассказываю историю запуска бизнеса без воды и красивых картинок”)Откровенно говоря / Спойлерчастые попытки создать ощущение личной беседы
2. Прямые кальки с английского ИИ-сленга
Let’s dive in / Deep dive[Давайте погрузимся / Погружение] (“В этой статье мы глубоко погрузимся в мир новейших технологий”)Plays a crucial / vital role[Играет ключевую / решающую роль] (“Правильное питание играет ключевую роль в формировании иммунитета”)Game-changer[Меняет правила игры] (“Появление этой фичи полностью меняет правила игры на рынке”)Delve into[Вникать / погружаться] (“Прежде чем мы вникнем в детали, стоит заметить”)It’s important to note[Важно отметить / подчеркнуть] (“Важно отметить, что данный метод подходит не всем”)
3. Пафосные прилагательные
Уникальный, инновационный, революционный, непревзойденный, мощный инструментитд.
4. Нейтрально-позитивные оценочные слова
Несомненно, безусловно, гармонично, комплексный подход, системноЯвляетсявместо глагола действия (“Данный инструмент является отличным решением” вместо “Этот инструмент помогает”).
Все эти слова также может использовать писатель в своем аутентичном тексте поэтому для определения ИИ-слопа куда важнее структурная составляющая, потому что модель воспроизводит определенные наборы композиционных приемов с частотой, недостижимой для живого автора. Я выделю 10 наиболее узнаваемых паттернов ИИ-стиля для русскоязычных текстов:
1. Тире как универсальный знак препинания
В обычной русской (и английской) пунктуации тире, двоеточие, скобки и точка распределяют разную смысловую нагрузку: тире передает акцент и неожиданность, двоеточие дает разъяснение, а скобки обрамляют второстепенную ремарку. У модели вся эта работа сворачивается в один знак, который к тому же ставится с пробелами по обе стороны, что для типографики нехарактерно и само по себе служит маркером.
Справочник Wikipedia “Signs of AI writing”, который редакторы энциклопедии ведут именно для отлова неразмеченных ИИ-правок, фиксирует это как один из самых частых и заметных признаков: языковые модели используют тире там, где человек поставил бы запятую, двоеточие или обычные скобки, причем делают это формульно, имитируя рекламную интонацию с нарочитым выделением каждой части фразы 3. Этот паттерн стал настолько узнаваемым, что после выхода GPT-5.1 в OpenAI даже начали программно подавлять эту привычку 3.
2. Антитеза “не X, а Y”
Тот же справочник обозначает это как негативный разворот: конструкция “это не про X, это про Y” стала настолько частой, что превратилась в самостоятельный опознавательный знак, наравне с тире 3. Прием вообще-то рабочий: антитеза с параллельной структурой является классическим риторическим инструментом, хорошо работающим пару раз за весь текст. Проблема исключительно в частоте: когда модель прогоняет читателя через одну и ту же конструкцию семь раз подряд, эффект неожиданности, на котором прием держится, исчезает уже после второго повтора.
3. Однострочные абзацы-обрывы и рубленые фразы для драматизации
А потом все пошло не так. Отдельной строкой. Этот прием был заимствован из журналистики и сторителлинга, где он служит для управления темпом чтения в сюжетном материале. В научно-популярном обзоре или техническом посте, где сюжета как такового нет, повторяющийся паттерн “тезис / пример / рубленый обрыв / вывод” считывается мозгом как шаблон уже к середине текста, и вместо драматического эффекта производит противоположный: ощущение манипуляции темпом.
4. Заголовки-клиффхэнгеры по одной и той же формуле
Невидимый орган, Трещина в фундаменте, Бесконечный спор как метафора плюс обещание драмы, повторенное в каждом подзаголовке, плюс одинаковая внутренняя структура раздела (тезис / пример / неожиданный поворот / афоризм-вывод). Жесткая повторяемость на уровне композиции вносит свой вклад в общее ощущение усталости от текста, потому что читатель считывает форму раздела быстрее, чем успевает вникнуть в его содержание и это ощущается как навязчивость.
5. Слова-связки без смысловой нагрузки
Вставки грубо говоря, по сути, казалось бы, хуже того, выходит в письме опытного автора сигнализируют реальную оговорку или смену интонации. У модели же они в основном декоративны и вставлены для связности на уровне поверхностной синтаксической структуры, а не смысла: если вычеркнуть половину, аргументация не изменится. Сюда же можно отнести пустое подытоживание (таким образом, в итоге, стоит отметить) как формульные связки, которые не добавляют информации 3.
6. Афористичные фразы-концовки
Стремление свернуть каждый смысловой блок в цитируемую фразу (мы измеряем поведение и называем это чтением умов) не является случайностью обучения. Это следствие обучения с подкреплением на основе разметки людьми (RLHF). Было проведено исследование показавшее, что модели после RLHF-дообучения заметно чаще используют категоричные, уверенные формулировки. При обучении самой модели вознаграждения уверенные утверждения получали высокую награду, а формулировки с оговорками устойчиво низкую 4. Афоризм является частным случаем уверенного (завершенного) высказывания и он статистически выгоднее для модели, чем менее эффектная промежуточная мысль.
7. Искусственная симметрия и триады
Модели очень любят симметрию вида не в четыре года, а гораздо раньше, быстрый автоматический и медленный осознанный, и списки строго по три пункта (инновационный, трансформирующий, прорывной). “Правило трех” даже отдельно задокументировано как узнаваемый ИИ-паттерн: модель тяготеет к триплетам при перечислении чего угодно: преимуществ, выводов и тд. Они используют эту структуру, чтобы поверхностный анализ выглядел более полным, чем он есть 3. Один такой список не режет глаз, но когда триада появляется в каждом разделе, паттерн становится очень заметен.
8. Затухание конкретики к концу текста
Первая половина текста обычно держится на частностях: конкретный эксперимент с числами, а также конкретными именами и датами. К середине и концу текст сползает в обобщенные формулировки и клише (не единое целое, а, не столько X, сколько Y).
Это согласуется с более широким выводом лингвистических работ о том, что генеративные модели производят менее разнообразный текст, чем люди, писавшие на ту же тему: сравнительное исследование человеческого и ИИ-письма зафиксировало устойчивый эффект гомогенизации (сужение словарного и структурного разнообразия), причем у более новых версий моделей этот эффект оказался даже сильнее, чем у более старых 5. Разнообразие, которого не хватает на уровне всего корпуса текстов модели, не хватает и внутри одного длинного текста: к концу генерации модель чаще соскальзывает в наиболее вероятные, т.е. наиболее шаблонные, продолжения.
9. Неопределенная атрибуция и раздутая значимость
Конструкции вида исследования показывают, эксперты отмечают, играет ключевую роль, служит доказательством, стало поворотным моментом звучат как аргументы, но ими не являются, потому что за ними не стоит ни одного конкретного имени, издания, даты или цифры, нет указания источника. Раздутая значимость проявляется в постоянном преувеличении значимости описываемого, когда, например, данные небольшого регионального офиса статистики превращаются в “переломный момент в развитии региональной статистики” 3. У человека, пишущего на знакомую тему, почти всегда есть конкретный источник в голове, а у модели вместо источника используется усредненная формулировка, которая маскируется под ссылку на авторитет, но им не является.
10. Псевдоискренность
Модели обучены имитировать доверительный тон человека, но из-за массовости это превратилось в штамп. ИИ пытаются заслужить внимание читателя, уверяя, что в тексте нет рекламы или фальши. Они не только пытаются навязать доверие, но и предпринимают попытки создать ощущение личной беседы. Корни этого поведения растут из старых дефолтных промтов вида {"role": "system", "content": "You are a helpful assistant."}.
Это далеко не полный список паттернов, но, по моему мнению, оказывающий наиболее весомый вклад в эффект песка в глазах. Другие паттерны, например, форматирование под копирку (жирный текст на каждом втором термине и курсивы в определениях) также влияют на восприятие, но довольно легко исправляются и особых проблем не доставляют. 3
Почему от этих паттернов действительно устает читатель?
Ответ лежит в плоскости экспериментальной психологии. В классических работах по эстетическому восприятию описан так называемый “эффект двух факторов”, суть которого заключается в том, что повторяющийся стимул сначала вызывает рост привлекательности за счет узнавания (позитивная габитуация), а затем, спустя некоторое время, накапливается противоположный эффект, появляется тедиум (скука из-за долгой и повторяющейся работы), который эту привлекательность снижает 11.
Применительно к тексту это означает: первое и второе тире работают положительно, третье уже нейтрально, а пятое-седьмое воспринимаются как раздражающий тик потому что мозг успевает выделить закономерность и дальше реагирует не на содержание фразы, а на факт повторения формы.
Дело не в сложности восприятия текста как таковой. Отдельное исследование текстовой дисфлюентности (когда текст читать физически трудно из-за шрифта, синтаксиса и так далее) показало, что такая трудность снижает мотивацию читать дальше, но заметно не влияет ни на блуждание внимания, ни на понимание прочитанного 12.
С ИИ-слопом ситуация прямо противоположная: он максимально “флюентен” на уровне отдельного предложения, легок и грамматически безупречен, и тем не менее вызывает усталость и отторжение. Это говорит о том, что источник проблемы не в трудности декодирования слов, а в предсказуемости композиции целиком, ведь паттерн угадывается на уровне абзаца и раздела, а не буквы или слова, и именно это вызывает ощущения “песка в глазах”.
Дополнительно появляется текстовый вариант эффекта зловещей долины в виде “читаю, но не могу поймать мысль либо растет чувство недоверия”, текст грамматически безупречен, структурно гладок и все же ощущается как “что-то не то”. Этот эффект подтверждается экспериментальными работами по восприятию сгенерированного контента в социальных сетях, где рост “человекоподобия” текста сначала увеличивает доверие к нему, а затем на определенном уровне схожести доверие резко проседает 13.
Кто виноват?
Разбираясь, почему модель вообще так пишет я обнаружил, что большинство этих паттернов являются следствием того, как именно дообучали модели. RLHF (обучение с подкреплением на основе предпочтений людей-разметчиков) устроено как поиск моды распределения: вместо того чтобы сохранять все разнообразие возможных ответов, процесс подталкивает модель к тем формулировкам, которые в среднем получали более высокую оценку у разметчиков, сужая диапазон генерации 6.
Отдельная работа по алгоритмическим искажениям RLHF показывает, что в крайних случаях это приводит к коллапсу предпочтений, когда менее распространенные стилистические решения практически перестают воспроизводиться моделью в пользу нескольких доминирующих 7. А исследователи, изучающие модели вознаграждения как черный ящик, прямо отмечают, что такие модели, обучаясь на большом зашумленном массиве разметки, попутно усваивают поверхностные стилистические предпочтения разметчиков, которые никто не закладывал туда осознанно 8.
Отсюда и любовь к тире, к правилу трех, к уверенным афоризмам и рубленым фразам для драматизации: не потому, что кто-то запрограммировал именно это, а потому, что разметчики в среднем чуть выше оценивали именно такие ответы. На фоне других, возможно неудачно оформленных текстов, такая разметка выглядела хорошо, но никто не задумался что модель возведет ее в абсолют.
Остальные же паттерны возникли из-за метода аугментации, применяемого в первичном обучении моделей. В нем часть корпуса русскоязычных текстов создается с помощью машинного перевода англоязычных текстов. А т.к. размеры получившихся корпусов текстов очень большие, то никто их не вычитывает и не правит вручную.
Что делать?
Я не знаю. Администрации Хабра куда важнее платные корпоративные блоги пусть и с ИИ-слопом, чем их отсутствие. У бизнеса свой список приоритетов, удобство читателей в нем не на первом месте и это НОРМАЛЬНО. Ждать подвижек с этой стороны как минимум наивно.
Все что я могу, так это открыто обратиться к авторам и редакторам статей (особенно из корпоративных блогов) с призывом уделять чуть больше времени на редактуру. Некоторые авторы уже используют автоматические “оживляторы” статей в виде различных скриптов (об этом были статьи на Хабре), но к сожалению, количество таких авторов не велико, а подобные скрипты решают от силы 20% проблем. Поэтому я позволю себе составить небольшой чек-лист для авторов и редактров с конкретныеми правками под паттерны описанные выше:
По тире. Пройдитесь по тексту и посчитайте тире на тысячу знаков. Если их больше двух-трех на абзац то это звоночек. Посмотрите на каждое и спросите себя: это акцент (тогда оставляем) или можно заменить на запятую, двоеточие или просто разбить на два предложения (тогда меняем)? Не нужно убирать все тире, нужно вернуть ему статус редкого инструмента.
По антитезе “не X, а Y”. Оставьте максимум одну-две таких конструкции на весь текст, причем в самых сильных смысловых точках. Остальные перепишите как обычное утвердительное высказывание без противопоставления. Часто выясняется, что вторая часть конструкции (“а Y”) и есть вся нужная мысль, а “не X” было лишь риторическим разгоном.
По абзацам-обрывам. Оставьте один-два таких приема на весь материал, в местах, где действительно есть смысловой перелом. Остальные однострочные абзацы либо слейте с соседним текстом, либо перепишите без драматической паузы.
По заголовкам. Проверьте подзаголовки списком, отдельно от текста. Если все построены по формуле “метафора + обещание драмы”, значит часть можно заменить на прямые, информативные формулировки. Разная структура заголовков тоже является частью разнообразия, которого не хватает при генерации текста.
По словам-связкам. Классический тест: мысленно вычеркните по сути, грубо говоря, стоит отметить, выходит и перечитайте абзац. Если смысл не изменился значит слово было декоративным. Оставляйте связку только там, где без нее теряется реальный логический переход или интонационная оговорка.
По афористичным концовкам. Не каждая мысль обязана заканчиваться цитируемой фразой. Разрешите себе закончить абзац фактом, вопросом или просто оборванной на полуслове мыслью, которая продолжится в следующем разделе ведь это ближе к тому, как рассуждает живой человек и сильно влияет на восприятие.
По триадам и симметрии. Специально ломайте параллелизм: если получилось несколько последовательных перечислений из трех одинаково устроенных элементов замените один пункт на два коротких или, наоборот, на один длинный со своей внутренней логикой. Разная длина и структура отличает человеческое письмо от машинного и на статистическом уровне и на уровне восприятия.
По затуханию деталей. Отдельно перечитайте вторую половину текста, игнорируя первую: если там резко меньше конкретных имен, чисел, дат и примеров, чем в начале, то это симптом “сползания” генерации в обобщения по мере разворачивания длинного ответа. Добавьте туда те же частности, что были вначале.
По атрибуции. У каждой фразы вида исследования показывают или играет ключевую роль должен быть либо конкретный источник в скобках, либо она вычеркивается целиком. Пустая ссылка на авторитет воспринимается хуже, чем ее отсутствие: она создает иллюзию доказательности там, где доказательства нет.
Общий процесс. Если текст создавался с помощью модели, будет полезно делать не одну правку, а последовательно три прохода: сначала проход на ритм (искусственно сбить регулярность длины предложений и повторяемость приемов через раздел), затем проход на конкретику (вернуть частности туда, где текст обобщается), и в конце проход на связки и концовки (вычеркнуть декоративные филлеры и половину афоризмов).
По отдельности каждая правка кажется мелкой, но вместе они убирают именно ту монотонность паттернов, из-за которой текст читается легко, но оставляет ощущение усталости гораздо раньше, чем должен был бы по объему.
Автоматизируем это
Если уж мы принимаем право авторов на использование современных инстурментов (ИИ) в своей работе, то было бы глупо не попробовать использовать этот же самый инструмент для проверки, нужно ли тексту в текущем виде еще немного заботы и внимания.
Современные статистические детекторы ИИ-текста измеряют перплексию (насколько предсказуемо каждое следующее слово для языковой модели) и бёрстинес (извините) это то, насколько сильно эта предсказуемость колеблется по тексту. У человека естественная перплексия высокая и неравномерная: мы непроизвольно избегаем повторения собственных недавних формулировок, поэтому предложения то предсказуемые, то нет. У модели перплексия в среднем ниже и куда равномернее, она выбирает наиболее вероятное продолжение и делает это с постоянным уровнем неожиданности на протяжении всего текста 9.
С ростом размера моделей эта статистическая разница сокращается и новые крупные модели уже трудно отличить от человека по одной лишь перплексии 10. Закон Ципфа также не прменим т.к. современные модели уверенного его обходят, а структурные и смысловые паттерны остаются заметны до сих пор и именно на них мы сосредоточимся.
Часть из десяти паттернов у нас чисто структурная (тире, обрывы, заголовки, связки, триады-списки, маркированные списки): они однозначно вычисляются по тексту и markdown-разметке, гонять их через модель было бы менее надежно, чем использовать regex.
Другая часть паттернов (антитеза как риторический разгон, афористичные концовки, неопределенная атрибуция, псевдоискренность, оценка конкретности абзаца) требует понимания смысла, а не только формы. Поэтому мы сделаем гибрид: структурные паттерны считаем детерминированно, а семантические отдаем на классификацию небольшой локальной LLM, после чего снова детерминированно (и нормируя на объем текста) считаем статистику.
Нам нужна модель размером до 8B параметров, которую можно будет использовать без мощных видеокарт, уверенно работающая с русским текстом и возвращающая структурированный JSON. Мой выбор пал, внезапно, на модель T-lite-it-2.1 (Qwen 3 архитектура) от T-Банк 14 (нет, не реклама; нет, не работаю с ними; да, ребята файнтюнят qwen-ы и у них получается).
У модели свой токенизатор под русский язык, что попутно ускоряет инференс на русском тексте примерно на 60% относительно базовой Qwen2.5-7B и помещается на видеокарты с 12Gb VRAM. Запускать модель будем максимально просто через Ollama:
ollama run hf.co/t-tech/T-lite-it-2.1-GGUF:Q8_0
Сам скрипт ai_slop_detector.py довольно прост и не требует отдельных пояснений:
#!/usr/bin/env python3 from __future__ import annotations import argparse import json import re import requests import statistics import sys from dataclasses import dataclass, field from typing import Dict, List, Optional, Sequence, Set, Tuple STRUCTURAL_IDS = ["em_dash", "one_liner", "heading", "filler", "triad"] SEMANTIC_IDS = ["antithesis", "aphorism", "vague_attribution", "pseudo_sincerity"] PATTERN_TITLES = { "em_dash": "1. Тире как универсальный знак препинания", "antithesis": "2. Антитеза не X, а Y", "one_liner": "3. Абзацы-обрывы в одну строку", "heading": "4. Заголовки-клиффхэнгеры", "filler": "5. Слова-связки без смысловой нагрузки", "aphorism": "6. Афористичные концовки", "triad": "7. Искусственная симметрия и триады", "concreteness_decay": "8. Затухание конкретики к концу текста", "vague_attribution": "9. Неопределенная атрибуция и раздутая значимость", "pseudo_sincerity": "10. Псевдо-искренность", } FILLER_PHRASES = [ "грубо говоря", "по сути", "казалось бы", "на самом деле", "хуже того", "выходит,", "стоит отметить", "важно отметить", "нельзя не отметить", "как ни странно", "так или иначе", "в конечном счете", "проще говоря", ] DRAMA_HEADING_MARKERS = [ "невидимый", "трещина", "секрет", "тайна", "загадка", "рухнул", "бесконечный", "правда о", "как на самом деле", "почему все", "и как быть", "фундамент", "честный" ] VAGUE_MARKERS = ["исследования показывают", "играет ключевую роль", "стало поворотным моментом"] SINCERITY_MARKERS = ["честно говоря", "без воды", "будем честны", "спойлер"] _CODE_FENCE_RE = re.compile(r"^\s*```") _BOLD_ONLY_RE = re.compile(r"^\s*\*\*[^*]+\*\*\s*$") SEMANTIC_SYSTEM_PROMPT = """Ты — литературный редактор. Тебе присылают пронумерованные \ абзацы русского текста. Для КАЖДОГО абзаца определи: 1. Какие из паттернов присутствуют (ноль, один или несколько): - antithesis: риторическое противопоставление не X, а Y / дело не в X, а в Y, \ использованное как разгон, а не по содержательной необходимости; - aphorism: абзац закругляется короткой цитируемой фразой-выводом, как афоризм; - vague_attribution: есть отсылка к обобщенному авторитету (исследования показывают, \ играет ключевую роль, стало поворотным моментом) без конкретного имени, источника или цифры; - pseudo_sincerity: попытка искусственно расположить читателя уверениями в честности \ (честно говоря, без воды, давайте будем честны, спойлер). 2. concreteness — оцени плотность конкретики в абзаце по шкале 0-2: 0 = только общие обтекаемые формулировки без единого факта, числа, имени; 1 = смешанный текст; 2 = плотная конкретика (числа, даты, имена, точные примеры). Ответь СТРОГО валидным JSON-объектом, содержащим единственный ключ "items", в котором \ лежит массив результатов для ВСЕХ переданных абзацев. Формат: {"items": [{"id": 1, "patterns": ["aphorism"], "concreteness": 0}, {"id": 2, "patterns": [], "concreteness": 2}]} Если для абзаца паттернов нет — верни пустой список "patterns". Не добавляй абзацы, \ которых не было во входных данных, и не меняй нумерацию id.""" TIER_LABELS = { "human": "Похоже на текст, написанный либо вычитанный и доработанный человеком.", "mixed": ("Черновик от модели доработан частично, либо это случайное совпадение по одному стилистическому признаку."), "ai": ("Высокая плотность типичных ИИ-паттернов: текст опубликован без редактуры после генерации."), } WEIGHTS = { "em_dash": 0.6, "antithesis": 1.0, "one_liner": 0.4, "heading": 0.9, "filler": 0.8, "aphorism": 0.9, "triad": 0.8, "concreteness_decay": 1.5, "vague_attribution": 0.9, "pseudo_sincerity": 0.7, } WEIGHT_POWER = 2.0 FULL_THRESHOLD = 0.90 HIGH_THRESHOLD = 0.55 MIN_HIGH_FOR_SYSTEMIC = 4 BAND_RANGES: Dict[str, Tuple[float, float]] = { "human": (0.00, 0.35), "mixed": (0.35, 0.65), "ai": (0.65, 1.00), } def split_paragraphs(text: str) -> List[str]: paras = [p.strip() for p in re.split(r"\n\s*\n", text)] return [p for p in paras if p] def split_sentences(fragment: str) -> List[str]: fragment = re.sub(r"\s+", " ", fragment).strip() parts = re.split(r"(?<=[.!?])\s+(?=[А-ЯA-Z0-9])", fragment) return [p.strip() for p in parts if p.strip()] def is_heading_line(line: str) -> bool: return bool(re.match(r"^\s{0,3}#{1,6}\s+\S", line)) def is_pseudo_heading(paragraph: str) -> bool: return bool(_BOLD_ONLY_RE.match(paragraph.strip())) def strip_bold_markers(text: str) -> str: t = text.strip() if t.startswith("**") and t.endswith("**") and len(t) > 4: return t[2:-2].strip() return t def extract_headings(text: str) -> List[str]: return [re.sub(r"^#{1,6}\s+", "", ln).strip() for ln in text.splitlines() if is_heading_line(ln)] def body_paragraphs(text: str) -> List[str]: paras = split_paragraphs(text) return [p for p in paras if not is_heading_line(p)] def word_count(text: str) -> int: return len(re.findall(r"[А-Яа-яA-Za-zее]+", text)) def clamp01(x: float) -> float: return max(0.0, min(1.0, x)) @dataclass class Metric: key: str title: str score: float detail: str def check_em_dash(paragraphs: Sequence[str], full_text: str) -> Tuple[Metric, List[bool]]: hits = [("—" in p) for p in paragraphs] ratio = sum(hits) / len(paragraphs) if paragraphs else 0 per_1000 = 1000 * full_text.count("—") / max(1, len(full_text)) score = clamp01((ratio - 0.15) / 0.55) detail = (f"тире в {sum(hits)} из {len(paragraphs)} абзацев ({ratio:.0%}), " f"{per_1000:.2f} на 1000 символов") return Metric("em_dash", PATTERN_TITLES["em_dash"], score, detail), hits def check_one_liners(paragraphs: Sequence[str]) -> Tuple[Metric, List[bool]]: eligible_mask = [not is_pseudo_heading(p) for p in paragraphs] hits = [ eligible_mask[i] and len(split_sentences(p)) == 1 and len(p) < 90 for i, p in enumerate(paragraphs) ] eligible_count = sum(eligible_mask) excluded = len(paragraphs) - eligible_count ratio = sum(hits) / eligible_count if eligible_count else 0 score = clamp01((ratio - 0.05) / 0.25) detail = (f"однострочных абзацев-обрывов: {sum(hits)} из {eligible_count} ({ratio:.0%})" + (f"; исключено псевдо-заголовков: {excluded}" if excluded else "")) return Metric("one_liner", PATTERN_TITLES["one_liner"], score, detail), hits def check_filler(paragraphs: Sequence[str], full_text: str) -> Tuple[Metric, List[bool]]: hits = [any(ph in p.lower() for ph in FILLER_PHRASES) for p in paragraphs] total_hits = sum(full_text.lower().count(ph) for ph in FILLER_PHRASES) per_1000w = 1000 * total_hits / max(1, word_count(full_text)) score = clamp01(per_1000w / 4.0) detail = f"слов-связок без смысла: {total_hits} ({per_1000w:.2f} на 1000 слов)" return Metric("filler", PATTERN_TITLES["filler"], score, detail), hits def check_triads(paragraphs: Sequence[str], full_text: str) -> Tuple[Metric, List[bool]]: triad_re = re.compile(r"\b\w+,\s*\w+\s+и\s+\w+\b", re.IGNORECASE) hits = [bool(triad_re.search(p)) for p in paragraphs] list_blocks: List[List[str]] = [] current: List[str] = [] for line in full_text.splitlines(): if re.match(r"^\s*[-*\d.]+\s+", line): current.append(line) elif current: list_blocks.append(current) current = [] if current: list_blocks.append(current) three_item_lists = sum(1 for b in list_blocks if len(b) == 3) inline_ratio = sum(hits) / max(1, len(paragraphs)) list_ratio = three_item_lists / len(list_blocks) if list_blocks else 0 score = clamp01(inline_ratio / 0.3 * 0.6 + list_ratio * 0.4) detail = (f"перечислений X, Y и Z: {sum(hits)}, списков из трех пунктов: {three_item_lists} из {len(list_blocks)}") return Metric("triad", PATTERN_TITLES["triad"], score, detail), hits def check_headings(headings: Sequence[str], n_pseudo: int = 0) -> Metric: if not headings: return Metric("heading", PATTERN_TITLES["heading"], 0.0, "заголовков не найдено") lengths = [len(h.split()) for h in headings] mean_len = statistics.mean(lengths) stdev_len = statistics.pstdev(lengths) if len(lengths) > 1 else 0 uniformity = 1 - clamp01(stdev_len / max(1.0, mean_len)) drama_hits = sum(1 for h in headings if any(m in h.lower() for m in DRAMA_HEADING_MARKERS)) drama_ratio = drama_hits / len(headings) score = clamp01(0.5 * uniformity + 0.7 * drama_ratio) pseudo_note = f" (из них {n_pseudo} — **жирные** подзаголовки без #)" if n_pseudo else "" detail = (f"{len(headings)} заголовков{pseudo_note}, разброс длины ~{stdev_len:.1f} слов, " f"драматизирующих: {drama_hits} ({drama_ratio:.0%})") return Metric("heading", PATTERN_TITLES["heading"], score, detail) @dataclass class ParagraphSemantics: patterns: Set[str] = field(default_factory=set) concreteness: Optional[int] = None class LLMEngine: def analyze_paragraphs(self, paragraphs: Sequence[str]) -> List[ParagraphSemantics]: raise NotImplementedError def _iter_balanced_bracket_spans(text: str): start = text.find("[") while start != -1: depth = 0 in_string = False escape = False for i in range(start, len(text)): ch = text[i] if in_string: if escape: escape = False elif ch == "\\": escape = True elif ch == '"': in_string = False continue if ch == '"': in_string = True elif ch == "[": depth += 1 elif ch == "]": depth -= 1 if depth == 0: yield text[start:i + 1] break start = text.find("[", start + 1) def _looks_like_items(value): if isinstance(value, dict): if "id" in value: return [value] list_candidates = [v for v in value.values() if isinstance(v, list) and v and all(isinstance(x, dict) for x in v)] if list_candidates: value = list_candidates[0] elif value and all(isinstance(v, dict) for v in value.values()): items = [] for k, v in value.items(): v = dict(v) v.setdefault("id", k) items.append(v) return items else: return None if isinstance(value, list) and value and all(isinstance(x, dict) and "id" in x for x in value): return value return None def extract_paragraph_items(raw: str) -> Optional[list]: for span in _iter_balanced_bracket_spans(raw): try: parsed = json.loads(span) except (json.JSONDecodeError, TypeError): continue items = _looks_like_items(parsed) if items is not None: return items try: whole = json.loads(raw) items = _looks_like_items(whole) if items is not None: return items except (json.JSONDecodeError, TypeError): pass salvaged = [] start = raw.find("{") while start != -1: depth = 0 in_string = False escape = False for i in range(start, len(raw)): ch = raw[i] if in_string: if escape: escape = False elif ch == "\\": escape = True elif ch == '"': in_string = False continue if ch == '"': in_string = True elif ch == "{": depth += 1 elif ch == "}": depth -= 1 if depth == 0: span = raw[start:i + 1] try: obj = json.loads(span) if isinstance(obj, dict) and "id" in obj: salvaged.append(obj) except (json.JSONDecodeError, TypeError): pass break start = raw.find("{", start + 1) if salvaged: return salvaged return None def looks_like_semantic_prose(paragraph: str) -> bool: stripped = paragraph.strip() if _CODE_FENCE_RE.match(stripped): return False if _BOLD_ONLY_RE.match(stripped): return False if word_count(stripped) < 6: return False return True class OllamaEngine(LLMEngine): def __init__(self, model: str, host: str = "http://localhost:11434", batch_size: int = 6, temperature: float = 0.1, timeout: int = 120): self.model = model self.host = host.rstrip("/") self.batch_size = batch_size self.temperature = temperature self.timeout = timeout def _call(self, user_content: str, n_paragraphs: int = 1) -> str: num_predict = min(8192, 300 * n_paragraphs + 200) resp = requests.post( f"{self.host}/api/chat", json={ "model": self.model, "messages": [ {"role": "system", "content": SEMANTIC_SYSTEM_PROMPT}, {"role": "user", "content": user_content}, ], "format": "json", "stream": False, "think": False, "options": {"temperature": self.temperature, "num_predict": num_predict}, }, timeout=self.timeout, ) resp.raise_for_status() data = resp.json() message = data.get("message", {}) or {} content = message.get("content") or "" if not content.strip(): content = message.get("thinking") or "" return content @staticmethod def _parse_batch_response(raw: str, expected_ids: Sequence[int], warn_on_failure: bool = True) -> Dict[int, ParagraphSemantics]: result: Dict[int, ParagraphSemantics] = {} items = extract_paragraph_items(raw) if items is None: if warn_on_failure: snippet = " ".join(raw.split())[:200] print(f"[!] Не удалось разобрать JSON от модели для батча " f"{expected_ids[0]}-{expected_ids[-1]}. Начало ответа модели: " f"{snippet!r}", file=sys.stderr) return result for item in items: try: pid = int(item["id"]) patterns = {p for p in item.get("patterns", []) if p in SEMANTIC_IDS} conc = item.get("concreteness") conc = int(conc) if conc is not None else None conc = conc if conc in (0, 1, 2) else None result[pid] = ParagraphSemantics(patterns=patterns, concreteness=conc) except (KeyError, ValueError, TypeError): continue return result def analyze_paragraphs(self, paragraphs: Sequence[str]) -> List[ParagraphSemantics]: results: List[Optional[ParagraphSemantics]] = [None] * len(paragraphs) eligible = [i for i, p in enumerate(paragraphs) if looks_like_semantic_prose(p)] for i in range(len(paragraphs)): if i not in eligible: results[i] = ParagraphSemantics() for start in range(0, len(eligible), self.batch_size): idx_batch = eligible[start:start + self.batch_size] ids_batch = [i + 1 for i in idx_batch] user_content = "\n\n".join(f"[{i + 1}] {paragraphs[i]}" for i in idx_batch) try: raw = self._call(user_content, n_paragraphs=len(idx_batch)) except Exception as e: print(f"[!] Ollama недоступна ({e}). Абзацы {ids_batch[0]}-" f"{ids_batch[-1]} останутся без семантической оценки.", file=sys.stderr) for i in idx_batch: results[i] = ParagraphSemantics() continue parsed = self._parse_batch_response(raw, ids_batch, warn_on_failure=False) missing = [i for i in idx_batch if (i + 1) not in parsed] if missing: snippet = " ".join(raw.split())[:200] print(f"[!] Батч {ids_batch[0]}-{ids_batch[-1]}: не разобрано " f"{len(missing)} из {len(idx_batch)} абзацев, начало ответа " f"модели: {snippet!r}. Повторяю по одному абзацу.", file=sys.stderr) for i in missing: try: single_raw = self._call(f"[{i + 1}] {paragraphs[i]}", n_paragraphs=1) single = self._parse_batch_response(single_raw, [i + 1]) parsed[i + 1] = single.get(i + 1, ParagraphSemantics()) except Exception as e: print(f"[!] Абзац {i + 1}: не удалось получить оценку ({e})", file=sys.stderr) parsed[i + 1] = ParagraphSemantics() for i in idx_batch: results[i] = parsed.get(i + 1, ParagraphSemantics()) return [r if r is not None else ParagraphSemantics() for r in results] def concreteness_trend_metric(concreteness_values: Sequence[Optional[int]]) -> Metric: points = [(i / max(1, len(concreteness_values) - 1), v) for i, v in enumerate(concreteness_values) if v is not None] if len(points) < 4: return Metric("concreteness_decay", PATTERN_TITLES["concreteness_decay"], 0.0, "недостаточно данных об оценке конкретности") xs = [p[0] for p in points] ys = [p[1](https://pmc.ncbi.nlm.nih.gov/articles/PMC12679996/) for p in points] x_mean, y_mean = statistics.mean(xs), statistics.mean(ys) num = sum((x - x_mean) * (y - y_mean) for x, y in zip(xs, ys)) den = sum((x - x_mean) ** 2 for x in xs) slope = num / den if den else 0.0 score = clamp01(-slope) detail = (f"тренд конкретности по {len(points)} абзацам: наклон {slope:+.2f} ") return Metric("concreteness_decay", PATTERN_TITLES["concreteness_decay"], score, detail) def pattern_metric_from_hits(pattern_id: str, hits: Sequence[bool]) -> Metric: ratio = sum(hits) / len(hits) if hits else 0 score = clamp01((ratio - 0.02) / 0.35) detail = f"встречается в {sum(hits)} из {len(hits)} абзацев ({ratio:.0%})" return Metric(pattern_id, PATTERN_TITLES[pattern_id], score, detail) @dataclass class ScoreResult: score: float tier: str n_full: int n_high: int label: str def classify_tier(valid_metrics: Sequence[Metric]) -> str: n_full = sum(1 for m in valid_metrics if m.score >= FULL_THRESHOLD) n_high = sum(1 for m in valid_metrics if m.score >= HIGH_THRESHOLD) if n_full >= 2 and n_high >= MIN_HIGH_FOR_SYSTEMIC: return "ai" if n_full >= 3: return "ai" if n_full == 1: return "mixed" if n_full >= 2 or n_high >= MIN_HIGH_FOR_SYSTEMIC: return "mixed" return "human" def weighted_intensity(valid_metrics: Sequence[Metric], power: float = WEIGHT_POWER) -> float: weighted_pairs = [ (m.score, (m.score ** power) * WEIGHTS.get(m.key, 1.0)) for m in valid_metrics ] total_w = sum(w for _, w in weighted_pairs) if total_w <= 0: return 0.0 return sum(s * w for s, w in weighted_pairs) / total_w def compute_score(valid_metrics: Sequence[Metric]) -> ScoreResult: if not valid_metrics: return ScoreResult(0.0, "human", 0, 0, verdict("human")) n_full = sum(1 for m in valid_metrics if m.score >= FULL_THRESHOLD) n_high = sum(1 for m in valid_metrics if m.score >= HIGH_THRESHOLD) tier = classify_tier(valid_metrics) intensity = clamp01(weighted_intensity(valid_metrics)) lo, hi = BAND_RANGES[tier] score = clamp01(lo + intensity * (hi - lo)) return ScoreResult(score, tier, n_full, n_high, verdict(tier)) def analyze(text: str, engine: LLMEngine) -> Tuple[List[Metric], ScoreResult]: paragraphs = body_paragraphs(text) headings = extract_headings(text) full_text = text em_dash_metric, em_dash_hits = check_em_dash(paragraphs, full_text) one_liner_metric, one_liner_hits = check_one_liners(paragraphs) filler_metric, filler_hits = check_filler(paragraphs, full_text) triad_metric, triad_hits = check_triads(paragraphs, full_text) heading_metric = check_headings(headings) semantics = engine.analyze_paragraphs(paragraphs) signatures: List[frozenset] = [] for i, sem in enumerate(semantics): struct_hits = set() if em_dash_hits[i]: struct_hits.add("em_dash") if one_liner_hits[i]: struct_hits.add("one_liner") if filler_hits[i]: struct_hits.add("filler") if triad_hits[i]: struct_hits.add("triad") signatures.append(frozenset(struct_hits | sem.patterns)) semantic_hits: Dict[str, List[bool]] = {pid: [] for pid in SEMANTIC_IDS} for sem in semantics: for pid in SEMANTIC_IDS: semantic_hits[pid].append(pid in sem.patterns) concreteness_values = [sem.concreteness for sem in semantics] metrics = [ em_dash_metric, pattern_metric_from_hits("antithesis", semantic_hits["antithesis"]), one_liner_metric, heading_metric, filler_metric, pattern_metric_from_hits("aphorism", semantic_hits["aphorism"]), triad_metric, concreteness_trend_metric(concreteness_values), pattern_metric_from_hits("vague_attribution", semantic_hits["vague_attribution"]), pattern_metric_from_hits("pseudo_sincerity", semantic_hits["pseudo_sincerity"]), ] valid_metrics = [ m for m in metrics if not m.detail.startswith("недостаточно") and not m.detail.startswith("заголовков не найдено") ] result = compute_score(valid_metrics) return metrics, result def verdict(tier: str) -> str: return TIER_LABELS[tier] def print_report(metrics: Sequence[Metric], result: ScoreResult) -> None: print("=" * 120) print(f"ИТОГОВЫЙ ИНДЕКС ИИ-СЛОПА: {result.score:.2f} / 1.00 [{result.tier}]") print(result.label) print("=" * 120) for m in metrics: bar = "#" * round(m.score * 20) print(f"[{m.score:>4.2f}] {bar:<20} {m.title}:", end=" ") print(f"{m.detail}") print("=" * 120) print("Внимание: возможны ошибки и ложные срабатывания! Используйте этот чек-лист для редактуры, а не как приговор.") print("=" * 120) def main() -> None: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("path", help="путь к файлу со статьей (markdown/текст), или '-' для stdin") parser.add_argument("--model", default="hf.co/t-tech/T-lite-it-2.1-GGUF:Q8_0", help="имя модели в Ollama") parser.add_argument("--host", default="http://localhost:11434", help="адрес сервера Ollama") args = parser.parse_args() text = sys.stdin.read() if args.path == "-" else open(args.path, "r", encoding="utf-8").read() engine = OllamaEngine(model=args.model, host=args.host, batch_size=6, temperature=0.0) metrics, result = analyze(text, engine) print_report(metrics, result) if __name__ == "__main__": main()
По понятной причине тестировать получившийся скрипт на тексте из этой статьи не имеет смысла, поэтому я взял одну из своих сложных статей и протестировал на ней:
./ai_slop_detector.py article.md ======================================================================================================================== ИТОГОВЫЙ ИНДЕКС ИИ-СЛОПА: 0.14 / 1.00 [human] Похоже на текст, написанный либо вычитанный и доработанный человеком. ======================================================================================================================== [0.17] ### 1. Тире как универсальный знак препинания: тире в 24 из 99 абзацев (24%), 0.82 на 1000 символов [0.00] 2. Антитеза не X, а Y: встречается в 2 из 99 абзацев (2%) [0.57] ########### 3. Абзацы-обрывы в одну строку: однострочных абзацев-обрывов: 19 из 99 (19%) [0.00] 4. Заголовки-клиффхэнгеры: заголовков не найдено [0.04] # 5. Слова-связки без смысловой нагрузки: слов-связок без смысла: 1 (0.17 на 1000 слов) [0.29] ###### 6. Афористичные концовки: встречается в 12 из 99 абзацев (12%) [0.17] ### 7. Искусственная симметрия и триады: перечислений X, Y и Z: 3, списков из трех пунктов: 3 из 11 [0.00] 8. Затухание конкретики к концу текста: тренд конкретности по 83 абзацам: наклон +0.23 [0.03] # 9. Неопределенная атрибуция и раздутая значимость: встречается в 3 из 99 абзацев (3%) [0.00] 10. Псевдо-искренность: встречается в 1 из 99 абзацев (1%) ======================================================================================================================== Внимание: возможны ошибки и ложные срабатывания! Используйте этот чек-лист для редактуры, а не как приговор. ========================================================================================================================
Остальные мои статьи колеблются в пределах 0.18-0.23. Пример использования на случайном блоке текста одного из произведений Виктора Пелевина:
./ai_slop_detector.py pelevin.md ======================================================================================================================== ИТОГОВЫЙ ИНДЕКС ИИ-СЛОПА: 0.21 / 1.00 [human] Похоже на текст, написанный либо вычитанный и доработанный человеком. ======================================================================================================================== [0.00] 1. Тире как универсальный знак препинания: тире в 0 из 10 абзацев (0%), 0.00 на 1000 символов [0.51] ########## 2. Антитеза не X, а Y: встречается в 2 из 10 абзацев (20%) [0.00] 3. Абзацы-обрывы в одну строку: однострочных абзацев-обрывов: 0 из 10 (0%) [0.00] 4. Заголовки-клиффхэнгеры: заголовков не найдено [0.32] ###### 5. Слова-связки без смысловой нагрузки: слов-связок без смысла: 1 (1.27 на 1000 слов) [0.80] ################ 6. Афористичные концовки: встречается в 3 из 10 абзацев (30%) [0.40] ######## 7. Искусственная симметрия и триады: перечислений X, Y и Z: 2, списков из трех пунктов: 0 из 0 [0.00] 8. Затухание конкретики к концу текста: тренд конкретности по 10 абзацам: наклон +0.55 [0.51] ########## 9. Неопределенная атрибуция и раздутая значимость: встречается в 2 из 10 абзацев (20%) [0.23] ##### 10. Псевдо-искренность: встречается в 1 из 10 абзацев (10%) ======================================================================================================================== Внимание: возможны ошибки и ложные срабатывания! Используйте этот чек-лист для редактуры, а не как приговор. ========================================================================================================================
Пример использования на тексте случайной статьи из одного корпоративного блога:
./ai_slop_detector.py corp.md ======================================================================================================================== ИТОГОВЫЙ ИНДЕКС ИИ-СЛОПА: 0.55 / 1.00 [mixed] Черновик от модели доработан частично, либо это случайное совпадение по одному стилистическому признаку. ======================================================================================================================== [0.95] ################### 1. Тире как универсальный знак препинания: тире в 49 из 73 абзацев (67%), 3.96 на 1000 символов [0.14] ### 2. Антитеза не X, а Y: встречается в 5 из 73 абзацев (7%) [0.46] ######### 3. Абзацы-обрывы в одну строку: однострочных абзацев-обрывов: 12 из 73 (16%) [0.00] 4. Заголовки-клиффхэнгеры: заголовков не найдено [0.58] ############ 5. Слова-связки без смысловой нагрузки: слов-связок без смысла: 6 (2.33 на 1000 слов) [0.45] ######### 6. Афористичные концовки: встречается в 13 из 73 абзацев (18%) [0.14] ### 7. Искусственная симметрия и триады: перечислений X, Y и Z: 5, списков из трех пунктов: 0 из 0 [0.53] ########### 8. Затухание конкретики к концу текста: тренд конкретности по 71 абзацам: наклон -0.53 [0.02] 9. Неопределенная атрибуция и раздутая значимость: встречается в 2 из 73 абзацев (3%) [0.00] 10. Псевдо-искренность: встречается в 0 из 73 абзацев (0%) ======================================================================================================================== Внимание: возможны ошибки и ложные срабатывания! Используйте этот чек-лист для редактуры, а не как приговор. ========================================================================================================================
И результирующий пример эталонного ИИ-слопа, сгененированного ChatGPT:
./ai_slop_detector.py slop.md ======================================================================================================================== ИТОГОВЫЙ ИНДЕКС ИИ-СЛОПА: 0.94 / 1.00 [ai] Высокая плотность типичных ИИ-паттернов: текст опубликован без редактуры после генерации. ======================================================================================================================== [0.13] ### 1. Тире как универсальный знак препинания: тире в 2 из 9 абзацев (22%), 1.20 на 1000 символов [0.58] ############ 2. Антитеза не X, а Y: встречается в 2 из 9 абзацев (22%) [0.24] ##### 3. Абзацы-обрывы в одну строку: однострочных абзацев-обрывов: 1 из 9 (11%) [1.00] #################### 4. Заголовки-клиффхэнгеры: 3 заголовков, разброс длины ~1.6 слов, драматизирующих: 3 (100%) [1.00] #################### 5. Слова-связки без смысловой нагрузки: слов-связок без смысла: 11 (48.46 на 1000 слов) [0.90] ################## 6. Афористичные концовки: встречается в 3 из 9 абзацев (33%) [0.44] ######### 7. Искусственная симметрия и триады: перечислений X, Y и Z: 2, списков из трех пунктов: 0 из 0 [0.67] ############# 8. Затухание конкретики к концу текста: тренд конкретности по 9 абзацам: наклон -0.67 [0.26] ##### 9. Неопределенная атрибуция и раздутая значимость: встречается в 1 из 9 абзацев (11%) [0.26] ##### 10. Псевдо-искренность: встречается в 1 из 9 абзацев (11%) ======================================================================================================================== Внимание: возможны ошибки и ложные срабатывания! Используйте этот чек-лист для редактуры, а не как приговор. ========================================================================================================================
В заключение хочу напомнить, что это не четкий классификатор “ИИ / человек”. Скрипт, как и любая модель, может ошибаться. Его можно обмануть косметическими правками, не трогая содержание, но даже такие правки заметно усилят восприятие текста, чего мы и добиваемся.
Скрипт написан не для того чтобы обличать и клеймить авторов в использовании ИИ, а для того чтобы помочь им развивать свои ИИ-черновики до полноценного материала, поэтому пробуя этот скрипт на чужих текстах относитесь к найденному по принципу “ну было и было”. Давайте вместе попробуем развернуть текущую ситуацию с засильем необработанного ИИ-слопа в конструктивное русло.
References
How AI-generated prose diverges from human writing and why it matters, Reuters Institute (2025)
Homogenizing effect of large language models (LLMs) on creative diversity, ScienceDirect (2025)
How RLHF Preference Model Tuning Works (And How Things May Go Wrong), AssemblyAI (2023)
Interpreting Black Box Reward Models, OpenAI Alignment, OpenAI (2026)
What is perplexity & burstiness for AI detection, GPTZero (2023)
Human Perception of LLM-generated Text Content in Social Media Environments, arXiv (2024)
Комментарии (24)

Tetragramaton
25.07.2026 20:47Рад что моя статья с подвинула кого то взять молоток в руки!
Только скрипт периодически придеться переобучать и расширять ибо будет потеря актуальности. Тут как с антивирусом

ToxaBes Автор
25.07.2026 20:47Рад что моя статья с подвинула кого то взять молоток в руки!
Меня попросили написать статью после комментариев на эту тему. Подобных статьей было много, но это первая, где не обвиняют авторов в использовании ИИ и дают им рабочий инструмент улучшения своих материалов.
Только скрипт периодически придеться переобучать и расширять ибо будет потеря актуальности. Тут как с антивирусом
Не придется. Скрипт проверяет смысл нейронной сетью, дообучать ее не нужно, а маркеры-слова не меняются с момента появления GPT-2, ведь датасеты для обучения в основе содержат те же самые размеченные корпусы текста.
Новые обучающие данные в датасетах не смогут подвинуть маркеры из базового огромного корпуса текста. Чтобы скрипт перестал работать нужны модели обученные на данных собранных с нуля и размеченных людьми заново (и иначе). Это потребует сотен миллионов долларов.

Tetragramaton
25.07.2026 20:47Тогда извиняюсь, моя статья вышла вчера и имела тот же посыл что и вас и ту же идею но другую реализацию, с тем же видом оценки но с другим видом необходимого детектора без хардкода который не обходиться стилем. Советую тоже глянуть.

kryak
25.07.2026 20:47Нейрослоп про нейрослоп? Хехе

Tetragramaton
25.07.2026 20:47
по содержанию не слоп

Tetragramaton
25.07.2026 20:47или все таки слоп - надо идти калибровать оценку , у меня в плагине человеческая пустота сбавляет меньше чем жестяная. Подкалибрую.
Выхолощенность означает: текст выглядит связным, профессиональным и остаётся по теме, но из него убраны несущие детали, ради которых его читают.
Признаки:
сказано что важно, но не как именно;
описано событие, но убран причинный механизм;
точные условия и шаги заменены на «различные факторы»;
отсутствуют ограничения, исключения и сценарии отказа;
проверяемое действие заменено субъективным советом.
Главный тест:
Может ли читатель после текста сделать или проверить что-то операционно новое?
Если нет, а текст лишь сообщает, что предмет «существует, важен и непрост», это выхолощенность.
От пустоты отличается так:
Пустота: почти нет проверяемого содержания, одни общие слова.
Выхолощенность: содержание и структура внешне есть, но текст не выполняет собственное объяснительное или практическое обещание.

Tetragramaton
25.07.2026 20:47Я в принципе сейчас понял в чем была ошибка моей статьи благодаря этой. Я запихнул свой инструмент по проверки смысла в класс ИИ детекторов семантически. Хотя я совершенно в другой весовой категории. Для людей разница с виду не очевидна, спасибо

kryak
25.07.2026 20:47НЕ думали о развитии инструмента в аналог спонсор блока, но для статей?
ВОт семантический анализ, n проверок выявили слоп, m пользователей указали статью как слоп. Статья помечается как слоп и просто скрывается иили как-то помечается в ленте пользователя.
ЕСть отдельный плагин для скрытия ии статей хабра, но он по тегам и только на хром.

Tetragramaton
25.07.2026 20:47Это чревато. Спонсор блок работает на Ютубе для обрезков видео. То что ты описываешь выше это уже полноценная цензура. Если плагин станет большим и меня прижмут как Павла, то это сразу поставит под удар большую часть интернета .

Tetragramaton
25.07.2026 20:47Моя личная оценка после оценки того что даёт инструмент, посыл настоящий, скрипт настоящий, а вот с наполнением посередине просто наполнение. Ну если смотреть целиком то статья полезна. Оценка сверху верна (моего личному мнению)

n0isy
25.07.2026 20:47Вашу статью авторы добавят в закладки. Сами догадаетесь почему? /s

kryak
25.07.2026 20:47Вспоминается как после статьи про плагин на опечатки в нейрослопе все редакторы хабра начали его использовать.
А когда их макнули в это, влепили капчу.

ToxaBes Автор
25.07.2026 20:47Именно для этого я ее и писал. Нельзя победить ИИ-слоп на ресурсе, который его опосредовано монетизирует. Можно лишь придать этому человеческий вид.

Zantiago
25.07.2026 20:47это кстати произошло около года назад
до этого достаточно было повелеть Чатгпт: «напиши по-человечески» - давал совершенно нормальный человеческий текст, даже с некоторым персональным «акцентом». Потом началось все вот эти вышеперечисленные слоп паттерны которые даже сейчас с трудом можно забороть. Причем практически в одно и то же время в Чатгпт, Клавдии и Гемини - как будто им повелели таким образом добавлять ии-ватермарк

Revertis
25.07.2026 20:47это кстати произошло около года назад
Так как раз тогда разработчики моделей нагенерировали себе текстов для обучения новых моделей, и всё.

Oeaoo
25.07.2026 20:47Шило на мыло. Или диктат стиля нейронки или диктат некого частного видения, возведенного в должное и универсальное. Выбираю ни то и ни другое.
Kot_na_klaviature
Авторские тексты должны писать авторы от начала и до конца. Людям интересны люди с их опытом, недостатками и болью. То что генерится за секунду не стоит ничего и никому не интересно.
ToxaBes Автор
А гуглом для подготовки материала можно пользоваться? А почему? Чем это отличается от получения выжимки от ИИ и доработки ее до качественной статьи?
Я согласен с тем что читать чистый ИИ-слоп “не интересно”, а вот по поводу “ничего не стоит” поинтересуйтесь стоимостью корпоративных блогов.
Компании платят немаленькие деньги за возможность публиковать ИИ-слоп по расписанию под своим именем для охвата аудитории, администрация, в свою очередь, обеспечивает этот самый охват не давая аудитории кнопку “игнорировать статьи данного автора”. В итоге, все счастливы, ну кроме читателей. Об этом я и пишу, на появление кнопки мы повлиять не можем, но можем попробовать предложить как дорабатывать слоп до хорошего уровня.
MountainGoat
Почему не можем? Можем! Хоткеем Ctrl+W.
kryak
Так если гуано подкрасить, конфеткой оно не станет
Проблема слопа не в том, что он выглядит криво, а в том, что за ним ничего нет. Это копирайт в самом худшем проявлении, когда "автор" не просто не понимает, о чем пишет, он даже не принимает заметного участия в написании.
Насрал 20 нейростатей за рабочий день, прогнал через корректор чтобы не бросалось в глаза и навалил на сайт.
А ты потом вместо беглого выявления слопа для скипа вынужден это прочитать, чтобы обнаружить отсутствие смысла в этой горе текста.
Revertis
Пора начинать заставлять нейронки делать проверку перед прочтением :)
Kot_na_klaviature
Много чем отличается. Искать - это работа, а нагенерить нейрослопа ничего не стоит. Поэтому и не пользуются Гуглом, а идут в нейросеть. А там где банят прямые генерации рерайтят, чтобы скрыть нейро происхождение. Типа сам писал и думал.