Alternar tema

Ollama Embedding na prática: busca vetorial local e RAG

Easton editorial illustration: MCP trust and permission gateway

Eu estava procurando, em mais de 200 PDFs no meu computador, um detalhe de uma solução técnica que havia lido seis meses antes. Busca por palavras-chave? Não adiantava: eu lembrava o sentido do conteúdo, não as palavras exatas. Levei quase uma hora para encontrar o documento e pensei que seria muito melhor ter uma ferramenta de busca capaz de “entender o significado”.

Havia outro problema: esses documentos tratavam da arquitetura interna da empresa. Enviá-los para a nuvem a fim de fazer uma busca vetorial estava fora de cogitação. A exigência de privacidade era incontornável.

Foi então que descobri que o recurso de embeddings do Ollama resolvia justamente esse caso. Ele roda localmente, mantém os dados no seu ambiente e permite fazer busca semântica. Passei alguns dias testando mxbai, nomic e Qwen3, além de comparar as opções de banco de dados vetorial. Há várias armadilhas: escolher o modelo errado piora a recuperação, enquanto começar com um banco pequeno pode complicar uma expansão futura.

Este artigo reúne o que aprendi nesses testes. Ao final, você poderá montar um sistema RAG local completo, do processamento dos documentos à busca semântica, com todo o código necessário.

Modelos de embedding disponíveis no Ollama

O Ollama oferece vários modelos de embedding, e no começo eu também não sabia qual escolher. A documentação oficial afirma que o mxbai supera o text-embedding-3-large da OpenAI. Parece ótimo, mas como ele funciona na prática? Testei as opções e cheguei a uma resposta direta.

Comece por esta tabela comparativa:

ModeloDimensão do vetorTamanho do contextoTamanho do modeloCaracterísticas
mxbai-embed-large1024512 tokens670MOpção geral recomendada, entre os primeiros do ranking MTEB
nomic-embed-text7688192 tokens274MSuporte a textos longos e extensão de contexto
Qwen3 Embedding10248192 tokenscerca de 600MLançado em 2026 e adequado para chinês

mxbai-embed-large é o modelo que mais usei. O motivo é simples: ele funciona bem e entrega resultados consistentes. Seus vetores de 1024 dimensões bastam para a maioria dos cenários, e o modelo aparece nas primeiras posições do MTEB (Massive Text Embedding Benchmark), com uma pontuação um pouco superior à do text-embedding-3-large da OpenAI. Para tarefas cotidianas, como busca em documentos ou em código, ele é uma escolha segura.

O destaque do nomic-embed-text é o contexto de 8192 tokens. Isso ajuda quando você precisa processar um artigo inteiro ou registros longos de conversas. Ele também é menor, com 274 milhões de parâmetros, e roda mais rápido que o mxbai. Em contrapartida, a dimensão vetorial cai para 768, o que em teoria reduz um pouco a capacidade de representação semântica. Nos meus testes, a diferença em textos curtos foi pequena, e o nomic levou vantagem com textos longos.

O Qwen3 Embedding foi lançado pela Alibaba em abril de 2026. Seu desempenho com chinês é realmente bom. Em testes com alguns artigos técnicos, ele conseguiu relacionar “mecanismo de tolerância a falhas em sistemas distribuídos” e “projeto tolerante a falhas”, enquanto o mxbai ficou um pouco atrás. Se o conteúdo for principalmente em chinês, vale a pena testá-lo.

Minha recomendação é direta: quem está começando pode escolher o mxbai sem perder tempo; para textos longos, use nomic; para conteúdo em chinês, priorize Qwen3. Testar os três leva cerca de meia hora, e o resultado real deve orientar a decisão.

Como escolher um banco de dados vetorial

Depois de escolher o modelo, onde armazenar os dados? É ainda mais fácil errar na escolha do banco de dados vetorial. Uma opção pequena demais dificulta a expansão; uma solução grande demais desperdiça recursos. Comparei três alternativas conhecidas:

Banco de dadosCaso de usoVolume de dadosCaracterísticas
ChromaDBDesenvolvimento inicial e projetos pessoais< 100 mil registrosPronto para usar, sem configuração
FAISSAlto desempenho em uma máquina e experimentos de pesquisa100 mil a 1 milhão de registrosCódigo aberto da Meta e muito rápido
MilvusImplantação em produção e uso empresarialmilhões de registros ou maisDistribuído, escalável e completo

