Intermediário 11 minStack

RAG com ChromaDB e Mistral

Resposta direta

Para um RAG local com Mistral, a pilha mais simples é Ollama (geração e embeddings com bge-m3) mais ChromaDB em modo arquivo, sem servidor nem PyTorch. Quanto ao modelo, ministral-3:8b (6,0 GB, licença Apache 2.0, contexto anunciado de 256K) é uma boa opção padrão para uma placa de 8 a 12 GB, ministral-3:14b para 16 GB e mistral-small3.2:24b (15 GB) para capacidades superiores. O ajuste que você não deve esquecer: aumentar a janela de contexto do Ollama para que os trechos caibam no prompt.

Este guia constrói um assistente documental completo, em dois scripts Python, com um modelo Mistral executado na sua máquina: seus PDFs e arquivos de texto são divididos, indexados no ChromaDB, e os trechos encontrados são passados para o modelo, que responde citando suas fontes. Ele também especifica qual modelo Mistral escolher de acordo com a sua memória gráfica e as armadilhas que fazem um RAG responder fora do assunto.

Por Mohamed Meguedmi·Atualização 2026-09-30·Testado no Windows, macOS e Linux

#O que construímos: um RAG Mistral totalmente local

O RAG (geração aprimorada por recuperação) consiste em localizar os trechos dos seus documentos relacionados à pergunta e colá-los no prompt do modelo para que ele responda com base nesses trechos. O resultado esperado aqui é um pequeno utilitário de linha de comando: um script indexa uma pasta de documentos; um segundo lê uma pergunta, encontra os cinco trechos mais próximos no ChromaDB, os envia a um modelo Mistral via Ollama juntamente com a pergunta e exibe a resposta seguida dos arquivos consultados. Nada sai da máquina: Ollama fornece o modelo de geração e o modelo de embeddings, o ChromaDB armazena os vetores em uma pasta local.

O termo “Mistral” é usado em dois sentidos: os modelos de pesos abertos da Mistral AI, que você baixa e executa por conta própria (objeto deste guia), e as APIs hospedadas pela empresa, que enviam seus trechos para os servidores dela. Para documentos confidenciais, apenas o primeiro atende à exigência “100% local”.

#Qual modelo Mistral escolher para o RAG

O kit RAG Local

Seus documentos, sua IA: um RAG local confiável para seus PDFs, notas e e-mails — sem enviar nada para a nuvem.

  • Espaço online vitalício
  • PDF + arquivos
  • Atualizações vitalícias

Um RAG tem necessidades específicas: o modelo deve seguir uma instrução rigorosa (« responda apenas com base nos trechos »), ler vários trechos sem se perder e responder em francês. O tamanho importa menos do que na conversa livre; no entanto, a memória disponível para o contexto é mais importante. Os tamanhos abaixo são os exibidos pela biblioteca Ollama, com a quantização padrão.

Modelos Mistral na biblioteca Ollama (relatório de setembro de 2026)
ModeloTamanho do OllamaContexto anunciadoPara quem
ministral-3:3b3,0 GB256KMáquina sem GPU dedicada; respostas simples, baixa tolerância a instruções complexas
ministral-3:8b6,0 GB256KOpção padrão razoável para uma placa de 8 a 12 GB ou um notebook com 16 GB de memória
ministral-3:14b9,1 GB256KPlaca de 16 GB, ou 12 GB com contexto moderado
mistral-nemo (12B)ver a página Ollama128KAlternativa mais antiga, ainda amplamente utilizada
mistral-small3.2:24b15 GB128KPlaca de 24 GB ou memória unificada de 32 GB ou mais; o mais confiável no cumprimento das instruções de formato
mistral (7B, versão 0.3)4,4 GB32KModelo antigo: reservar para máquinas muito limitadas

A família Ministral 3 (3B, 8B e 14B) é publicada sob licença Apache 2.0, conforme indicado no anúncio do Mistral 3, e a página do Ollama a descreve como projetada para implantação na borda, capaz de rodar em uma ampla gama de equipamentos. Mistral Small 4, lançado em 2026 com 119 bilhões de parâmetros no total segundo o nome de sua ficha no Hugging Face, é voltado para hardware de servidor: não é um candidato para uma máquina pessoal. Para ter uma ideia da ordem de grandeza da memória necessária, a calculadora de VRAM do site fornece o tamanho do modelo mais o cache de contexto.

i
Contexto anunciado e contexto útil
Um contexto de 128K ou 256K é a capacidade máxima do modelo, não uma configuração: Ollama utiliza muito menos por padrão, e um grande contexto consome memória adicional. Para um RAG com cinco passagens, 8.000 tokens são suficientes.

