Модель предложила вызвать parseDateSafe() — метода с таким именем в библиотеке никогда не было, но звучало это так же уверенно, как всё остальное. Это и есть галлюцинация: модель выдаёт неверную информацию так же уверенно, как верную — несуществующий метод, выдуманную ссылку, факт, которого не было. Для продукт-инженера это не редкий баг, а базовое свойство, с которым нужно уметь работать каждый день.

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

Обязательно

Почему это происходит

Модель всегда выдаёт что-то, потому что её задача — продолжить текст. Если точного ответа в её «памяти» нет, она может и не сказать «не знаю» — а подставить наиболее правдоподобный по форме вариант. Признавать незнание современные модели умеют и делают это регулярно, но как на гарантию на это не полагаются: насколько охотно — зависит и от модели, и от того, как поставлен вопрос. Выдуманный метод parseDateSafe() выглядит ровно так, как должен выглядеть метод такой библиотеки, поэтому модель его уверенно «вспоминает», хотя его не существует.

Ключевая ловушка: уверенность тона не связана с правильностью. Модель одинаково уверенно звучит и когда воспроизводит реальный факт, и когда достраивает несуществующий. Тон — не сигнал истинности.

Насколько это часто. Точного числа нет и быть не может: доля выдумок зависит от задачи, модели и того, дали ли ей опору. Но порядки, которые стоит держать в голове, такие. На вопросах про популярное (стандартная библиотека языка, известный фреймворк, распространённый факт) — единицы процентов и меньше: тут модель воспроизводит то, что видела миллионы раз. На редком и узком (малоизвестная библиотека, внутренний инструмент, свежая версия) доля растёт до заметной части ответов, и это уже не «бывает», а «ожидается». Хуже всего с точными мелочами — номер версии, в которой появился флаг, точная подпись метода с редкими параметрами, ссылка на страницу документации: здесь выдумка скорее правило, чем исключение, потому что такие детали в данных встречались по одному разу и «сжались» неточно.

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

Где галлюцинации вероятнее всего

Риск не равномерный. Он резко растёт там, где данных было мало или их не могло быть:

  • Редкие и узкие API — малоизвестные библиотеки, экзотические флаги. Популярный код модель знает, редкий — достраивает.
  • Свежие версии и события — всё, что появилось после обучения модели. Она не знает, что изменилось, и отвечает по старому.
  • Точные факты — конкретные числа, даты, цитаты, ссылки, имена. Здесь особенно легко получить правдоподобную выдумку.
  • Ваш приватный контекст — внутренний код, документы, договорённости команды. Модель их не видела; если не дать в контекст, она их «домыслит».
  • Слишком общий запрос — чем меньше опоры в вопросе, тем больше модель достраивает сама.
популярный API данных много единицы процентов прочитать и запустить редкий флаг данных мало заметная часть открыть документацию свежая версия данных не было ответит по старому сверить с версией ваш код модель не видела домыслит своё дать в контекст точная цифра видела один раз скорее правило сверить с источником

Пять типов вопроса по убыванию данных у модели: слева сколько она видела, в середине что выдаёт, справа какая проверка нужна.

Самая опасная выдумка — про существующий метод

Выдуманное имя вроде parseDateSafe() — учебный пример, и в жизни он самый безобидный: код не соберётся, редактор подчеркнёт, агент сам увидит ошибку и исправится. Настоящие потери приносит другой вид, который не ловит ни компилятор, ни редактор: метод существует, а работает не так, как сказала модель.

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

  • Параметр значит другое. Модель уверенно пишет, что третий аргумент — срок ожидания в секундах, а на самом деле в миллисекундах. Код собирается, тесты проходят, а в проде срок ожидания в тысячу раз короче.
  • Флаг существует, но делает не то. Настройка называется похоже и включает соседнее поведение: код работает, но не то, что вы просили.
  • Поведение по умолчанию описано неверно. «По умолчанию повторяет три раза» — а библиотека не повторяет вовсе. Ошибка проявится в первый сетевой сбой.
  • Метод есть, но не в вашей версии. Он появился в следующей, и модель про это не знает.
  • Гарантии придуманы. «Этот метод потокобезопасен», «это атомарная операция» — самое дорогое из всего списка, потому что проверяется только под нагрузкой.

