Привет, дорогой читатель. Если тебя хоть раз тревожило то, почему так быстро улетают лимиты Claude Code или любого другого ИИ-тула для кодинга, ты пришел по адресу. Буквально пара правок, и хоба дневной лимит исчерпан, а баланс API показывает дно. Вся эта боль в статье про экономию токенов на инструментах сборки контекста.

И так сойдет — и лимиты улетели
И так сойдет — и лимиты улетели

Написано для комьюнити AI Enginees Guild https://t.me/ai_engineers_guild

Обычно мы не сильно задумываемся об оптимизации процесса: либо кодят с ИИ как есть (копипастя файлы вручную), либо пихают в контекст всю папку проекта целиком, либо на авось подрубают самые разные тулы в надежде, что «и так сойдет». А под капот умным агентам разработчики пихают всё подряд: вот тебе ripgrep, вот AST-парсер, вот чтение файлов целиком, развлекайся. Но я задался вопросом: какой конкретно поисковый тул реально помогает агенту решать задачи и экономить токены, а какой — только заставляет его блуждать по репозиторию и беспощадно жрать контекст?

Чтобы это выяснить, я собрал подобие бенчмарка (исходники выложил в letya999/tools-token-economy) и прогнал 21 конфигурацию тулов на реальных задачах разной сложности. Думал, что навороченный семантический поиск раскатает дедовский grep в сухую, но результаты оказались куда интереснее. А по пути я еще и словил критический баг в LLM-судье.

Ниже — честный технический разбор без воды, реальные данные и мысли вслух за кружкой сидра.

Сводный график прогона на сложной задаче (eval_score и статус)
Сводный график прогона на сложной задаче (eval_score и статус)

1. Проблема: как агенту ходить по файлам?

Когда разработчик пытается затащить ИИ в свой рабочий процесс, обычно всё сводится к трем сценариям:

  1. Кодят с ИИ как есть — копипастят файлы в окно чата вручную и надеются, что модель угадает контекст.

  2. Пихают всю папку проекта целиком — скармливают LLM весь репозиторий со всеми конфигами и доками, сжигая тонны токенов за один запрос.

  3. На авось подрубают все доступные тулы — дают агенту карт-бланш на использование ripgrep, AST-парсеров, LSP и векторного поиска одновременно, надеясь, что «и так сойдет».

Знакомая картина? Агент начинает судорожно вычитывать файлы один за другим, надеясь наткнуться на нужный кусок:

Судорожный перебор и чтение файлов агентом
Судорожный перебор и чтение файлов агентом

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

  1. Навигация (поиск): найти нужные файлы, понять зависимости, структуры и вызовы.

  2. Исполнение (редактирование): применить патч и запустить тесты.

С редактированием всё понятно, а вот с навигацией беда. Я выдвинул простую гипотезу: избыток инструментов навигации вредит агенту не меньше, чем их недостаток.
Когда у модели разбегаются глаза от обилия возможностей, она либо бесконечно переключается между стратегиями поиска (context waste), либо затягивает в контекст столько мусора, что забывает изначальную задачу (эффект context explosion).

Мне было интересно нащупать, какой конкретно тул дает самый чистый контекст. В качестве метрики я взял SPT (Success Per Token) — количество успешных решений на каждую тысячу потраченных токенов.

2. Дизайн песочницы

Для экспериментов я поднял изолированную среду на Python 3.13 и WSL2 Ubuntu. Я виндов-вод, а хотелось чуть более чистый эксперимент. В целом в проекте есть скрипты под linux/macos/windows, на любой вкус. В роли агента выступал фреймворк Agno https://github.com/agno-agi/agno с gpt-4.1-mini. Почему Агно? Причины 3: я устал настраивать opencode, задолбался настраивать opencode и я хотел померить без костылей тулов, который скрыто для пользователя оптимизируют работу.

Все параметры (температура, промпты) зафиксированы, менял только доступные агенту инструменты поиска.

Получился 21 конфиг, которые я разбил на 6 категорий:

1. Семейство Grep-инструментов (Ablation-тесты)

Оставляем только классический текстовый поиск:

  • 07_grep: дефолтный POSIX grep.

  • 08_git_grep: поиск по индексу git.

  • 09_rg: реактивный ripgrep.

  • 10_ugrep: расширенный поиск с нечеткими совпадениями.

  • 11_ast_grep: поиск по синтаксическому дереву.

2. Чтение контекста (Read-стратегии)

  • 05_read_only: агент ищет только по glob-паттернам и читает файлы.

  • 06_read_all: насильно закидываем в контекст вообще все файлы проекта со старта.

3. Семантическая навигация (Semantic)

Инструменты, строящие граф кода:

  • 16_serena_only: доступ к MCP-серверу Serena, который дает символьный граф (кто кого вызывает, где лежат типы).

  • 17_semble_only: поиск по локальным векторным эмбеддингам.

  • 20_serena_semble: гибрид графа Serena и векторов Semble (⭐ спойлер: пушка-гонка).