ChromaDB é a opção que mais recomendo para começar. A instalação exige apenas um comando: pip install chromadb. A API é simples; armazenar e consultar dados leva poucas linhas de código. Ele usa um índice HNSW (Hierarchical Navigable Small World), cujo desempenho é mais do que suficiente para conjuntos pequenos. A desvantagem é a implantação em uma única máquina: acima de 100 mil registros, o desempenho começa a cair.

FAISS é uma ferramenta consolidada e de código aberto da Meta. Sua implementação em C++ é realmente rápida. Em um teste com 500 mil registros, a latência de busca se manteve na casa dos milissegundos. Porém, ele se parece mais com uma biblioteca de busca vetorial do que com um banco de dados completo: você precisa gerenciar o armazenamento e os arquivos de índice. É indicado para quem quer mais controle ou precisa do máximo de desempenho.

Milvus pertence a outra categoria e foi projetado para produção. Ele aceita implantação distribuída, armazenamento persistente, vários tipos de índice e também tem uma versão em nuvem, o Zilliz Cloud. Em contrapartida, a configuração é mais complexa e o custo de implantação é maior. Só vale esse investimento quando há milhões de registros, exigência de alta disponibilidade ou colaboração em equipe.

Minha estratégia: uso ChromaDB para projetos pessoais e protótipos rápidos; FAISS em pesquisas ou cenários sensíveis a desempenho; Milvus ou um serviço em nuvem quando o sistema realmente vai para produção. Não conte com uma migração simples do ChromaDB para o Milvus no futuro: os formatos de dados e as APIs são diferentes, e o custo da mudança não é pequeno.

Implementação completa do fluxo RAG

É melhor partir para o código do que continuar na teoria. O exemplo abaixo implementa um RAG local completo, do PDF à busca semântica, com Ollama e ChromaDB.

Preparação do ambiente

Primeiro, instale as dependências:

pip install ollama chromadb langchain langchain-community pypdf

Verifique se o Ollama está em execução e baixe os modelos:

ollama pull mxbai-embed-large
ollama pull qwen2.5:7b  # Usado para gerar respostas

Implementação em código

import ollama
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import chromadb

# 1. Carregar o documento PDF
loader = PyPDFLoader("./your_document.pdf")
docs = loader.load()

# 2. Dividir o documento — esta etapa exige atenção: blocos grandes prejudicam a precisão, e blocos pequenos perdem informações
splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,      # 800 caracteres por bloco
    chunk_overlap=100,   # Sobreposição de 100 caracteres para preservar informações nas bordas
)
chunks = splitter.split_documents(docs)

# 3. Gerar embeddings e armazená-los no ChromaDB
client = chromadb.Client()
collection = client.create_collection("my_docs")

for i, chunk in enumerate(chunks):
    # Chamar a API do Ollama para gerar o vetor
    response = ollama.embed(
        model="mxbai-embed-large",
        input=chunk.page_content,
    )
    embedding = response["embeddings"][0]

    # Armazenar no banco de dados vetorial
    collection.add(
        ids=[str(i)],
        embeddings=[embedding],
        documents=[chunk.page_content],
        metadatas=[&#123;"source": chunk.metadata.get("source", "unknown")&#125;],
    )

print(f"Foram armazenados &#123;len(chunks)&#125; trechos de documentos")

# 4. Busca semântica
query = "O que é tolerância a falhas em sistemas distribuídos?"
query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
)["embeddings"][0]

results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,  # Retornar os três trechos mais relevantes
)

# 5. Gerar uma resposta com os resultados recuperados
context = "\n\n".join(results["documents"][0])
response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
        &#123;
            "role": "system",
            "content": "Responda com base no conteúdo dos documentos abaixo. Se eles não contiverem a informação, diga isso com clareza.",
        &#125;,
        &#123;"role": "user", "content": f"Conteúdo dos documentos: &#123;context&#125;\n\nPergunta: &#123;query&#125;"&#125;,
    ],
)

print(f"Resposta: &#123;response['message']['content']&#125;")

Agora execute o código. O fluxo não é complicado: dividir os documentos → gerar vetores → armazenar → consultar → montar a resposta.

Algumas armadilhas importantes:

A primeira é definir chunk_size sem critério. Testei blocos de 200 caracteres, mas o resultado da recuperação trazia apenas fragmentos que não formavam uma resposta completa. O intervalo de 500 a 1000 é mais consistente.

