0.17.1 te permitió ejecutar cada embedding que genera Octocode en hardware que tú controlas — cualquier servidor compatible con OpenAI, cualquier modelo, sin que tu código salga nunca de tu red. Esa versión iba sobre dónde se ejecuta la búsqueda.

0.18.0 va sobre qué tan buena es realmente la búsqueda — y demostrarlo con números que puedes reproducir.

La mayoría de las herramientas de búsqueda de código presumen calidad. Muy pocas lanzan un benchmark que puedas ejecutar tú mismo. Esta versión incluye dos: un benchmark de recuperación curado contra el propio código fuente de Octocode, y un harness de Loc-Bench sobre 560 issues reales de GitHub. También añade expansión GraphRAG a nivel de archivo — un paso de búsqueda que trae archivos estructuralmente relacionados antes de que el reranker los puntúe.

Esto es lo que cambió, y lo que dicen los números — incluyendo las partes que no funcionaron.


¿Por Qué Hacer un Benchmark de Búsqueda de Código?

Cuando cambias una heurística de chunking o un peso de fusión, quieres saber si la recuperación mejoró o empeoró. Mirar a ojo unas pocas consultas no te lo dice — solo confirma lo que ya creías. La única respuesta honesta es un conjunto fijo de consultas, un ground truth fijo, y un número que se mueve.

Así que 0.18.0 incluye uno de verdad. Vive en benchmark/ en el repositorio, y mide el pipeline completo de Octocode de principio a fin: chunking → embedding → búsqueda vectorial → fusión híbrida → reranking.

El ground truth

code.csv contiene 127 consultas, cada una anotada con 1–3 ubicaciones de código correctas como rangos de líneas:

query,result1,result2,result3

Cada resultado es src/path/file.rs:start-end:relevance, donde la relevancia 2 es primaria (responde directamente a la consulta) y 1 es secundaria (contexto útil). Un resultado de búsqueda coincide por superposición de rangos de líneas — un acierto en las líneas 40–90 coincide con el ground truth 45–92 porque los rangos se intersecan.

El corpus está fijado a un único commit (b1771ba). El ground truth está anotado contra esos números de línea exactos, así que indexar cualquier otro checkout desviaría los rangos y rompería silenciosamente las puntuaciones. Fija primero, luego indexa.

Cómo se construyó el ground truth importa, porque una clave de respuestas descuidada hace que cualquier benchmark mienta:

  1. Candidatos generados por IA a través de cada módulo principal y archivo de documentación.
  2. Verificación de fuente — cada archivo y rango de líneas referenciado fue leído y comprobado contra el código real.
  3. Validación multi-agente — agentes en paralelo re-verificaron de forma independiente los rangos de líneas, las rutas y los grados de relevancia.
  4. Correcciones informadas por la búsqueda — si la búsqueda encontró la misma lógica en una ubicación alternativa válida, esa ubicación se añadió como resultado secundario, no se eliminó.

Las consultas difíciles

Las últimas 27 de las 127 son deliberadamente adversariales: lenguaje natural que no refleja ningún nombre de función ni comentario. Prueban la comprensión semántica, no la suerte con palabras clave. Algunas:

Intención de la consulta Por qué es difícil
Evitar bucles infinitos en chunks superpuestos una guarda de 5 líneas, sin palabras clave de código en la consulta
Saltarse la indexación cuando el repo no cambió enterrado 100+ líneas dentro de una función de 900 líneas
Estimación aproximada de tokens sin un tokenizer la respuesta es una sola línea: s.len() / 4
Evitar reindexación concurrente en el servidor MCP AtomicBool + compare_exchange, un patrón de concurrencia

Si las consultas estándar puntúan cerca de 1.0 pero las difíciles se quedan atrás, el benchmark está haciendo su trabajo: expone debilidades reales de recuperación en lugar de halagar a la herramienta.

Las métricas

Cuatro métricas de IR estándar, todas "más alto es mejor":

  • Hit@k — ¿apareció algún resultado correcto en el top k? (cobertura)
  • MRR — rango recíproco del primer resultado correcto. (¿qué tan alto está el primer acierto?)
  • NDCG@10 — recompensa poner los resultados más relevantes más arriba, ponderando grado de relevancia y posición. (calidad del ranking)
  • Recall@k — ¿qué fracción de todos los bloques del ground truth se encontró? (completitud)

Las definiciones y un ejemplo resuelto están en benchmark/README.md.


