Продолжение статьи про graphlens. Там я описал, что инструмент делает и как устроен, и по дороге уверенно заявил, что «агент жжёт токены, бегая grep’ом по репозиторию». Заявил — но ни одной цифры не привёл. Эта статья закрывает дыру: вот замеры, вот данные, вот воспроизводимый стенд. Спойлер: вывод оказался не таким, каким я его себе рисовал, и это самое интересное.
Коротко
Я взял одного и того же агента (Claude Code), менял у него ровно одну вещь — какой MCP-сервер отдаёт контекст по коду, — и гонял по 26 задачам на apache/superset. Четыре «руки»: filesystem (grep + read), graphlens (структурный граф), serena (LSP) и codegraph. Три модели (haiku / sonnet / opus), три сида — 936 прогонов.
Главный результат: вывод переворачивается в зависимости от типа задачи.
-
На простых «где определён X / от чего наследуется» — все четыре инструмента равны по точности, разница только в цене (~3×). graphlens тут ничем не выделяется.
-
На задачах «оцени радиус поражения / найди все переопределения / разреши неоднозначное имя» инструменты резко расходятся: grep разваливается (точность 0.71, до финиша доходит 83% прогонов, а те, что доходят, стоят в 10–23 раза дороже), а структурные инструменты остаются дешёвыми и точными.
Если бы я мерил только простые задачи, я бы написал «граф не нужен, grep справляется». Если бы только сложные — «grep не нужен, берите граф». Правда — посередине, и она про то, какую работу вы поручаете агенту.
Бизнес-кейс, который мы на самом деле измеряем
Представьте типичную ситуацию. Есть большой проект: сотни тысяч строк, бэкенд на Python, фронт на TypeScript, легаси, в которое страшно лезть. Вы подключаете к нему кодового агента — для ревью, для рефакторинга, для ответов на вопросы вроде «что сломается, если я поменяю сигнатуру вот этого метода».
Агент не видит весь репозиторий разом. Кто-то должен подавать ему контекст: какие функции где определены, кто кого вызывает, что от чего наследуется. И вот тут возникает архитектурное решение, у которого есть цена: чем именно кормить агента?
Вариантов, по сути, четыре класса:
-
Дать ему grep и read — пусть ищет текстом и читает файлы. Ноль инфраструктуры, работает везде.
-
Построить структурный граф кода (graphlens) — узлы-сущности, типизированные рёбра, точные ответы на «кто вызывает».
-
Поднять LSP (serena поверх language server) — то, чем питается ваша IDE.
-
Взять готовый code-graph продукт (codegraph).
Каждый вариант — это деньги (токены), время (латентность) и риск (агент не справится и упрётся в лимит ходов). apache/superset — почти идеальный стенд под этот кейс: ~400k строк, Python + TypeScript, граница /api/v1/... между фронтом и бэком. Большой полиглотный проект — ровно то, ради чего этот вопрос вообще стоит задавать.
Так сколько стоит каждое из решений? Давайте мерить.
Дизайн эксперимента: меняем одну переменную
Вся методология держится на одном принципе: зафиксировать всё, кроме одного. Модель, системный промпт, настройки, набор задач — константы. Меняется только MCP-сервер, отдающий контекст. Тогда любая разница в цифрах — это вклад именно инструмента, а не случайности конфигурации.
Никакой инструмент не назначен «бейзлайном, который надо побить». Все четыре меряются на равных, и пусть числа их ранжируют.
Четыре «руки»
|
Рука |
Провайдер контекста (MCP-сервер) |
Шаг индексации |
|---|---|---|
|
|
|
нет |
|
|
граф graphlens поверх MCP |
|
|
|
Serena (LSP) |
прогрев LSP-воркспейса |
|
|
конкурент на графах |
|
Важная деталь честности стенда: встроенные инструменты Claude Code (Read / Grep / Bash и прочие) выключены. Если их не отнять, агент проигнорирует MCP и пойдёт своим привычным путём — и мы измерим не то. Поэтому стенд запускает claude -p в «чистой комнате»: свежий CLAUDE_CONFIG_DIR только с кредами подписки (без хуков, плагинов, скиллов, памяти), --strict-mcp-config (виден только сервер этой руки), --disallowedTools на все встроенные инструменты (именно запрет, а не отсутствие в allow-list — в headless-режиме allow-list сам по себе ничего не запрещает) и --allowedTools mcp__<server>, чтобы автоматически разрешить единственный сервер руки.
Вторая ось: модели
Параллельно я варьировал модель, которая отвечает на вопрос:
|
Ключ |
model id |
|---|---|
|
|
|
|
|
|
|
|
|
Зачем вторая ось — станет ясно ближе к концу: оптимальный инструмент зависит от того, какую модель вы взяли. Это, пожалуй, самый неочевидный вывод всего замера.
Итого: 4 руки × 3 модели × 26 задач × 3 сида = 936 прогонов (на стеке Claude Code 2.1.187).
Что я считаю честным замером
Бенчмарки легко подкрутить под нужный вывод. Поэтому правила игры зафиксированы заранее, и вот они — без них цифрам верить нельзя.
-
Эталонные ответы выверены руками по исходникам на теге
6.0.0(каждая задача несёт ссылкуfile:line). Принципиально: эталон не генерируется ни одним из тестируемых инструментов (ни ty, ни pyright, ни самим graphlens). Иначе сравнение смещено в пользу того, чьим выводом мы размечали. Эталонные множества для set-задач выверены независимым оракулом — питоновскимast. -
У «наивной» руки есть руки.
filesystem— это grep + read, а не «агент без инструментов». «Наивно» ≠ «без рук». -
Стоимость индексации меряется отдельно, один раз. grep не платит за индекс ничего, граф — амортизирует. Смешивать эти валюты нельзя.
-
Детерминизма нет.
temperature=0у этих моделей не детерминирует вывод. Поэтому 3 сида, и в отчёте — медиана, а не среднее. -
Записаны версии моделей и каждого MCP-сервера, снимок цен и дата.
-
cost_usd— это API-эквивалент, а не ваш счёт. Подписка — flat-rate, так чтоcost_usd(его отдаёт CLI) — это сколько те же токены стоили бы по API. Это не ваш реальный чек, но это корректная относительная метрика $/задача для сравнения рук между собой. -
Прогоны в чистой комнате — токены отражают только системный промпт + инструменты MCP-руки, без вашего личного конфига.
-
Использовать инструмент обязательно. Системный промпт запрещает отвечать по памяти; прогон, не сделавший ни одного вызова инструмента, перезапускается (а упорный отказ помечается
__NO_TOOLS__). Ответ «из головы» про известный репозиторий не измерял бы провайдера контекста.
И отдельно — провал засчитывается как точность 0. Если grep упёрся в потолок 50 ходов и не выдал ответ — это не «нет данных», это «инструмент не справился в рамках бюджета». Так и считаем.
Задачи: два режима, и почему их нельзя смешивать
26 задач делятся на два класса.
SIMPLE — 20 точечных запросов («где определён X / от чего наследуется X»). Ответ — одна точка, проверяется вхождением подстроки:
|
Тип |
# |
Что проверяет |
|---|---|---|
|
|
7 |
Python-класс → файл определения |
|
|
5 |
Python-класс → базовый класс |
|
|
1 |
ABC → его абстрактные методы |
|
|
1 |
TS-хук → файл определения |
|
|
4 |
роут |
|
|
2 |
TS-потребитель → Python-обработчик через границу API |
HARD — 6 задач на радиус поражения и неоднозначность. Это режим, где структура и семантика должны бить текстовый поиск — и который точечные запросы в принципе не измеряют:
|
Тип |
# |
Что проверяет |
Оценка |
|---|---|---|---|
|
|
2 |
неоднозначное голое имя метода (напр. |
подстрока |
|
|
2 |
полный набор подклассов, переопределяющих метод базы |
F1 по множеству |
|
|
2 |
все файлы, вызывающие данный метод (радиус поражения) |
F1 по множеству |
Set-задачи оцениваются по F1: награда за полноту (найти всех) и штраф за мусор в точности (текстовый поиск любит вывалить каждое вхождение .get_indexes(). Эталонные множества держим маленькими (3–5 элементов, одно ≈17), чтобы их можно было исчерпывающе проверить руками.
Почему я стратифицирую, а не усредняю
Набор намеренно несбалансирован — 20 простых против 6 сложных. Если посчитать одно общее среднее, оно будет полностью продиктовано лёгкими задачами и спрячет ровно ту разницу, которую вскрывают сложные. Поэтому я докладываю каждый режим отдельно и никогда не смешиваю.
И да — я сознательно не «балансирую до 50/50» выкидыванием простых задач. Это потеряло бы данные и статистическую мощность и открыло бы дверь для cherry-pick. Стратификация нейтрализует перекос без выброса данных. Это, кстати, общий принцип: если режимы дают разные ответы, честнее показать оба, чем спрятать конфликт под усреднением.
Результаты
SIMPLE — 20 точечных запросов
|
Инструмент |
точность |
заверш. |
токены |
вызовы |
$/задача |
сек |
|---|---|---|---|---|---|---|
|
filesystem |
0.97 |
100% |
1780 |
10 |
$0.063 |
43 |
|
graphlens |
0.98 |
100% |
690 |
3 |
$0.038 |
13 |
|
serena |
0.99 |
100% |
402 |
3 |
$0.031 |
20 |
|
codegraph |
0.99 |
100% |
372 |
1 |
$0.022 |
10 |
Точность — ничья (формально: критерий Фридмана χ²=0.40, незначимо). Инструменты различаются только ценой: разброс ~3×, выигрывают самые «немногословные». graphlens здесь ничем не примечателен — крепкий середняк.
Вот ровно ту историю рассказал бы стенд, измеряющий только точечные запросы: «структурные инструменты — приятно, но grep почти справляется, а самый дешёвый ответ даёт codegraph». И это была бы неполная правда.
HARD — 6 задач на радиус поражения и неоднозначность
|
Инструмент |
точность |
заверш. |
токены |
вызовы |
$/задача |
сек |
|---|---|---|---|---|---|---|
|
filesystem |
0.71 |
83% |
12596 |
27 |
$0.424 |
165 |
|
graphlens |
0.84 |
100% |
748 |
1 |
$0.018 |
9 |
|
serena |
0.85 |
98% |
1368 |
5 |
$0.065 |
29 |
|
codegraph |
0.93 |
100% |
1114 |
2 |
$0.036 |
16 |
А вот тут инструменты расходятся.
grep схлопывается. Самая низкая точность (0.71), до финиша доходит лишь 83% прогонов (остальные упираются в потолок 50 ходов), а те, что доходят, стоят в 10–23 раза дороже (~$0.42 против $0.018–0.065) и работают в 10–18 раз дольше (~165 секунд против 9–29). Текстовый поиск тонет в шуме, когда вопрос — «все вызовы вот этого» или «какой именно из десятка одноимённых методов».
Структурные инструменты держатся дёшево и точно. И вот ключевое: graphlens — середняк на простых задачах — здесь самый дешёвый ($0.018) и самый быстрый (9 секунд). Его семантический граф наконец окупается: один вызов вместо двадцати семи. Самым точным оказывается codegraph (0.93). serena конкурентна (0.85).
То есть тот самый graphlens, который на точечных запросах выглядел невзрачно, в режиме реальной работы — про анализ влияния, про рефакторинг — становится самым экономичным. Ранжирование инвертируется между режимами.
Заметка о честности стенда. MCP-ресурсы отключены для всех рук. graphlens — единственный сервер, выставлявший ресурсы, и в одном из ранних прогонов агент уходил в их перечисление и раздувал стоимость ~на 24%, пока я это не запретил. Все цифры выше — с чистого перепрогона.
Где уходят деньги: механизм — это round-trips
Разница в цене — это в основном число обращений к инструменту, а оно вытекает из того, как сервер нарезает свои примитивы.
На простом «символ → файл» (where_defined) всем хватает одного вызова. Разрыв открывается на запросах об отношениях — наследование, роут → обработчик, межъязыковые связи. Здесь graphlens цепочкой дёргает мелкозернистые примитивы (find → neighbors → references), а codegraph упаковывает «исходник + пути вызовов за один заход» (explore / node).
Это не разница в том, что знает граф — графы знают примерно одно. Это разница в гранулярности API: меньше round-trips → дешевле и быстрее. Вот откуда у codegraph преимущество в эффективности на простых задачах, и вот почему grep на сложных задачах разоряется — он делает 27 обращений там, где графу хватает одного-двух.
Взаимодействие модель × инструмент: ранжирование плывёт от цены модели
Это самая неочевидная часть. Возьмём медианную $/задача (по обоим режимам) в разрезе модели:
|
Инструмент |
haiku |
sonnet |
opus |
|---|---|---|---|
|
filesystem |
$0.053 |
$0.080 |
$0.087 |
|
graphlens |
$0.020 |
$0.041 |
$0.046 |
|
serena |
$0.026 |
$0.033 |
$0.042 |
|
codegraph |
$0.023 |
$0.041 |
$0.031 |
Ранжирование по дешевизне внутри каждой модели:
-
haiku: graphlens $0.020 < codegraph $0.023 < serena $0.026 < filesystem $0.053
-
sonnet: serena $0.033 < graphlens $0.041 < codegraph $0.041 < filesystem $0.080
-
opus: codegraph $0.031 < serena $0.042 < graphlens $0.046 < filesystem $0.087
Смотрите, что происходит с graphlens. На haiku он самый дешёвый из всех. На opus он становится самым дорогим из структурных инструментов (хотя всё ещё дешевле grep).
Механизм: результаты graphlens токеноёмкие — окрестности графа, списки ссылок. На дешёвой модели этот многословный контекст почти бесплатен, на дорогой — opus тарифицирует те же токены кратно выше, и многословность бьёт по карману. serena и codegraph дёшевы на любой модели, потому что возвращают точечные результаты — они устойчивы к выбору модели, а graphlens нет.
Отсюда практический вывод, который дороже всех остальных: дешёвая модель на структурном инструменте бьёт дорогую модель на grep. codegraph + haiku (~$0.023, точность ~0.99) делает filesystem + opus (~$0.087, точность 0.93) по всем осям сразу.
Гипотеза, которая не подтвердилась
Я закладывал пару xlang_link как стресс-тест: TS-вызов резолвится в Python-обработчик через границу /api/v1/..., и я был уверен, что одноязычные инструменты на этом споткнутся.
Не споткнулись. Все руки, включая grep, решили обе межъязыковые задачи. Агент сам перешагивает границу — независимо от провайдера контекста. На этом наборе гипотеза не подтвердилась, и я докладываю это ровно так же громко, как и подтвердившиеся выводы. Бенчмарк, который рапортует только то, что хотелось увидеть, — это не бенчмарк.
Статистика честно
Критерий Фридмана по четырём инструментам, по блокам-задачам, внутри каждого режима (df=3; критические значения: 0.05 → 7.82, 0.01 → 11.34):
SIMPLE: точность n=20 χ²= 0.40 (н.з.) — ничья стоимость n=20 χ²=18.42 (p<.01) — serena < codegraph < graphlens < filesystemHARD: точность n= 6 χ²= 3.50 (н.з.) — недостаточно мощности стоимость n= 6 χ²=11.80 (p<.01) — graphlens < codegraph < serena < filesystem
Что отсюда честно сказать:
-
Разница в стоимости значима в обоих режимах (p<.01). На HARD graphlens достоверно самый дешёвый, grep достоверно самый дорогой. Это твёрдый результат.
-
Разница в точности на HARD большая, но статистически незначима при n=6 (χ²=3.50). Это сильный описательный сигнал, но ещё не доказанный. Шесть задач — мало.
-
Чтобы укрепить вывод про точность, нужно добавить сложных задач, а не убирать простые. Урезание простого режима не даёт сложному ни капли мощности — оно лишь выбрасывает хорошие данные.
Я специально оставляю это в статье. Соблазн написать «graphlens/codegraph точнее grep, доказано» велик, но n=6 этого не вытягивает, и притворяться было бы нечестно.
Амортизация индекса: разные валюты
Структурные инструменты строят индекс один раз — это чистая статика, ноль LLM-токенов, только wall-clock:
|
Инструмент |
разовая индексация |
|---|---|
|
filesystem |
0 с |
|
codegraph |
48 с |
|
graphlens |
84 с |
|
serena |
94 с |
grep не платит вперёд ничего, но платит больше за каждый запрос. Это разные валюты (секунды против $/токенов), поэтому никакой единой «точки безубыточности» я не рисую — это была бы натяжка. Картина простая: индекс — разовая трата времени без единого токена, а экономия $/задача капает на каждой задаче. На длинной сессии структурные инструменты амортизируются; на паре разовых запросов нулевой сетап grep может выиграть по «времени до первого ответа».
Выводы для бизнес-кейса
Вернёмся к исходному вопросу: чем кормить агента на большом проекте?
Ответа «вот этот инструмент всегда лучший» — нет. Есть ответ «зависит от того, какую работу вы поручаете»:
-
Разовые точечные справки («где определён класс», «от чего наследуется»): берите что угодно. grep справляется, точность та же, нулевой сетап. Платите вы тут разве что небольшим overhead’ом в токенах.
-
Постоянная работа с анализом влияния — рефакторинг, оценка радиуса поражения, разрешение неоднозначностей на большой кодовой базе: структурные инструменты режут стоимость в 10–23 раза и латентность в 10–18 раз против grep — и, что не менее важно, не упираются в потолок ходов. grep на этих задачах не просто дорог, он в 17% случаев вообще не доходит до ответа.
-
Выбор модели взаимодействует с выбором инструмента. Многословный граф дёшев на маленькой модели и дорог на большой. Если гоняете opus — берите инструмент с точечной отдачей (codegraph, serena). Если haiku — graphlens внезапно самый дешёвый.
-
Самая дешёвая комбинация — не «дорогая модель + простой инструмент», а «дешёвая модель + структурный инструмент».
И честные оговорки, без которых выводы нельзя переносить на ваш проект:
Один репозиторий (
apache/superset@ 6.0.0), один стенд, 26 задач (20 простых / 6 сложных). Режимы докладываются раздельно и никогда не смешиваются.cost_usd— API-эквивалент, а не счёт по подписке. Провал = точность 0. Это не универсальный рейтинг — это воспроизводимый замер на конкретном кейсе.
Где здесь graphlens
Раз уж это продолжение статьи про него — скажу прямо. Этот бенчмарк не доказывает, что graphlens «лучший». Он показывает конкретный режим, в котором его структурный граф окупается (анализ влияния, дёшево и быстро на дешёвых моделях), и так же прямо показывает, где он проседает (на opus его многословная отдача дороже, чем у codegraph и serena; codegraph точнее на сложных задачах).
Для меня это полезнее любой победной реляции. graphlens задумывался как движок и точная мультиязычная модель графа, а не как готовое приложение. Бенчмарк ровно это и подтверждает: на структурных вопросах граф бьёт текстовый поиск с большим запасом, и одновременно есть куда расти — гранулярность MCP-инструментов (меньше round-trips, как у codegraph) и компактность отдачи (чтобы не разоряться на дорогих моделях). Это мой следующий пункт работ, и он теперь подкреплён числами, а не интуицией.
Воспроизвести
Весь стенд и сырые данные — открыты. Прогон полностью детерминированно собирается из data/.
-
Репозиторий бенчмарка: https://github.com/Neko1313/agent-context-bench
-
Смотреть
metrics.ipynb(все графики и постатейная статистика) иREADME.md(методология). -
uv runmain.pyгоняет весь пайплайн (клонирование superset → сборка индексов → 936 прогонов, resumable в рамках лимитов подписки), дальше открываетеmetrics.ipynb.
Если у вас есть свой большой проект и желание прогнать стенд на нём — буду рад issue и результатам. Чем больше независимых прогонов на разных кодовых базах, тем ближе мы к ответу, который можно переносить, а не «работает на superset».
ссылка на оригинал статьи https://habr.com/ru/articles/1051504/