Alejandro Rioja.
AI Agents

Почему ваш ИИ-агент продолжает давать сбои в продакшне (И как это исправить)

Alejandro Rioja
Alejandro Rioja
5 мин чтения
TL;DR

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

Бесплатная рассылка

Каждую среду. 28 400+ читателей. Никакой воды.

Содержание

Обновлено июнь 2026.

TL;DR: Большинство продакшн-отказов агентов происходит из-за пяти причин: хрупкие промпты, не обрабатывающие граничные случаи; отсутствие логики повторных попыток для переходящих ошибок API; нулевая наблюдаемость, не позволяющая видеть что ломается; бесконечные циклы без условия выхода; определения инструментов, достаточно неоднозначные, чтобы модель выбрала неправильный. Все пять поддаются исправлению без смены модели или фреймворка.

[Чтение оператора] Я управляю более чем 30 агентами в продакшне. Все эти сбои случались у меня. Те, что сожгли больше всего времени, были не экзотическими — это были скучные инфраструктурные сбои, которые я думал, что обработал.

Сбой 1: Хрупкие промпты, ломающиеся на граничных входных данных

Промпт, работающий на тестовых случаях, провалится на входных данных, которые вы не предвидели. Это не ограничение модели — это проблема написания инструкций.

Симптомы: Агент выдаёт бессмысленный результат, вызывает неправильный инструмент или выдаёт некорректный JSON, когда входные данные слегка отличаются от протестированных.

Первопричина: Ваш системный промпт описывает только счастливый путь. Он не говорит модели, что делать, когда данные отсутствуют, повреждены или неоднозначны.

Исправление: Добавьте явную обработку граничных случаев в системный промпт:

code
If the input data is missing a required field, return:
{ "status": "error", "reason": "missing_field", "field": "<fieldname>" }
Do NOT attempt to infer or hallucinate missing values.

If you are uncertain which tool to call, call no tool and return:
{ "status": "clarification_needed", "question": "..." }

Модель надёжно следует явным инструкциям для граничных случаев. Ошибка — предполагать, что она обобщит инструкции счастливого пути на грязные случаи.

Сбой 2: Отсутствие логики повторных попыток для переходящих ошибок API

Каждый внешний API, который вызывает ваш агент, в какой-то момент даст сбой. API Claude, Meta Graph API, ваша база данных — все они возвращают ошибки 5xx, таймаут или ограничения скорости. Если у агента нет логики повторных попыток, одна переходящая ошибка уничтожает весь прогон.

Симптомы: Прогоны агента случайно завершаются неудачей на разных шагах. Логи показывают 503 или 429 без последующей попытки.

Исправление: Оберните каждый внешний вызов в повторную попытку с экспоненциальной задержкой:

typescript
async function withRetry<T>(fn: () => Promise<T>, retries = 3, baseDelayMs = 500): Promise<T> {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const isTransient = err.status === 429 || err.status >= 500 || err.code === "ECONNRESET";
      if (!isTransient || attempt === retries) throw err;
      const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 100;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

// Usage
const result = await withRetry(() => client.messages.create({ ... }));

Три повторные попытки с экспоненциальной задержкой обрабатывают ~99% переходящих сбоев. Добавьте это к каждому внешнему вызову, и половина ваших случайных сбоев исчезнет.

Сбой 3: Нулевая наблюдаемость — вы не можете видеть, что ломается

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

Симптомы: Вы знаете, что что-то не так, но не можете определить шаг. Вы добавляете операторы console.log и вручную перезапускаете, пытаясь воспроизвести.

Исправление: Структурированное логирование на каждом шаге с идентификатором прогона, отслеживающим всё выполнение:

typescript
function createLogger(runId: string, agentName: string) {
  return {
    step: (step: string, data: object) =>
      console.log(JSON.stringify({ runId, agent: agentName, step, ts: new Date().toISOString(), ...data })),
    error: (step: string, err: unknown) =>
      console.error(JSON.stringify({ runId, agent: agentName, step, error: String(err), ts: new Date().toISOString() })),
  };
}

const log = createLogger(crypto.randomUUID(), "newsletter-agent");
log.step("fetch_topic", { topicId: topic.id, topic: topic.name });
// ... do work ...
log.step("draft_complete", { subject: draft.subject, wordCount: draft.body.split(" ").length });

Если вы на Cloudflare Workers, логи идут в Logpush или Workers Tail. При локальном запуске или на VPS направьте в агрегатор логов. Структурированный JSON позволяет фильтровать по runId, чтобы точно видеть, что произошло в одном прогоне.

Сбой 4: Бесконечные циклы без условия выхода

Агентские циклы — где модель вызывает инструменты и итерирует до выполнения условия — могут работать вечно, если условие никогда не выполняется или модель неправильно его идентифицирует.

Симптомы: Агент тратит сотни долларов на API-расходы до таймаута. Или многократно вызывает один и тот же инструмент, не продвигаясь вперёд.

Исправление: Всегда имейте жёсткий лимит итераций и проверку прогресса:

typescript
const MAX_ITERATIONS = 10;
let iterations = 0;
let lastToolCallName = "";
let sameToolCallCount = 0;

while (true) {
  iterations++;
  if (iterations > MAX_ITERATIONS) {
    log.error("loop", { reason: "exceeded_max_iterations" });
    break;
  }

  const response = await client.messages.create({ ... });

  // Detect stuck loops: same tool called 3x in a row
  const toolCall = response.content.find(b => b.type === "tool_use");
  if (toolCall?.name === lastToolCallName) {
    sameToolCallCount++;
    if (sameToolCallCount >= 3) {
      log.error("loop", { reason: "stuck_loop", tool: toolCall.name });
      break;
    }
  } else {
    sameToolCallCount = 0;
    lastToolCallName = toolCall?.name ?? "";
  }

  if (response.stop_reason === "end_turn") break;
}

Это перехватывает как режим «работал слишком долго», так и режим «крутился на месте». Лимит должен быть достаточно щедрым для счастливого пути, но достаточно жёстким, чтобы ограничить радиус поражения.

Сбой 5: Неоднозначные определения инструментов, которые модель разрешает неверно

Если дать модели два инструмента с перекрывающимися описаниями, она иногда вызовет неправильный. Это особенно распространено с инструментами вроде search_database vs get_record или send_email vs create_draft.

Симптомы: Модель вызывает правильную категорию инструмента, но выбирает неправильный конкретный. Или вызывает инструмент в неправильном контексте (использует инструмент записи, когда уместно было только чтение).

Исправление: Сделайте описания инструментов взаимоисключающими и явно добавьте «когда НЕ использовать»:

typescript
const tools = [
  {
    name: "get_subscriber",
    description: "Fetch a single subscriber record by email. Use ONLY when you have a specific email address. Do NOT use for searching or listing subscribers.",
    input_schema: { ... }
  },
  {
    name: "search_subscribers",
    description: "Search subscribers by tag, segment, or status. Use when you need to find subscribers matching a criteria — NOT when you have a specific email address.",
    input_schema: { ... }
  }
];

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

Ещё одна вещь: тестируйте агентов на плохих входных данных

Большинство агентов тестируются только на чистых входных данных счастливого пути. В продакшне грязные входные данные: пустые строки, null-поля, граничные случаи Unicode, ответы API, возвращающие 200, но с неожиданной схемой.

Добавьте тестовый набор, явно упражняющий:

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

Итог оператора

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

Читать дальше

Получайте ИИ-руководство на почту

Каждую среду. 28 400+ читателей. Никакой воды.

↵ — все результаты esc esc — закрыть