0.17.1 позволила запускать каждый эмбеддинг, который генерирует Octocode, на железе, которым управляете вы — любой OpenAI-совместимый сервер, любая модель, и ваш код никогда не покидает вашу сеть. Тот релиз был про то, где работает поиск.

0.18.0 — про то, насколько поиск на самом деле хорош, и про то, как доказать это числами, которые вы можете воспроизвести.

Большинство инструментов поиска по коду заявляют о качестве. Очень немногие выпускают бенчмарк, который вы можете запустить сами. В этом релизе их два: выверенный бенчмарк поиска против собственного исходного кода Octocode и harness Loc-Bench на 560 реальных GitHub-issue. Также добавлено GraphRAG-расширение на уровне файлов — шаг поиска, который подтягивает структурно связанные файлы до того, как реранкер их оценит.

Вот что изменилось и что говорят числа — включая то, что не сработало.


Зачем вообще бенчмаркить поиск по коду?

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

Поэтому 0.18.0 включает настоящий бенчмарк. Он живёт в benchmark/ в репозитории и измеряет весь пайплайн Octocode от начала до конца: чанкинг → эмбеддинг → векторный поиск → гибридное слияние → реранкинг.

Эталон (ground truth)

code.csv содержит 127 запросов, каждый размечен 1–3 правильными местами в коде в виде диапазонов строк:

query,result1,result2,result3

Каждый результат — это src/path/file.rs:start-end:relevance, где релевантность 2 — первичная (прямо отвечает на запрос), а 1 — вторичная (полезный контекст). Результат поиска засчитывается по пересечению диапазонов строк — попадание на строках 40–90 совпадает с эталоном 45–92, потому что диапазоны пересекаются.

Корпус зафиксирован на одном коммите (b1771ba). Эталон размечен против именно этих номеров строк, поэтому индексация любого другого checkout сдвинула бы диапазоны и тихо сломала бы оценки. Сначала зафиксируйте, потом индексируйте.

Как был построен эталон — важно, потому что небрежный «ключ ответов» заставит любой бенчмарк лгать:

  1. Кандидаты, сгенерированные ИИ по всем основным модулям и файлам документации.
  2. Проверка по исходнику — каждый упомянутый файл и диапазон строк был прочитан и сверен с реальным кодом.
  3. Мульти-агентная валидация — параллельные агенты независимо перепроверили диапазоны строк, пути и степени релевантности.
  4. Поправки по итогам поиска — если поиск находил ту же логику в валидном альтернативном месте, это место добавляли как вторичный результат, а не удаляли.

Сложные запросы

Последние 27 из 127 нарочно состязательные: естественный язык, не повторяющий ни имя функции, ни комментарий. Они проверяют семантическое понимание, а не везение с ключевыми словами. Несколько:

Смысл запроса Почему это сложно
Предотвращение бесконечных циклов в перекрывающихся чанках защита в 5 строк, в запросе нет кодовых ключевых слов
Пропуск индексации, когда репозиторий не менялся спрятано на 100+ строк внутри функции в 900 строк
Грубая оценка токенов без токенизатора ответ — одна строка: s.len() / 4
Предотвращение параллельной переиндексации в MCP-сервере AtomicBool + compare_exchange, паттерн конкурентности

Если стандартные запросы набирают около 1.0, а сложные отстают, бенчмарк делает свою работу: он вскрывает реальные слабости поиска, а не льстит инструменту.

Метрики

Четыре стандартные IR-метрики, везде «больше — лучше»:

  • Hit@k — попал ли хоть один правильный результат в топ-k? (покрытие)
  • MRR — обратный ранг первого правильного результата. (насколько высоко первое попадание?)
  • NDCG@10 — вознаграждает за то, что самые релевантные результаты стоят выше, учитывая степень релевантности и позицию. (качество ранжирования)
  • Recall@k — какую долю из всех эталонных блоков нашли? (полнота)

Определения и разобранный пример — в benchmark/README.md.


Числа: локальный пол без API-ключа

Эти результаты используют полностью локальный стек без API-ключаjina-embeddings-v2-base-code через fastembed, без реранкера. Это делает их полом, а не потолком: платный реранкер, понимающий код, поднимает выше. Запуск против собственного исходного кода Octocode, 127 запросов по коду:

