Сколько стоит контекст для кодового агента: grep vs граф vs LSP на большом проекте (936 прогонов). AI для разработки.. AI для разработки. claude code.. AI для разработки. claude code. codegraph.. AI для разработки. claude code. codegraph. graphlens.. AI для разработки. claude code. codegraph. graphlens. LLM-агенты.. AI для разработки. claude code. codegraph. graphlens. LLM-агенты. lsp.. AI для разработки. claude code. codegraph. graphlens. LLM-агенты. lsp. mcp.. AI для разработки. claude code. codegraph. graphlens. LLM-агенты. lsp. mcp. статический анализ кода.

Продолжение статьи про 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-сервер)

Шаг индексации

filesystem

@modelcontextprotocol/server-filesystem (read_file + grep)

нет

graphlens

граф graphlens поверх MCP

graphlens analyze

serena

Serena (LSP)

прогрев LSP-воркспейса

codegraph

конкурент на графах

codegraph init

Важная деталь честности стенда: встроенные инструменты 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

haiku

claude-haiku-4-5

sonnet

claude-sonnet-4-6

opus

claude-opus-4-8

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

Итого: 4 руки × 3 модели × 26 задач × 3 сида = 936 прогонов (на стеке Claude Code 2.1.187).

Что я считаю честным замером

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

  1. Эталонные ответы выверены руками по исходникам на теге 6.0.0 (каждая задача несёт ссылку file:line). Принципиально: эталон не генерируется ни одним из тестируемых инструментов (ни ty, ни pyright, ни самим graphlens). Иначе сравнение смещено в пользу того, чьим выводом мы размечали. Эталонные множества для set-задач выверены независимым оракулом — питоновским ast.

  2. У «наивной» руки есть руки. filesystem — это grep + read, а не «агент без инструментов». «Наивно» ≠ «без рук».

  3. Стоимость индексации меряется отдельно, один раз. grep не платит за индекс ничего, граф — амортизирует. Смешивать эти валюты нельзя.

  4. Детерминизма нет. temperature=0 у этих моделей не детерминирует вывод. Поэтому 3 сида, и в отчёте — медиана, а не среднее.

  5. Записаны версии моделей и каждого MCP-сервера, снимок цен и дата.

  6. cost_usd — это API-эквивалент, а не ваш счёт. Подписка — flat-rate, так что cost_usd (его отдаёт CLI) — это сколько те же токены стоили бы по API. Это не ваш реальный чек, но это корректная относительная метрика $/задача для сравнения рук между собой.

  7. Прогоны в чистой комнате — токены отражают только системный промпт + инструменты MCP-руки, без вашего личного конфига.

  8. Использовать инструмент обязательно. Системный промпт запрещает отвечать по памяти; прогон, не сделавший ни одного вызова инструмента, перезапускается (а упорный отказ помечается __NO_TOOLS__). Ответ «из головы» про известный репозиторий не измерял бы провайдера контекста.

И отдельно — провал засчитывается как точность 0. Если grep упёрся в потолок 50 ходов и не выдал ответ — это не «нет данных», это «инструмент не справился в рамках бюджета». Так и считаем.

Задачи: два режима, и почему их нельзя смешивать

26 задач делятся на два класса.

SIMPLE — 20 точечных запросов («где определён X / от чего наследуется X»). Ответ — одна точка, проверяется вхождением подстроки:

Тип

#

Что проверяет

where_defined

7

Python-класс → файл определения

inherits_from

5

Python-класс → базовый класс

abstract_methods

1

ABC → его абстрактные методы

ts_where_defined

1

TS-хук → файл определения

ts_route_call

4

роут /api/v1/... → TS-хук, который его дёргает

xlang_link

2

TS-потребитель → Python-обработчик через границу API

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

Тип

#

Что проверяет

Оценка

disambiguate

2

неоднозначное голое имя метода (напр. cache_key, определён во многих классах) → тот самый класс

подстрока

overrides_count

2

полный набор подклассов, переопределяющих метод базы

F1 по множеству

impact_set

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 цепочкой дёргает мелкозернистые примитивы (findneighborsreferences), а 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 < filesystem

HARD:
  точность  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 run main.py гоняет весь пайплайн (клонирование superset → сборка индексов → 936 прогонов, resumable в рамках лимитов подписки), дальше открываете metrics.ipynb.

Если у вас есть свой большой проект и желание прогнать стенд на нём — буду рад issue и результатам. Чем больше независимых прогонов на разных кодовых базах, тем ближе мы к ответу, который можно переносить, а не «работает на superset».

Автор: Neko1313

Источник