Общее у всех случаев одно: выход выглядит полностью корректным. Проверка чтением здесь не помогает — читая, вы сверяете код со своим представлением, а представление вам только что и сформировала модель.

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

Модель согласится с неверной посылкой

Есть вид выдумки, который не требует от модели ничего выдумывать: достаточно неверного допущения в вашем вопросе. Спросите «почему метод flush() закрывает соединение» — и получите стройное объяснение, почему он его закрывает. Хотя он не закрывает.

Механика ровно та же, что у остальных галлюцинаций: модель продолжает текст правдоподобно, а правдоподобное продолжение вопроса «почему X делает Y» — это объяснение, а не возражение. Признавать ложную посылку современные модели умеют и делают это тем чаще, чем очевиднее ошибка, но полагаться на это нельзя: чем специальнее тема и чем уверенней задан вопрос, тем охотнее модель подыграет.

Для работы с агентом это ловушка того же рода и встречается чаще выдуманных имён, потому что вопросы мы задаём уже с версией в голове: «как обойти ограничение библиотеки на N соединений», «почему наш кеш инвалидируется при каждом запросе», «почему эта настройка не работает в контейнере». Если предпосылка неверна, вы получите развёрнутый ответ на несуществующую проблему и потратите день на её решение.

Лечится это двумя привычками.

Спрашивать без готового ответа внутри. Вместо «почему метод X делает Y» — «что делает метод X» или «делает ли метод X — Y, и как это проверить». Формулировка без утверждения не даёт модели чему подыгрывать.

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

И полезная привычка при работе с агентом: явно разрешить ему возражать. Строка «если предпосылка в моём вопросе неверна, скажи об этом вместо ответа» заметно повышает шанс услышать «метод так не делает» вместо готового объяснения.

Как снижать риск

Полностью убрать галлюцинации нельзя — можно резко снизить их вероятность и цену:

  • Заземляйте на данные. Не спрашивайте «из головы» — дайте нужный кусок документации, кода, фактов прямо в запрос. Тогда модель опирается на них, а не на вероятности. Это же делают внешние источники через вызов инструментов (поиск, доступ к базе), а когда нужный кусок находят автоматически и подмешивают в запрос — это называют RAG, про него дальше в программе.
  • Просите проверяемое. Требуйте ссылки на источник, конкретные места в коде, обоснование. Выдумку проще поймать, когда её можно сверить.
  • Проверяйте выход. Скомпилировать, запустить тесты, свериться с реальной документацией — проверка результата ИИ должна быть не опцией, а частью процесса. Особенно это касается ревью AI-кода.
  • Разрешайте «не знаю». Явно скажите модели, что лучше признать незнание, чем выдумать. Это снижает уверенные ошибки.
  • Не доверяйте фактам без проверки. Числа, ссылки, API-имена, цитаты — всегда сверяйте с первоисточником, даже если звучит убедительно.
Дополнительно: при первом чтении можно пропустить

Глубже: как выглядит запрос с опоройрасширенное

«Слишком общий запрос» звучит как упрёк, пока не видно разницы. Она вот такая.

Плохо — модели не на что опереться, и она достраивает всё: и версию, и наши правила, и поведение библиотеки.

Напиши повторные попытки для HTTP-клиента.

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

Файл OrderClient.java (ниже) обращается к платёжному провайдеру.
Добавь повторные попытки: три попытки, задержка с ростом, повторять
только на 5xx и таймауты, не повторять на 4xx.
У нас уже подключён Resilience4j 2.2 (см. build.gradle ниже) — используй его,
новых зависимостей не добавляй. Ограничение: метод charge() не идемпотентен
на стороне провайдера без ключа идемпотентности, ключ есть в поле requestId.

<содержимое OrderClient.java>
<фрагмент build.gradle>
Файл internal/payment/client.go (ниже) обращается к платёжному провайдеру.
Добавь повторные попытки: три попытки, задержка с ростом, повторять
только на 5xx и таймауты, не повторять на 4xx.
У нас уже подключён github.com/cenkalti/backoff/v5 (см. go.mod ниже) — используй
его, новых зависимостей не добавляй. Ограничение: метод Charge не идемпотентен
на стороне провайдера без ключа идемпотентности, ключ есть в поле RequestID.

