Quando falamos em RAG (Retrieval-Augmented Generation), a parte mais glamurosa costuma ser o LLM que gera a resposta. Mas o segredo de um RAG que funciona está na etapa de recuperação: encontrar, entre milhares de documentos, os trechos realmente relevantes para a pergunta do usuário. É aqui que o MongoDB entra com o Atlas Vector Search, permitindo guardar embeddings ao lado dos seus dados e consultá-los por similaridade semântica. Neste artigo o foco é bem concreto: quais índices você precisa criar e como escrever a consulta.
O fluxo de RAG com MongoDB Atlas
Antes dos índices, vale fixar o fluxo. Ele tem duas fases distintas:
- Ingestão (offline): você quebra seus documentos em pedaços (chunks), gera um embedding para cada chunk usando um modelo (OpenAI, Voyage AI, Cohere etc.) e grava esse vetor dentro do documento no MongoDB, junto com o texto e metadados.
- Consulta (online): a pergunta do usuário também vira um embedding; o Atlas busca os vetores mais próximos (vizinhos mais próximos), e os trechos recuperados são injetados no prompt do LLM como contexto.
A busca vetorial não substitui o LLM — ela alimenta o LLM com o contexto certo. Índice bom significa contexto bom; contexto bom significa resposta confiável.
Como armazenar embeddings
No MongoDB, um embedding é simplesmente um array de números (double) em um campo do documento. Um documento típico da coleção fica assim:
{
"_id": ObjectId("..."),
"texto": "O MongoDB Atlas Vector Search permite...",
"embedding": [0.0123, -0.0456, 0.0789, ...], // ex.: 1536 dimensões
"categoria": "backend",
"idioma": "pt-BR",
"publicado_em": ISODate("2026-01-10")
}
Duas regras de ouro: o número de dimensões do array precisa bater exatamente com o modelo usado (por exemplo, 1536 para text-embedding-3-small da OpenAI), e todo documento indexado precisa ter esse campo preenchido.
Os índices que você precisa criar
Aqui está o coração do artigo. Para um RAG completo você pode combinar até três tipos de índice, mas apenas o primeiro é obrigatório.
1. Índice do tipo vectorSearch (obrigatório)
É o índice que habilita a busca por similaridade. Ele é definido em JSON e tem dois tipos de campo: vector (o campo do embedding) e filter (campos de metadados usados na pré-filtragem). Os parâmetros essenciais do campo vector são:
path: o caminho do campo que guarda o embedding.
numDimensions: quantas dimensões o vetor tem — precisa ser igual ao modelo de embedding.
similarity: a métrica de distância. Os valores válidos são euclidean, cosine e dotProduct. Para embeddings de texto normalizados, cosine é a escolha mais comum; dotProduct tende a ser mais eficiente quando os vetores já estão normalizados.
A definição do índice fica assim:
{
"fields": [
{
"type": "vector",
"path": "embedding",
"numDimensions": 1536,
"similarity": "cosine"
},
{
"type": "filter",
"path": "categoria"
},
{
"type": "filter",
"path": "idioma"
}
]
}
Para coleções grandes, você ainda pode ativar quantização (por exemplo "quantization": { "type": "scalar" } ou "int8") para reduzir o uso de memória do índice sem perder muita precisão.
2. Índices de filtro (para pré-filtragem de metadados)
Note, no exemplo acima, que categoria e idioma aparecem como campos filter dentro do próprio índice vectorSearch. Isso é fundamental: no Atlas Vector Search, a pré-filtragem de metadados não usa índices B-tree tradicionais — os campos precisam ser declarados como filter na definição do índice vetorial para poderem ser usados no operador filter da consulta.
A pré-filtragem acontece antes da busca vetorial, restringindo o espaço de candidatos. Isso é ótimo em RAG multi-tenant ou multi-idioma: você garante que o usuário só recupere trechos da sua organização, do idioma certo ou dentro de uma janela de data, melhorando a precisão e o desempenho.
3. Índice de busca textual (Atlas Search) para busca híbrida
A busca puramente vetorial é excelente para semântica, mas às vezes o usuário procura um termo exato — um código de produto, um nome próprio, uma sigla. Nesses casos vale combinar com um índice do Atlas Search (busca full-text, baseada em Lucene) sobre o campo de texto. Isso é a chamada busca híbrida.
São dois índices separados e independentes: um índice vectorSearch sobre embedding e um índice search (full-text) sobre texto. Na consulta, você executa os dois estágios ($vectorSearch e $search) e combina os resultados com fusão de rankings — o Atlas oferece o estágio $rankFusion (Reciprocal Rank Fusion), que mescla as listas ordenadas dando peso a cada abordagem.
Exemplo de consulta com $vectorSearch
Com os índices no lugar, a recuperação é feita com um aggregation pipeline. O estágio $vectorSearch precisa ser o primeiro do pipeline. Seus campos principais:
index: o nome do índice vectorSearch criado.
path e queryVector: o campo do embedding e o vetor da pergunta.
numCandidates: quantos candidatos considerar na busca aproximada (ANN); a doc recomenda pelo menos 20x o limit.
limit: quantos documentos retornar.
filter: a pré-filtragem de metadados (usa os campos filter do índice).
db.artigos.aggregate([
{
"$vectorSearch": {
"index": "idx_embedding",
"path": "embedding",
"queryVector": [0.0021, -0.0339, 0.0517, ...],
"numCandidates": 150,
"limit": 5,
"filter": {
"$and": [
{ "categoria": "backend" },
{ "idioma": "pt-BR" }
]
}
}
},
{
"$project": {
"_id": 0,
"texto": 1,
"categoria": 1,
"score": { "$meta": "vectorSearchScore" }
}
}
])
O estágio $project traz o vectorSearchScore via $meta — a pontuação de similaridade (quanto mais perto de 1, mais similar). É esse score que você pode usar para descartar trechos fracos antes de montar o prompt.
Regra prática: comece com numCandidates em torno de 10x a 20x o limit. Se a recall estiver baixa, aumente; se a latência incomodar, reduza. Para bases pequenas, o campo exact: true faz busca exata (ENN) sem numCandidates.
Juntando tudo
Um RAG de produção com MongoDB costuma ter: um índice vectorSearch com o campo do embedding e os metadados relevantes declarados como filter; opcionalmente um índice search full-text para busca híbrida; e um pipeline que pré-filtra por tenant/idioma, recupera por similaridade e projeta o score. Os trechos retornados viram o contexto do LLM. Simples de descrever, poderoso quando bem indexado.
Fontes