RAG local sin vectores: SQLite FTS5 y BM25 para buscar en el código
Por qué los índices vectoriales densos sobrecargan las máquinas de desarrollo y cómo SQLite FTS5 con BM25 ofrece búsqueda de código exacta y más rápida con poca memoria.
Durante un par de años, la receta para la búsqueda local de código fue siempre la misma: dividir el repositorio en fragmentos, convertir cada fragmento en un embedding de 1536 dimensiones y mantener vivo un índice HNSW en un proceso en segundo plano. Parecía moderno, como si el portátil hubiera ganado un pequeño buscador que entiende lo que significa el código. En una estación de trabajo cargada, sin embargo, esa receta tiene un coste que rara vez aparece en un README.
Las cifras destacadas de la búsqueda vectorial local impresionan: buenas puntuaciones de recuperación en MTEB, millones de tokens de código convertidos en embeddings en minutos, "inteligencia semántica instantánea" en hardware de consumo. Si se leen las notas sobre memoria en lugar del marketing, aparece otra imagen. La indexación vectorial densa compra su exhaustividad a costa del ancho de banda de memoria, de la RAM o la VRAM y de la previsibilidad.
Cuando los vectores en todas partes chocan con el muro de la memoria
Si pones una base de datos vectorial junto a un LLM local, un IDE y unos cuantos contenedores Docker, no se queda quieta. Mantiene grandes estructuras de grafo en RAM o VRAM, compite con las demás herramientas por el ancho de banda y trae deriva semántica: buscas la cadena exacta ERR_AUTH_EXPIRED y recibes código que solo tiene que ver con la autenticación de forma vaga.
Por eso muchos equipos están volviendo en silencio a algoritmos nativos en C, en concreto a SQLite FTS5 con BM25. En vez de matemática vectorial para las búsquedas locales, usan una clasificación determinista por frecuencia de términos y encuentran identificadores exactos en menos de 5 milisegundos.
Coincidencia de conceptos frente a velocidad predecible
No es un simple ajuste. Cambia la forma en que se construyen las herramientas para desarrolladores. Si cambias la sobrecarga vectorial local por FTS5, renuncias a la coincidencia teórica de conceptos a cambio de una ejecución ligera y rápida.
- Eficiencia: prescindir de la indexación densa libera gigabytes de memoria. Un trabajo que dedicaba unos 200 ms a calcular embeddings termina de forma nativa en menos de 5 ms.
- Precisión: las búsquedas de palabras clave exactas y nombres de funciones, como
getUserById(), son totalmente deterministas, así que el índice no devuelve cuerpos de funciones sin relación.
Los dos flujos se comparan así:
[Dense Vector RAG Flow]
Code Query -> Generate Vector Embeddings -> HNSW Index Search -> Semantic Drift Risk -> LLM Context
[Deterministic BM25 Flow]
Code Query -> Native SQLite C-Index -> Exact Keyword Match (5ms) -> Minimal LLM Context
Síntomas que quizá ya conoces
Quien ejecuta agentes locales o búsqueda de código probablemente ya ha notado la fricción:
- Competencia por los recursos. El ventilador se acelera, la batería dura dos horas y la inferencia local se arrastra porque un motor en segundo plano compite por la memoria.
- La penalización de la coincidencia exacta. Buscar una cadena de error o una variable concreta devuelve cinco archivos cercanos a la autenticación por su significado, pero no el único archivo que contiene la bandera real.
Una comparación lado a lado, con cifras aproximadas:
| Dimensión | Base vectorial local (Chroma, Qdrant) | SQLite FTS5 (BM25) |
|---|---|---|
| Consumo de memoria | Unos 1,5 a 4 GB de RAM o VRAM | Unos 15 a 30 MB de RAM |
| Latencia de búsqueda | 120 a 400 ms, sobre todo por los embeddings | Menos de 5 ms, C nativo |
| Coincidencia exacta de identificadores | Débil, propensa a la deriva | Fuerte, coincidencia determinista de palabras clave |
| Sobrecarga del sistema | Demonios en segundo plano, enlaces a C++ | Nada en marcha cuando está inactivo |
| Ideal para | Búsquedas amplias y temáticas en texto | Consultas precisas en código, registros y RAG local |
Tómalas como cifras orientativas, ya que los resultados dependen del tamaño del corpus, del modelo elegido y del hardware.
Qué significa para los responsables de ingeniería
El reflejo de añadir una base de datos vectorial a cada problema local se está apagando. Un hábito más útil es el sentido de la proporción. La habilidad de un arquitecto sénior no consiste en meter una infraestructura de IA cada vez más pesada en los portátiles de los desarrolladores. Consiste en reconocer cuándo diez líneas de código nativo o un archivo SQLite de 30 MB superan a una canalización neuronal que se mide en gigabytes.
A medida que las herramientas se vuelven más rápidas y los flujos más ligeros, no ganarán los stacks que ejecutan los modelos más grandes. Ganarán los que entregan código limpio y sin lastre. Eso no vuelve inútiles los embeddings: para preguntas difusas y conceptuales sobre texto siguen teniendo su lugar. Pero para encontrar un identificador exacto en una base de código, empieza por la herramienta determinista y barata y añade vectores solo donde falle.