#Stack técnica

Geração
Um modelo Mistral servido por Ollama, via API HTTP local na porta 11434.
Embeddings
bge-m3 disponibilizado pelo Ollama: a página da biblioteca o descreve como um modelo da BAAI versátil, multilíngue e com múltiplas granularidades, de 567 milhões de parâmetros. Ele dispensa a instalação de PyTorch e sentence-transformers.
Base vetorial
ChromaDB em modo local (PersistentClient): uma pasta, nenhum servidor. O Chroma fornece um wrapper, OllamaEmbeddingFunction, que chama a API de embeddings do Ollama.
Leitura de arquivos
pypdf para PDFs que contêm texto, leitura direta para Markdown e texto simples. Um PDF escaneado é uma imagem: primeiro é necessário fazer o reconhecimento de caracteres.

#Preparar o ambiente

  1. 01
    Instalar Ollama e baixar os modelos
    Instale o Ollama, depois baixe o modelo de geração e o modelo de embeddings com os dois comandos abaixo.
  2. 02
    Criar o ambiente Python
    Python 3.10 ou superior. Um ambiente virtual mantém as dependências do projeto separadas.
  3. 03
    Colocar os documentos
    Copie seus arquivos PDF, Markdown e texto para uma pasta docs/ ao lado dos scripts.
Modelos e dependências
ollama pull ministral-3:8b
ollama pull bge-m3

mkdir mon-rag && cd mon-rag
python3 -m venv venv
source venv/bin/activate   # .\venv\Scripts\activate sous Windows
pip install chromadb pypdf requests

#2. Indexar os documentos no ChromaDB

O script lê cada arquivo, divide o texto em trechos de aproximadamente 1.800 caracteres, cortando entre parágrafos, e depois entrega esses trechos ao Chroma, que chama o bge-m3 via Ollama para calcular os vetores. Dois pontos são importantes: cada trecho mantém o nome do arquivo como metadado (para citar a fonte) e as adições são feitas em lotes, em vez de um trecho por vez.

index.py
from pathlib import Path
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction
from pypdf import PdfReader

ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_or_create_collection("mes_docs", embedding_function=ef)

def lire(path: Path) -> str:
    if path.suffix.lower() == ".pdf":
        return "\n\n".join(p.extract_text() or "" for p in PdfReader(str(path)).pages)
    return path.read_text(encoding="utf-8", errors="ignore")

def decouper(texte: str, max_chars=1800):
    """Regroupe des paragraphes entiers jusqu'à max_chars ; un paragraphe trop long est coupé."""
    chunks, courant = [], ""
    for para in (p.strip() for p in texte.split("\n\n")):
        if not para:
            continue
        while len(para) > max_chars:
            if courant:
                chunks.append(courant); courant = ""
            chunks.append(para[:max_chars]); para = para[max_chars:]
        if len(courant) + len(para) + 2 > max_chars and courant:
            chunks.append(courant); courant = ""
        courant = (courant + "\n\n" + para).strip()
    if courant:
        chunks.append(courant)
    return chunks

n = 0
for path in sorted(Path("docs").rglob("*")):
    if path.suffix.lower() not in {".pdf", ".md", ".txt"}:
        continue
    chunks = decouper(lire(path))
    if not chunks:
        print(f"  ! {path.name} : aucun texte extrait (PDF scanné ?)")
        continue
    for i in range(0, len(chunks), 32):  # par lots de 32
        lot = chunks[i:i + 32]
        coll.upsert(
            ids=[f"{path.name}-{i + j}" for j in range(len(lot))],
            documents=lot,
            metadatas=[{"source": path.name}] * len(lot),
        )
    n += len(chunks)
    print(f"  + {path.name} : {len(chunks)} passages")
print(f"Terminé : {n} passages indexés")

O uso de upsert com identificadores construídos a partir do nome do arquivo e do número do trecho permite executar o script novamente: reindexar a mesma pasta atualiza os trechos em vez de duplicá-los. Atenção, porém: se um documento ficar mais curto, os trechos antigos excedentes permanecem na base; para uma alteração importante, exclua a pasta chroma_db e reindexe. A escolha do tamanho dos trechos é detalhada no guia sobre estratégias de chunking.

#3. Consultar: busca e depois geração

