Conteúdo

Tutorial de embeddings e RAG no Ollama: faça o modelo responder com base nos seus documentos

Um modelo de linguagem não conhece os regulamentos da sua empresa, os manuais dos seus produtos nem suas anotações pessoais. RAG (Retrieval-Augmented Generation, geração aumentada por recuperação) funciona assim: primeiro, encontre nos seus documentos os trechos relacionados à pergunta. Depois, envie esses trechos junto com a pergunta ao modelo para que ele responda com base nos dados.

Pergunta ──▶ Encontre os trechos mais relevantes ──▶ “Responda com base nos dados a seguir: …” ──▶ Modelo ──▶ Resposta

A tecnologia central para encontrar os “trechos relevantes” é o embedding de texto (embedding).

O que é embedding de texto

Um modelo de embedding converte um texto em uma sequência de números (um vetor). Textos com significados parecidos também têm vetores parecidos, mesmo que usem palavras completamente diferentes:

  • “Como faço para solicitar uma licença?”
  • “Como pedir folga?”

Os vetores dessas duas frases ficam muito próximos. Assim, é possível fazer uma “busca semântica”, algo que a busca por palavras-chave não consegue fazer.

Baixe os modelos de embedding

ollama pull bge-m3            # multilíngue, bom desempenho em chinês
ollama pull nomic-embed-text  # leve, principalmente para inglês

Modelos de embedding não servem para conversar; eles apenas geram vetores.

Gere embeddings

curl http://localhost:11434/api/embed -d '{
  "model": "bge-m3",
  "input": ["Como faço para solicitar uma licença?", "Como pedir folga?"]
}'
import ollama

result = ollama.embed(model="bge-m3", input=["Como faço para solicitar uma licença?", "Como pedir folga?"])
vectors = result.embeddings
print(len(vectors), len(vectors[0]))  # 2 vetores, cada um com 1024 dimensões

input pode ser uma única string ou uma lista de strings. Processar em lote é muito mais rápido do que enviar uma por vez.

Calcule a similaridade

A medida mais usada é a similaridade de cosseno. Seu valor varia de -1 a 1; quanto mais próximo de 1, maior a similaridade:

import math


def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))


print(cosine(vectors[0], vectors[1]))

Na prática: perguntas e respostas com uma base de conhecimento local

Suponha que você tenha um arquivo com os regulamentos da empresa, handbook.txt. Programa completo:

import math
import ollama

EMBED_MODEL = "bge-m3"
CHAT_MODEL = "qwen3:8b"


def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))


# 1. Leia o documento e divida-o em trechos (separados por linhas em branco)
text = open("handbook.txt", encoding="utf-8").read()
chunks = [c.strip() for c in text.split("\n\n") if c.strip()]

# 2. Gere um embedding para cada trecho (faça isso uma vez e salve para reutilizar)
chunk_vectors = ollama.embed(model=EMBED_MODEL, input=chunks).embeddings


def ask(question: str, top_k: int = 3) -> str:
    # 3. Encontre os trechos mais parecidos com a pergunta
    q_vec = ollama.embed(model=EMBED_MODEL, input=question).embeddings[0]
    ranked = sorted(zip(chunks, chunk_vectors), key=lambda p: cosine(q_vec, p[1]), reverse=True)
    context = "\n\n".join(c for c, _ in ranked[:top_k])

    # 4. Envie os trechos e a pergunta ao modelo
    response = ollama.chat(
        model=CHAT_MODEL,
        messages=[
            {
                "role": "system",
                "content": "Você é um assistente interno da empresa. Responda somente com base nos dados fornecidos, em português brasileiro."
                "Se os dados não contiverem a resposta, diga ‘Não há informações relevantes nos dados’ e não faça suposições.",
            },
            {"role": "user", "content": f"Dados:\n{context}\n\nPergunta: {question}"},
        ],
        options={"temperature": 0.2},
    )
    return response.message.content


while True:
    q = input("\nPergunta: ").strip()
    if not q:
        break
    print(ask(q))

Esse é um sistema RAG básico, mas completo e funcional. Todos os dados permanecem na sua máquina.

Como melhorar

Divisão em trechos (chunking)

Se os trechos forem grandes demais, incluirão conteúdo irrelevante; se forem pequenos demais, perderão o contexto. Algumas práticas comuns:

  • Use cerca de 300 a 800 caracteres por trecho, com uma sobreposição de 50 a 100 caracteres entre trechos adjacentes.
  • Dividir de acordo com a estrutura do documento (títulos e parágrafos) é melhor do que usar um tamanho fixo.
  • Acrescente o título correspondente no início de cada trecho, por exemplo, “Capítulo 3: Regras para licenças > Férias”.

Use um banco de dados vetorial

Quando há muitos trechos, comparar cada um com todos os vetores a cada busca fica lento, e você precisa recalcular tudo sempre que reinicia. Você pode usar um banco de dados vetorial para armazenar e pesquisar os vetores:

FerramentaCaracterísticas
ChromaMais fácil de começar a usar em Python; pode ser armazenado em uma pasta local
SQLite + sqlite-vecUm único arquivo, adequado para projetos pequenos
PostgreSQL + pgvectorA melhor opção quando você já usa PostgreSQL
Qdrant, MilvusPara grandes volumes de dados e serviços independentes

Busca híbrida

A busca semântica não é boa para correspondências exatas, como códigos de produtos e nomes de pessoas. Em geral, os resultados melhoram quando você combina a busca por palavras-chave (BM25) com a busca semântica.

Atenção ao tamanho do contexto

Ao incluir vários trechos no prompt, o tamanho total não pode ultrapassar o limite de contexto do modelo. Caso contrário, o conteúdo inicial será cortado. Se necessário, aumente num_ctx; consulte Variáveis de ambiente e configurações.

Não quer escrever o código?

O Open WebUI inclui recursos para enviar documentos e criar uma base de conhecimento. Você pode enviar arquivos PDF e Word diretamente e pedir ao modelo que responda com base neles.

Atualizado 12 de outubro de 2026