Конфигурация Hit@5 Hit@10 MRR NDCG@10 Recall@10
Только плотный вектор 0.598 0.717 0.485 0.528 0.671
Гибрид, веса RRF по умолчанию (0.7/0.3) 0.598 0.717 0.485 0.528 0.671
Гибрид, со сдвигом к keyword (0.3/0.7) 0.732 0.835 0.572 0.620 0.807

Бросаются в глаза две вещи.

Веса RRF по умолчанию ведут себя как «только вектор». При слиянии со сдвигом 0.7 в сторону плотного сигнала векторные результаты доминируют, а keyword-сигнал почти не учитывается — две верхние строки идентичны.

Сдвиг к keyword — это большой и бесплатный выигрыш для кода. Переверните веса на 0.3/0.7 — дайте сигналу BM25/keyword вести — и Hit@5 подскакивает на +22%, а Recall@10 на +20%, без дополнительных затрат. Почему? Код полон точных идентификаторов — compare_exchange, get_code_blocks_by_path, RelationshipDirection — и точное совпадение токенов имеет непропорционально большой вес, когда то, что вы ищете, и есть токен. Чисто семантическое сходство размывает это; слияние с keyword обостряет.

Такой результат находишь только измеряя. Не очевидно, что для кода вы хотите, чтобы keyword перевешивал эмбеддинг.


Что не помогло (и почему это в бенчмарке)

Бенчмарк, который сообщает только о победах, — это маркетинг. Этот сообщает и о регрессах — полная матрица из 6 вариантов лежит в benchmark/RESULTS.md.

Универсальный реранкер сделал хуже. Добавление локального cross-encoder общего назначения (bge-reranker-base) поверх лучшей конфигурации не помогло — он откатил Hit@5 с 0.732 обратно до 0.598, перечеркнув весь выигрыш от настройки keyword. И он был примерно в 4× медленнее (≈1900с против ≈490с за прогон).

Урок не в том, что «реранкеры — это плохо». Он в том, что поиску по коду нужен реранкер, понимающий код. Реранкер, обученный на прозе, оценивает fn verify_hmac против «валидации подписи вебхука» хуже, чем уже сделало гибридное слияние. Реранкер, настроенный на код, вроде voyage:rerank-2.5, — другое дело, но это нужно проверять на собственном eval, а не предполагать. Именно бенчмарк это и позволяет.


GraphRAG-расширение на уровне файлов

Главная фича рядом с бенчмарком. И плотный, и keyword-поиск оценивают блоки изолированно — они не знают, что нужный вам файл импортирует найденный файл или вызывает функцию, определённую по соседству. GraphRAG-расширение на уровне файлов закрывает этот пробел.

После того как первоначальный гибридный поиск выдаёт ранжированных кандидатов, Octocode берёт самые сильные, обходит граф GraphRAG в поисках файлов, структурно связанных через imports, calls или extends, вытягивает горсть блоков из этих соседних файлов и передаёт расширенный набор кандидатов реранкеру:

// Seeds are already RRF-ranked; only expand from the strongest few.
const SEED_LIMIT: usize = 10;        // expand from top 10 hits
const MAX_NEIGHBOR_FILES: usize = 8; // pull at most 8 related files
const BLOCKS_PER_FILE: usize = 3;    // 3 blocks per neighbor

Соседи оцениваются по weight × confidence по всем seed-файлам, самые сильные связи первыми. Это на уровне файлов, потому что граф на уровне файлов (CodeNode.id == относительный путь). И это best-effort: любая ошибка графа деградирует до пропуска исходных результатов напрямую — поиск никогда не ломается.

Включите в config.toml:

[search]
graph_expansion = false  # default; requires [graphrag] enabled

Поставляется выключенным по умолчанию — намеренно. Вот честная часть из бенчмарка:

+graph = GraphRAG-расширение на уровне файлов, которое сдвигает результаты только после того, как реранкер заново оценит расширенный набор.

Без реранкера расширение добавляет кандидатов, но не меняет ранжирование — поэтому hybrid_30_70 и hybrid_30_70+graph дают одинаковые оценки в локальной матрице без ключа выше. Отдача от расширения приходит, когда реранкер (понимающий код) заново оценивает увеличенный пул и продвигает структурно связанный файл, который чистое сходство ранжировало слишком низко. Наивное расширение может и размыть точность. Поэтому совет, зашитый в комментарий конфига, прямой: сделайте A/B на собственном eval, прежде чем доверять. Бенчмарк существует именно для этого.