4. Структурный поиск

  • 12_tree_sitter: разбор файлов через AST.

  • 13_lsp: плоский список символов по протоколу LSP.

5. Интегрированные архетипы

  • 01_cursor_like: смесь repo_map и RAG (как под капотом Cursor).

  • 02_claude_code_like: гибрид globripgrep и чтения файлов.

  • 03_gemini_like: экстремальная солянка (read_all + repo_map + rg).

  • 18_rg_repo_map: ripgrep плюс карта репозитория.

6. Минимализм

  • 21_bash_only: голый терминал. Выживай как хочешь.

Чтобы эта банда конфигураций не оставалась просто набором букв в коде, я завёл реестр в дашборде. Вот как выглядит этот зоопарк под капотом (разбил на две части, чтобы не ломать вам глаза мелким шрифтом):

Реестр конфигураций — Часть 1
Реестр конфигураций — Часть 1
Реестр конфигураций — Часть 2
Реестр конфигураций — Часть 2

Сценарии

Взял две задачи из реальной кодовой базы:

  1. Medium: поправить регистрозависимость Bearer-токена в middleware. Найти функцию, поправить строку.

  2. Hard: фича aging_stale в пайплайне Polars. Надо прокинуть новое поле, обновить логику в 3 файлах и дописать тесты.

Оценка качества

Просто прогнать pytest  недостаточно. Я натравил на результаты LLM-судью (gpt-5.4-nano), который ревьюил код по 7 критериям (качество, лаконичность, следование паттернам). Ну и считал токены и процент context waste (когда файл прочитан, но не использован).

3. Анатомия LLM-судьи: веса, эвалы и фильтр Неймана

В нашем бенчмарке LLM-судья не просто ставит лайк/дизлайк. Он оценивает код по 7 независимым метрикам ивалам: task_solvedcorrectnesstool_correctnesscontext_qualityminimalitypattern_adherencetool_sequence. Каждому критерию назначен свой вес, а итоговая оценка (Eval Score) считается как взвешенное среднее.

Но и этого мало. Возникла проблема: если агент ломает бизнес-логику (0 за correctness), но при этом пишет красивый минималистичный код (1 за minimality), линейное среднее даст ему завышенный балл. Поэтому я использую геометрический показатель качества, где критические метрики выступают множителями: если correctness равна нулю, обнуляется весь скор.

А чтобы убрать шум от случайных галлюцинаций судьи на гигантских контекстах, я применил пару научных приколов:

  • Фильтр Неймана: статистический механизм, который отсекает выбросы (outliers) в оценках судьи, опираясь на дисперсию соседних прогонов. Если судья внезапно ставит 1.0 за решение, где ломается синтаксис, фильтр сглаживает эту галлюцинацию.

  • Поправка Демискар (Demiscar 2006): математический штраф за избыточное чтение. Этот алгоритм нелинейно понижает итоговый Eval Score, если параметр Waste% (прочитанные, но неиспользованные токены) превышает здоровый порог для данной задачи.

Именно этот строгий аппарат позволил отсеять хитрецов, которые ломали тесты, чтобы пройти их на шару.

4. Средняя задача: триумф дедовских методов

Для начала размялись на простой задаче — исправлении регистрозависимости Bearer-токена. Вот как выглядят параметры запуска и описание таски в интерфейсе дашборда:

Параметры запуска средней задачи
Параметры запуска средней задачи
Описание средней задачи на дашборде
Описание средней задачи на дашборде

На простой задаче (Bearer-токен) я прогнал 20 конфигов. 14 из них справились (70% pass rate).

И вот тут случилось первое открытие. Посмотрите на топ самых экономных:

Конфиг

Инструменты

Токены

Тесты

Примечание

10_ugrep

ugrep + edit

2,697

✅ PASS

⭐ Абсолютный чемпион

18_rg_repo_map

rg + repo_map

3,003

✅ PASS

Очень близко

05_read_only

glob + read

3,276

✅ PASS

07_grep

grep + edit

3,439

✅ PASS

02_claude_code_like

glob + rg + edit

3,559

✅ PASS

13_lsp

lsp + edit

9,994

✅ PASS

Начало усложнения

20_serena_semble

serena + semble

32,747

✅ PASS

Тяжелая артиллерия

03_gemini_like

read_all + repo_map

175,208

❌ FAIL

Context explosion

⭐ Главный вывод фазы Medium: на простых, точечных задачах легковесные утилиты типа grepugrep или ripgrep рвут всех. Агенту нужен ровно один шаг: найти строку Bearer, прочитать этот кусок, заменить. Копеечный расход (меньше 3 тысяч токенов) и моментальный патч.

А вот стратегия gemini_like (закинь в контекст всё) предсказуемо свалилась. Агент наглотался кода, запутался, слил 175 тысяч токенов в унитаз и выдал нерабочий мусор.

5. Сложная задача: когда Grep заводит в тупик

