BM25 Sobre SQLite vs um Banco Vetorial
A categoria pressupõe um serviço de embeddings, um índice ANN e uma GPU. Medimos a busca de entrada em recall@5 = 1.00 a 1.3 ms p95 com BM25 sobre SQLite, e mantivemos o embedder opcional.

A busca de entrada no MonkeyLLM roda em BM25 sobre SQLite, e alcança recall@5 = 1.00 a 1.3 ms p95 no nosso corpus de benchmark. Antes de objetar que a busca lexical morreu anos atrás, olhe para o que o trabalho realmente é. O trabalho é menor do que a categoria pressupõe, e isso muda a economia.
O que a categoria pressupõe
A stack padrão de recuperação vem com pressupostos embutidos: um modelo de embedding (uma API hospedada, ou uma GPU que você mantém aquecida), um banco vetorial com índice ANN, um pipeline que refaz embeddings a cada edição de documento e uma história de migração para o dia em que você troca de modelo de embedding e todo vetor armazenado fica obsoleto. Tudo isso é superfície operacional, e tudo isso é pago antes de a primeira consulta retornar.
Nada disso é errado. Só raramente é questionado, porque "busca semântica" soa como requisito, não como escolha de design.
O que a busca de entrada realmente precisa fazer
Em uma floresta de conhecimento (um grafo interligado de documentos, cada um carregando um passaporte curado: título, resumo, tags), a busca não é a máquina de respostas. É a porta. O agente busca uma vez para achar um nó de partida e então caminha: lê rótulos, segue links e abre só o que precisa. A caminhada resolve a pergunta; a busca só precisa deixar você perto.
Esse reenquadramento encolhe o requisito. A porta não precisa entender uma paráfrase da pergunta inteira. Precisa casar um nome, e casar nomes é o que a busca lexical faz de melhor.
O que medimos
BM25 sobre o índice full-text do SQLite: recall@5 = 1.00 a 1.3 ms p95. Sem embeddings, sem banco vetorial, sem GPU. O detalhe que mais importa: dar peso aos campos curados (título, aliases) fechou completamente a lacuna para a busca híbrida. BM25 sem pesos sobre corpos crus não chegou lá; BM25 sobre um passaporte curado chegou.
O SQLite merece uma palavra aqui. Ele é um único arquivo dentro do deployment. Não há serviço de busca para rodar, fazer backup ou monitorar, e o mesmo banco já armazena a própria floresta. Para um sistema auto-hospedado que promete que seus dados nunca saem da sua infra, apagar um serviço stateful inteiro do diagrama de arquitetura não é uma vitória pequena.
Quando vetores valem o custo
Isto não é um argumento de que embeddings são inúteis. Eles valem o custo quando as consultas são cheias de paráfrase e não compartilham vocabulário com os documentos, quando o corpus é multilíngue, ou quando você só tem chunks crus, sem metadados curados e sem orçamento de escrita para criar algum. Nesses regimes, a busca lexical não tem onde se agarrar.
É por isso que o embedder é opcional e suportado, não removido. Aponte um binding de modelo para um endpoint de embeddings e a busca de entrada vira híbrida. O que removemos foi o pressuposto, não a capacidade.
A troca que fizemos de fato
O número de recall não é grátis; é pré-pago. A curadoria acontece no tempo de escrita: a ingestão roda a 1.71 s por documento, com 100% dos resumos passando num contrato de sessenta tokens, e é dali que vêm os títulos, aliases e resumos em que o BM25 se agarra. Gaste inteligência no ambiente para gastar menos no modelo (spend intelligence on the environment so you can spend less on the model). A versão longa desse argumento é o Princípio da Floresta.
Reproduza
O corpus e o harness estão commitados, e a tabela de busca de entrada é regenerada com um comando. Reproduza você mesmo, leia como as peças se encaixam nos docs de primitivos, ou comece por monkeyllm.com.