ConstructiCat Logo
CodeBust.
Browse section ▾

Галлюцинированный API.

Сгенерированный ИИ код, который вызывает функции, методы, параметры, ключи конфигурации или пакеты, выглядящие правдоподобно, но не существующие в той версии библиотеки, от которой вы зависите.

##Signs and Symptoms

Ревьюер замечает галлюцинированный API, когда код читается гладко и "выглядит как" идиоматичное использование библиотеки, однако конкретный вызов, опцию или импорт невозможно найти в реальной поверхности этой библиотеки. Характерные признаки:

  • Метод или опция, имя которых слишком удобно — они делают ровно то, о чём просил запрос, с именем, смешивающим два реальных API (например, findLastWhere, includesAll, parseDateSafe).
  • Выдуманные параметры у реальной функции (опасный вариант — он часто компилируется и падает лишь во время выполнения или молча игнорирует опцию).
  • import пакета, которого нет в package.json / requirements.txt, или именованный импорт, который модуль никогда не экспортирует.
  • Формы API, смешивающие версии: сигнатура v2, вызванная на клиенте v5, или метод, удалённый/переименованный несколько релизов назад.
  • Уверенные встроенные комментарии, утверждающие, что вызов корректен ("// возвращает возмещённый платёж").
// Сгенерировано ИИ — гладко, правдоподобно и неверно
import { formatRelative } from 'date-fns';

// `roundingMethod` выдуман — date-fns не предоставляет такой опции, она молча игнорируется
const label = formatRelative(date, new Date(), { roundingMethod: 'floor' });

// `findLastWhere` не существует у Array — TypeError во время выполнения
const lastActive = items.findLastWhere(i => i.active);

// Смешанный вызов SDK: реальная форма Stripe — stripe.refunds.create({ charge })
await stripe.charges.refund(chargeId, { amount: 500 });

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

##Reasons for the Problem

Почему модели это порождают

  • Правдоподобие следующего токена, а не поиск. LLM предсказывает статистически наиболее вероятное продолжение, что фактически является усреднением всех похожих API, которые она видела. Это усреднение часто оказывается методом, который должен бы существовать — модель выдаёт "удобное имя", а не настоящее. Исследования рекомендаций API показывают, что от 58,1% до 84,1% рекомендованных API не существуют в указанном пакете, и преобладающая ошибка — несуществующие имена методов (arXiv 2404.00971, ACM TOSEM 2025).
  • Смешение версий. Обучающие корпуса смешивают множество версий библиотеки, поэтому модель сплавляет сигнатуры v2 и v5 в одну, которая не соответствует ни одной из них. Именно такие "галлюцинации, конфликтующие со знанием" (например, несуществующие параметры) проскальзывают мимо линтеров и падают во время выполнения.
  • Устаревание из-за порога обучения. Модель уверенно использует API, переименованные, объявленные устаревшими или удалённые после её порога обучения, и хватается за то, что чаще всего встречалось в корпусе — а это, как отмечает OX Security, означает, что рекомендуются более старые, иногда уязвимые, версии пакетов (отчёт OX, октябрь 2025).
  • Отсутствие контекста репозитория/зависимостей. Без вашего package.json или исходного кода реального модуля модель выдумывает вспомогательные функции, которые "на ощупь" кажутся частью вашего стека.
  • Подхалимство / стремление ответить. Ассистент почти никогда не говорит "я не уверен, что этот метод существует" — он выдаёт уверенный, выглядящий работоспособным код, что снижает настороженность ревьюера.
  • Детерминизм делает это эксплуатируемым. Галлюцинация пакетов — не случайный шум: на 16 моделях и 2,23 млн генераций 19,7% рекомендованных пакетов оказались вымышленными (205 474 уникальных имени), а 58% галлюцинаций повторялись в пределах 10 повторных запросов (USENIX Security 2025; обзор SecurityWeek).