O segundo script incorpora a pergunta, recupera os cinco trechos mais próximos e compõe o prompt. A instrução é decisiva: ela pede que o modelo responda apenas com base nos trechos, admita quando faltar informação e cite o arquivo. O parâmetro num_ctx aumenta a janela de contexto: a documentação do Ollama indica que a janela padrão é de 4.096 tokens e que a variável OLLAMA_CONTEXT_LENGTH ou o parâmetro num_ctx a modificam. Com cinco trechos de 400 a 500 tokens, a instrução e a resposta, 4.096 tokens ficam no limite: um contexto muito curto é cortado silenciosamente, e o modelo responde sem ter lido o final dos seus trechos.

ask.py
import sys, requests
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction

MODELE = "ministral-3:8b"
ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_collection("mes_docs", embedding_function=ef)

SYSTEME = (
    "Tu réponds en français, uniquement à partir des passages fournis. "
    "Si la réponse n'y figure pas, dis-le clairement au lieu de deviner. "
    "Termine chaque affirmation par le nom du fichier source entre crochets."
)

def repondre(question: str, k: int = 5):
    res = coll.query(query_texts=[question], n_results=k)
    passages = list(zip(res["documents"][0], res["metadatas"][0]))
    contexte = "\n\n---\n\n".join(f"[{m['source']}]\n{p}" for p, m in passages)
    r = requests.post("http://localhost:11434/api/chat", json={
        "model": MODELE,
        "stream": False,
        "options": {"temperature": 0.2, "num_ctx": 8192},
        "messages": [
            {"role": "system", "content": SYSTEME},
            {"role": "user", "content": f"PASSAGES :\n{contexte}\n\nQUESTION : {question}"},
        ],
    }, timeout=300)
    r.raise_for_status()
    return r.json()["message"]["content"], sorted({m["source"] for _, m in passages})

if __name__ == "__main__":
    q = " ".join(sys.argv[1:]) or input("Question : ")
    reponse, sources = repondre(q)
    print("\n" + reponse)
    print("\nSources consultées :", ", ".join(sources))
Iniciar
python index.py
python ask.py "Quel est le délai de préavis prévu au contrat ?"

#Verificar o que o ChromaDB retorna antes de culpar o modelo

Quando uma resposta é ruim, a causa está em um de dois pontos: a busca não trouxe o trecho correto ou o modelo o usou mal. É possível distinguir os dois casos exibindo os trechos recuperados com suas respectivas distâncias, sem chamar o modelo. Se o trecho correto não estiver entre os cinco primeiros, mude a divisão do texto em trechos, adicione uma busca por palavras-chave ou um reranker. Se ele estiver presente e a resposta continuar errada, o problema está no prompt, no contexto truncado ou no modelo: experimente o modelo de tamanho superior antes de concluir.

debug.py: exibir os trechos e suas distâncias
import sys
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction

ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_collection("mes_docs", embedding_function=ef)
res = coll.query(query_texts=[" ".join(sys.argv[1:])], n_results=8)
for doc, meta, dist in zip(res["documents"][0], res["metadatas"][0], res["distances"][0]):
    print(f"{dist:.3f}  {meta['source']}  {doc[:120]!r}")

#Orçamento de memória: o que deve caber ao mesmo tempo

O RAG faz dois modelos coexistirem, o que gera e o que calcula os vetores, além do cache de contexto do primeiro. O Ollama carrega cada modelo sob demanda e pode descarregar um para liberar espaço para o outro, o que acrescenta um atraso a cada troca se houver pouca memória disponível. A tabela apresenta uma ordem de grandeza para três configurações; o tamanho do modelo vem da biblioteca do Ollama, e o restante é um cálculo a refinar com a calculadora de VRAM do site.

Memória necessária (pesos do modelo no Ollama, contexto de 8 192 tokens)
ConfiguraçãoPeso do modelo de geraçãoA adicionarPlaca-alvo
ministral-3:8b + bge-m36,0 GBCache de contexto, modelo de embeddings (567 milhões de parâmetros, pouco mais de um GB em meia precisão), margem para o sistema8 a 12 GB
ministral-3:14b + bge-m39,1 GBIdem; o contexto longo torna-se o fator limitante em 12 GB12 a 16 GB
mistral-small3.2:24b + bge-m315 GBIdem; prever uma margem confortável24 GB ou mais
→
Se faltar memória
Reduza primeiro num_ctx (8.192 já é generoso para cinco trechos), depois passe para o modelo inferior. Evite também aumentar o número de trechos: muitos trechos diluem a resposta tanto quanto preenchem a memória.

#As armadilhas que levam a respostas que fogem da pergunta

