# Octocode 0.18.0: воспроизводимый бенчмарк качества поиска и GraphRAG-расширение на уровне файлов

> Octocode 0.18.0 включает воспроизводимый бенчмарк семантического поиска по коду — 127 выверенных запросов с эталоном по диапазонам строк, плюс harness Loc-Bench на 560 реальных GitHub-issue — и добавляет GraphRAG-расширение на уровне файлов, которое подтягивает структурно связанные файлы в поиск перед реранкингом. Open source, local-first, Apache-2.0.

[0.17.1 позволила запускать каждый эмбеддинг, который генерирует Octocode, на железе, которым управляете вы](/blog/octocode-0-17-1-local-embedding-providers) — любой OpenAI-совместимый сервер, любая модель, и ваш код никогда не покидает вашу сеть. Тот релиз был про то, _где_ работает поиск.

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

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

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

---

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

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

Поэтому 0.18.0 включает настоящий бенчмарк. Он живёт в [`benchmark/`](https://github.com/Muvon/octocode/tree/master/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`](https://github.com/Muvon/octocode/tree/master/benchmark).

---

## Числа: локальный пол без 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`](https://github.com/Muvon/octocode/tree/master/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**, вытягивает горсть блоков из этих соседних файлов и передаёт _расширенный_ набор кандидатов реранкеру:

```rust
// 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`:

```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`](https://github.com/Muvon/octocode/tree/master/benchmark) оценивает 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:

```bash
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`](https://github.com/Muvon/octocode/tree/master/benchmark) прогоняет несколько по **одному общему индексу** — так что единственное, что меняется между строками, это параметры на этапе поиска, а не корпус и не эмбеддинги. Он пишет сравнительную таблицу в `RESULTS.md` (и сырой `RESULTS.json`) и накапливает результаты между прогонами, так что точечный повторный запуск вливается в существующую таблицу.

```bash
# Сначала зафиксируйте корпус на коммите эталона — иначе диапазоны строк уплывут
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 в соответствии с реальным набором инструментов, чтобы реестры и клиенты видели правильную схему. Плюс обычное обслуживание зависимостей и тулинга — без изменений в поведении.

---

## Обновление

```bash
# 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](https://github.com/Muvon/octocode) — бенчмарк, эталон и сырые результаты включены. Это движок поиска по коду за [Octomind](https://octomind.run), и теперь качество его поиска — это то, что вы можете измерить сами, а не просто поверить нам на слово.
