Мы много вложили в точный граф кода: вызовы, ссылки на типы, наследование и реализации интерфейсов, и у каждой связи есть точное место в исходнике. На корпусах, где эталон строит компилятор, точность графа около 0,98 (сравнение с открытыми индексаторами).
Потом мы отдали граф нашему исследовательскому агенту и измерили, что изменилось. На репозитории среднего размера почти ничего. На большом и более сложном граф помог заметнее. А главной проблемой оказалось не построить граф, а добиться, чтобы агент им пользовался. Здесь многое зависит от модели.
Как мы мерили
Мы взяли RepoContextBench v2: 40 вопросов по двум зафиксированным репозиториям, ответ на каждый нужно подтвердить кодом.
Во всех конфигурациях работал один и тот же исследовательский агент CodeAlive на одном индексе, оценивал один и тот же судья (Muse Spark 1.3, два голоса на задачу). Менялось только то, как граф связей попадал к модели. В большинстве прогонов отвечал DeepSeek V4.1 Flash. Шкала от 0 до 100.
Сразу оговорка. Hard отличается от Lite не только размером: вопросы там сложнее, языки другие. Поэтому корректно говорить «на большой и более сложной кодовой базе граф помогает сильнее», а не «прирост дал именно размер». Повтор одного и того же прогона сдвигал оценку примерно на ±2 пункта, всё, что меньше, мы считаем шумом.
Мы дали агенту инструмент для графа, и он его ни разу не вызвал
Сначала граф был отдельным инструментом, get_artifact_relationships. Он возвращает вызывающих и вызываемых, реализации и ссылки для символа.
У DeepSeek V4.1 Flash этот инструмент был доступен во всех 40 задачах. Он не вызвал его ни разу. Когда мы убрали инструмент совсем, оценка не упала: 62,2 без него против 59,4 с ним, в пределах шума.
Тогда мы стали подталкивать модель к графу:
Почему так, стало понятно из рассуждений модели в этих точках. Ей нужны литеральные факты: сигнатуры, имена свойств, ключевые слова. Поиск и чтение файлов дают ровно это, поэтому граф она даже не рассматривает. А практические вопросы чаще звучат как «как работает X», а не «кто вызывает X». Подсказки в результатах инструментов хорошо работали у нас для другого поведения, но это не изменили.
Всё зависит от модели
Те же задачи на других моделях:
Более сильная модель сама пошла в граф, причём на большом репозитории в пять раз чаще. Для Luna наличие инструмента дало около 4–5 пунктов на обоих треках. Это примерно два уровня шума, и по одному прогону на конфигурацию.
Если агент не спрашивает, кладём граф туда, где он и так читает
Код модель стабильно читает через fetch_artifacts. Мы встроили граф вызовов прямо в этот ответ: рядом с кодом функции агент теперь видит, кто её вызывает и что вызывает она.
Граф модель теперь видела почти в каждой задаче. Оставалось понять, помогает ли он:
На Lite разница в пределах шума. На Hard агент собрал заметно больше нужного кода, плюс 4–5 пунктов по каждой метрике поиска. Итоговый ответ вырос меньше, на 1,2 пункта, это ниже нашего порога шума.
Лишний контекст мешает
Ещё мы попробовали добавить к графу точные позиции каждого вызова. Выглядело полезно: агент мог бы сразу перейти к нужной строке.
Стало хуже: минус 6,2 пункта на Hard и минус 3,5 на всех 40 задачах. Агенту показали 2 276 позиций вызовов, и он не процитировал ни одной. Они заняли место в контексте и ничем не помогли.
Что мы из этого вынесли
Прежде чем сравнивать оценки, посчитайте вызовы графа в трассах. Инструмент, который модель не вызывает, помочь не может, а по списку доступных инструментов этого не видно.
Если модель не просит граф, отдавайте его в том ответе, который она и так читает. У нас это fetch_artifacts: вызывающие и вызываемые рядом с кодом улучшили поиск на большом репозитории. А позиции вызовов в том же месте ухудшили ответы, так что в выдаче по умолчанию им не место.
На репозитории среднего размера поиск и так находит почти всё, что добавил бы граф. Мы ждём от графа пользы на больших кодовых базах с плотными связями. Чтобы это честно измерить, в бенчмарке нужны вопросы о вызывающих и о последствиях изменений, а в нашем текущем наборе их мало.
И делайте повторы. Шум между прогонами у нас ±2 пункта, и дважды промежуточный результат на 6–11 задачах показывал лидера, который проигрывал на полном наборе.
Гораздо сильнее графа на результат повлияла сама обвязка агента. На той же модели harness CodeAlive набрал на 18,7 пункта больше, чем универсальный агент для кода: Одна модель, разные harness: CodeAlive против OpenCode.