A segunda é o processamento em lote. Quando há muitos documentos, chamar a API do Ollama uma vez por entrada fica lento. Você pode acumular um lote e processá-lo de uma só vez:

# Gerar embeddings em lote melhora bastante o desempenho
batch_texts = [chunk.page_content for chunk in chunks[:50]]
batch_embeddings = ollama.embed(
    model="mxbai-embed-large",
    input=batch_texts,
)["embeddings"]

A terceira é o limiar de similaridade. Por padrão, o ChromaDB retorna a quantidade definida em n_results, mesmo quando os resultados não são relevantes. Em alguns cenários, é preciso filtrar o conteúdo sem relação com a consulta:

# Filtrar com um limiar de distância personalizado
results = collection.query(
    query_embeddings=[query_embedding],
    n_results=10,
)
# Manter apenas resultados com distância inferior a 0,3 (quanto menor, maior a similaridade)
filtered = [
    doc for doc, dist in zip(results["documents"][0], results["distances"][0])
    if dist < 0.3
]

Depois de executar esse código, você terá seu próprio RAG local. Troque o arquivo PDF pelo seu documento e altere query para a pergunta desejada; o restante pode permanecer igual.

Ajustes de desempenho e recomendações práticas

Com o sistema em funcionamento, começa a etapa de ajuste. Alguns parâmetros afetam diretamente o resultado; estas são as conclusões dos meus testes.

Qual valor usar para chunk_size?

Minha referência é de 500 a 1000 caracteres. Blocos pequenos demais não preservam o sentido: uma frase pode ser cortada ao meio e deixar de corresponder à pergunta. Blocos grandes demais adicionam ruído à busca: um único trecho passa a conter vários assuntos e perde limites claros.

Também há diferenças entre tipos de documento. Documentação técnica costuma ter uma estrutura clara e pode ser dividida por parágrafo. Registros de conversas são mais fragmentados e funcionam melhor em blocos de 500 caracteres. Teste no seu cenário; não existe um valor universal.

Acelere o processamento com lotes

Ao chamar a API do Ollama para cada entrada, cada solicitação espera uma ida e volta pela rede. Reunir de 50 a 100 entradas em um lote pode multiplicar a velocidade. Não exagere no tamanho: modelos de embedding têm limites de entrada e retornam erro quando eles são ultrapassados.

Como definir o limiar de similaridade

O intervalo de 0,7 a 0,85 depende da precisão esperada. Um limiar alto, como 0,85, mantém apenas resultados muito relacionados, com menor recuperação e maior precisão. Um valor baixo, como 0,7, recupera mais conteúdo, mas pode adicionar ruído. Se a base estiver limpa e a pergunta for clara, aumente o valor; se a pergunta for vaga e exigir mais contexto, reduza-o.

Uma recomendação prática: comece com 50 a 100 registros e observe a qualidade da recuperação antes de decidir os parâmetros. Ajustar depois de carregar todo o conjunto custa muito mais tempo. Faça ajustes iterativos enquanto testa.

Conclusão

Os pontos principais são poucos:

A escolha do modelo depende do cenário: mxbai para uso geral, nomic para textos longos e Qwen3 para chinês. Para o banco de dados, ChromaDB é adequado para começar, FAISS atende casos sensíveis a desempenho e Milvus é voltado à produção. O código do fluxo inteiro está disponível acima e pode ser adaptado diretamente.

As vantagens são claras: a implantação local mantém o controle sobre a privacidade dos dados; os modelos do Ollama são gratuitos; e o ChromaDB é simples, com uma curva de aprendizado pequena. Também há limitações: o desempenho de uma única máquina tem um teto e conjuntos com mais de um milhão de registros exigem outra solução.

Minha sugestão é executar primeiro o código deste artigo com 50 documentos. A qualidade da recuperação e a precisão das respostas precisam ser avaliadas na prática. Só amplie para todos os dados depois de ajustar os parâmetros.

Para avançar na integração entre LangChain e Ollama e criar aplicações mais complexas, consulte o artigo anterior, “Integração entre LangChain e Ollama na prática”. Ele trata de cadeias de conversa e chamadas de ferramentas, enquanto este artigo se concentra na busca vetorial. Juntos, os dois formam um caminho completo para desenvolver aplicações locais com LLM.

Montar um sistema RAG local

