Saltar para o conteúdo
Aguiar Labs

RAG em produção: o que muda quando existe SLA

Orçamento de latência, custo por token e qualidade de contexto. O que separa um RAG de demonstração de um que aguenta produção — com números de um assistente real.

Publicado
Leitura
6 min
Render 3D escuro de um campo de placas finas deitadas em grelha, com exactamente três placas de pé entre elas, a apanhar um pouco mais de luz.

Montar um RAG hoje é uma tarde de trabalho: LangChain, uma base de dados Postgres com pgvector, um modelo de embedding e pronto, o chat responde a citar os seus documentos. A demo funciona. Funciona sempre.

O que separa essa demo de um sistema em produção são três números que a demo nunca o obriga a olhar: a latência p95, o custo por consulta e o quanto da resposta veio mesmo do contexto recuperado.

Os números abaixo são de um assistente interno que responde com base nas regras de produto de uma plataforma em produção. Não cito nomes nem valores de cliente — o que interessa aqui são as ordens de grandeza e as decisões.

Latência é orçamento, não sorte

Quando alguém promete "resposta em até 2 segundos", isso deixa de ser uma métrica e passa a ser um orçamento a dividir entre as etapas:

EtapaOrçamento p95
Embedar a pergunta40 ms
Procurar no índice vectorial120 ms
Reordenar os trechos80 ms
Gerar a resposta1.400 ms

Se a soma rebentar o SLA, não adianta trocar de modelo: a arquitectura é que está errada. E a etapa que quase toda a gente subestima é a procura.

No pgvector, a escolha do índice é a primeira alavanca. O HNSW entrega melhor relação entre velocidade e recall, mas demora mais a construir e ocupa mais memória. O IVFFlat constrói depressa e ocupa pouco, com pior recall para o mesmo tempo de procura. Em produção, na prática, o HNSW ganha quase sempre — o custo é a construção do índice, que paga uma vez.

O botão que controla o trade-off fica no tempo de consulta: hnsw.ef_search (por omissão 40) no HNSW e ivfflat.probes (por omissão 1) no IVFFlat. Subir estes valores melhora o recall e custa latência. É aí, e não no modelo, que se compra qualidade de procura.

-- índice HNSW por similaridade de cosseno
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

-- recall x latência: ajuste por consulta, não na base de dados inteira
SET LOCAL hnsw.ef_search = 100;

SELECT id, conteudo
FROM chunks
WHERE tenant_id = $1 AND doc_type = 'regra'
ORDER BY embedding <=> $2
LIMIT 3;

Repare no WHERE daquela consulta, porque é ele que guarda a armadilha mais cara do pgvector: com índice aproximado, o filtro é aplicado depois da varredura do índice. Pede 3 resultados, o índice devolve os vizinhos mais próximos, o filtro deita fora a maior parte e sobra 1 — ou nenhum. A resposta vem vazia e parece bug de modelo. A saída é o iterative scan (hnsw.iterative_scan), que faz a base de dados procurar mais candidatos até completar o LIMIT. Descobrir isto em produção, com um cliente a perguntar por que razão o assistente "esqueceu" a regra, é caro.

Token é contexto, e contexto é escolha

O tutorial padrão envia os 10 trechos mais próximos para o modelo. É o maior desperdício silencioso de um RAG: paga entrada por 10 trechos, o modelo lê 10 trechos e a resposta sai de 2.

No assistente que medi, o corpus é pequeno e bem delimitado: 16 documentos de regras dão 384 chunks, cerca de 530 mil caracteres, algo perto de 133 mil tokens. Cada pergunta leva 3 trechos, à volta de 1.400 tokens de contexto, mais a pergunta embedada. A indexação inteira, num modelo de embedding de 0,02 dólares por milhão de tokens, custa menos de um cêntimo de dólar. E reindexar toca só no que mudou, porque o hash do conteúdo decide o que é novo.

Três decisões fazem esse número ficar pequeno sem perder resposta:

  • Chunk com fronteira semântica. Cortar a cada mil caracteres é simples e parte tabela, lista e regra ao meio. Cortar por secção custa um dia de trabalho e melhora o recall e a resposta ao mesmo tempo.
  • Rerank antes de enviar. Trazer 20 candidatos da procura e enviar 3 para o modelo é mais barato e mais preciso do que enviar 10 directamente.
  • Metadado como filtro, não como texto. Tenant, tipo de documento e versão entram no WHERE, não no prompt.

