В последнее время на ленту Хабра стало больно смотреть, ощущение песка в глазах от статей, которые узнаются с первого абзаца: гладкий, грамматически безупречный текст, который при этом физически неприятно дочитывать до конца. Особенно это касается корпоративных блогов, где материал выходит по редакционному календарю, а авторы явно прогоняют тему через модель и публикуют результат, даже толком не прочитав его перед публикацией. Кнопки “скрыть все статьи этого блога от греха подальше” или фильтра по признаку качества у Хабра до сих пор по понятным причинам нет, а модерация тут явно не ставит себе такую задачу как поддержание качества публикаций.
Хочу сразу уточнить: для меня сам факт использования нейросетей авторами при подготовке статьи не является негативным моментом. Желание авторов пользоваться таким плодом прогресса как современные большие языковые модели вполне объяснимо, ведь они значительно ускоряют создание черновика с правильной структурой повествования или стилем после перевода. Проблема начинается в тот момент, когда автор публикует этот черновик без единой правки. Разница между текстом подготовленным с помощью ИИ и неотредактированным ИИ-слопом видна невооруженным глазом.
Сети слишком хорошо пишут текст, настолько хорошо что у человека возникает Ощущение (с большой буквы О), что тут что-то не так. Это ощущение трудно сформулировать точнее, чем “песок в глазах”. Текст вроде бы написан гладко, факты на месте, да и структура логичная, а к третьему абзацу читать физически тяжело и хочется закрыть вкладку. Чаще всего это симптом не плохого содержания, а слишком узнаваемой формы, так называемого ИИ-акцента, на который наш мозг реагирует усталостью быстрее, чем можно было бы объяснить объемом материала. ИИ-акцент является определенным стилем написания текста и как любой стиль его можно описать как совокупность двух составляющих: лексической и структурной.
Лексическая составляющая заключается в том что у моделей есть набор слов, которые они используют статистически чаще людей. Для английского языка это подробно задокументировано: анализ более 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 python3from __future__ import annotationsimport argparseimport jsonimport reimport requestsimport statisticsimport sysfrom dataclasses import dataclass, fieldfrom typing import Dict, List, Optional, Sequence, Set, TupleSTRUCTURAL_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.0FULL_THRESHOLD = 0.90HIGH_THRESHOLD = 0.55MIN_HIGH_FOR_SYSTEMIC = 4BAND_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 tdef 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))@dataclassclass Metric: key: str title: str score: float detail: strdef 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), hitsdef 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), hitsdef 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), hitsdef 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), hitsdef 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)@dataclassclass ParagraphSemantics: patterns: Set[str] = field(default_factory=set) concreteness: Optional[int] = Noneclass LLMEngine: def analyze_paragraphs(self, paragraphs: Sequence[str]) -> List[ParagraphSemantics]: raise NotImplementedErrordef _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 Nonedef 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 Nonedef 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 Trueclass 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)@dataclassclass ScoreResult: score: float tier: str n_full: int n_high: int label: strdef 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_wdef 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, resultdef 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)
ссылка на оригинал статьи https://habr.com/ru/articles/1063010/