К этому бенчмарку меня вернули два числа. README codebase-memory-mcp — графа кода на tree-sitter, который отдаётся по MCP, — обещает на 99 % меньше токенов. Мой собственный прошлый прогон говорил, что cbm-lean, тонкая обёртка, которую я написал вокруг него, делает агента на 26 % дешевле обычного grep на haiku и на 18 % дешевле на sonnet. Первое число мне так и не удалось воспроизвести, а второе получилось из-за изъяна в моей постановке. Это повторный прогон на открытых репозиториях, с исправленным изъяном.
Постановка
Каждый вопрос прогонялся в трёх вариантах:
- cbm-lean плюс правило маршрутизации в системном промпте: структурные вопросы идут в граф, точные строки — в Grep;
- сырой сервер codebase-memory-mcp плюс то же правило;
- только Read, Grep и Glob.
На каждый репозиторий приходилось двенадцать вопросов. Шесть структурных: кто вызывает функцию, что она вызывает, цепочка вызовов от HTTP-эндпойнта до записи в базу, устройство модулей. Шесть точных: значение из конфига, где читается переменная окружения, текст ошибки, строка, где определён класс. У каждого вопроса есть список подстрок, которые должен содержать правильный ответ, и каждую я вручную сверил с кодом.
Репозитории такие: три небольших (шаблон FastAPI full-stack, ltx-2-mlx и avoid-ai-writing, от 1,6 тыс. до 5 тыс. узлов графа) и dub, монорепозиторий на Next.js с 25 тыс. узлов. Haiku 4.5 прошёл по всем, sonnet 5 — по dub: 252 запуска, $14,69 по ценам API.
Стоимость измеряется как tokenEquiv: входные токены, плюс 1,25 × записи в кеш, плюс 0,1 × чтения из кеша. Сырой оплаченный вход вводит в заблуждение, когда включено кеширование промптов: вариант, которому повезло попасть на прогретый кеш, выглядит дешёвым по причинам, не связанным с самим вариантом.
Результаты
Медианный tokenEquiv на запуск, в скобках — число правильных ответов:
| условия | запусков | cbm-lean | сырой граф | grep |
|---|---|---|---|---|
| Малые репозитории, haiku 4.5 | 108 | 30 321 (28/36) | 43 846 (32/36) | 31 159 (33/36) |
| dub, haiku 4.5 | 72 | 18 791 (22/24) | 20 687 (22/24) | 14 955 (20/24) |
| dub, sonnet 5 | 72 | 17 227 (22/24) | 23 085 (22/24) | 13 348 (22/24) |
Три результата выдерживают проверку:
- По токенам граф не выиграл ни разу. Против grep не было измеримой разницы ни на небольших репозиториях (p = 0,13), ни на dub с haiku (p = 0,18), а на dub с sonnet grep был дешевле на 11 вопросах из 12 (p = 0,034) при той же точности — 22 из 24 в каждом варианте.
- Обёртка обыграла сырой сервер во всех условиях (p ≤ 0,021). Сырой сервер возвращает около 1,3 КБ на одно попадание поиска, в основном дампы идентификаторов и хеш-векторы, а cbm-lean урезает попадание примерно до 250 байт.
- Единственная явная победа графа — точность маленькой модели на большом репозитории. Haiku правильно ответил на структурные вопросы по dub 12 раз из 12 с графом и 8 раз из 12 с grep, при медиане 23,5 тыс. токенов против 16,0 тыс.
Значения p получены знаковым ранговым критерием Уилкоксона по медианам для каждого вопроса.
Где ошибался каждый вариант
- Номера строк из графа. Когда нужно было назвать строку с текстом ошибки, вариант с графом вычитал ответ из вывода графа и дал login.py:33; raise стоит на строке 34, а 33 — это if над ним. Он же назвал config.py:74 для класса, который сам граф хранит на строках с 15 по 88, и client.ts:129 для класса, определённого на строке 59. Правило маршрутизации велело ему идти в grep с точными вопросами, и haiku не всегда ему следовал.
- Одинаковые имена. В шаблоне FastAPI два обработчика с именем create_user. На вопрос об эндпойнте для админа оба варианта с графом в 5 запусках из 6 прослеживали тот, что без аутентификации. Grep каждый раз находил тот, что стоит за проверкой суперпользователя.
- Ранняя остановка. На dub haiku с grep дважды пропустил настоящую запись в базу в конце цепочки вызовов.
- Моя собственная проверка ответов. Каждый запуск sonnet, в каждом варианте, не упоминал packages/tinybird в ответе про архитектуру dub. В этой папке лежат файлы данных Tinybird и нет package.json, так что модели, пожалуй, были правы, а моя проверка — нет. Все шесть промахов sonnet приходятся на этот один вопрос, и если его убрать, ни одно сравнение не меняется.
Подключить граф — не значит им пользоваться
Самый полезный результат дал пилот, а не основной прогон. Claude Code 2.1.283 по умолчанию прячет схемы MCP-инструментов за шагом ToolSearch: модель видит имена инструментов и должна загрузить схемы, прежде чем что-то вызвать. Haiku в headless-режиме этого не делал. В шести пилотных запусках граф не был вызван ни разу, с правилом маршрутизации или без него. На двенадцати вопросах по небольшим репозиториям:
| конфигурация | вызвал граф | верно | медиана tokenEquiv |
|---|---|---|---|
| Загрузка по умолчанию, с правилом | 2 из 12 | 11 из 12 | 41 008 |
| Схемы загружены, без правила | 4 из 12 | 9 из 12 | 62 008 |
| Схемы загружены, с правилом | 22 из 36 | 28 из 36 | 30 321 |
С загруженными схемами (ENABLE_TOOL_SEARCH=false) тот же вопрос о вызывающих функциях прошёл через два вызова графа за три хода. Подключённый граф без правила оказался самой дорогой конфигурацией из всех: каждая сессия платит за схемы, а модель в основном оставляет их без дела.
Если вы выпускаете MCP-сервер и тестируете его с настройками по умолчанию, сначала проверьте, вызывает ли его модель вообще.
Почему прежнее число было неверным
Первый бенчмарк шёл внутри моей папки с проектами. Её CLAUDE.md велел агенту использовать граф для структурных вопросов, а Claude Code подгружает файлы CLAUDE.md из родительских каталогов, поэтому эта инструкция дошла до каждого варианта, включая тот, где графа не было. Контрольная группа получила инструкции экспериментальной, а значит, сравнение измеряло правило не меньше, чем инструменты.
Повторный прогон держит репозитории вне любых деревьев, над которыми лежит CLAUDE.md, пропускает пользовательские
настройки и хуки (--setting-sources project) и даёт правило только вариантам, у которых есть граф. Каждый из
остальных контролей исправил неверный вывод в одном из прошлых раундов. Bash и Agent запрещены во всех вариантах,
потому что ограниченные разрешения Bash не совпадают с командами через конвейер, а субагенты прячут ходы, хотя
токены всё равно тратят. Порядок вариантов меняется между повторами, так что ни один вариант не идёт всегда на
прогретом кеше. Запуск, который вызывает инструмент чужого варианта, отбрасывается. И ответы проверяются, потому
что токены, сэкономленные на неверном ответе, — не экономия.
Что я сказал бы команде, которая подключает граф к агенту
- Не ждите экономии токенов от графа кода с моделями Claude на репозиториях до 25 тыс. узлов. Сначала измерьте на своих вопросах.
- Если всё же подключаете, урежьте его вывод, загрузите его схемы и дайте агенту правило маршрутизации. Без последних двух он — мёртвый груз в каждом промпте.
- Не доверяйте номерам строк, которые модель вычитывает из результатов графа, и проверяйте их через grep.
- Граф оправдывает себя, когда маленькая модель отвечает на структурные вопросы по большой кодовой базе: архитектура и цепочки вызовов.
Ограничения
Две модели, четыре репозитория до 25 тыс. узлов, два или три повтора, по двенадцать вопросов на каждый. Ни Opus, ни долгих интерактивных сессий, ничего больше 25 тыс. узлов. Приведены медианы, потому что повторные запуски одного и того же вопроса сильно различались.
Воспроизвести
Всё лежит в code-graph-vs-grep: вопросы с закреплёнными коммитами, драйвер бенчмарка, сырые запуски со всеми ответами и сам cbm-lean.
bash bench/setup.sh
node bench/run.mjs --reps 3
python3 bench/stats.py results/*.json