De volta ao assistente jurídico da aula passada. Um contrato de serviços, dezoito páginas, uns doze mil tokens.
O pipeline quebra o contrato em pedaços de quinhentos tokens e busca os quatro mais próximos de cada pergunta. Quatro pedaços de quinhentos dá dois mil tokens. De doze mil.
Então, quando alguém perguntou sobre rescindir por atraso, o modelo respondeu olhando um sexto do documento. E não sabia disso. Disse que não tinha multa. A cláusula 14.3 dizia outra coisa.
Nesta aula, você vai tirar a busca, com segurança, colocar o contrato inteiro na frente do modelo e manter a conta sob controle.
A ideia
A refatoração cabe em duas frases. Mande o material inteiro no contexto, num bloco fixo no começo da chamada. E ligue o cache de prompt, para não pagar a leitura cheia em toda pergunta.
A busca vetorial existe para escolher o pouco que cabe, quando o material é grande. Com material pequeno, essa escolha só tira informação. A quebra separa cláusulas que dependem uma da outra, e a busca erra quando as palavras da pergunta não batem com o texto.
Antes e depois
Antes, o contrato vira pedaços e embeddings. A pergunta escolhe quatro deles. E o modelo responde com o que a busca escolheu.
Depois, o documento inteiro vai num bloco fixo, logo depois das instruções.
Na primeira pergunta, esse prefixo é gravado no cache.
Nas seguintes, o modelo lê o contrato do cache, por uma fração do custo.
E a resposta pode ligar cláusulas distantes, porque nada foi cortado.
A mesma pergunta
Então, a mesma pergunta de novo. Com quatro pedaços, o contexto tinha a rescisão e não a multa. Com o contrato inteiro, as duas cláusulas estão no mesmo prefixo.
E a instrução pede ao modelo que cite artigo e parágrafo. Então a resposta vira esta. Não. A cláusula 8 permite rescindir por atraso, mas a 14.3 aplica multa por inadimplemento, e atraso na entrega entra nessa definição. É uma resposta que um advogado confere em dez segundos.
Um alerta continua valendo. Um contexto enorme não garante que o modelo ache a cláusula do meio. A pesquisa chama isso de lost in the middle. Por isso a condição é caber com folga. Doze mil tokens numa janela grande é folga. Cem mil tokens de anexos empilhados é outro projeto.
Um desafio
Uma pergunta para você. Depois da troca, a segunda pergunta do usuário mostra zero tokens lidos do cache. Olhando o prompt, o bloco de sistema começa com a data de hoje, para o modelo saber o dia, e depois o contrato. Qual é a correção?
Tire a data do prefixo e mande na mensagem do usuário, depois do documento em cache. O cache reaproveita um começo de prompt idêntico. Uma data que muda todo dia quer dizer que nunca tem nada para reaproveitar.
Não é culpa do fornecedor, e fazer cache da mensagem do usuário também não ajuda, porque ela muda a cada pergunta. Instruções e contrato ficam estáveis. Data, usuário e pergunta vêm depois.
Passo a passo seguro
Agora, o passo a passo.
Passo zero, medir. Rode o eval de perguntas e inclua perguntas que cruzam seções, como uma regra e a sua exceção. Anote acerto, custo e latência por pergunta. Essas perguntas cruzadas são o motivo da troca.
Confira o tamanho. Conte os tokens com o contador do fornecedor, não no chute. Deixe espaço para a conversa e a resposta. Se não sobra espaço, esta refatoração não se aplica.
Monte o prefixo fixo. Instruções primeiro, depois o documento. E nada que varia antes do fim do documento. Nem data, nem id de usuário, nem hora.
Ligue o cache de prompt no bloco do documento.
Confirme que funciona. Da segunda chamada em diante, o relatório de uso deve mostrar tokens lidos do cache. Se mostrar zero, o prefixo está mudando em algum lugar.
Meça de novo e compare acerto e custo por pergunta. As perguntas cruzadas são o teste que importa. No exemplo, a busca acertou 61%, e o documento inteiro, 88%.
E só depois que a versão nova estabilizar, remova o pipeline de busca. Não deixe atrás de uma flag, só por garantia. Enquanto os dois existirem, alguém vai só ajustar o número de pedaços, no meio de um incidente.
Riscos
Alguns riscos. O material cresce. Hoje é um contrato. Amanhã são cento e vinte contratos de fornecedores, e o prefixo não cabe mais com folga. O tamanho mudou, então a ferramenta muda. Volte para um índice, filtrado pelo contrato do usuário, e mantenha o contexto direto para o documento único que ainda cabe.
Perguntas muito espaçadas. O cache expira depois de alguns minutos sem uso. Com pouco tráfego, o desconto aparece menos. O ganho de acerto nas perguntas cruzadas continua.
Partes que o usuário não pode ver. Se alguns usuários só podem ver o anexo de preços, um prompt pedindo ao modelo que esconda o resto não é controle de acesso. Uma pergunta capciosa vaza. A permissão vale antes de montar o contexto, com busca filtrada ou um prefixo por perfil.
E achar que caber basta. Se o eval de perguntas cruzadas continua ruim com o documento inteiro, o problema não era a busca. Pode ser o prompt, o modelo, ou um contrato que é ambíguo de verdade.
E uma fronteira rápida. Uma loja online pensa em colar o catálogo de oito mil produtos no prefixo, com cache. Isso não é esta refatoração. Não cabe com folga, cada pergunta precisa de poucas linhas, e o preço de um produto é uma consulta pelo código. É uma ferramenta de consulta, ou busca, não um prefixo de oito mil itens.
O código, por intenção
O código fica menor, o que é um bom sinal.
O contrato é carregado uma vez, como texto.
A parte de sistema tem dois blocos. As instruções, e depois o contrato, marcado para cache.
E a pergunta vai sozinha na mensagem do usuário, depois de tudo que é estável. As três linhas que geravam o embedding da pergunta, buscavam no banco e juntavam os pedaços simplesmente somem.
Na página, você encontra a mesma mudança nas abas de código. O que muda entre elas é só como cada uma marca o bloco para cache, e onde informa os tokens lidos do cache.
Fechamento
Então, a ideia para levar. Quando o material cabe com folga, a busca é um filtro que só tira. Ponha o documento primeiro, a pergunta depois, deixe o cache carregar o custo e meça nas perguntas que cruzam seções.
Mas às vezes o material realmente não cabe, e você precisa de pedaços. Na próxima aula, a gente faz cada pedaço carregar o contexto que perdeu quando foi cortado: contextual chunking. Até lá.
18Rd
RefatoraçãoOrganizando dados
Trocar RAG por Contexto Direto
Replace RAG with Direct Context
Trocar RAG por Contexto Direto (Replace RAG with Direct Context) é uma refatoração de IA para sistemas com LLM, do grupo Organizando dados. Quando o material cabe na janela, mandar inteiro e usar cache de prompt.
Um pipeline de busca vetorial atende um material pequeno, que caberia inteiro no contexto, e perde trechos importantes no caminho. É o smell RAG Desnecessário.
O assistente jurídico responde perguntas sobre um contrato de prestação de 18 páginas (cerca de 12 mil tokens). A arquitetura que saiu da demo: quebrar em pedaços de 500 tokens, embedding, buscar os 4 mais parecidos. A pergunta “posso rescindir sem multa se o fornecedor atrasar a entrega?” trouxe a cláusula de rescisão e não a de penalidades, que falava de “inadimplemento” em vez de “atraso”. O modelo respondeu “sim, sem multa”. A cláusula 14.3 dizia o contrário.
O contrato inteiro cabia na janela com folga. Quatro pedaços de 500 tokens são 2 mil dos 12 mil: o modelo respondeu olhando para um sexto do documento, sem saber disso. A busca fez o trabalho para o qual ela existe quando o corpus não cabe. Aqui o corpus cabia.
Solução
Mande o material inteiro no contexto, num bloco fixo no começo da chamada, e use cache de prompt para não pagar a leitura completa a cada pergunta.
Antes e depois
-chunks = vector_db.search(embed(question), k=4)-context = "\n".join(c.text for c in chunks)-reply = llm(SYSTEM, f"{context}\n\nPergunta: {question}")+CONTRACT = load_text("contrato.pdf") # 18 páginas, cerca de 12 mil tokens++reply = client.messages.create(+ model=MODEL, max_tokens=2000,+ system=[+ {"type": "text", "text": SYSTEM},+ {"type": "text", "text": f"<contrato>\n{CONTRACT}\n</contrato>",+ "cache_control": {"type": "ephemeral"}}, # prefixo cacheado+ ],+ messages=[{"role": "user", "content": question}],+)
Antes: o modelo vê os 4 pedaços que a busca escolheu.
flowchart LR
D(["1. Contrato"]):::io --> C["2. Pedaços e embeddings"]:::tool
Q(["Pergunta"]):::io --> S["3. Buscar 4 pedaços"]:::tool
C --> S
S --> M(["4. Modelo com parte do contrato"]):::llm
Depois: o modelo vê o contrato inteiro, e o cache absorve o custo.
flowchart LR
D(["1. Contrato inteiro no prefixo"]):::io --> K[("2. Cache de prompt")]:::store
Q(["Pergunta"]):::io --> M(["3. Modelo com o contrato todo"]):::llm
K --> M
M --> OUT(["4. Resposta que considera todas as cláusulas"]):::io
O documento inteiro vai num bloco fixo, depois das instruções.
Na primeira pergunta, o prefixo é gravado no cache.
Nas seguintes, o modelo lê o contrato do cache, a uma fração do custo.
A resposta pode cruzar cláusulas distantes, porque nada foi cortado.
A pergunta que o RAG errava
Com os 4 pedaços, o contexto tinha a rescisão e não a multa. Com o contrato inteiro, a mesma pergunta vê as duas cláusulas no mesmo prefixo. A instrução pede artigo e parágrafo. A resposta passa a ser do tipo: “não: a cláusula 8 permite rescisão por atraso, mas a 14.3 aplica multa sobre o inadimplemento, e atraso de entrega entra nessa definição”.
Os números desta página seguem o contrato de 18 páginas já usado no smell. Não são uma medição nova de produto.
Por que funciona
A busca vetorial existe para escolher o pouco que cabe no contexto quando o material é grande. Com material pequeno, essa escolha só tira informação: o corte separa cláusulas que dependem umas das outras, e a busca erra quando as palavras da pergunta não batem com as do texto.
Com o documento inteiro, o modelo faz o que faz bem: lê, relaciona e cita. O cache de prompt resolve a objeção do custo, porque o prefixo idêntico (instruções e documento) é cobrado com desconto a partir da segunda chamada. E o sistema fica mais simples: sem índice, sem embeddings, sem ajuste de busca.
Esta refatoração é o outro lado de Tirar o Conhecimento de Dentro do Prompt. Lá o corpus não cabe e muda o tempo todo. Aqui o material cabe e a busca é o que está atrapalhando.
O paper “Lost in the Middle” (Liu et al., 2023) ainda vale como alerta: contexto enorme não garante que o modelo ache a cláusula do meio. Por isso o “cabe com folga” importa. Doze mil tokens num modelo de janela grande é folga. Cem mil tokens de anexos empilhados já não é o mesmo desenho.
Passo a passo seguro
Meça. Rode o eval de perguntas, incluindo perguntas que cruzam seções. Guarde acerto, custo e latência por pergunta. As perguntas cruzadas são o motivo da troca.
Confira o tamanho. Conte os tokens do material com o contador da API. Deixe folga para a conversa e a resposta. Se não houver folga, esta refatoração não se aplica.
Monte o prefixo fixo: instruções primeiro, documento depois, sem nada variável (data, user id, hora) antes do fim do documento.
Ative o cache de prompt no bloco do documento.
Confira o cache funcionando: a partir da segunda chamada, o usage deve mostrar tokens lidos do cache. Se vier zero, o prefixo está mudando.
Meça de novo e compare acerto e custo por pergunta. O acerto das perguntas cruzadas é o teste que importa.
Remova o pipeline de busca só depois que a nova versão estiver estável. Enquanto os dois existirem, alguém vai “só ajustar o k”.
Riscos
O material cresce. Hoje é um contrato; amanhã são cem. Monitore o tamanho e volte para busca quando passar do limite em que ainda há folga.
Cache que não acerta. Uma data ou um id antes do documento muda o prefixo e zera o cache. Mantenha o que varia depois, na mensagem do usuário.
Perguntas muito espaçadas. O cache expira depois de alguns minutos sem uso (há opções de duração maior). Com tráfego baixo, o desconto aparece menos. O ganho de acerto das cláusulas cruzadas continua valendo.
Documento com partes que o usuário não pode ver. Mandar tudo ignora permissões por trecho. Nesse caso, a busca com filtro é necessária, mesmo que o arquivo caiba.
Achar que “cabe” é o bastante. Se o eval de perguntas cruzadas continuar ruim com o documento inteiro, o problema não era o RAG. Pode ser o prompt, o modelo ou um contrato realmente ambíguo.
Erro comum: prefixo que parece fixo e não é
O time coloca o contrato no system prompt e, no mesmo bloco, a data de hoje “para o modelo saber o dia”. Cada dia o prefixo muda, o cache não acerta, e a fatura volta ao valor da leitura completa. Data, nome do usuário e id da sessão vão na mensagem, depois do documento.
Desafio
Um assistente responde dúvidas sobre o regulamento de um condomínio (25 páginas). Hoje ele usa RAG e erra perguntas como “posso fazer obra no sábado à tarde?”, porque a regra geral está numa seção e a exceção de sábado em outra. Como fica a versão com contexto direto, e o que conferir?
Ver solução
O regulamento inteiro vai num bloco fixo do system prompt, com cache, e as instruções pedem para citar artigo e parágrafo. Confira: o tamanho em tokens com folga para a conversa; o cache_read_input_tokens positivo a partir da segunda pergunta; e o eval com perguntas que dependem de regra e exceção em seções diferentes, que eram justamente as que o RAG errava.
Teste seu entendimento
Checagem da aula
Responda as 6 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Perguntas rápidas
O que é Trocar RAG por Contexto Direto?
Trocar RAG por Contexto Direto (Replace RAG with Direct Context) é uma refatoração de IA para sistemas com LLM, do grupo Organizando dados. Quando o material cabe na janela, mandar inteiro e usar cache de prompt.
Que problema Trocar RAG por Contexto Direto resolve?