Use Ollama e ChromaDB para montar um sistema local de busca vetorial

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Instalar as dependências e preparar os modelos

    Execute os comandos abaixo para instalar as dependências:

    ```bash
    pip install ollama chromadb langchain langchain-community pypdf
    ollama pull mxbai-embed-large
    ollama pull qwen2.5:7b
    ```

    Verifique se o serviço do Ollama está em execução.
  2. 2

    Step 2: Carregar e dividir o documento

    Use PyPDFLoader para carregar o PDF e RecursiveCharacterTextSplitter para dividi-lo:

    ```python
    loader = PyPDFLoader("./your_document.pdf")
    docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=100,
    )
    chunks = splitter.split_documents(docs)
    ```

    O valor recomendado para chunk_size fica entre 500 e 1000 caracteres.
  3. 3

    Step 3: Gerar vetores e armazená-los no banco de dados

    Chame a API do Ollama para gerar embeddings e armazená-los no ChromaDB:

    ```python
    client = chromadb.Client()
    collection = client.create_collection("my_docs")

    for i, chunk in enumerate(chunks):
    response = ollama.embed(
    model="mxbai-embed-large",
    input=chunk.page_content,
    )
    embedding = response["embeddings"][0]
    collection.add(
    ids=[str(i)],
    embeddings=[embedding],
    documents=[chunk.page_content],
    )
    ```

    Use processamento em lote quando houver muitos documentos.
  4. 4

    Step 4: Fazer a busca semântica e gerar a resposta

    Converta a consulta em vetor, recupere os documentos relacionados e use um LLM para gerar a resposta:

    ```python
    query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
    )["embeddings"][0]

    results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,
    )

    context = "\n\n".join(results["documents"][0])
    response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
    {"role": "system", "content": "Responda à pergunta com base no conteúdo dos documentos"},
    {"role": "user", "content": f"Documentos: {context}\nPergunta: {query}"},
    ],
    )
    ```

    Ajuste o limiar de similaridade conforme necessário para filtrar os resultados.

FAQ

Qual é o melhor modelo de embedding do Ollama?
Não existe um modelo absolutamente superior; a escolha depende do cenário:

• mxbai-embed-large: opção geral recomendada, com resultados consistentes na maioria dos casos
• nomic-embed-text: indicado para textos longos, com suporte a 8192 tokens
• Qwen3 Embedding: funciona bem com chinês e foi lançado em 2026

Vale a pena testar os três e decidir com base nos resultados reais.
Qual escolher entre ChromaDB, FAISS e Milvus?
Escolha conforme o volume de dados e o caso de uso:

• ChromaDB: melhor opção para começar, sem configuração, indicado para até 100 mil registros
• FAISS: prioriza desempenho, suporta milhões de registros em uma única máquina, mas exige que você gerencie o armazenamento
• Milvus: indicado para produção, com implantação distribuída e conjuntos acima de milhões de registros

Use ChromaDB em projetos pessoais e Milvus em ambientes de produção.
Qual valor usar para chunk_size?
O intervalo recomendado é de 500 a 1000 caracteres. Blocos pequenos demais perdem o sentido completo; blocos grandes demais adicionam ruído à busca. Divida documentos técnicos por parágrafo e registros de conversas em blocos de 500 caracteres. Teste e ajuste para o seu cenário.
Como acelerar a geração de embeddings?
Use processamento em lote: reúna de 50 a 100 entradas em cada chamada à API do Ollama. Isso é várias vezes mais rápido do que enviar uma entrada por chamada. Controle o tamanho do lote para não ultrapassar o limite de entrada do modelo.
Qual limiar de similaridade devo usar?
Ajuste o valor entre 0,7 e 0,85. Um limiar alto, como 0,85, aumenta a precisão, mas reduz a recuperação; um limiar baixo, como 0,7, recupera mais conteúdo, porém pode adicionar ruído. Use um valor mais alto quando a base estiver limpa e a pergunta for clara, e mais baixo quando a pergunta for vaga.
Quais são as principais vantagens e desvantagens de um RAG local?
Vantagens: controle sobre a privacidade dos dados, modelos gratuitos sem custo de uso e implantação simples.

Desvantagens: desempenho limitado em uma única máquina, necessidade de outra solução para conjuntos acima de milhões de registros e responsabilidade pela manutenção do serviço.

10 min de leitura · Publicado em: 8 abr 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog