Главная / Статьи / Меньше токенов — не значит дешевле: агенты для кода тратят впустую работу, а не токены

Меньше токенов — не значит дешевле: агенты для кода тратят впустую работу, а не токены

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

2258 слов

С тех пор как агенты для кода взлетели, разработчики зациклились на одной цифре — токенах. Сколько сгенерировала модель, сколько вернул инструмент, какого размера контекст, сколько сжёг агент? Логика кажется железной: меньше токенов — меньше счёт.

Анализ GitHub по агенту Copilot для кода показывает, почему это допущение рискованно. Если урезать каждый ответ инструмента, вся задача может подорожать, когда в отрезанном тексте было что-то важное. Агент тогда перезапускает команды, выкапывает исходный вывод, тратит лишние ходы на рассуждения и тащит раздутый контекст через всё, что идёт дальше.

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

Ловушка локальной оптимизации

Допустим, агент запускает команду тестов:

npm test

Она печатает 50 000 токенов. Выглядит расточительно, поэтому вы пишете обёртку, которая ужимает вывод до 2 000. На бумаге это победа: 48 000 токенов исчезли.

А теперь представьте, что в эти 2 000 токенов не попало то, что было нужно агенту. Он отвечает примерно так:

I need more information about the failing test.

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

Original command
      ↓
Compressed output
      ↓
Missing information
      ↓
Agent searches again
      ↓
Command runs again
      ↓
More context
      ↓
More reasoning

Отдельный шаг подешевел, а задача подорожала. GitHub столкнулся с этим, когда пробовал способы сократить вывод для Copilot. В одном эксперименте инструмент под названием RTK укорачивал вывод оболочки, и агенты часто заново открывали или перезапускали команды, как только недостающая деталь оказывалась важной. В итоге ходов стало больше, а в среднем выросли и токены, и время выполнения. Задним числом это звучит очевидно, но легко упустить, когда оптимизируешь не ту цифру.

Оценивайте задачу, а не вызов

Агент нужен, чтобы довести до конца задачу по разработке, а не чтобы минимизировать стоимость одного API-запроса. Разница между этими целями больше, чем кажется. Сравним двух агентов. Агент A:

Task
↓
8 tool calls
↓
100K tokens
↓
Completed in 4 minutes

и агент B:

Task
↓
15 tool calls
↓
80K tokens
↓
Completed in 9 minutes

Если смотреть только на токены, выигрывает B. Но если вы руководите инженерной организацией, ответ уже не так ясен. A закончил раньше, вероятно, потребовал меньше повторов, мог израсходовать меньше вычислений, мог выдать патч получше и меньше заставил разработчика ждать. Если кому-то пришлось ещё пять минут нянчиться с B, реальная цена B растёт ещё сильнее.

Поэтому GitHub утверждает, что число токенов на одно взаимодействие мало что говорит об эффективности агента. Оптимизировать нужно результат всей задачи.

Модель стоимости от начала до конца

Опытные инженеры знают это по другой работе. Сократите запрос к базе с 200 мс до 100 мс, и это звучит как явный выигрыш. Но если новый запрос заставляет другой сервис отправить три лишних запроса, система в целом может стать медленнее. Настраивают систему, а не отдельную операцию.

С агентами то же правило. Полезная мысленная модель полной стоимости:

Total agent cost =
model inference
+ tool execution
+ retries
+ repeated work
+ context growth
+ developer waiting
+ infrastructure

И закономерность, которая встречается часто:

smaller response
+
missing context
=
more recovery work

Вот почему ИИ-инженерия всё больше похожа на системную инженерию.

Не любой вывод одинаково ценен

Важное наблюдение: плотность информации в выводе команд бывает очень разной. Однообразные логи установки, сборки, тестов и прогресса могут быть забиты шумом. А вывод, похожий на исходный код, и результаты произвольных команд, наоборот, могут содержать ровно то, что нужно агенту. Вместо одного правила сжатия для всего GitHub сделал избирательный подход. Концептуально он устроен так:

Command output
      │
      ▼
┌──────────────────────┐
│ What kind of output? │
└──────────┬───────────┘
           │
     ┌─────┼─────┐
     │     │     │
     ▼     ▼     ▼
 Source  Search  Noise
 output  results logs
     │     │     │
     ▼     ▼     ▼
  Keep   Reorganize Compress
  all     without   selectively
          losing
          content

Это лучше грубой альтернативы:

if output.length > X:
    truncate()

Грубое правило оптимизирует цифру. Избирательное — способность агента продолжать работу.

Чему учит git diff

Представьте агента, который отредактировал пять файлов и теперь запускает:

git diff

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

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

Как проектировать свои инструменты

Если вы делаете агента для кода, можно написать так:

const result = await exec(command);

а затем жёсткий лимит:

return result.stdout.slice(0, 5000);

Выглядит разумно, но молча предполагает, что в первых 5 000 символах есть всё, что нужно агенту. Умнее сначала классифицировать вывод. Начало то же:

const result = await exec(command);

Дальше ветвление по типу вывода:

const outputType = classifyOutput(result);switch (outputType) {
  case "source":
    return result.stdout;  case "search":
    return reorganize(result.stdout);  case "repetitive-log":
    return compress(result.stdout);  default:
    return result.stdout;
}

Даже здесь нужен запасной выход, чтобы агент мог вернуть оригинал:

return {
  summary,
  compressedOutput,
  fullOutputAvailable: true
};

Агент получает компактное представление, и при этом ничего по-настоящему не пропадает. Для автономных систем это гораздо лучшая архитектура.

Сжатие должно откладывать, а не удалять

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

Original output
       ↓
Throw away 90%
       ↓
Agent cannot recover it

Хорошее сжатие оставляет путь назад открытым:

Original output
       ↓
Create useful summary
       ↓
Agent continues
       ↓
Needs more detail?
       ↓
Retrieve original

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

Иногда больше контекста дешевле

Звучит наоборот. Разработчики годами урезали контекст. Но больший контекст может предотвратить лишнюю работу. Допустим, агент видит сжатое сообщение о сбое:

Test failed:
Expected 401
Received 500

Полный вывод мог бы показать ещё и это:

Database connection refused
JWT middleware initialized correctly
Request reached /api/auth/login
PostgreSQL timeout after 5 seconds

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

Почему агенты будто ходят по кругу

Вы наверняка видели, как агент делает что-то вроде этого:

Run command
↓
Read partial output
↓
Run another command
↓
Search same file
↓
Run original command again
↓
Read more output
↓
Change code
↓
Run test
↓
Go back to original command

Похоже на растерянность, и иногда так и есть. Но порой инфраструктура просто не дала модели достаточно информации, и агент компенсирует неполный интерфейс. Если агент снова и снова просит одно и то же, проблема может быть в устройстве инструментов, а не в модели.

Дизайн инструментов — это UX для моделей

Разработчики обычно оценивают API по входам, выходам, задержке, ошибкам и аутентификации. Для инструментов под ИИ нужен ещё один вопрос: как этот вывод повлияет на следующее решение модели? Возьмём два ответа. API A:

{
  "status": "failed"
}

и API B:

{
  "status": "failed",
  "error": "PostgreSQL connection timeout",
  "service": "auth-api",
  "retryable": true,
  "attempt": 2,
  "duration_ms": 5040
}

В B больше данных, но он может снизить общую стоимость системы, потому что агент сразу принимает правильное решение. Цель — не самый маленький JSON. Цель — информация, которая позволяет агенту сделать следующий верный шаг.

Настройка всего цикла

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

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

Наблюдаемость для агентов

Это меняет то, что вы отслеживаете в продакшене. Для обычных сервисов вы бы смотрели на:

Latency
Error rate
CPU
Memory
Throughput

Для агентов добавьте ещё:

Task completion rate
Tool-call count
Retry rate
Repeated-command rate
Context growth
Model escalation rate
Time to completion
Cost per completed task
Human intervention rate

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

Стоимость успешной задачи

Самое простое изменение — перестать отслеживать вот это:

cost per request

и отслеживать вместо этого вот это:

cost per successfully completed task

Это не одно и то же. Возьмём агента с такими цифрами:

Agent A
$0.20/task
70% success rate

и сравним с этим:

Agent B
$0.35/task
95% success rate

Более дешёвый запрос — не обязательно более дешёвая система. Нормализуем:

Effective cost
= inference cost / successful tasks

Для A:

$0.20 / 0.70
≈ $0.286

Для B:

$0.35 / 0.95
≈ $0.368

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

Та же идея работает при выборе моделей

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

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

Cheap model
+
Good context
+
Efficient tools
+
Verification

и обойти вот такой:

Expensive model
+
Poor context
+
Repeated tool calls

Интеллект больше не живёт только в модели. Он распределён по всей системе.

Если вы делаете ИИ-продукты

Соблазнительно сначала оптимизировать очевидные цифры: меньше токенов, короче промпты, короче вывод, дешевле модели. Не настраивайте только их. Инструментируйте весь рабочий процесс:

User request
    ↓
Model inference
    ↓
Tool call
    ↓
Tool response
    ↓
Model reasoning
    ↓
Another tool call
    ↓
Verification
    ↓
Final result

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

Будущее — не в меньшем числе токенов

Соблазнительно свести всю проблему стоимости к этому:

tokens ↓
cost ↓

На деле зависимость выглядит скорее так:

Useful context
      ↓
Better decisions
      ↓
Fewer retries
      ↓
Fewer unnecessary tool calls
      ↓
Shorter task duration
      ↓
Lower total cost

Иногда путь к меньшей стоимости начинается с того, что модели дают больше информации. Это контринтуитивная часть, и именно поэтому ИИ-инженерия превращается в системную дисциплину, а не в упражнение по написанию промптов.

Измеряйте выполненную работу

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

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