---
title: "Галлюцинированный API"
type: "ai-smell"
slug: "hallucinated-api"
url: "http://localhost:3000/ru/ai-smells/hallucinated-api.md"
category: "Корректность"
description: "Сгенерированный ИИ код, который вызывает функции, методы, параметры, ключи конфигурации или пакеты, выглядящие правдоподобно, но не существующие в той версии библиотеки, от которой вы зависите."
---
# Галлюцинированный API

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

## Signs and Symptoms

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

* Метод или опция, имя которых _слишком удобно_ — они делают ровно то, о чём просил запрос, с именем, смешивающим два реальных API (например, `findLastWhere`, `includesAll`, `parseDateSafe`).
* Выдуманные **параметры** у реальной функции (опасный вариант — он часто компилируется и падает лишь во время выполнения или молча игнорирует опцию).
* `import` пакета, которого нет в `package.json` / `requirements.txt`, или именованный импорт, который модуль никогда не экспортирует.
* Формы API, смешивающие версии: сигнатура v2, вызванная на клиенте v5, или метод, удалённый/переименованный несколько релизов назад.
* Уверенные встроенные комментарии, утверждающие, что вызов корректен ("// возвращает возмещённый платёж").

```js
// Сгенерировано ИИ — гладко, правдоподобно и неверно
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](https://arxiv.org/pdf/2404.00971), [ACM TOSEM 2025](https://dl.acm.org/doi/pdf/10.1145/3728894)).
* **Смешение версий.** Обучающие корпуса смешивают множество версий библиотеки, поэтому модель сплавляет сигнатуры v2 и v5 в одну, которая не соответствует ни одной из них. Именно такие "галлюцинации, конфликтующие со знанием" (например, несуществующие параметры) **проскальзывают мимо линтеров и падают во время выполнения**.
* **Устаревание из-за порога обучения.** Модель уверенно использует API, переименованные, объявленные устаревшими или удалённые после её порога обучения, и хватается за то, что чаще всего встречалось в корпусе — а это, как отмечает OX Security, означает, что **рекомендуются более старые, иногда уязвимые, версии пакетов** ([отчёт OX, октябрь 2025](https://www.ox.security/blog/ai-code-security-common-threats-and-best-practices-for-securing-ai-generated-code/)).
* **Отсутствие контекста репозитория/зависимостей.** Без вашего `package.json` или исходного кода реального модуля модель выдумывает вспомогательные функции, которые "на ощупь" кажутся частью вашего стека.
* **Подхалимство / стремление ответить.** Ассистент почти никогда не говорит "я не уверен, что этот метод существует" — он выдаёт уверенный, выглядящий работоспособным код, что снижает настороженность ревьюера.
* **Детерминизм делает это эксплуатируемым.** Галлюцинация пакетов — не случайный шум: на 16 моделях и 2,23 млн генераций **19,7% рекомендованных пакетов оказались вымышленными (205 474 уникальных имени), а 58% галлюцинаций повторялись в пределах 10 повторных запросов** ([USENIX Security 2025; обзор SecurityWeek](https://www.securityweek.com/ai-hallucinations-create-a-new-software-supply-chain-threat/)).

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

* **Корректность.** Выдуманные параметры и молча игнорируемые опции порождают неверное поведение на путях кода, которые тесты редко покрывают; сбой проявляется в продакшене, а не на этапе компиляции.
* **Безопасность / цепочка поставок.** Галлюцинированное имя пакета — это цель для регистрации: злоумышленники публикуют вредоносное ПО под предсказанным именем, и следующий разработчик, принявший подсказку, его устанавливает — атака **slopsquatting** (термин ввёл Seth Larson из PSF). Вариант с устареванием молча возвращает устаревшие или содержащие CVE API.
* **Нагрузка на ревью.** Правдоподобный код перекладывает на ревьюеров бремя проверки каждого незнакомого вызова по документации; беглый, гладкий текст делает такую проверку _менее_ вероятной.
* **Накопление техдолга.** Разработчики часто вставляют "почти работающую" галлюцинированную заготовку и обкладывают её заплатками, вместо того чтобы исправить корневой вызов — подпитывая более широкий тренд эпохи ИИ: рост копипасты и спад рефакторинга ([GitClear 2025: клонов больше примерно в 8×, доля перемещённых строк, связанных с рефакторингом, упала с 25% до <10%](https://www.gitclear.com/ai%5Fassistant%5Fcode%5Fquality%5F2025%5Fresearch)). Этот паттерн теперь занесён в каталог специфичных для ИИ запахов кода ([arXiv 2509.20491](https://arxiv.org/abs/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** (Извлечение функции), вместо того чтобы рассеивать поддельный вызов.

```ts
// До — галлюцинированный параметр + смешанная форма 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` — Неразрешённый импорт (https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md)
- **eslint-plugin-import** `import/named` — Несуществующий именованный экспорт (https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/named.md)
- **typescript-eslint** `@typescript-eslint/no-deprecated` — Использование устаревшего (реального, но неактуального) API (https://typescript-eslint.io/rules/no-deprecated/)
- **Pylint** `no-member (E1101)` — Обращение к несуществующему члену (https://pylint.readthedocs.io/en/stable/user_guide/messages/error/no-member.html)
- **mypy** `attr-defined` — Атрибут/метод не определён для типа (https://mypy.readthedocs.io/en/stable/error_code_list.html#check-that-attribute-exists-attr-defined)