Для сложной задачи (добавление поля в Polars-пайплайн) я прогнал каждый конфиг по 6 раз для надежности. И тут картина перевернулась с ног на голову.

Лидерборд (отсортирован по оценке судьи p75):

Конфиг

Инструменты

Успех (pytest)

Оценка судьи (p75)

Средние токены

04_codex_like

grep + read + edit

6/6 (100%)

0.85

861,000

20_serena_semble

serena + semble

5/6 (83%)

0.79

120,000

14_repo_map

repo_map + edit

4/6 (67%)

0.76

97,000

16_serena_only

serena

6/6 (100%)

0.68

79,000

17_semble_only

semble

5/6 (83%)

0.58

84,000

10_ugrep

ugrep + edit

4/6 (67%)

0.62

1,224,000

19_rg_lsp

rg + lsp

4/6 (67%)

0.37

425,000

18_rg_repo_map

rg + repo_map

0/6 (0%)

0.49

79,000

03_gemini_like

read_all + repo_map

0/6 (0%)

0.00

419,000

21_bash_only

bash

0/6 (0%)

0.10

85,000

А вот как эти результаты выглядят в интерфейсе нашего дашборда со всеми деталями (включая Net SPT и стоимость в центах):

Детальный лидерборд сложной задачи в Streamlit
Детальный лидерборд сложной задачи в Streamlit

И для любителей сырого терминального вывода — ловите скриншот финального отчёта прямо из консоли после завершения всех 126 раундов:

Итоговый отчет в терминале
Итоговый отчет в терминале

⚠️ Что мы тут видим:

  • Serena Only (16_serena_only) творит чудеса: 100% успех при средних затратах всего 79,000 токенов на раунд!

  • Codex-like (04_codex_like) тоже выдал 100% успех, но ценой чудовищного обжорства — 861,000 токенов. В 11 раз прожорливее Серены!

6. Инсайт: почему так происходит

Всё упирается в когнитивную глубину задачи.

На средней сложности нужно просто найти и заменить. Грепы тут идеальны. Сложные графы (Serena) для этого как микроскоп для забивания гвоздей.

На высокой сложности агенту нужно распутать клубок: понять, откуда вызывается функция в aging.py, посмотреть маппинг в commitment_resolver.py, чекнуть схему валидации.
С одним только grep агент начинает слепо блуждать. Ищет age, получает 150 совпадений по всему проекту и читает их все, раздувая контекст до 1.2 млн токенов (привет, ugrep).

А вот тулы вроде Serena дают семантику. Агент дергает find_symbol("calculate_work_item_aging_facts") и мгновенно получает граф вызовов. Он скачет по цепочке, читая только то, что нужно.

Вывод: Не пихайте в агента один универсальный тулсет. Для малых проектов вам ничего не надо, край давайте ripgrep. Для сложного рефакторинга забирайте grep и давайте LSP/MCP тулы (а лучше просто ставьте Serena MCP https://github.com/oraios/serena, иначе агент сожрет весь лимит токенов на чтение мусора.

7. Эпик фейл с LLM-судьей (и как я спас данные)

Ну и вишенка на торте. Запустил я пачку ранов, открываю логи, а там... у всех запусков оценка судьи 0.0. При этом pytest зеленый.

Полез дебажить и нашел баг в моем llm_judge.py:

# Старый код, который всё сломал:
try:
    itok = int(getattr(resp.metrics, "input_tokens", 0) or 0)
except (TypeError, ValueError):
    itok = 0

Оказалось, в свежей версии Agno у ModelResponse пропал атрибут .metrics (всё переехало в resp.response_usage). Падал AttributeError, который я не ловил. Ошибка летела выше и ломала весь скоринг.

# Фикс здорового человека:
try:
    usage = getattr(resp, "response_usage", None)
    if usage is not None:
        itok = int(getattr(usage, "input_tokens", 0) or 0)
    else:
        itok = int(getattr(resp, "input_tokens", 0) or 0)
except (TypeError, ValueError, AttributeError): # Добавил AttributeError!
    itok = 0

Перезапускать все раны с нуля было бы больно. Но меня спасло простое правило: я всегда логирую сырые транзакции (agent_messages.json)..

9. Итоги

Если вы собираете свой Agent Harness, вот выжимка:

  1. Grep для простых задач, семантика для сложных. ripgrep идеален для локальных фиксов. Для рефакторинга нужны LSP-серверы и графы, иначе агент захлебнется в поиске. Serena MCP!;

  2. Прочитай всё (read_all) зло. Заливать все файлы репо в контекст со старта — верный путь к context explosion;

А какие тулы вы даете своим агентам под капот? Оставляете им свободу терминала или жестко ограничиваете MCP-серверами? Пишите в комменты, буду рад обсудить!

P.S. Исходный код харнесса и все сырые метрики лежат в репозитории letya999/tools-token-economy. Забирайте, ковыряйте, снижайте энтропию в своих агентах!

Комментарии (0)