O ganho do RAG não é poupança, é acerto com fonte

Existe um argumento de venda comum: "o RAG poupa tokens porque não precisa de enviar o manual inteiro no prompt". A conta parece boa — enviar 133 mil tokens a cada pergunta, a um preço típico de 3 dólares por milhão de tokens de entrada, daria uns 40 cêntimos de dólar por pergunta.

Só que essa não é a alternativa real. Ninguém ia enviar o manual inteiro a cada pergunta. Sem RAG, a alternativa é o assistente não saber responder — ou pior, inventar uma regra que soa plausível.

O ganho do RAG é outro: acerto com fonte citada. Uma resposta que aponta de que documento e de que trecho saiu pode ser conferida por quem a recebeu. É isso que permite usar o assistente em cima de regra de negócio, preço e política. Poupança de tokens é consequência de um contexto bem escolhido, não o objectivo.

O dinheiro quase nunca está onde o procura

Ao medir o custo dessa plataforma, o RAG não era o problema. A indexação custa cêntimos, e o contexto por pergunta é pequeno.

O que apareceu foi outra coisa: em dezenas de milhares de chamadas ao modelo, o cache de prompt estava desligado — o contador de tokens lidos do cache estava a zero em todas elas. A feature mais cara, responsável pela maior parte da conta, envia cerca de 8,5 mil tokens de entrada por chamada, e boa parte disso é prefixo estável: regras de segurança, instruções de sistema, dados da oferta.

Leitura de cache custa cerca de 10% do preço da entrada normal (gravar custa à volta de 25% a mais do que uma entrada comum, uma vez). Num item que domina a conta e repete o mesmo prefixo milhares de vezes por dia, a ordem de grandeza da poupança é de centenas de dólares por mês.

Três detalhes decidem se isto funciona:

  • O cache é por prefixo. Um byte que muda no início invalida tudo o que vem depois. Timestamp no system prompt, JSON com chaves por ordem aleatória e lista de ferramentas que muda de ordem deitam abaixo o cache inteiro.
  • Existe um tamanho mínimo. O prefixo precisa de ter entre 512 e 4.096 tokens, consoante o modelo. Abaixo disso, simplesmente não é cacheado, em silêncio.
  • Dá para verificar. O campo de tokens lidos do cache na resposta responde num minuto o que uma discussão de arquitectura leva uma semana a especular.

A lição que levo: antes de prometer poupança, meça que fatia do prompt é realmente fixa. O número honesto vem da medição, não da estimativa.

Há ainda uma armadilha de observabilidade que vale a pena registar. O gasto com embedding pode não aparecer no seu painel de custo de IA, porque o registo costuma ser um middleware que embrulha o modelo de linguagem — e o fornecedor de embedding não passa por ali. O custo é pequeno, mas o painel fica a mentir, e painel que mente é pior do que painel que não existe — o mesmo padrão dos controlos que ninguém testou.

O que meço antes de dizer que está pronto

  1. Orçamento de latência por etapa, com p95 medido em produção, não em média local.
  2. Recall da procura num conjunto de perguntas reais, antes de culpar o modelo pela má resposta.
  3. Tokens por resposta, separando prefixo fixo de contexto recuperado.
  4. Taxa de leitura do cache, que precisa de ser maior do que zero.
  5. Quanto da resposta é sustentado pelo contexto, com a citação do trecho de origem visível para quem lê.
  6. Um caminho de fallback determinístico para quando o modelo estiver em baixo — mesmo que seja uma procura por padrões explícitos, respondendo menos e errando menos.

Nada disto aparece na demo. Tudo aparece na primeira semana de produção.

Leitura complementar

Fontes

Newsletter mensal da Aguiar Labs.

Uma ideia técnica por mês. Curta e sem rodeios.

Ao subscrever, concorda em receber a nossa newsletter e com a nossa Política de Privacidade. Cancele a qualquer momento.

Continue a ler