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:
| Ferramenta | Características |
|---|---|
| Chroma | Mais fácil de começar a usar em Python; pode ser armazenado em uma pasta local |
| SQLite + sqlite-vec | Um único arquivo, adequado para projetos pequenos |
| PostgreSQL + pgvector | A melhor opção quando você já usa PostgreSQL |
| Qdrant, Milvus | Para 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