Loc-Bench: issue → код, ничего не размечено нами

Выверенный бенчмарк размечаем мы. Справедливый вопрос: не слишком ли запросы дружелюбны к нашему собственному инструменту? 0.18.0 включает второй harness, который полностью убирает нас из уравнения.

benchmark/locbench.py оценивает Octocode на Loc-Bench V1 (czlll/Loc-Bench_V1): 560 реальных GitHub-issue из популярных Python-репозиториев. Запрос — это оригинальный текст issue. Эталон — набор файлов и функций, которые реальный смёрдженный фикс действительно изменил (выверенное поле edit_functions датасета). Ничего не размечено нами — это стандартная индустриальная задача «по баг-репорту найди код, который надо менять».

На каждый экземпляр harness клонирует репозиторий (blobless, с кэшем), делает checkout базового коммита, запускает octocode index, ищет по тексту issue и оценивает найденные файлы против реального фикса — как на уровне файлов (hit@k, recall@k, MRR, accuracy), так и на уровне hunk (пересёкся ли найденный результат со строками, которые патч реально тронул?), с разбивкой по категориям issue:

python3 benchmark/locbench.py --limit 10 --verbose   # smoke test
python3 benchmark/locbench.py                         # full 560
python3 benchmark/locbench.py --category "Bug Report"

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


Матрица вариантов

score.py измеряет одну конфигурацию. run_matrix.py прогоняет несколько по одному общему индексу — так что единственное, что меняется между строками, это параметры на этапе поиска, а не корпус и не эмбеддинги. Он пишет сравнительную таблицу в RESULTS.md (и сырой RESULTS.json) и накапливает результаты между прогонами, так что точечный повторный запуск вливается в существующую таблицу.

# Сначала зафиксируйте корпус на коммите эталона — иначе диапазоны строк уплывут
git worktree add /tmp/corpus b1771ba

# Полная локальная матрица (fastembed, без API-ключа): только-вектор vs перебор гибридных весов
CORPUS=/tmp/corpus python3 benchmark/run_matrix.py

# Добавьте локальный cross-encoder реранкер (всё ещё без ключа) — переиспользует тот же индекс
CORPUS=/tmp/corpus SKIP_INDEX=1 ONLY=rerank \
  RERANK_MODEL=fastembed:bge-reranker-base python3 benchmark/run_matrix.py

Варианты: vector_only, hybrid_70_30 (веса по умолчанию), hybrid_30_70 (сдвиг к keyword), +graph (GraphRAG-расширение) и +rerank (cross-encoder). Если задать VOYAGE_API_KEY, варианты с платным реранкером, понимающим код, включаются автоматически — так вы находите реальный потолок над локальным полом.


Всё остальное

Обновлены метаданные реестра MCP и схема сервера. Это держит опубликованный дескриптор MCP-сервера Octocode в соответствии с реальным набором инструментов, чтобы реестры и клиенты видели правильную схему. Плюс обычное обслуживание зависимостей и тулинга — без изменений в поведении.


Обновление

# Homebrew
brew upgrade muvon/tap/octocode

# Универсальный установщик
curl -fsSL https://raw.githubusercontent.com/Muvon/octocode/master/install.sh | sh

# Cargo
cargo install octocode --version 0.18.0

Ничто в этом релизе не меняет ваш индекс или конфиг — обновляться безопасно, и ваша текущая настройка продолжает работать. Две вещи, которые стоит попробовать:

  1. Сдвиньте веса слияния к keyword. Если вы ищете по коду больше, чем по прозе, бенчмарк говорит, что RRF с упором на keyword — это бесплатный выигрыш в точности. Измерьте это на своём репозитории.
  2. Запустите бенчмарк. Склонируйте Octocode, зафиксируйте корпус и прогоните run_matrix.py против собственной модели эмбеддингов и реранкера. Числа выше — это локальный пол; найдите свой потолок.

Чтобы попробовать GraphRAG-расширение, включите [graphrag], поставьте search.graph_expansion = true, соедините его с реранкером, понимающим код, и сделайте A/B против своего baseline, прежде чем оставлять включённым.


Octocode — open source (Apache 2.0) на github.com/Muvon/octocode — бенчмарк, эталон и сырые результаты включены. Это движок поиска по коду за Octomind, и теперь качество его поиска — это то, что вы можете измерить сами, а не просто поверить нам на слово.