Los Números: Un Piso Local, Sin API Key

Estos resultados usan el stack totalmente local, sin API keyjina-embeddings-v2-base-code vía fastembed, sin reranker. Eso los convierte en un piso, no en un techo: un reranker de pago consciente del código sube más alto. Ejecutado contra el propio código fuente de Octocode, 127 consultas de código:

Configuración Hit@5 Hit@10 MRR NDCG@10 Recall@10
Solo vector denso 0.598 0.717 0.485 0.528 0.671
Híbrido, pesos RRF por defecto (0.7/0.3) 0.598 0.717 0.485 0.528 0.671
Híbrido, ajustado a keyword (0.3/0.7) 0.732 0.835 0.572 0.620 0.807

Dos cosas saltan a la vista.

Los pesos RRF por defecto se comportan como solo-vector. Con la fusión inclinada 0.7 hacia la señal densa, los resultados vectoriales dominan y la señal de keyword apenas registra — las dos filas superiores son idénticas.

Inclinarse hacia keyword es una ganancia grande y gratis para el código. Da la vuelta a los pesos a 0.3/0.7 — dejando que la señal BM25/keyword lidere — y Hit@5 sube +22% y Recall@10 +20%, sin coste añadido. ¿Por qué? El código está lleno de identificadores exactos — compare_exchange, get_code_blocks_by_path, RelationshipDirection — y la coincidencia exacta de tokens tiene un peso desproporcionado cuando lo que buscas es un token. La similitud puramente semántica difumina eso; la fusión con keyword lo afina.

Es el tipo de resultado que solo encuentras midiendo. No es intuitivo que para el código quieras que la keyword pese más que el embedding.


Lo Que No Ayudó (Y Por Qué Está en el Benchmark)

Un benchmark que solo reporta victorias es marketing. Este reporta también las regresiones — la matriz completa de 6 variantes está en benchmark/RESULTS.md.

Un reranker genérico empeoró las cosas. Poner un cross-encoder local de propósito general (bge-reranker-base) sobre la mejor configuración no ayudó — regresó Hit@5 de 0.732 de vuelta a 0.598, deshaciendo toda la ganancia del ajuste a keyword. Y fue aproximadamente 4× más lento (≈1900s vs ≈490s en la ejecución).

La lección no es "los rerankers son malos". Es que la recuperación de código necesita un reranker consciente del código. Un reranker entrenado en prosa puntúa fn verify_hmac contra "validación de firma de webhook" peor de lo que ya lo hizo la fusión híbrida. Un reranker ajustado a código como voyage:rerank-2.5 es otra historia — pero tienes que verificarlo en tu propio eval, no asumirlo. El benchmark es lo que te lo permite.


Expansión GraphRAG a Nivel de Archivo

La característica estrella junto al benchmark. La búsqueda densa y la de keyword puntúan bloques de forma aislada — no saben que el archivo que quieres importa el archivo que encontraron, o llama a una función definida al lado. La expansión GraphRAG a nivel de archivo cierra esa brecha.

Después de que la búsqueda híbrida inicial produce candidatos rankeados, Octocode toma los más fuertes, recorre el grafo de GraphRAG en busca de archivos estructuralmente relacionados por imports, llamadas o extends, extrae un puñado de bloques de esos archivos vecinos, y entrega el conjunto de candidatos ampliado al reranker:

// 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

Los vecinos se puntúan por weight × confidence a través de todos los archivos seed, los enlaces más fuertes primero. Es a nivel de archivo porque el grafo es a nivel de archivo (CodeNode.id == ruta relativa). Y es best-effort: cualquier error del grafo degrada a pasar los resultados originales directamente — nunca rompe la búsqueda.

Actívalo en config.toml:

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

Se entrega desactivado por defecto, a propósito. Aquí está la parte honesta del benchmark:

+graph = expansión GraphRAG a nivel de archivo, que solo mueve resultados una vez que un reranker vuelve a puntuar el conjunto ampliado.

Sin un reranker, la expansión añade candidatos pero no cambia el ranking — por eso hybrid_30_70 y hybrid_30_70+graph puntúan idéntico en la matriz local sin key de arriba. El beneficio de la expansión llega cuando un reranker (consciente del código) vuelve a puntuar el conjunto más grande y promueve un archivo estructuralmente relacionado que la similitud bruta rankeó demasiado bajo. La expansión ingenua también puede diluir la precisión. Así que la guía grabada en el comentario de configuración es contundente: haz A/B en tu propio eval antes de confiar en ella. El benchmark existe precisamente para que puedas hacerlo.


