São nesses momentos que a busca por texto comum fica devendo. Veja como isso aparece na prática:
- Um portal de suporte retorna nenhum resultado quando o usuário digita "não liga" em vez da frase exata usada no artigo de ajuda.
- Uma plataforma de e-commerce não encontra nada quando o cliente busca por "algo quente para o inverno" porque nenhuma descrição de produto contém exatamente essas palavras.
- Uma base de conhecimento perde o documento certo porque o usuário formulou a pergunta de um jeito diferente do texto do documento.
A busca semântica resolve isso. Ela entende o significado e a intenção por trás da consulta, e não só a correspondência literal das palavras.
Este tutorial mostra como implementar busca semântica no MongoDB usando Python e o modelo de embeddings gratuito nomic-embed-text-v1.
Observação: nomic-embed-text-v1 ocupa aproximadamente 0,27 GB quando carregado na memória, então garanta que sua máquina tenha pelo menos 1 GB de RAM disponível antes de rodar qualquer script deste tutorial. Na primeira execução, o modelo é baixado do Hugging Face — reserve um tempo extra conforme a sua conexão. As execuções seguintes carregam o modelo do cache local. Inferência em CPU é significativamente mais lenta que em GPU. Em máquinas sem GPU dedicada, o modelo roda em CPU e usa RAM em vez de VRAM, então a geração de embeddings pode levar mais tempo.
Você vai aprender neste tutorial:
- O que são embeddings vetoriais e como eles representam significado
- Como gerar e armazenar embeddings no MongoDB
- Como criar um índice de Vector Search no MongoDB
- Como converter a consulta do usuário em um vetor
- Como rodar um pipeline de agregação
$vectorSearche interpretar os resultados
Você encontra todos os exemplos de código deste tutorial no repositório no GitHub.
Busca por texto vs busca semântica
A busca por texto encontra documentos que contêm exatamente as palavras da sua consulta. A busca semântica retorna documentos que carregam o mesmo significado da sua consulta, mesmo que usem palavras diferentes.
A tabela abaixo mostra o que cada abordagem retorna para a consulta "heart problems":
| Texto do documento | Busca por texto | Busca semântica | Por que a busca por texto retorna |
|---|---|---|---|
| "...heart problems in adults..." | Sim | Sim | Contém exatamente as palavras "heart problems" |
| "...cardiac conditions and symptoms..." | Não | Sim | Não contém exatamente as palavras "heart problems" |
| "...risk factors for heart disease..." | Não | Sim | Não contém exatamente as palavras "heart problems" |
| "...chest pain and shortness of breath..." | Não | Sim | Não contém exatamente as palavras "heart problems" |
Conceitos de busca semântica
Três conceitos são centrais para a busca semântica no MongoDB. Eles ajudam a entender por que cada etapa deste tutorial funciona como funciona.
Embeddings vetoriais
Um modelo de embedding converte texto em uma lista de números de comprimento fixo chamada vetor. Cada número no vetor representa uma dimensão de significado.
Dois textos com significados semelhantes geram vetores numericamente próximos. "cardiac conditions" e "heart problems" ficam pertos no espaço vetorial, mesmo sem compartilhar palavras. Essa proximidade é o que torna a busca semântica possível.
Este tutorial usa o modelo de embedding nomic-embed-text-v1, que é gratuito, open source e roda totalmente na sua máquina local. O modelo é baixado automaticamente do Hugging Face na primeira execução do script e fica salvo para as próximas. Ele gera 768 números por entrada.
Índices de busca vetorial
Um índice de busca vetorial informa ao MongoDB qual campo guarda os embeddings, quantas dimensões esperar e qual função de similaridade usar na comparação. Você precisa criar esse índice antes de rodar qualquer consulta $vectorSearch.
A etapa de agregação $vectorSearch
$vectorSearch é a etapa de agregação que executa a busca. Ela recebe um vetor de consulta, pesquisa no campo indexado e retorna documentos ranqueados por similaridade semântica. Você pode encadear com $project e outras etapas, como em qualquer pipeline de agregação.
Agora, mãos à obra.
Pré-requisitos
Antes de começar, garanta o seguinte:
- Python 3.8 ou superior instalado
- Conta no MongoDB Atlas com um cluster M0 (camada gratuita) configurado
pymongo4.7 ou superior, e os pacotes Pythonsentence-transformerseeinopsinstalados- Familiaridade básica com Python e coleções do MongoDB
- Sua string de conexão do Atlas, disponível na interface do Atlas em **Database > Connect > Drivers**
- Seu endereço IP permitido no Atlas antes de rodar qualquer script. Vá em **Security > Network Access** na interface do Atlas e adicione seu IP atual.
Configure seu projeto
Crie uma pasta para o projeto e navegue até ela no terminal:
mkdir mongodb-semantic-search
cd mongodb-semantic-search
Crie e ative um ambiente virtual para isolar as dependências do projeto:
python -m venv venv
source venv/bin/activate
No Windows, ative o ambiente virtual com:
venv\Scripts\activate
Agora instale os pacotes necessários:
pip install pymongo sentence-transformers einops
Você criará um arquivo Python para cada etapa deste tutorial. Todos os arquivos ficam na pasta mongodb-semantic-search.
Crie o arquivo de utilitários de embedding
Crie um arquivo chamado embedding_utils.py. Ele guarda as duas funções de embedding usadas neste tutorial:
from sentence_transformers import SentenceTransformer
# Load the free, open-source embedding model.
# The model downloads from Hugging Face on first run and saves locally.
# trust_remote_code=True is required by this model:
model = SentenceTransformer("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)
def get_embedding(text, precision="float32"):
# Use this function when embedding text you plan to store in MongoDB:
return model.encode(text, precision=precision).tolist()
def get_query_embedding(text, precision="float32"):
# Use this function when embedding a user's search query:
return model.encode(text, precision=precision).tolist()
Atenção: trust_remote_code=True permite que o modelo execute, na sua máquina, código Python específico baixado do Hugging Face. Se o código-fonte do modelo no Hugging Face for comprometido ou atualizado com mudanças maliciosas, esse código rodará automaticamente no seu ambiente. Tenha cautela antes de usar esse parâmetro em produção.
Gere e armazene embeddings vetoriais
Crie um arquivo chamado generate_embeddings.py. Ele importa a função de embedding de embedding_utils.py, gera um vetor para o campo text de cada documento e armazena o documento completo, incluindo o embedding, no MongoDB:
from embedding_utils import get_embedding
from pymongo import MongoClient
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
"mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search"
)
collection = mongodb_client["sample_db"]["documents"]
# Sample data:
sample_documents = [
{
"title": "MongoDB Atlas",
"text": "MongoDB Atlas is a fully managed cloud database."
},
{
"title": "Vector Search",
"text": "Vector search finds results based on semantic meaning."
},
{
"title": "Nomic AI",
"text": "nomic-embed-text-v1 is a free, open-source embedding model."
},
]
docs_to_insert = []
for doc in sample_documents:
embedding = get_embedding(doc["text"])
docs_to_insert.append({
"title": doc["title"],
"text": doc["text"],
# The vector lives alongside your original data in the same document:
"embedding": embedding
})
# Drop the collection before each run to avoid inserting duplicate documents:
collection.drop()
result = collection.insert_many(docs_to_insert)
print(f"Inserted {len(result.inserted_ids)} documents with embeddings.")
mongodb_client.close()
No código acima, collection.drop() apaga todos os documentos e índices da coleção antes de cada execução. Se você rodar generate_embeddings.py novamente sem essa linha, inserirá documentos duplicados, fazendo com que o $vectorSearch retorne o mesmo documento várias vezes. Derrubar a coleção também remove o índice de busca vetorial, então é preciso rodar create_vector_index.py sempre que você rodar generate_embeddings.py de novo.
Execute o script:
python generate_embeddings.py
Você verá a seguinte saída no terminal:
<All keys matched successfully>
Inserted 3 documents with embeddings.
Cada documento no MongoDB agora guarda o texto original e seu vetor de 768 dimensões no campo embedding. O vetor fica ao lado dos seus dados no mesmo documento, sem necessidade de armazenamento separado ou busca adicional na hora da consulta.
Crie um índice de Vector Search no MongoDB
Crie um arquivo chamado create_vector_index.py. Ele define e cria um índice de busca vetorial no campo embedding para que o MongoDB possa executar consultas $vectorSearch na sua coleção.
A definição do índice exige três campos:
path: o campo que guarda os embeddings (embeddingneste tutorial)numDimensions: deve corresponder ao tamanho de saída do modelo (768paranomic-embed-text-v1)similarity: a função de comparação (cosineé a correta para este modelo)
O valor de numDimensions deve corresponder exatamente ao seu modelo de embedding. Qualquer divergência faz a criação do índice falhar:
from pymongo.mongo_client import MongoClient
from pymongo.operations import SearchIndexModel
import time
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
"mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search"
)
# Point to the same database and collection you used in the previous step:
database = mongodb_client["sample_db"]
collection = database["documents"]
# Define the vector search index.
# The three required fields tell MongoDB what to index and how to compare vectors:
search_index_model = SearchIndexModel(
definition={
"fields": [
{
"type": "vector",
"path": "embedding", # The field name from generate_embeddings.py
"numDimensions": 768, # nomic-embed-text-v1 always outputs 768 dimensions
"similarity": "cosine" # Recommended similarity function for this model
}
]
},
name="vector_index",
type="vectorSearch"
)
result = collection.create_search_index(model=search_index_model)
print("New search index named " + result + " is building.")
# Poll every five seconds until the index is ready to accept queries:
print("Polling to check if the index is ready. This may take up to a minute.")
predicate = lambda index: index.get("queryable") is True
while True:
indices = list(collection.list_search_indexes(result))
if len(indices) and predicate(indices[0]):
break
time.sleep(5)
print(result + " is ready for querying.")
mongodb_client.close()
Execute o script:
python create_vector_index.py
Você verá uma saída como esta no terminal:
New search index named vector_index is building.
Polling to check if the index is ready. This may take up to a minute.
vector_index is ready for querying.
O índice leva até um minuto para ser construído. O loop de polling verifica a cada 5 segundos e só sai quando o MongoDB confirma que o índice está pronto para consultas. Não avance até ver "vector_index is ready for querying."
Converta sua consulta de busca em um vetor
Crie um arquivo chamado generate_query_vector.py. Ele importa a função get_query_embedding() do arquivo embedding_utils.py. Rode o generate_query_vector.py para verificar se o modelo de embedding carrega corretamente e produz um vetor de 768 dimensões:
- Ele define a função
get_query_embedding()que será importada no próximo passo. - Você pode rodá-lo isoladamente para checar se o modelo está funcionando.
Você deve usar o mesmo modelo aqui que usou no primeiro passo. Um modelo diferente gera vetores em um espaço numérico diferente, tornando a comparação sem sentido:
from embedding_utils import get_query_embedding
user_query = "I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)
print(f"Query: '{user_query}'")
print(f"Vector dimensions: {len(query_vector)}")
print(f"First 5 values: {query_vector[:5]}")
Execute o script:
python generate_query_vector.py
Você verá uma saída semelhante a esta:
<All keys matched successfully>
Query: 'I need an automated, scalable system for serious information storage'
Vector dimensions: 768
First 5 values: [0.0025914530269801617, 0.09862980246543884, -0.023092379793524742, -0.0171672236174345, -0.05548065900802612]
Na saída acima, Vector dimensions: 768 confirma que a saída do modelo bate com seus embeddings armazenados.
Execute uma consulta $vectorSearch
Crie um arquivo chamado run_vector_search.py. Ele importa a função get_query_embedding() do arquivo embedding_utils.py e converte o termo de busca do usuário em um vetor. O run_vector_search.py executa um pipeline de agregação $vectorSearch e imprime os resultados ranqueados por similaridade semântica:
from embedding_utils import get_query_embedding
from pymongo import MongoClient
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient(
"mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search"
)
collection = mongodb_client["sample_db"]["documents"]
# Generate a query vector from the user's search input:
user_query = "I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)
# Define the $vectorSearch aggregation pipeline:
pipeline = [
{
"$vectorSearch": {
"index": "vector_index", # The index created in create_vector_index.py
"path": "embedding", # The field that holds your stored vectors
"queryVector": query_vector, # The vector generated from the user's query
"numCandidates": 150, # How many neighbors MongoDB considers
"limit": 3 # How many results to return
}
},
{
"$project": {
"_id": 0,
"title": 1,
"text": 1,
"score": {
"$meta": "vectorSearchScore" # Relevance score for each result
}
}
}
]
results = collection.aggregate(pipeline)
print(f"\nTop results for query: '{user_query}'\n")
for doc in results:
print(f"Title: {doc['title']}")
print(f"Text: {doc['text']}")
print(f"Score: {doc['score']:.4f}")
print()
mongodb_client.close()
No código acima, dois parâmetros controlam o comportamento da busca:
numCandidatesdefine o tamanho do conjunto inicial que o MongoDB examina antes de chegar aos resultados finais. Um valor maior melhora o recall, mas leva um pouco mais de tempo.limitdefine quantos resultados você recebe. Neste tutorial,limitestá em 3 enumCandidatesem 150. Um bom ponto de partida é definirnumCandidatesentre 10 e 15 vezes o seulimit.
Agora rode o script:
python run_vector_search.py
Você verá uma saída semelhante a esta:
<All keys matched successfully>
Top results for query: 'I need an automated, scalable system for serious information storage.'
Title: MongoDB Atlas
Text: MongoDB Atlas is a fully managed cloud database.
Score: 0.7211
Title: Nomic AI
Text: nomic-embed-text-v1 is a free, open-source embedding model.
Score: 0.6818
Title: Vector Search
Text: Vector search finds results based on semantic meaning.
Score: 0.6642
"MongoDB Atlas" tem a maior pontuação mesmo que a consulta "I need an automated, scalable system for serious information storage" não compartilhe palavras com "MongoDB Atlas is a fully managed cloud database." O resultado aparece porque seus vetores são próximos em significado: "...um sistema automatizado e escalável para armazenamento de informações" mapeia semanticamente para "...um banco de dados em nuvem totalmente gerenciado". Uma busca por texto com a mesma consulta retornaria zero resultados, pois nenhuma das palavras exatas aparece em qualquer documento. É a busca semântica funcionando como deve.
Principais aprendizados
- O MongoDB Vector Search roda em um cluster M0 (camada gratuita). Não é necessário plano pago.
- Você deve usar o mesmo modelo de embedding tanto para gerar os embeddings armazenados quanto para gerar os vetores de consulta. Misturar modelos produz resultados sem sentido.
- O valor de
numDimensionsna definição do índice deve corresponder exatamente ao tamanho de saída do seu modelo.nomic-embed-text-v1sempre produz 768 dimensões. input_type="document"otimiza embeddings para armazenamento.input_type="query"os otimiza para recuperação. Use o tipo correto em cada etapa.numCandidatescontrola o tamanho da rede de busca que o MongoDB lança antes de reduzir aos resultados finais dolimit. Um valor maior melhora o recall ao custo do tempo de consulta.vectorSearchScoreranqueia os resultados por similaridade semântica. Os resultados não precisam conter nenhuma palavra exata da consulta. As faixas de pontuação variam por modelo e dataset, mas os limites abaixo são um bom ponto de partida paranomic-embed-text-v1com similaridade cosseno:- 0,9 ou mais: significado quase idêntico. Documento e consulta são semanticamente quase iguais.
- 0,7 a 0,9: alta relevância. O documento claramente se relaciona à intenção da consulta.
- 0,5 a 0,7: relevância moderada. O documento é relacionado ao tema, mas usa outra abordagem ou contexto.
- Abaixo de 0,5: baixa relevância. A conexão é fraca e o resultado pode não ser útil.
Você encontra todos os exemplos de código deste tutorial no repositório no GitHub.
Leituras recomendadas
- Visão geral do MongoDB Vector Search cobre todos os recursos do MongoDB Vector Search, incluindo filtragem e quantização.
- Como realizar busca híbrida mostra como combinar busca vetorial e busca full-text em uma única consulta.
- Como criar embeddings vetoriais explica como gerar embeddings para dados de texto nas suas coleções usando modelos da Voyage AI, OpenAI e outros provedores open source.
- Retrieval-Augmented Generation (RAG) com MongoDB mostra como usar a busca semântica como a camada de recuperação em uma aplicação de geração aumentada por recuperação.
FAQs
Preciso de um plano pago do Atlas para implementar busca semântica?
Não. As quatro etapas deste tutorial rodam em um cluster M0 (camada gratuita), que é gratuito.
O que acontece se eu usar um modelo de embedding diferente na consulta e nos documentos armazenados?
Os resultados não farão sentido. Os vetores não serão comparáveis porque modelos diferentes mapeiam o texto para espaços numéricos distintos. Sempre use o mesmo modelo para indexação e consulta.
Qual a diferença entre `numCandidates` e `limit`?
numCandidates é quantos vetores o MongoDB examina durante a busca. limit é quantos dos melhores resultados ele retorna para você. Um valor mais alto de numCandidates melhora a qualidade dos resultados ao custo de consultas um pouco mais lentas. Um ponto de partida comum é definir numCandidates entre 10 e 15 vezes o seu limit.
Posso usar busca semântica e busca por texto juntas?
Sim. O MongoDB oferece busca híbrida, que combina $vectorSearch e $search em um único pipeline.
A busca semântica funciona para idiomas além do inglês?
Depende do seu modelo de embedding. O nomic-embed-text-v1 é treinado principalmente em inglês. Para casos multilíngues, escolha um modelo multilíngue treinado nos idiomas presentes nos seus dados.
