A maioria das demonstrações de RAG morre em produção. Elas funcionam no conjunto de demonstração com 10 perguntas e quebram assim que os usuários perguntam algo novo. Este guia apresenta a arquitetura e os padrões que resistem: divisão que respeita limites semânticos, recuperação híbrida que captura consultas aproximadas e literais, estrutura de prompt que evita alucinações e disciplina de custos que mantém a conta estável conforme você escala.
As cinco decisões que importam
- Estratégia de divisão — afeta mais a qualidade da recuperação do que o modelo
- Modelo de embeddings — define o teto de recall
- Estratégia de recuperação — apenas vetores não é suficiente
- Estrutura do prompt — decide se o modelo alucina
- Modelo de geração + cache — define a economia unitária
1. Divisão — o básico vence o engenhoso
Não pense demais nisso. Divisor recursivo de caracteres com 1000–1500 caracteres por bloco e sobreposição de 100–200 caracteres. Tenta quebrar primeiro nos limites de parágrafos, depois nas frases e, por fim, nas palavras. Estável entre domínios:
# Recursive character splitter with overlap — the boring choice that wins
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1200,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_text(document)
# 1200 chars ≈ 300-400 tokens — fits 5+ chunks in a Sonnet context window
# with room for system prompt + question + answerCoisas que parecem mais inteligentes, mas raramente ajudam: modelos cientes de frases, divisores cientes de Markdown e janela deslizante com embeddings. Não são ruins — são marginalmente melhores com um esforço enorme. Invista esse esforço na recuperação.
2. Modelo de embeddings — text-embedding-3-large é o padrão
Os embeddings não são fornecidos pelo Kunavo — chame diretamente um provedor de embeddings para esta etapa. /v1/embeddings é um formato de comunicação implementado sem nenhum modelo habilitado por trás dele, portanto uma solicitação para ele falha; GET /v1/models é sempre a autoridade sobre o que pode ser chamado. Na prática, isso não custa nada além de uma segunda chave para um pipeline RAG: a chamada de embeddings e a chamada de geração são solicitações separadas de qualquer forma, então gere embeddings usando OpenAI, Voyage ou Cohere e gere usando Kunavo.
text-embedding-3-large atinge o ponto ideal entre multilíngue e precisão para a maioria dos casos de uso em produção, à taxa publicada pela própria OpenAI — confira na página de preços deles, e não aqui, pois não revendemos o serviço e não devemos citar um número para ele. Gere novos embeddings quando alterar a estratégia de divisão ou o próprio modelo; não quando o conteúdo for atualizado (apenas adicione novos blocos).
Exceções em que embeddings menores funcionam: recuperação de textos curtos puramente em inglês (FAQs, cartões de produto). Para esses casos, text-embedding-3-small custa 4x menos, com perda de qualidade insignificante. Para conteúdo multilíngue ou técnico, permaneça no modelo grande.
3. Recuperação — vetores puros perdem para o híbrido
A similaridade vetorial é excelente para correspondência semântica aproximada ("como cancelo" → "política de cancelamento da assinatura"). É péssima para termos literais ("SKU-A92837" ou "pedido nº 4729"). A solução é híbrida: busca por palavras-chave BM25 em paralelo e, depois, fusão de classificação recíproca:
# Hybrid retrieval: vector similarity + BM25 keyword scoring
# Pure vector misses literal keywords (product SKUs, IDs, dates)
def retrieve(question: str, k: int = 5) -> list[dict]:
q_embed = embed([question])[0]
vector_hits = vector_db.similarity_search(q_embed, k=k * 2)
keyword_hits = bm25_search(question, k=k * 2)
# Reciprocal rank fusion — simple, robust
scores: dict[str, float] = {}
for rank, hit in enumerate(vector_hits):
scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)
for rank, hit in enumerate(keyword_hits):
scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)
top_ids = sorted(scores, key=scores.get, reverse=True)[:k]
return [chunk_by_id[i] for i in top_ids]Este é o maior ganho individual de qualidade depois de escolher um modelo de embeddings adequado. Acompanhe recall@5 em uma avaliação retida de 100 perguntas — se passar de 70% para 90% após adicionar BM25, você acabou de remover um terço das falhas de "não sei".
4. Estrutura do prompt — três padrões que evitam alucinações
# Final RAG prompt structure — three sections, citations enforced
SYSTEM_PROMPT = """You answer based exclusively on the supplied Context.
- Cite the [doc_id] for each factual claim.
- If the context doesn't answer the question, say "I don't have that
information" — do not extrapolate or use general knowledge.
- Be concise. No throat-clearing."""
def answer(question: str) -> dict:
chunks = retrieve(question, k=5)
context = "\n\n---\n\n".join(
f"[doc:{c['id']}] {c['text']}" for c in chunks
)
resp = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{"role": "system", "content": [{
"type": "text",
"text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}]},
{"role": "user", "content": f"# Context\n{context}\n\n# Question\n{question}"},
],
max_tokens=600,
)
return {
"text": resp.choices[0].message.content,
"sources": [c["id"] for c in chunks],
}Três pontos inegociáveis no prompt do sistema:
- Cite as fontes — faça o modelo anexar
[doc:42]a cada afirmação. Se o ID citado não for real, você detectou uma alucinação - Recuse explicitamente — "se o contexto não responder, diga que não tenho essa informação". Sem isso, o modelo completa usando o conhecimento do mundo
- Saída concisa — respostas curtas se correlacionam com respostas precisas. No máximo 600 tokens é um bom padrão de produção
5. Modelo de geração + cache — a camada de custos
Claude Sonnet 4.6 é o padrão. Claude Haiku 4.5 é a alternativa econômica quando o custo é uma preocupação. Sempre envolva o prompt do sistema em cache_control — o prompt do sistema é o mesmo em todas as chamadas, então o cache reduz essa parte para 10% da taxa de entrada.
Custo realista por consulta em escala de produção (contexto de 5K, 500 tokens de saída, cache ativado):
| Modelo | Custo por consulta | A 10.000 consultas/dia | Qualidade vs. Sonnet |
|---|---|---|---|
claude-sonnet-4-6 | ~$0.007 | ~US$ 70/dia | Base |
claude-haiku-4-5 | ~$0.001 | ~US$ 10/dia | ~85%, 4× mais barato |
A 10.000 consultas/dia → US$ 70/dia no Sonnet, US$ 10/dia no Haiku. Escolha Sonnet para casos de uso de alto risco (voltados ao cliente, jurídicos, médicos) e Haiku para processamento interno/em lote.
O que quebra em produção (e como detectar)
- Desvio de distribuição: o corpus de treinamento deixa de corresponder às perguntas reais dos usuários. Colete 100 consultas por semana e verifique recall@5 manualmente
- Embeddings obsoletos: os documentos de origem foram atualizados, mas os embeddings não foram renovados. Acompanhe semanalmente o tamanho do índice em comparação com o tamanho da fonte
- Citações falsas: o modelo inventa IDs de documentos que parecem reais. Valide cada
[doc:N]em relação à lista real de IDs recuperados antes da renderização - Penhascos de latência: o banco de dados vetorial entra em colapso após um milhão de vetores. Use indexação HNSW e particione por locatário se houver vários locatários
Um roteiro de produção de 4 semanas
- Semana 1: protótipo com 100 documentos, 10 perguntas de teste, avaliação manual
- Semana 2: escale para o corpus completo, crie a recuperação híbrida e escreva um conjunto de avaliação com 100 perguntas
- Semana 3: ajuste a segmentação e o prompt do sistema iterando sobre a avaliação; disponibilize para usuários internos
- Semana 4: monitoramento, orçamento de custos e lançamento público com interface para citar fontes
Perguntas frequentes
Qual tamanho de bloco um sistema RAG de produção deve usar?
1.000–1.500 caracteres por bloco, com sobreposição de 100–200 caracteres, divididos por um divisor recursivo de caracteres que primeiro quebra nos limites de parágrafos, depois nas frases e, por fim, nas palavras. Modelos cientes de frases, divisores cientes de Markdown e janelas deslizantes são marginalmente melhores com um esforço muito maior — esse esforço compensa mais quando investido na recuperação.
Qual modelo de embeddings um pipeline RAG deve usar?
text-embedding-3-large é o padrão para conteúdo multilíngue e técnico e, para recuperação de textos curtos puramente em inglês, como FAQs e cartões de produto, text-embedding-3-small custa cerca de 4× menos, com perda de qualidade insignificante. Observe que os embeddings não são fornecidos pelo Kunavo: /v1/embeddings é um formato de comunicação implementado sem nenhum modelo habilitado por trás dele, portanto a etapa de embeddings chama diretamente a OpenAI, a Voyage ou a Cohere, enquanto a etapa de geração chama o Kunavo. Isso não custa nada além de uma segunda chave para o pipeline, pois as duas são solicitações separadas de qualquer forma.
A busca vetorial sozinha é suficiente para a recuperação RAG?
Não. A similaridade vetorial lida bem com correspondência semântica aproximada, mas falha em termos literais, como um SKU ou número de pedido. Executar uma busca por palavras-chave BM25 em paralelo e combinar com fusão de classificação recíproca normalmente eleva recall@5 de cerca de 70% para cerca de 90% — o maior ganho individual de qualidade depois de escolher um modelo de embeddings adequado.
Como impedir que um sistema RAG alucine?
Três padrões no prompt do sistema: exigir uma citação [doc:N] em toda afirmação factual, instruir o modelo a dizer que não tem a informação quando o contexto não responder e manter as respostas curtas — no máximo 600 tokens de saída é um bom padrão de produção. Depois, valide cada ID citado contra o conjunto recuperado antes da renderização; um ID que não existe é uma alucinação detectada.
Quanto custa uma consulta RAG de produção?
Com contexto de 5K e 500 tokens de saída, com cache de prompt ativado: cerca de US$ 0,007 por consulta no Claude Sonnet 4.6 e cerca de US$ 0,001 no Claude Haiku 4.5 (4× mais barato, com aproximadamente 85% da qualidade). A 10.000 consultas por dia, isso equivale a cerca de US$ 70/dia no Sonnet e cerca de US$ 10/dia no Haiku.