<содержимое internal/payment/client.go>
<фрагмент go.mod>
Файл src/payment/order-client.ts (ниже) обращается к платёжному провайдеру.
Добавь повторные попытки: три попытки, задержка с ростом, повторять
только на 5xx и таймауты, не повторять на 4xx.
У нас уже подключён p-retry 6 (см. package.json ниже) — используй его,
новых зависимостей не добавляй. Ограничение: метод charge() не идемпотентен
на стороне провайдера без ключа идемпотентности, ключ есть в поле requestId.

<содержимое src/payment/order-client.ts>
<фрагмент package.json>
Файл app/payment/order_client.py (ниже) обращается к платёжному провайдеру.
Добавь повторные попытки: три попытки, задержка с ростом, повторять
только на 5xx и таймауты, не повторять на 4xx.
У нас уже подключён tenacity 9 (см. pyproject.toml ниже) — используй его,
новых зависимостей не добавляй. Ограничение: метод charge() не идемпотентен
на стороне провайдера без ключа идемпотентности, ключ есть в поле request_id.

<содержимое app/payment/order_client.py>
<фрагмент pyproject.toml>

Что здесь сделано, по частям: дан настоящий код (модель не выдумывает, как у нас устроен клиент), названы версии и библиотека (не выдумает подходящую), перечислены требования как проверяемые условия (не выдумает, что считать ошибкой), и названо ограничение предметной области (не предложит повторять неидемпотентную операцию). Каждый пункт закрывает один источник выдумки.

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

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

Глубже: чем ловить выдумку машиннорасширенное

Совет «проверяйте выход» становится выполнимым, когда у него есть инструменты. Порядок от самого дешёвого к самому дорогому — и первые три не требуют вашего внимания вовсе.

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

Запуск. Тесты, которые уже есть в проекте, ловят изменение поведения; новый код без теста — запустить хотя бы руками на одном случае. Правдоподобная выдумка про поведение метода почти всегда проявляется на первом же запуске.

Ссылки и цитаты — открыть. Любая ссылка в ответе проверяется щелчком, любая цитата — поиском по документу. Это секунды, а выдуманные ссылки встречаются постоянно: они выглядят правильно, потому что собраны по образцу настоящих.

Версии — сверить с тем, что у вас. Не с ответом модели, а с файлом зависимостей и с документацией вашей версии. Кодовый агент делает это сам, если попросить посмотреть в файл, а не отвечать по памяти.

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

Заставить модель показать источник в коде. Не «объясни, как работает», а «покажи, в каком файле и в какой строке это происходит». Проверка сводится к открытию файла, и это самый быстрый способ отличить настоящее объяснение от правдоподобного.

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

Коротко

  • Галлюцинация — не сбой, а следствие устройства: модель предсказывает правдоподобное продолжение, а не проверяет истинность, и тон ответа не связан с правильностью.
  • Доля выдумок зависит от редкости темы: на популярном — единицы процентов, на редкой библиотеке и точных мелочах (версии, подписи, ссылки) выдумка ожидаема.
  • Опаснее выдуманного имени — правдоподобная неправда про существующий метод: другой смысл параметра, неверное поведение по умолчанию, придуманные гарантии; компилятор этого не ловит.
  • Модель охотно соглашается с неверной посылкой в вопросе: спрашивать без готового ответа внутри и проверять допущение первым.
  • Запрос с опорой даёт настоящий код, версии, требования как условия и ограничения предметной области: всё, чего в запросе нет, модель допишет сама.
  • Заземление на данные переносит выдумку, а не убирает: пересказ поданного документа тоже бывает неточным.
  • Машинная проверка по порядку: собрать, запустить, открыть каждую ссылку, сверить версии со своим файлом зависимостей, попросить показать место в коде.

Что почитать дальше

Галлюцинации усиливаются, когда модель работает без нужной опоры. Разберите, как этой опорой управляют: контекст (что дать модели), вызов инструментов (как дать доступ к настоящим данным) и проверка выхода ИИ (чем ловить выдумку до продукта).