O contexto padrão é muito curto
Veja mais acima: sem aumentar num_ctx, os últimos trechos são truncados. Sintoma típico: a resposta correta é de fato recuperada pelo ChromaDB, mas o modelo diz que não a encontra.
PDFs escaneados
pypdf lê apenas texto já presente. Um documento digitalizado retorna um resultado vazio: o script mostra isso. Primeiro, passe o documento por um OCR, descrito no guia sobre Tesseract.
Trechos sem contexto
Um trecho retirado de seu documento (« o prazo é de 30 dias ») não indica a que se refere. Acrescente o título do documento ou da seção ao início de cada trecho.
Pergunta sem resposta nos documentos
Sem a instrução « diga claramente », um modelo preenche o vazio com o que sabe. Sempre teste uma pergunta cuja resposta não está em seus arquivos.
Identificadores e termos exatos
Um número de contrato ou de processo não é bem recuperado pelos embeddings: adicione uma busca por palavras-chave, como descrito no guia sobre busca híbrida.
!
Verificar antes de confiar
Um RAG cita suas fontes, mas isso não prova que a resposta é correta: abra o arquivo citado para as decisões que importam (contratos, números, prazos).

#Para se aprofundar

Melhorias classificadas pela relação entre esforço e efeito
MelhoriaEsforçoQuando fazer
Aumentar o número de passagens (k) de 5 para 8Uma linhaA resposta está distribuída em vários trechos
Chunking por títulos em vez de parágrafosMédioDocumentos estruturados (documentação, contratos divididos em artigos)
Busca híbrida BM25 + vetorialMédioPerguntas por identificador, sigla ou nome próprio
Reranker (bge-reranker-v2-m3)MédioA resposta correta é recuperada, mas fica abaixo da 5ª posição na classificação
Interface de chat (Open WebUI, API FastAPI)VariávelOutras pessoas precisam usar a ferramenta
Backup e reindexação programadosBaixoO diretório de documentos evolui semanalmente

Cada melhoria tem seu próprio guia: meça o recall em 30 a 50 perguntas reais antes e depois, em vez de acumular técnicas. Se você prefere uma interface pronta sem escrever código, o guia de RAG sem código apresenta Open WebUI e AnythingLLM.

#Perguntas frequentes sobre RAG com Mistral

FAQ
Qual modelo Mistral para um RAG local?+
Para uma placa com 8 a 12 GB, ministral-3:8b (6,0 GB em Ollama, licença Apache 2.0) é um bom ponto de partida; ministral-3:14b (9,1 GB) para 16 GB; mistral-small3.2:24b (15 GB) para 24 GB ou mais. Escolha de acordo com a memória restante após o peso do modelo: é preciso espaço para o contexto.
Ollama consegue calcular os embeddings no lugar de sentence-transformers?+
Sim: o Ollama oferece uma API de embeddings e disponibiliza o bge-m3, um modelo multilíngue com 567 milhões de parâmetros. O ChromaDB fornece o wrapper OllamaEmbeddingFunction para chamá-lo. A vantagem é ter apenas um motor para instalar, sem PyTorch; a desvantagem é que é necessário manter o Ollama ativo durante a indexação.
Por que o modelo diz que não encontra a resposta, mesmo que ela esteja em meus documentos?+
Duas causas frequentes. Ou a janela de contexto do Ollama é muito curta e os trechos são truncados: aumente num_ctx para 8.192. Ou os trechos recuperados não contêm a resposta: verifique o que o ChromaDB retorna antes da geração, depois ajuste a divisão em trechos ou a busca.
É possível usar a API Mistral no lugar do Ollama?+
Tecnicamente sim, mas seus trechos seriam então enviados aos servidores da empresa, o que contradiz a exigência de confidencialidade para documentos sensíveis. Para manter a execução local, continue usando os modelos com pesos abertos executados pelo Ollama. Para documentos não sensíveis, é possível usar a API; nesse caso, consulte as condições de tratamento de dados do fornecedor.
Como adicionar novos documentos sem reindexar tudo?+
Copie-os para docs/ e execute index.py novamente: com upsert e identificadores estáveis, os trechos existentes são atualizados e os novos são adicionados. Se você modificar o tamanho dos trechos ou o modelo de embeddings, remova a pasta chroma_db e reindexe tudo: os vetores antigos não são mais comparáveis.
É necessário um GPU para esse RAG?+
Não necessariamente: ministral-3:3b roda em um processador recente com 8 GB de memória, com respostas lentas. A indexação, por sua vez, ocorre apenas uma vez. Uma GPU melhora principalmente a resposta: mais velocidade e a possibilidade de usar um modelo maior. Meça o tempo de resposta em sua máquina antes de decidir investir.
Este guia ajudou você?

Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.