Почему это вредит

  • Корректность. Выдуманные параметры и молча игнорируемые опции порождают неверное поведение на путях кода, которые тесты редко покрывают; сбой проявляется в продакшене, а не на этапе компиляции.
  • Безопасность / цепочка поставок. Галлюцинированное имя пакета — это цель для регистрации: злоумышленники публикуют вредоносное ПО под предсказанным именем, и следующий разработчик, принявший подсказку, его устанавливает — атака slopsquatting (термин ввёл Seth Larson из PSF). Вариант с устареванием молча возвращает устаревшие или содержащие CVE API.
  • Нагрузка на ревью. Правдоподобный код перекладывает на ревьюеров бремя проверки каждого незнакомого вызова по документации; беглый, гладкий текст делает такую проверку менее вероятной.
  • Накопление техдолга. Разработчики часто вставляют "почти работающую" галлюцинированную заготовку и обкладывают её заплатками, вместо того чтобы исправить корневой вызов — подпитывая более широкий тренд эпохи ИИ: рост копипасты и спад рефакторинга (GitClear 2025: клонов больше примерно в 8×, доля перемещённых строк, связанных с рефакторингом, упала с 25% до <10%). Этот паттерн теперь занесён в каталог специфичных для ИИ запахов кода (arXiv 2509.20491).

##Treatment

Процесс и тактики промптинга

  • Заземлите модель на реальные поверхности. Вставьте в контекст настоящие заглушки типов, нужную страницу документации или исходный код установленной версии, либо используйте инструмент извлечения документации (в стиле Context7 / RAG). Сообщите ей точные версии зависимостей из вашего lock-файла.
  • Требуйте ссылок-обоснований. Просите модель называть официальную запись в документации или сигнатуру типа для каждого используемого стороннего вызова. Практическое правило: каждый вызов метода сторонней библиотеки должен быть прослежен до записи в её документации до одобрения PR.
  • Заставьте это запускаться, в цикле. Требуйте, чтобы tsc / mypy / pylint / сборка и набор тестов проходили, и пусть агент действительно выполняет npm install / pip install, чтобы галлюцинированный пакет падал сразу, а не доходил до ревью.
  • Просите переиспользовать, а не выдумывать. Подайте ей результат grep по существующему модулю и предписывайте вызывать уже имеющиеся вспомогательные функции (reuse the helpers in utils/http.ts), а не создавать новые — это также противодействует смежному запаху дублирования переизобретённых вспомогательных функций.
  • Поставьте зависимости на контроль. Используйте lock-файл, белый список разрешённых к установке пакетов и сканер цепочки поставок (Socket/Snyk) до добавления любого нового пакета, чтобы имена-slopsquat не могли просочиться.

Рефакторинг кода

Замените выдуманный вызов на проверенный настоящий. Если вам действительно нужно удобство, которое вообразила модель, реализуйте его один раз поверх реального API через обёртку Extract Function (Извлечение функции), вместо того чтобы рассеивать поддельный вызов.

// До — галлюцинированный параметр + смешанная форма SDK
async function refundLast(chargeId: string) {
  // `stripe.charges.refund` и эта форма опций не существуют
  return stripe.charges.refund(chargeId, { amount: 500, reason: 'requested' });
}

// После — проверено по типам/документации установленного Stripe SDK,
// а "удобство" обёрнуто один раз, чтобы реальный API не повторялся
async function refundCharge(chargeId: string, amountCents: number) {
  return stripe.refunds.create({
    charge: chargeId,
    amount: amountCents,
    reason: 'requested_by_customer',
  });
}

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

Ограничения. Проверяющие типы и резолверы ловят разрешимое подмножество (типизированные объекты, неразрешённые импорты). Динамический/нетипизированный остаток — выдуманные опции на any, строковые ключи конфигурации, поля REST-нагрузки — не имеет надёжного автоматического детектора и должен проверяться человеком по реальной документации.

##Detected by

  • eslint-plugin-import import/no-unresolvedНеразрешённый импорт
  • eslint-plugin-import import/namedНесуществующий именованный экспорт
  • typescript-eslint @typescript-eslint/no-deprecatedИспользование устаревшего (реального, но неактуального) API
  • Pylint no-member (E1101)Обращение к несуществующему члену
  • mypy attr-definedАтрибут/метод не определён для типа