# Агент съедает 88 токенов на «думание» и возвращает пустую строку — разбираем Reasoning Lock
Каноническая страница: https://plainews.ru/posts/reasoning-lock-llm-empty-response-fix
Опубликовано: 2026-09-14T07:01:20.421Z

Реальный баг в проде: LLM тратит все токены на reasoning и возвращает пустой ответ. Разбор причин и пошаговое лечение для n8n, LangChain, OpenRouter.
Продакшн-агент принимает сообщения, генерация закрывается со статусом stop, в amoCRM тишина. Нода отправки падает с non-empty string, клиент не получает ответа, заказчик злится. Именно так выглядит Reasoning Lock — баг, который не бросается в глаза, потому что всё «успешно».

Ниже — механика бага, пошаговая диагностика по слоям и конкретные правки, которые убирают пустые ответы.

## Что происходит внутри модели

Reasoning-модели (DeepSeek V4 Pro, Qwen3, Kimi K2, GPT-5-nano и другие) перед финальным ответом генерируют внутренние «мысли» — reasoning_content. Это отдельный поток токенов, невидимый в обычном выводе.

Проблема возникает, когда лимит max_tokens (или его аналог max_completion_tokens) выставлен слишком низко — или не выставлен вообще, и шлюз применяет внутренний дефолт. Модель тратит весь бюджет на reasoning, до генерации видимого content токены заканчиваются, и поле text возвращается пустым. finish_reason при этом честно пишет stop или length — в зависимости от провайдера. Коннектор передаёт пустоту дальше как есть.

В разобранном кейсе: 95 completion-токенов всего, из них 88 ушли в reasoning, на content осталось 7 — и модель вернула "".

## Диагностика: смотрим по слоям

Баг прячется между тремя слоями. Проверять нужно именно в таком порядке — снизу вверх.

**Слой 1 — нода отправки.** Если в логах n8n видите Invalid or missing message text parameter или аналогичный non-empty string — это симптом, не причина. Нода честно валидирует то, что получила.

**Слой 2 — выход агента.** Открываете OUTPUT ноды агента в execution. Смотрите на три поля:

```json
{
  "text": "",
  "finish_reason": "stop",
  "completionTokens": 95,
  "promptTokens": 6677
}
```

Пустой text при stop и маленьком completionTokens — почти гарантированный Reasoning Lock. Запомните число completion-токенов.

**Слой 3 — лог провайдера.** Идёте в OpenRouter (или другой шлюз) и открываете карточку конкретной генерации. Там будет разбивка: сколько токенов ушло в reasoning, сколько в content. Если reasoning_tokens ≈ completionTokens, а content_tokens близко к нулю — диагноз подтверждён.

> **К сведению.** В OpenRouter карточка генерации доступна в разделе Activity → конкретный запрос. Там же видно модель, латентность и точную разбивку токенов.

## Три способа починить

### 1. Поднять max_tokens до разумного значения

Самое прямое решение. В вызове модели явно задайте лимит, достаточный для reasoning + ответа. Для агентов с промптом 6–7k токенов минимальный рабочий порог — 2048, комфортный — 4096–8192.

В n8n это параметр в ноде LangChain или HTTP-запросе к OpenRouter:

```json
{
  "model": "deepseek/deepseek-v4-pro-20260423",
  "max_tokens": 4096,
  "messages": [...]
}
```

Если используете legacy-параметр max_tokens вместо max_completion_tokens — у некоторых шлюзов они считаются по-разному. Для reasoning-моделей предпочтительнее max_completion_tokens.

### 2. Ограничить reasoning effort

Большинство провайдеров поддерживают параметр reasoning_effort (или аналог). Значение low или minimal сокращает бюджет на внутренние размышления и оставляет больше токенов на видимый ответ.

```json
{
  "model": "deepseek/deepseek-v4-pro-20260423",
  "max_tokens": 4096,
  "reasoning_effort": "low",
  "messages": [...]
}
```

Для диалоговых агентов, где нужна скорость и короткие реплики, low обычно достаточно. Для аналитических задач — medium.

### 3. Fallback на reasoning_content

Если пустой content всё же прилетел — не роняйте воркфлоу, а попробуйте достать ответ из reasoning_content. Некоторые модели (Kimi K2, Qwen3) при исчерпании токенов кладут весь текст именно туда.

В n8n это можно сделать через Function-ноду перед отправкой:

