Ollama: rodando LLM e embeddings 100% locais (e por que isso importa)
Quarto post da série sobre o Konhecia. Já temos infraestrutura, API e persistência. Agora o modelo.
O requisito que define o projeto inteiro: nenhuma chamada a API externa de LLM. Os documentos que o agente lê são manuais internos. Mandá-los para um endpoint de terceiro é uma decisão de segurança — e, em muitos contextos, uma que você não tem autoridade para tomar sozinho.
Por que local não é preciosismo
Vale ser concreto, porque “privacidade” virou palavra vazia.
O documento vira dado de entrada de terceiro. Todo chunk recuperado é enviado no prompt. Num RAG corporativo, isso significa que trechos de manuais internos, procedimentos e políticas saem do perímetro a cada pergunta. Não é o upload de um arquivo uma vez — é um vazamento contínuo, proporcional ao uso.
Contrato e regulação. Um NDA que proíbe compartilhar material com terceiros não abre exceção para “mas é só a API do modelo”. Sob LGPD, se houver dado pessoal nos documentos, você acabou de adicionar um operador à cadeia — com tudo que isso implica de base legal e transferência internacional.
Superfície de ataque e dependência. Chave de API é segredo que vaza. Custo por token é conta que escala com o uso. Modelo hospedado é fornecedor que deprecia versão, muda comportamento e altera preço sem pedir licença.
Rodar local troca tudo isso por um custo diferente: hardware e qualidade menor de modelo. É um trade-off real, não um almoço grátis. Para um agente que responde sobre documentos internos, a troca compensa — a tarefa é fundamentar resposta em texto recuperado, não raciocinar sobre problemas abertos.
O que o Ollama é
Ollama é um servidor de inferência com uma API HTTP simples. Ele cuida de baixar modelos, carregá-los na memória, gerenciar GPU/CPU e servir requisições.
O modelo mental correto: é o “Docker dos modelos”. Você faz pull de um modelo por
nome e tag, ele fica num registro local, e você conversa com ele por HTTP.
ollama pull qwen2.5:7b
ollama list
Em container, com um one-shot de inicialização baixando os modelos num volume compartilhado — o padrão que descrevi no post sobre Compose.
Quantização: o que os números no nome significam
qwen2.5:7b — o 7b são 7 bilhões de parâmetros. É o tamanho do modelo.
Em precisão nativa (16 bits por parâmetro), 7B pediria cerca de 14 GB só de pesos. Não cabe confortavelmente na maioria das placas de consumo.
Quantização reduz a precisão numérica dos pesos — de 16 bits para 4 ou 5. O modelo encolhe para uns 4,7 GB e roda em hardware modesto, perdendo uma qualidade que, para a maioria das tarefas práticas, é pequena.
A analogia útil: é compressão com perdas, como um JPEG. Você não distingue um JPEG bem comprimido do original no uso normal; se for ampliar muito, os artefatos aparecem. Modelo quantizado é igual — bom para a tarefa típica, degrada nos casos que exigem precisão fina.
A regra prática de memória: o modelo precisa caber inteiro na VRAM para rodar rápido. Não cabendo, o Ollama divide entre GPU e RAM, e a inferência despenca de velocidade. Escolher o tamanho do modelo é, na prática, escolher em função da VRAM disponível.
Dois modelos, dois trabalhos
Este é o ponto que mais confunde quem monta o primeiro RAG. Um sistema desses usa dois modelos diferentes, e eles não são intercambiáveis:
OLLAMA_LLM_MODEL=qwen2.5:7b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
Modelo de geração (qwen2.5:7b) recebe texto e produz texto. É o que escreve
a resposta.
Modelo de embedding (nomic-embed-text) recebe texto e produz um vetor de
números — 768 no caso dele. Não gera nada legível. Sua única função é transformar
texto num ponto no espaço, de modo que textos com significado próximo fiquem
próximos.
Por que não usar o modelo grande para as duas coisas? Custo e propósito. O
embedding roda em cada chunk na ingestão e em cada pergunta na consulta. Um modelo
de 7B para isso seria absurdamente lento e caro, produzindo um resultado pior — o
nomic-embed-text tem 274 MB, é treinado especificamente para representação
semântica e é ordens de grandeza mais rápido.
A dimensão do vetor é um contrato rígido. nomic-embed-text produz 768
dimensões, e a collection no banco vetorial precisa ser criada com exatamente esse
número:
QDRANT_VECTOR_SIZE=768
Trocar o modelo de embedding depois invalida toda a base indexada. Os vetores antigos foram gerados por outra função — mesmo que a dimensão bata por acaso, a geometria é outra e a busca devolve lixo silenciosamente. Trocar de modelo de embedding significa reindexar tudo. Não é opcional.
Temperatura: por que 0.1 e não 0.7
temperature controla a aleatoriedade na escolha do próximo token. Perto de 0, o
modelo pega quase sempre o mais provável — saída determinística e conservadora.
Alto, ele arrisca — saída variada e criativa.
OLLAMA_TEMPERATURE=0.1
Num agente de RAG, criatividade é exatamente o defeito que se está tentando evitar. A tarefa não é escrever bonito: é repetir fielmente o que está no contexto recuperado, ou recusar. Temperatura baixa é a primeira linha contra alucinação — barata, e ninguém liga.
Não confunda com garantia: temperatura 0.1 reduz a variação, não impede o modelo
de inventar quando o contexto é ruim. Isso exige outras camadas, que são assunto
dos próximos
posts.
Servindo em container
A API do Ollama é HTTP simples, o que torna o healthcheck trivial:
async def _check_ollama(settings: Settings) -> str:
try:
async with httpx.AsyncClient(timeout=3) as client:
response = await client.get(f"{settings.ollama_base_url}/api/tags")
response.raise_for_status()
return "ok"
except Exception:
logger.exception("Falha no healthcheck do Ollama")
return "unavailable"
/api/tags lista os modelos disponíveis. Se responde, o servidor está de pé e o
volume dos modelos está montado — dois problemas verificados numa requisição.
Um detalhe operacional que economiza confusão: a primeira requisição depois de um período ocioso é lenta. O Ollama descarrega o modelo da memória após um tempo sem uso e precisa recarregar os gigabytes de pesos. Não é bug, e não adianta caçar no seu código.
GPU opcional, CPU garantido
Com o NVIDIA Container Toolkit configurado, o Ollama usa a placa. Sem ela, cai para CPU automaticamente — bem mais lento, porém correto.
Isso é um princípio de projeto que vale além deste caso: a dependência que degrada é melhor que a dependência que quebra. Alguém consegue clonar o repositório e rodar num notebook sem placa dedicada. Vai esperar mais por resposta, mas não precisa editar YAML nem entender de CUDA para ver o sistema funcionando.
O que fica
Rodar o modelo localmente deixou de ser exótico. Um modelo quantizado de 7B num container, um modelo de embedding de 274 MB ao lado, e você tem um agente que nunca manda um byte de documento interno para fora.
O que se paga é qualidade bruta do modelo. O que se ganha é que o dado não sai do perímetro, o custo é fixo, não há chave de API para vazar e nenhum fornecedor muda o comportamento do seu sistema num release.
No próximo post: o banco vetorial — o que é um embedding na prática, como funciona similaridade de cosseno e por que um limiar de score sozinho não segura alucinação.