Loc-Bench: Issue → Código, Nada Auto-Anotado

El benchmark curado lo anotamos nosotros. Una pregunta justa: ¿son las consultas demasiado amables con nuestra propia herramienta? 0.18.0 incluye un segundo harness que nos saca por completo de la ecuación.

benchmark/locbench.py evalúa Octocode sobre Loc-Bench V1 (czlll/Loc-Bench_V1): 560 issues reales de GitHub de repos populares de Python. La consulta es el texto original del issue. El ground truth es el conjunto de archivos y funciones que el fix real ya fusionado editó (el campo curado edit_functions del dataset). Nada está auto-anotado — es la tarea estándar de la industria de "dado un reporte de bug, encuentra el código a cambiar".

Por instancia, el harness clona el repo (blobless, cacheado), hace checkout del commit base, ejecuta octocode index, busca con el texto del issue, y puntúa los archivos recuperados contra el fix real — tanto a nivel de archivo (hit@k, recall@k, MRR, accuracy) como a nivel de hunk (¿un resultado recuperado se superpuso con las líneas que el parche realmente tocó?), desglosado por categoría de issue:

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

Esto es una herramienta de medición, no una afirmación de leaderboard — está ahí para que puedas ejecutar localización de issues del mundo real en tu propio hardware y modelos, y para que cada cambio futuro de Octocode pueda medirse contra un benchmark que no escribimos nosotros.


La Matriz de Variantes

score.py mide una configuración. run_matrix.py hace un barrido de varias sobre un único índice compartido — así lo único que cambia entre filas son los parámetros en tiempo de búsqueda, no el corpus ni los embeddings. Escribe una tabla de comparación en RESULTS.md (y RESULTS.json crudo), y acumula entre ejecuciones, de modo que una re-ejecución enfocada se fusiona con la tabla existente.

# Fija el corpus al commit del ground truth primero — o los rangos de líneas se desvían
git worktree add /tmp/corpus b1771ba

# Matriz local completa (fastembed, sin API key): solo-vector vs barrido de pesos híbridos
CORPUS=/tmp/corpus python3 benchmark/run_matrix.py

# Añade un cross-encoder reranker local (aún sin key) — reutiliza el mismo índice
CORPUS=/tmp/corpus SKIP_INDEX=1 ONLY=rerank \
  RERANK_MODEL=fastembed:bge-reranker-base python3 benchmark/run_matrix.py

Variantes: vector_only, hybrid_70_30 (pesos por defecto), hybrid_30_70 (inclinado a keyword), +graph (expansión GraphRAG), y +rerank (cross-encoder). Si configuras VOYAGE_API_KEY, las variantes de reranker de pago consciente del código se activan automáticamente — así es como encuentras el techo real por encima del piso local.


Todo lo Demás

Metadatos del registro MCP y esquema del servidor actualizados. Mantiene el descriptor del servidor MCP publicado de Octocode al día con la superficie real de herramientas, para que los registros y clientes vean el esquema correcto. Más el mantenimiento habitual de dependencias y tooling — sin cambios de comportamiento.


Actualizar

# Homebrew
brew upgrade muvon/tap/octocode

# Instalador universal
curl -fsSL https://raw.githubusercontent.com/Muvon/octocode/master/install.sh | sh

# Cargo
cargo install octocode --version 0.18.0

Nada en esta versión cambia tu índice o configuración — actualizar es seguro, y tu setup existente sigue funcionando. Dos cosas que probar:

  1. Inclina tus pesos de fusión hacia keyword. Si buscas código más que prosa, el benchmark dice que el RRF liderado por keyword es una ganancia de precisión gratis. Mídelo en tu repo.
  2. Ejecuta el benchmark. Clona Octocode, fija el corpus, y ejecuta run_matrix.py contra tu propio modelo de embedding y reranker. Los números de arriba son un piso local — encuentra tu propio techo.

Para probar la expansión GraphRAG, activa [graphrag], pon search.graph_expansion = true, combínala con un reranker consciente del código, y haz A/B contra tu baseline antes de dejarla activada.


Octocode es open source (Apache 2.0) en github.com/Muvon/octocode — benchmark, ground truth y resultados crudos incluidos. Es el motor de búsqueda de código detrás de Octomind, y ahora su calidad de recuperación es algo que puedes medir tú mismo, no solo creer bajo nuestra palabra.