```javascript
const text = $input.item.json.text;
const reasoning = $input.item.json.reasoning_content;

if (text && text.trim() !== '') {
  return { text };
}

if (reasoning && reasoning.trim() !== '') {
  // Опционально: обрезать до первого абзаца
  return { text: reasoning.split('\n\n')[0] };
}

// Если оба пустые — явная ошибка для алертинга
throw new Error('LLM returned empty content and empty reasoning_content');
```

> **Важно.** Fallback на reasoning_content — временная мера. Модель не предназначала эти токены для пользователя: там могут быть внутренние рассуждения, незаконченные мысли или служебные метки. Используйте только как страховку, пока не поднят `max_tokens`.

## Почему промпт стал частью проблемы

В разобранном кейсе системный промпт разросся до 6677 токенов — инструкции по оплате, VIP-сценарии, уточнения формулировок после каждого спорного диалога. Плотность директив («КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО», «СТРОГО этот текст») выросла до максимума.

Большой и жёсткий промпт создаёт двойное давление: модель тратит больше токенов на reasoning, чтобы «разобраться» в противоречивых инструкциях, и одновременно пытается точно выполнить все запреты. При низком max_tokens это прямой путь к Reasoning Lock.

Практическое правило: если промпт превысил 4k токенов, max_tokens для completion должен быть не меньше 2048. При 6k+ промпте — минимум 4096.

## Где ещё ломается

- **Резервная модель с другим поведением.** Если основная модель упала и включился fallback (например, с DeepSeek на Qwen3 Flash) — у резервной может быть другой дефолт по reasoning. Параметры нужно задавать явно для каждой модели в цепочке.
- **Шлюз с внутренним лимитом.** Некоторые OpenAI-совместимые шлюзы применяют свой max_output_tokens, который перекрывает ваш max_tokens. Проверяйте документацию провайдера и смотрите в логах фактический лимит, а не тот, что вы передали.
- **Нет алертинга на пустой ответ.** Без явной проверки text !== '' перед отправкой воркфлоу молча падает, клиент не получает ничего, а вы узнаёте об этом от заказчика через два дня.

## Что попробовать дальше

Добавить в воркфлоу явную проверку длины ответа перед нодой отправки и счётчик пустых генераций в отдельный лог или метрику. Семь дней без пустых ответов — нормальный KPI для такого агента, но без мониторинга вы не узнаете, когда счётчик снова пойдёт вверх.

Если промпт продолжает расти — рассмотрите разбивку на несколько специализированных агентов вместо одного с мегапромптом. Меньше контекста на вход = меньше reasoning на «разбор» инструкций = стабильнее вывод.

---

## FAQ

### Почему finish_reason возвращает «stop», если ответ пустой?

stop означает, что модель завершила генерацию штатно — не по обрыву, не по ошибке. Если весь бюджет ушёл в reasoning до генерации видимого текста, модель считает задачу выполненной и закрывает запрос с stop. Это поведение на стороне провайдера, не баг коннектора.

### Какое значение max_tokens ставить для диалогового агента?

Зависит от длины промпта. Ориентир: промпт до 4k токенов — max_tokens: 2048; промпт 4–8k — max_tokens: 4096; если агент пишет длинные структурированные ответы — 8192. Начните с 4096 и смотрите на фактический completionTokens в логах.

### Помогает ли смена модели на не-reasoning?

Да, если задача не требует сложных рассуждений. Классические instruction-tuned модели без встроенного reasoning не тратят токены на внутренние мысли. Но для агентов с многошаговой логикой reasoning-модели обычно дают лучший результат — лучше настроить параметры, чем менять модель.

### Как поймать баг до того, как его заметит клиент?

Добавьте Function-ноду сразу после агента с проверкой if (!text || text.trim() === '') throw new Error('empty_llm_response'). Это переведёт молчание в явную ошибку execution, которую видно в логах n8n и можно алертить.

### Влияет ли размер системного промпта на частоту бага?

Напрямую. Чем больше промпт и чем больше в нём противоречивых директив, тем больше токенов модель тратит на reasoning перед ответом. При фиксированном max_tokens это сокращает бюджет на видимый текст. Рост промпта с 3k до 6k токенов при том же лимите completion — типичный триггер Reasoning Lock.

## Источники

- Habr AI: Reasoning Lock: ИИ‑агент в проде тратит токены и молчит — живой разбор бага
