Lab 83 — RAG mínimo que funciona, com citação
O problema, e a empresa que o tem
A central de atendimento da Cadência recebe cerca de 1.200 perguntas por dia, entre lojistas parceiros e clientes finais, pelo chat do painel de atendimento. Três semanas atrás, alguém do mesmo time que fez a L81 plugou uma chamada direta ao Bedrock no painel: o atendente digita a pergunta do cliente, e uma sugestão de resposta aparece ao lado, pronta para copiar. Para perguntas genéricas — "vocês entregam em todo o Brasil?", "como funciona o PIX parcelado?" — funciona bem, e o time adotou sem cerimônia.
O problema aparece nas perguntas específicas da Cadência: política de devolução, garantia por categoria de produto, frete por lojista. O modelo nunca viu os documentos internos que descrevem essas políticas — eles nunca foram publicados na internet, e não fazem parte de nenhum corpus de treino público. Sem nenhum trecho desses documentos no prompt, o modelo completa a resposta com o padrão mais provável de uma política de e-commerce genérica: soa igualmente confiante, igualmente bem escrita, e não tem relação nenhuma com a política real da Cadência.
O caso que chegou à equipe de dados: um atendente aceitou a sugestão do modelo para "qual a política de devolução de material elétrico?" — a resposta dizia "30 dias corridos, sem necessidade de nota fiscal". A política real da Cadência para material elétrico é 7 dias, com nota fiscal e produto lacrado, por exigência de segurança elétrica dos fornecedores. O cliente devolveu um produto usado fora do prazo real, a loja parceira reclamou do prejuízo, e o caso virou chamado formal — o primeiro sinal de que "parece uma resposta boa" não é o mesmo que "é a resposta certa".
Resposta plausível e errada, sem fonte
Uma resposta plausível e bem escrita não é uma resposta correta, e ninguém no painel de atendimento tinha como distinguir as duas só lendo o texto. É esse o defeito que este laboratório resolve — não "o modelo erra às vezes", mas "o modelo nunca teve acesso ao documento real, e nada no fluxo avisava disso".
O que este laboratório NÃO é
Não é sobre escolher o banco vetorial certo — as quatro opções (Knowledge Bases/OpenSearch, OpenSearch provisionado, S3 Vectors, Aurora pgvector) são comparadas na mesma base de conhecimento pelo L84. Não é sobre melhorar a recuperação com busca híbrida e reranking — isso é o L85. Não é sobre guardrails como controle de segurança — isso é o L86, e a distinção entre "filtro na saída" e "controle de verdade" importa. Este laboratório prova a forma mínima que funciona: recuperar antes de gerar, citar a fonte, e medir a taxa de acerto com um número — não com a sensação de que "agora parece melhor".
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando ou uma medição na seção de implantação, não com a sensação de ter entendido RAG.
- Medir a taxa de acerto de um LLM respondendo direto, sem nenhum trecho de documento recuperado.
- Publicar os documentos reais da Cadência (política de devolução, garantia, frete e catálogo por loja) numa base de conhecimento gerenciada, com chunking configurado.
- Configurar a ingestão que transforma cada documento em trechos, embeddings e um índice vetorial, sem escrever o pipeline de sincronização à mão.
- Fazer uma consulta em C#/.NET 8 que devolve resposta com o trecho exato citado, não um resumo sem fonte.
- Construir um golden set de perguntas com resposta oficial conhecida, e medir a taxa de acerto do sistema com RAG contra ele.
- Explicar por que RAG resolve o problema de conhecimento desatualizado sem re-treinar o modelo.
- Diagnosticar um erro residual do RAG e dizer se a causa é chunking, recuperação ou documento ausente/duplicado.
- Restringir a chamada ao modelo e à base de conhecimento por IAM, com o ARN específico do recurso, não `"Resource": "*"`.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| RAG (Retrieval-Augmented Generation) | AIF-C01, MLA-C01 | LLM direto e com Knowledge Bases, medidos lado a lado no mesmo golden set | por que RAG resolve conhecimento desatualizado sem re-treinar o modelo |
| Chunking (estratégia de divisão de documento) | MLA-C01 | tamanho fixo com sobreposição, calibrado para não cortar uma tabela ao meio | o trade-off entre trecho pequeno (preciso, pouco contexto) e grande (contexto, impreciso) |
| Embedding e busca por similaridade | AIF-C01, MLA-C01 | a Knowledge Base gera o embedding e indexa no OpenSearch Serverless | embedding é vetor; busca vetorial encontra sentido, não palavra exata |
| Atribuição / citação (grounding) | AIF-C01 | RetrieveAndGenerate devolve o trecho e a fonte usados para gerar cada resposta | resposta sem fonte rastreável não é confiável em domínio regulado |
| Bedrock Knowledge Bases (RAG gerenciado) | AIF-C01, MLA-C01 | ingestão, chunking, embedding e indexação geridos pelo serviço, sem servidor próprio | a diferença entre "gerenciado" (este módulo) e "eu opero o índice" (L84) |
| Alucinação e mitigação | AIF-C01 | medida diretamente: taxa de acerto do golden set antes e depois do RAG | RAG não elimina alucinação sozinho — reduz drasticamente quando o dado existe no acervo |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um chatbot que responde bem em teste e erra em produção sobre um fato específico da empresa, e pede a causa. A resposta esperada não é "o modelo é ruim" nem "falta prompt engineering" — é que o modelo nunca teve o documento no contexto. O erro de raciocínio mais comum é tratar "o modelo é treinado com muito texto" como sinônimo de "o modelo tem acesso ao documento da minha empresa": são coisas diferentes, e só a segunda resolve o problema.
Requisitos, e como cada um muda o desenho
Requisito não funcional que não vira uma linha de configuração é intenção. A coluna da direita é onde cada um deixou marca no Terraform, no prompt ou no pipeline de ingestão.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Resposta cita o trecho exato que a fundamenta | obrigatório | RetrieveAndGenerate com citações habilitadas; sem citação, a resposta não é mostrada ao atendente como sugestão pronta |
| Pergunta fora do que os documentos cobrem não vira resposta inventada | obrigatório | prompt de fundamentação instrui "responda só com o contexto recuperado; sem trecho suficiente, diga que não encontrou" |
| Taxa de acerto medida, não estimada | golden set de 40 perguntas com resposta oficial | suíte de avaliação roda contra o mesmo conjunto antes e depois de qualquer mudança de prompt ou chunking |
| Resposta cabe no tempo de um atendimento ao vivo | até 3 s | orçamento de latência somando busca vetorial e geração, medido e com margem |
| Documento alterado pelo jurídico aparece na resposta em tempo razoável | algumas horas, não dias | job de ingestão disparado por evento de escrita no S3, não reingestão agendada do acervo inteiro |
| Trecho recuperado não pode vir cortado no meio de uma tabela | obrigatório | chunking de tamanho fixo com sobreposição, calibrado pela maior tabela do acervo |
| Cada resposta permite auditoria de qual trecho foi usado | obrigatório | log estruturado com id do documento e do trecho citado, publicado no CloudWatch |
| Chamada ao modelo e à base de conhecimento restrita à aplicação de atendimento | segurança | IAM role com `bedrock:InvokeModel`/`bedrock:Retrieve` restrito ao ARN do modelo e da knowledge base específicos |
Arquitetura mínima: o modelo respondendo sem nenhum trecho recuperado
Este é exatamente o desenho que a Cadência tem hoje — não uma versão simplificada de propósito. Ele é implantável de verdade, responde em menos de um segundo, e o defeito só aparece quando a pergunta exige um fato que só existe dentro da empresa.
- → pergunta digitada em português, sem estrutura
- → encaminha a chamada HTTP
- → prompt = só a pergunta do atendente, sem nenhum trecho de documento
- → resposta fluente, sem fonte nem confiança declarada
- → sugestão que o atendente aceita na maioria das vezes
- Fora da AWS
- Rede e entrega
- Compute
- IA e machine learning
- Armazenamento
É o que a Cadência tem hoje: uma chamada direta ao Bedrock, sem nenhum trecho de documento no prompt. Percorra os passos e repare no último nó: os documentos reais da política de devolução estão ali, no S3, e nenhuma seta chega até eles — é essa ausência de aresta que é o defeito, não um bug de código.
- Atendente pergunta em português simples. É o fluxo comum: o cliente pergunta pelo chat, o atendente reformula como pergunta objetiva e envia ao painel.
- A API só repassa a pergunta. Não há nenhuma etapa de busca entre receber a pergunta e chamar o modelo — a função existe só para formatar a chamada HTTP.
- O prompt enviado ao modelo é a pergunta pura. Nenhum trecho de documento, nenhuma instrução de "responda só com o que souber com certeza" — é a pergunta e nada mais.
- O modelo responde com o que aprendeu no treino. Sem contexto da Cadência, o modelo completa com o padrão estatístico mais provável de uma política de e-commerce genérica — fluente, coerente, e sem relação com a política real.
- A resposta chega sem fonte, e sem sinalizar incerteza. Nada no texto avisa "isto não veio de nenhum documento real" — a resposta parece tão confiável quanto uma resposta certa.
- Os documentos reais estão ali, intocados. A política de devolução de material elétrico existe, escrita, no mesmo bucket que o L62 já usa — e nenhuma seta deste desenho chega até ela.
A confiança do texto não mede a correção do fato
Fluência não é acurácia, e o atendente não tinha como distinguir as duas só lendo o texto. O modelo foi treinado para completar texto de forma coerente, não para admitir incerteza — por isso a resposta errada sobre material elétrico saiu tão bem escrita quanto uma resposta certa teria saído.
Arquitetura para produção: do documento ao trecho citado
A diferença em relação à Figura 1 não é "adicionar um banco de dados" — é que a resposta passa a depender de uma busca que acontece ANTES da geração, sobre um acervo que é atualizado de forma independente da pergunta. Cada peça nova rastreia a uma linha da tabela de requisitos.
- → pergunta digitada em português, mesma interface da Figura 1
- → encaminha a chamada HTTP
- → evento de escrita dispara ingestão incremental do objeto alterado
- → quebra o documento em trechos com sobreposição
- → cada trecho vira um vetor de embedding
- → grava vetor e texto original do trecho no índice
- → RetrieveAndGenerate: pergunta do atendente + id da knowledge base
- → busca por similaridade: os k trechos mais próximos do vetor da pergunta
- → os k trechos recuperados, com o texto original de cada um
- → resposta gerada citando o trecho exato usado
- → resposta com citação: trecho + referência ao documento de origem
- → loga o id do trecho citado em cada resposta
- Fora da AWS
- Rede e entrega
- Compute
- Armazenamento
- IA e machine learning
- Conceito de arquitetura
- Analytics
- Gestão e governança
A mesma pergunta, o mesmo atendente — o que muda é tudo entre a API e a resposta. Cada peça nova rastreia a um requisito da tabela: o pipeline de ingestão existe porque o documento tem de virar trecho buscável; o índice vetorial existe porque a busca precisa achar sentido, não palavra exata; e a chamada ao modelo agora inclui os trechos recuperados — é essa inclusão, não o modelo em si, que produz a citação.
- Atendente pergunta, mesma interface da versão mínima. Nada muda do lado de quem pergunta — a diferença inteira está atrás da API.
- O documento real vira trechos, antes de qualquer pergunta existir. A ingestão roda de forma assíncrona, disparada por evento de escrita no bucket — não espera o atendente perguntar nada.
- Cada trecho vira um vetor, e o vetor vai para o índice. O embedding captura o SENTIDO do trecho, não as palavras exatas — é o que permite achar "devolução de item elétrico" mesmo que a pergunta diga "trocar uma tomada".
- A aplicação chama RetrieveAndGenerate, não o modelo puro. A Lambda não monta prompt nem decide o que buscar — delega as duas coisas para uma única chamada gerenciada.
- O modelo busca no índice antes de gerar qualquer palavra. A pergunta vira vetor, o índice devolve os k trechos mais próximos, e só então o modelo recebe pergunta e trechos no mesmo prompt.
- A resposta cita o trecho exato que a fundamenta. Cada frase gerada aponta para o documento e o trecho recuperado — é a diferença estrutural em relação à Figura 1, não um ajuste de prompt.
- Toda citação usada fica registrada para auditoria. Sem esse log, o efeito observável de uma resposta correta e de uma resposta sem fonte seria o mesmo texto bem escrito — a auditoria é o que torna a citação verificável depois.
O que a citação entrega, que fluência nunca entregou
O ganho que mais importa não é a velocidade — é que a resposta passa a ter algo que o atendente pode CONFERIR: um trecho exato, de um documento real, que ele pode abrir e ler antes de repassar ao cliente. Isso não existia na versão mínima; e não é o modelo que mudou, é o fato de ele agora ter algo real para citar.
Como funciona, ponta a ponta
O log abaixo é o que a aplicação escreve no CloudWatch a cada resposta gerada — é o material bruto da auditoria: qual trecho, de qual documento, fundamentou aquela resposta específica.
{
"evento": "RESPOSTA_COM_CITACAO_GERADA",
"timestamp": "2026-08-08T14:02:11.340Z",
"pergunta": "Qual a política de devolução de material elétrico?",
"knowledgeBaseId": "KB7F3A9C1D",
"trechosRecuperados": 3,
"trechoCitado": {
"documento": "s3://cadencia-documentos-atendimento/politicas/devolucao-material-eletrico.md",
"chunkId": "devolucao-material-eletrico.md#chunk-4",
"texto": "Material elétrico: devolução em até 7 dias corridos, mediante nota fiscal e produto lacrado, por exigência de segurança elétrica dos fornecedores."
},
"latenciaMs": 1487,
"_comentario": "chunkId e documento sao o que a auditoria confere depois — nao o texto da resposta gerada, que pode parafrasear o trecho."
}
O caminho de "não encontrei" é parte do desenho, não uma falha dele
Quando os k trechos recuperados não têm relação suficiente com a pergunta, o prompt de fundamentação instrui o modelo a responder que não encontrou a informação — não a completar com conhecimento geral. Esse caminho aparece medido na seção de prova adiante: é uma resposta CORRETA quando a pergunta realmente não tem resposta no acervo.
As decisões, e o que se perde em cada uma
📋 A central de atendimento da Cadência precisa responder perguntas sobre política em linguagem natural, sem inventar, com um jeito de provar taxa de acerto para o jurídico — e uma equipe pequena, sem time dedicado a operar banco vetorial.
Knowledge Bases resolve ingestão, chunking, embedding e indexação como serviço gerenciado — sem exigir que a equipe opere um pipeline próprio. RetrieveAndGenerate resolve a exigência de "nunca inventar" ao gerar só a partir de trechos recuperados, com citação obrigatória. E o golden set resolve a exigência do jurídico de um número comparável, em vez de "parece melhor".
Alt: Manter o LLM direto e só melhorar o prompt ("responda com precisão") — prompt engineering não dá acesso a um documento que o modelo nunca viu; a taxa de acerto sobe pouco, porque o problema é falta de CONTEXTO, não de instrução — e é exatamente o que a seção de prova mede.
Alt: Fine-tuning do modelo com os documentos da Cadência — a política de devolução é revisada pelo jurídico com frequência; re-treinar o modelo a cada alteração é caro e lento — RAG só reingere o arquivo alterado, em minutos.
Alt: Operar o próprio banco vetorial (Aurora com pgvector, por exemplo) — ganha controle fino sobre o índice, perde velocidade de entrega — é exatamente a comparação de quatro opções que o L84 aprofunda; aqui o objetivo é provar que RAG funciona, não otimizar o banco vetorial ainda.
Alt: Amazon Kendra para a busca, orquestrando a geração separadamente — Kendra é ótimo para busca empresarial, mas não entrega geração de resposta com citação no mesmo fluxo — exigiria orquestrar busca e chamada ao modelo manualmente, reconstruindo o que Knowledge Bases já entrega pronto.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Onde indexar o vetor | OpenSearch Serverless, gerenciado pela Knowledge Base | Aurora pgvector; S3 Vectors; OpenSearch provisionado | zero servidor para operar, e a Knowledge Base já cuida do ciclo de vida do índice | menos controle sobre o algoritmo de busca e sobre custo por hora fixo — é a comparação do L84 |
| Tamanho do chunk | 500 tokens, 20% de sobreposição | chunk por documento inteiro; chunk semântico por seção | a maior tabela de garantia por categoria cabe inteira num chunk, sem cortar linha | trecho maior às vezes traz contexto irrelevante junto com o relevante |
| O que fazer sem trecho relevante | responder "não encontrei isso nos documentos" | deixar o modelo completar com o que sabe do treino | evita fabricar resposta sobre o que não está no acervo | o atendente recebe "não sei" às vezes onde uma busca manual mais paciente acharia a resposta |
| Frequência de reingestão | job disparado por evento de escrita no S3 | reingestão agendada a cada N horas | documento alterado pelo jurídico aparece em minutos, não no fim do dia | evento perdido, raro, atrasa a atualização até a próxima escrita disparar de novo |
| Onde a citação é validada | golden set de 40 perguntas com resposta conhecida, revisado por humano | confiar na taxa de satisfação do atendente | dá um número comparável entre versões de prompt e de chunking | 40 perguntas não cobrem toda pergunta possível — é amostra, não garantia total |
A dívida que este laboratório não paga
O golden set de 40 perguntas cobre política de devolução, garantia e frete — não cobre negociação de preço, disponibilidade de estoque em tempo real, nem dúvida sobre pedido específico de um cliente. Este laboratório não estende o RAG para esses casos: cada um tem uma fonte de dado diferente (o pedido vive no RDS do ERP, não em documento), e misturar os dois no mesmo acervo é o antipadrão que a tabela adiante nomeia.
Construir: a base de conhecimento (S3 → chunking → embedding → índice)
O bucket é o mesmo tipo de fonte que o L62 já organiza em camadas: aqui ele guarda a política de devolução, a política de garantia, a política de frete e um export em texto plano do catálogo ouro por loja. A Knowledge Base é quem transforma isso em algo buscável — sem que o jurídico precise saber o que é chunking.
# base-conhecimento.tf — bucket de documentos, índice vetorial e a Knowledge Base
# que os conecta. Confira a versão do provider AWS antes de aplicar: os recursos
# aws_bedrockagent_* e aws_opensearchserverless_* mudaram de shape mais de uma vez
# desde que Knowledge Bases foi lançado.
resource "aws_s3_bucket" "documentos_atendimento" {
bucket = "cadencia-documentos-atendimento"
tags = { squad = "atendimento", projeto = "rag-politica", lab = "L83" }
}
resource "aws_s3_bucket_versioning" "documentos_atendimento" {
bucket = aws_s3_bucket.documentos_atendimento.id
versioning_configuration { status = "Enabled" }
# versionamento evita que uma sobrescrita do jurídico apague, sem histórico, a
# versão anterior de uma política — o mesmo cuidado que o L62 já aplica ao lake.
}
# ── Índice vetorial: OpenSearch Serverless, com as duas políticas que ele exige ──
resource "aws_opensearchserverless_security_policy" "criptografia" {
name = "cadencia-rag-criptografia"
type = "encryption"
policy = jsonencode({
Rules = [{ ResourceType = "collection", Resource = ["collection/cadencia-rag-*"] }]
AWSOwnedKey = true
})
}
resource "aws_opensearchserverless_security_policy" "rede" {
name = "cadencia-rag-rede"
type = "network"
policy = jsonencode([{
Rules = [{ ResourceType = "collection", Resource = ["collection/cadencia-rag-*"] }]
AllowFromPublic = false
SourceVPCEs = [aws_ec2_managed_prefix_list.acesso_kb.id]
}])
}
resource "aws_opensearchserverless_collection" "base_conhecimento" {
name = "cadencia-rag-politica-atendimento"
type = "VECTORSEARCH"
depends_on = [
aws_opensearchserverless_security_policy.criptografia,
aws_opensearchserverless_security_policy.rede,
]
}
# ── Papel que a Knowledge Base assume para ler o S3 e chamar o modelo de embedding ──
resource "aws_iam_role" "knowledge_base" {
name = "cadencia-rag-knowledge-base-role"
assume_role_policy = data.aws_iam_policy_document.confianca_bedrock.json
}
resource "aws_iam_role_policy" "kb_acesso_restrito" {
role = aws_iam_role.knowledge_base.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:ListBucket"]
# Resource especifico do bucket de documentos — nao "arn:aws:s3:::*".
Resource = [
aws_s3_bucket.documentos_atendimento.arn,
"${aws_s3_bucket.documentos_atendimento.arn}/*",
]
},
{
Effect = "Allow"
Action = ["bedrock:InvokeModel"]
# Restrito ao MODELO DE EMBEDDING, nao a bedrock:* — a Knowledge Base nunca
# gera texto de resposta, so vetoriza.
Resource = ["arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"]
},
{
Effect = "Allow"
Action = ["aoss:APIAccessAll"]
Resource = [aws_opensearchserverless_collection.base_conhecimento.arn]
},
]
})
}
resource "aws_bedrockagent_knowledge_base" "politica_atendimento" {
name = "cadencia-politica-atendimento"
role_arn = aws_iam_role.knowledge_base.arn
knowledge_base_configuration {
type = "VECTOR"
vector_knowledge_base_configuration {
embedding_model_arn = "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"
}
}
storage_configuration {
type = "OPENSEARCH_SERVERLESS"
opensearch_serverless_configuration {
collection_arn = aws_opensearchserverless_collection.base_conhecimento.arn
vector_index_name = "cadencia-politica-index"
field_mapping {
vector_field = "embedding"
text_field = "texto"
metadata_field = "metadados"
}
}
}
}
resource "aws_bedrockagent_data_source" "documentos" {
knowledge_base_id = aws_bedrockagent_knowledge_base.politica_atendimento.id
name = "documentos-juridico-e-catalogo"
data_source_configuration {
type = "S3"
s3_configuration { bucket_arn = aws_s3_bucket.documentos_atendimento.arn }
}
vector_ingestion_configuration {
chunking_configuration {
chunking_strategy = "FIXED_SIZE"
fixed_size_chunking_configuration {
max_tokens = 500
overlap_percentage = 20
# 20% de sobreposicao e o numero calibrado pela maior tabela de garantia
# por categoria do acervo — ver a secao de prova para o efeito medido.
}
}
}
}
A primeira ingestão não é o padrão de operação
O job de ingestão inicial processa o acervo inteiro e pode levar minutos, dependendo do volume de documentos. Alterações pontuais depois disso são incrementais — só o objeto que mudou é reprocessado, não o bucket inteiro de novo. Confirme isso no console: uma ingestão completa disparada a cada pequena edição é o antipadrão que a seção de anti-padrões nomeia adiante.
Construir: a consulta com citação (C#/.NET 8)
A função de atendimento troca uma chamada de `InvokeModel` — a que o tema do L81 mostra — por uma chamada de `RetrieveAndGenerate`. A aplicação não decide mais o que buscar nem monta o prompt com o contexto: delega as duas coisas numa única requisição gerenciada, e recebe de volta a resposta já acompanhada da citação.
// ServicoRespostaComCitacao.cs — chama RetrieveAndGenerate em vez de InvokeModel
// puro. A diferenca estrutural em relacao ao cliente do L81 nao e o SDK, e o que
// a chamada FAZ: busca antes de gerar, e devolve a fonte junto da resposta.
public class ServicoRespostaComCitacao
{
private readonly AmazonBedrockAgentRuntimeClient _cliente = new();
private const string KnowledgeBaseId = "KB7F3A9C1D";
private const string ModeloArn =
"arn:aws:bedrock:us-east-1:111122223333:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0";
// Instrucao de fundamentacao: o unico lugar do sistema que probe o modelo de
// completar com conhecimento geral quando o contexto recuperado nao basta.
private const string InstrucaoDeFundamentacao =
"Responda SOMENTE com base nos trechos em $search_results$. " +
"Se os trechos nao contiverem a resposta, diga que nao encontrou essa " +
"informacao nos documentos da Cadência — nunca complete com conhecimento geral.";
public async Task<RespostaComCitacao> ResponderAsync(string pergunta)
{
var pedido = new RetrieveAndGenerateRequest
{
Input = new RetrieveAndGenerateInput { Text = pergunta },
RetrieveAndGenerateConfiguration = new RetrieveAndGenerateConfiguration
{
Type = RetrieveAndGenerateType.KNOWLEDGE_BASE,
KnowledgeBaseConfiguration = new KnowledgeBaseRetrieveAndGenerateConfiguration
{
KnowledgeBaseId = KnowledgeBaseId,
ModelArn = ModeloArn,
GenerationConfiguration = new GenerationConfiguration
{
PromptTemplate = new PromptTemplate { TextPromptTemplate = InstrucaoDeFundamentacao },
},
},
},
};
var resposta = await _cliente.RetrieveAndGenerateAsync(pedido);
// Cada citacao aponta pro trecho (chunk) e pro documento de origem — e isso
// que vira o link clicavel na tela do atendente, nao um resumo sem fonte.
var citacoes = resposta.Citations
.SelectMany(c => c.RetrievedReferences)
.Select(r => new Citacao(r.Content.Text, r.Location.S3Location.Uri))
.ToList();
if (citacoes.Count == 0)
{
// Sem trecho recuperado, a resposta NAO sai como sugestao pronta para
// copiar — vira "nao encontrei isso nos documentos", nunca uma invencao.
return RespostaComCitacao.SemFonte();
}
return new RespostaComCitacao(resposta.Output.Text, citacoes);
}
}
O ponto que mais separa este cliente do da versão mínima não é a chamada em si — é o que acontece quando `citacoes.Count == 0`. Na versão mínima, o modelo sempre responde alguma coisa. Aqui, a ausência de trecho recuperado é um caminho de código explícito, não uma exceção não tratada.
Segurança: quem pode chamar o modelo e a base
Duas roles distintas participam deste desenho, com privilégio calculado pelo uso real de cada uma — o mesmo princípio do L41, aplicado a Bedrock em vez de a um banco. A role da Knowledge Base (seção de construção anterior) só lê o bucket de documentos e só invoca o modelo de embedding. A role da função de atendimento, abaixo, só invoca o modelo de geração e só consulta ESTA knowledge base — nunca `bedrock:*`, e nunca uma base de conhecimento de outro time.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ChamarModeloDeGeracaoRestrito",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:Retrieve", "bedrock:RetrieveAndGenerate"],
"Resource": [
"arn:aws:bedrock:us-east-1:111122223333:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0",
"arn:aws:bedrock:us-east-1:111122223333:knowledge-base/KB7F3A9C1D"
]
},
{
"Sid": "SemAcessoAOutraKnowledgeBase",
"Effect": "Deny",
"Action": ["bedrock:Retrieve", "bedrock:RetrieveAndGenerate"],
"NotResource": "arn:aws:bedrock:us-east-1:111122223333:knowledge-base/KB7F3A9C1D"
}
]
}
Por que o Deny explícito, e não só a ausência de Allow
A instrução negada explicitamente (`NotResource`) importa mais do que parece: sem ela, a mesma credencial da função de atendimento poderia, no futuro, ser reusada para consultar uma outra knowledge base criada por outro time — por exemplo, uma que indexasse dado de RH. `Deny` explícito fecha essa porta mesmo que uma policy futura, mal escrita, tente abri-la — `Deny` sempre vence sobre `Allow` na avaliação do IAM.
Implantar, e provar com número
A prova não é "a resposta parece melhor" — é rodar o mesmo conjunto de 40 perguntas duas vezes, contra os dois desenhos, e contar.
# prova.sh — roda o MESMO golden set de 40 perguntas contra os dois desenhos, e
# conta. Nenhuma conclusao aqui vem de "a resposta parece melhor".
GOLDEN_SET=golden-set-politica-cadencia.jsonl # 40 linhas: {pergunta, resposta_oficial}
# ── Rodada 1: LLM direto (Figura 1), sem nenhuma recuperacao ─────────────────
python3 avaliar.py --endpoint sugerir-resposta --golden "$GOLDEN_SET" \
--saida resultado-sem-rag.json
# Resultado medido: 14 de 40 corretas (35,0%); 0 de 40 com trecho citavel;
# latencia media 700 ms
# ── Rodada 2: RAG (Figura 2), com recuperacao e citacao ──────────────────────
python3 avaliar.py --endpoint responder-com-citacao --golden "$GOLDEN_SET" \
--saida resultado-com-rag.json
# Resultado medido: 37 de 40 corretas (92,5%); 37 de 40 com trecho citavel;
# latencia p95 1,6 s
# ── Diagnostico dos 3 erros residuais ─────────────────────────────────────────
jq '.[] | select(.correto == false)' resultado-com-rag.json
# 2 casos: tabela de garantia cortada no limite do chunk — o trecho recuperado
# continha a categoria, mas nao o prazo, que ficava no chunk seguinte.
# 1 caso: duas versoes da politica de frete no bucket (a antiga nao tinha sido
# arquivada) — a busca recuperou um trecho da versao errada.
| Prova | Antes — LLM direto (Figura 1) | Depois — com RAG (Figura 2) | O que mudou |
|---|---|---|---|
| Taxa de acerto no golden set (40 perguntas) | 14/40 — 35,0% | 37/40 — 92,5% | a recuperação deu ao modelo o trecho que ele nunca teve no treino |
| Respostas com trecho citável | 0/40 — 0% | 37/40 — 92,5% | RetrieveAndGenerate só gera resposta a partir de trecho recuperado |
| Latência p95 por pergunta | ≈700 ms | ≈1,6 s | a busca soma tempo, mas ainda cabe no orçamento de 3 s declarado para o atendimento |
| Respostas que assumem "não sei" quando devia | 0/40 (sempre respondia algo) | 1/40 (é um acerto) | RAG pode recusar responder quando o acervo não cobre; o LLM direto nunca recusava |
| Erros residuais, e a causa de cada um | — | 2 de tabela cortada no limite do chunk, 1 de documento duplicado no bucket | os 3 apontam para decisão de chunking e higiene do acervo — não para falha do padrão RAG |
A taxa de acerto aponta ONDE olhar, não conserta sozinha
Os 3 erros residuais não são aleatórios, e por isso são corrigíveis: os 2 casos de tabela cortada pedem um chunk maior ou uma estratégia por seção nesse documento específico (assunto do L84/L85); o caso de documento duplicado pede disciplina de arquivamento no bucket, não mudança de arquitetura. Nenhum dos três é resolvido re-rodando o mesmo teste — cada um exige uma correção diferente, identificada pelo diagnóstico, não pela taxa agregada sozinha.
Quebrar de propósito: quatro falhas, e a que reintroduz o problema original
92,5% de acerto contra o golden set é o resultado do caminho feliz. As quatro injeções abaixo produzem resposta errada COM citação — que é pior que resposta errada sem citação, porque a citação é justamente o que faz o atendente confiar.
| Falha injetada | Como injetar | Sintoma enganoso | Diagnóstico correto |
|---|---|---|---|
| Documento removido do bucket, índice não sincronizado | Apagar a política de garantia do S3 e perguntar sobre garantia sem reingerir | A resposta vem completa, com citação apontando para um arquivo que não existe mais. Nada no log indica problema | O índice vetorial é uma CÓPIA do acervo, não uma visão dele. Enquanto a ingestão não roda, o índice responde por um mundo que já mudou. O sintoma inverso é o perigoso: documento ATUALIZADO e não reingerido faz o sistema citar, com fonte e confiança, a política revogada. Toda alteração de documento precisa disparar reingestão, e o painel precisa mostrar a idade da última sincronização |
| Pergunta cuja resposta não está no acervo | Perguntar sobre uma política que a Cadência nunca escreveu — férias, por exemplo | O modelo responde bem, de forma plausível, sobre política de férias de e-commerce em geral | É o defeito do L81 voltando por dentro do RAG. Recuperação que não encontra nada relevante ainda assim devolve os trechos MENOS distantes, e o modelo redige em cima deles ou do que sabe de treino. A defesa não é o prompt pedir para não inventar: é um piso de score de relevância abaixo do qual a aplicação responde "não encontrei" sem chamar geração nenhuma. Sem esse piso, o RAG não elimina a alucinação — só a acompanha de uma citação |
| Chunk cortando a tabela de prazos ao meio | Reduzir o tamanho do chunk e reingerir a política de garantia, que tem os prazos numa tabela | A resposta cita o trecho certo do documento certo. E dá o prazo errado | O trecho recuperado contém o cabeçalho da tabela e não a linha da categoria, ou a linha sem o cabeçalho que diz o que a coluna significa. A citação está correta — ela aponta para onde o modelo leu — e o conteúdo lá não sustenta a resposta. Citação prova PROVENIÊNCIA, nunca correção. É o defeito residual que o L85 reabre e diagnostica de novo com outro instrumento |
| Documento duplicado no acervo, em versões diferentes | Subir a política de devolução duas vezes, uma com o prazo antigo | As duas versões são recuperadas, o modelo escolhe uma, e a resposta é internamente coerente | O acervo passou a conter uma contradição, e não existe nada no desenho que detecte contradição — a recuperação otimiza semelhança com a pergunta, não consistência entre trechos. Higiene de acervo não é tarefa de arrumação: é requisito de correção. Um único documento por assunto, com versão, e ingestão que substitui em vez de acrescentar |
As quatro produzem resposta com citação
É a característica comum e o motivo de estarem juntas. A citação foi vendida internamente como a garantia de que a resposta é confiável, e ela não é isso — ela diz de onde o texto veio, não que a conclusão esteja certa. Ao apresentar este sistema a quem vai usá-lo, essa distinção precisa ser dita em voz alta, senão o atendente para de conferir exatamente porque existe citação.
Um atendente pergunta ao LLM "qual a política de devolução de material elétrico da Cadência?" e recebe uma resposta fluente e específica, mas errada. O modelo foi treinado com bilhões de documentos — por que ele não sabe a política real da Cadência?
Observabilidade: as perguntas que o painel tem de responder
Um RAG quebra de um jeito específico: continua respondendo. Não há erro 500, não há fila crescendo, não há CPU subindo. Estas são as perguntas que separam "respondendo" de "respondendo certo".
- Qual a proporção de respostas SEM nenhuma citação nos últimos sete dias? É o indicador direto de recuperação vazia — e cada uma delas é uma resposta gerada do conhecimento de treino, não do acervo da Cadência.
- Qual o score de relevância do melhor trecho recuperado, em distribuição? A cauda inferior dessa distribuição é o conjunto de perguntas que o acervo não cobre.
- Quanto tempo faz desde a última sincronização bem-sucedida da base de conhecimento?
- Quais documentos do acervo NUNCA foram recuperados em 30 dias? São peso morto no índice, ou assunto sobre o qual ninguém pergunta — e as duas conclusões mudam o que fazer.
- Qual a divisão da latência entre recuperação e geração? Elas escalam por motivos diferentes e a soma esconde qual das duas piorou.
- Quantas respostas o atendente editou antes de enviar ao cliente? É o único sinal de qualidade que vem de humano, e é o mais valioso do painel.
| Alarme | Limiar inicial | O que ele pega |
|---|---|---|
| RespostasSemCitacao | acima de 5% da janela de 1 hora | recuperação devolvendo vazio — acervo incompleto, índice corrompido ou ingestão quebrada |
| IdadeDaSincronizacao | acima de 24 horas | documento atualizado no S3 que o índice ainda não conhece: a primeira falha injetada, detectada antes de alguém citar política revogada |
| ScoreMaximoBaixo | melhor trecho abaixo do piso de relevância em mais de 10% das perguntas da hora | lacuna de acervo — um assunto novo que os clientes começaram a perguntar |
| LatenciaRecuperacaoP95 | acima do dobro da linha de base medida na implantação | degradação do índice conforme o acervo cresce, separada da latência do modelo |
| TaxaDeEdicaoPeloAtendente | acima da linha de base medida nas duas primeiras semanas | queda de qualidade percebida por quem usa, antes de qualquer métrica técnica mudar |
Escala: 1.200 perguntas por dia, 12 mil, 120 mil — e a perda de uma zona
| Ordem de grandeza | O que muda no desenho | O que NÃO muda |
|---|---|---|
| 1.200 perguntas/dia, 4 documentos (hoje) | Nada. A base gerenciada absorve isso sem tuning, e o gargalo é humano — o atendente lendo a sugestão | O piso de custo do índice vetorial, que existe desde a primeira pergunta e independe do volume |
| 12 mil perguntas/dia, 400 documentos | A ingestão deixa de ser evento raro e vira pipeline: reingerir tudo a cada mudança passa a ser caro e lento, e a atualização precisa ser incremental. O limite de taxa do modelo de geração passa a ser sentido no pico da tarde | A forma da chamada. Recuperar-e-gerar continua sendo uma requisição, e a aplicação continua sem montar prompt |
| 120 mil perguntas/dia, 40 mil documentos | A recuperação vira o gargalo antes da geração, e a qualidade do ranking passa a dominar a qualidade da resposta — é onde o L85 deixa de ser refinamento e vira necessidade. Cache de resposta para perguntas repetidas (L89) passa a pagar sozinho | A necessidade de piso de relevância e de higiene de acervo, que já eram obrigatórias com 4 documentos |
| Perda de uma zona de disponibilidade | A base de conhecimento gerenciada e o modelo continuam respondendo — são serviços regionais, não zonais. O que cai é a sua aplicação, se ela estiver em uma zona só | A resposta correta aqui é a do L01, sem novidade: tarefas em duas zonas atrás do balanceador. A camada de IA deste laboratório não acrescenta requisito zonal nenhum |
| Perda da região inteira | Tudo para. O índice vive numa região, e reconstruí-lo em outra leva o tempo de uma ingestão completa do acervo | É o que o L99 trata, e o número que interessa não é o RTO da aplicação — é o tempo de reingestão do acervo, que ninguém mede até precisar |
Custo: as quatro dimensões, e a que cresce sem ninguém mexer em nada
A intuição diz que RAG custa o preço do modelo. A fatura mostra quatro linhas, e a que mais surpreende não é nenhuma das duas óbvias.
| Cenário | O que domina | O que ninguém nota |
|---|---|---|
| Protótipo — 4 documentos, 50 perguntas por dia | O piso do índice vetorial, sozinho, é praticamente a conta inteira | Um RAG parado custa quase o mesmo que um RAG em uso nesta escala. Piloto esquecido ligado é a forma mais comum de desperdício desta banda |
| Produção — 1.200 perguntas por dia | O contexto recuperado passa a dominar: milhares de tokens de entrada, 1.200 vezes por dia | Reduzir de cinco para três trechos recuperados corta a maior linha da conta em quase 40% — e só é seguro fazer isso medindo o acerto contra o golden set, que é exatamente o instrumento que este laboratório constrói |
| Acervo vivo — jurídico revisando política toda semana | A reingestão passa a competir com o uso | Reingestão completa semanal de um acervo grande pode custar mais que as perguntas do mês. Ingestão incremental não é otimização prematura aqui — é a diferença entre custo proporcional ao acervo e custo proporcional ao acervo vezes a frequência de revisão |
A conta que decide
Preço de embedding, de armazenamento vetorial, de instância e por 1.000 tokens muda por região e por modelo. Os valores relativos abaixo servem para dimensionar a decisão; confirme o número vigente na página de preços antes de usar em proposta. O número que autoriza este sistema não é o custo por pergunta: é o custo por pergunta comparado ao minuto de atendente que ele economiza, e ao custo de UMA resposta errada sobre prazo de devolução de material elétrico — que foi o evento que originou o laboratório.
Well-Architected nos seis pilares
| Pilar | Situação hoje | Risco | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | Ingestão disparada à mão quando alguém lembra | Índice defasado respondendo política revogada com citação | Ingestão disparada por evento de escrita no bucket, com alarme de idade da sincronização | Alta |
| Segurança | Papel escopado à base específica; documentos internos num bucket privado | O acervo inteiro é visível a qualquer pergunta — não há noção de quem pode ver o quê | Filtro de permissão por fonte na recuperação, que é exatamente o L93 | Alta se o acervo passar a ter documento restrito; irrelevante enquanto tudo for política pública |
| Confiabilidade | Serviços gerenciados regionais; aplicação em duas zonas | A região é ponto único, e reconstruir o índice em outra leva o tempo de uma ingestão completa | Medir o tempo de reingestão do acervo e declará-lo como RTO real; o desenho multi-região é o L99 | Média |
| Eficiência de desempenho | Recuperação vetorial pura, cinco trechos, sem reordenação | O trecho certo pode existir no índice e não entrar no prompt — a falha que o L85 diagnostica | Busca híbrida com reordenação (L85), medida contra o mesmo golden set | Alta — é o maior ganho de qualidade disponível |
| Otimização de custo | Cinco trechos recuperados por pergunta, sem cache | O contexto recuperado é a maior linha da fatura e ninguém mediu se cinco trechos são melhores que três | Medir acerto por número de trechos e ajustar; cache de resposta para pergunta repetida (L89) | Média |
| Sustentabilidade | Índice provisionado 24 horas para tráfego de horário comercial | Capacidade ociosa à noite e no fim de semana | Avaliar backend com cobrança por requisição para acervo pequeno — a comparação do L84 | Baixa |
Onde mais IA entra neste RAG, e onde ela só acrescenta custo
O laboratório inteiro é IA, então a pergunta útil é onde cabe mais um modelo. Três candidatos aparecem sempre nesta conversa, e só um deles se paga aqui.
| Ideia | Vale a pena? | Por quê |
|---|---|---|
| Um modelo reordenando os trechos recuperados | Sim, e é o próximo laboratório | É o único ponto da lista em que um modelo enxerga o que a busca vetorial não enxerga: ele lê a pergunta e o trecho JUNTOS, em vez de comparar dois vetores produzidos separadamente. Dois dos três erros residuais deste laboratório são exatamente disso, e o L85 mede o ganho contra o mesmo golden set |
| Um modelo decidindo se vale a pena buscar antes de buscar | Não | Acrescenta uma chamada de modelo em toda pergunta para evitar uma busca que custa uma fração dela. E erra: a decisão "isto está no acervo?" só pode ser respondida com confiança DEPOIS de buscar, olhando o score de relevância — que é grátis, determinístico e já está na resposta |
| Um modelo gerando as perguntas do golden set | Parcialmente, e com cuidado | Serve para AMPLIAR cobertura a partir de perguntas reais do chat, e não serve para criar o gabarito: gabarito conferido por modelo mede o quanto dois modelos concordam, não o quanto o sistema acerta. A pergunta pode ser gerada; a resposta esperada precisa de humano que conheça a política |
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Sintoma em produção | Forma correta |
|---|---|---|---|
| Perguntar direto ao LLM sem nenhuma recuperação | é o caminho mais rápido para uma demo, e funciona bem o suficiente até alguém perguntar algo específico | resposta plausível e errada, sem fonte, aceita pelo atendente sem checar | recuperar o trecho relevante ANTES de gerar qualquer palavra da resposta |
| Instruir "responda com precisão e não invente" sem dar contexto | parece que uma instrução de comportamento resolve o que na verdade é falta de dado | a taxa de acerto sobe pouco ou nada, porque o modelo continua sem acesso ao documento real | dar o TRECHO certo no prompt — instrução sem dado não cria conhecimento |
| Chunk grande demais, "para não perder contexto" | intuitivamente parece mais seguro incluir mais texto do que menos | a recuperação traz páginas inteiras; o modelo tem de achar a informação certa dentro do próprio contexto, e às vezes erra por excesso | chunk do tamanho da unidade de resposta, com sobreposição pequena e calibrada |
| Reindexar o acervo inteiro a cada alteração pequena | é mais simples de implementar do que detectar exatamente o que mudou | job de ingestão caro e demorado rodando por uma vírgula corrigida num documento | ingestão incremental, disparada pelo evento de escrita do objeto específico |
| Confiar na taxa de satisfação do atendente como medida de qualidade | é o número que já existe, sem construir nada novo para medir | o atendente aceita resposta errada com confiança porque o texto parece bem escrito — fluência não é acurácia | golden set com resposta oficial conhecida, comparado por acerto, não por impressão |
Evolução em níveis: da resposta sem fonte à plataforma compartilhada
A terceira arquitetura não é um desenho novo — é a resposta a QUANDO trocar de desenho. Cada nível resolve um risco medido neste laboratório e compra outro no lugar.
Chamada direta ao Bedrock, sem nenhuma recuperação. É o que a Cadência tinha, e sobreviveu 3 semanas sem ninguém notar.RAG mínimo: Knowledge Bases + OpenSearch Serverless, taxa de acerto medida contra golden set (35% → 92,5%).Comparar as quatro opções de banco vetorial (L84) e somar busca híbrida — léxica mais vetorial — com reranking (L85).Guardrails como controle real de tópico e conteúdo (L86), e agente com ferramenta quando a resposta exige AGIR, não só responder (L87).O golden set vira suíte que reprova regressão de qualidade no CI a cada mudança de prompt ou chunking, com LLM-como-juiz complementando a checagem manual (L88).Uma base de conhecimento compartilhada entre atendimento, lojista e busca de catálogo (L94), com múltiplos acervos e controle de quem vê o quê — a mesma pergunta "onde guardar o dado" que o L84 aprofunda, agora em escala de plataforma. É também o padrão descrito na solução S4 do catálogo de arquiteturas de IA na AWS.Por que este laboratório vem antes do L84 e do L85
A ordem não é negociável: busca híbrida e reranking (nível 3) pressupõem que a recuperação básica já funciona e já está medida — é o texto que ancora a comparação com o resultado vetorial, e é exatamente o que este laboratório entrega. Quem tenta reranking antes de ter RAG mínimo funcionando está otimizando um número que ainda não mediu.
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Log ou métrica | Correção |
|---|---|---|---|---|
| A resposta vem sem nenhuma citação | Recuperação não devolveu nada acima do piso de relevância | Repetir a mesma pergunta chamando só a recuperação, sem geração, e olhar os scores | Proporção de respostas sem citação; distribuição de score do melhor trecho | Se o acervo não cobre o assunto, responder "não encontrei" é o comportamento certo — a correção é no acervo, não no prompt |
| A citação aponta para o documento certo e o prazo está errado | O trecho recuperado cortou a tabela: veio a linha sem o cabeçalho, ou o contrário | Ler o texto exato do trecho citado, não o documento inteiro | O conteúdo do trecho retornado, registrado para amostra de perguntas | Ajustar a estratégia de divisão para não partir tabela, e conferir contra o golden set — mudar o tamanho do chunk conserta um caso e quebra outro se ninguém medir |
| Responde com a política antiga, com confiança | Documento atualizado no bucket e índice não reingerido | Comparar a data de modificação do objeto no S3 com a da última sincronização | Idade da última sincronização bem-sucedida | Reingerir e, principalmente, disparar a ingestão por evento de escrita — este sintoma é o que mais custa credibilidade |
| Duas perguntas quase iguais recebem respostas contraditórias | Documento duplicado em versões diferentes no acervo | Recuperar sem gerar e verificar se os trechos vêm de arquivos distintos com o mesmo assunto | Origem dos trechos recuperados por pergunta | Um documento por assunto. A contradição está no acervo, e nenhum ajuste de modelo a resolve |
| A latência dobrou sem mudança de código | O acervo cresceu e a recuperação passou a dominar o tempo | Separar no traço o tempo de recuperação do tempo de geração | Latência p95 de recuperação e de geração, em séries separadas | É o sinal de que a escala mudou de faixa. Sem a separação das duas métricas, o time otimiza o modelo enquanto o problema está no índice |
Limpeza: o que o destroy não leva
# limpeza.sh — ordem importa: a Knowledge Base referencia o índice e o bucket,
# e o Terraform destroy sozinho nao apaga objeto versionado nem esvazia bucket.
# 1) Apagar a data source e a knowledge base primeiro (dependencia do indice)
aws bedrock-agent delete-data-source --knowledge-base-id KB7F3A9C1D --data-source-id DS9E2B
aws bedrock-agent delete-knowledge-base --knowledge-base-id KB7F3A9C1D
# 2) Esvaziar TODAS as versoes do bucket antes do destroy — bucket versionado
# com objeto dentro nao e removido por "terraform destroy" nem por
# "aws s3 rb" simples; e o bucket que mais dinheiro esconde depois do laboratorio.
aws s3api list-object-versions --bucket cadencia-documentos-atendimento \
--query "[Versions,DeleteMarkers][].{Key:Key,VersionId:VersionId}" --output json \
| jq -c '.[]' | while read -r obj; do
aws s3api delete-object --bucket cadencia-documentos-atendimento \
--key "$(echo "$obj" | jq -r .Key)" --version-id "$(echo "$obj" | jq -r .VersionId)"
done
# 3) terraform destroy cuida do resto: colecao OpenSearch Serverless, políticas
# de seguranca, roles do IAM, o proprio bucket vazio, e o grupo de logs da
# funcao de atendimento
terraform destroy -auto-approve
# 4) Conferir que nao sobrou colecao do OpenSearch Serverless cobrando OCU
aws opensearchserverless list-collections \
--query "collectionSummaries[?name=='cadencia-rag-politica-atendimento']"
# Esperado: lista vazia
O que continua cobrando depois de "destruir"
Duas coisas que `terraform destroy` sozinho não resolve: um bucket S3 versionado com objeto dentro fica preso (o provider recusa apagar bucket não-vazio, e cada versão conta como objeto), e o grupo de logs da função de atendimento continua existindo com a retenção configurada se ele foi criado fora do módulo Terraform que você está destruindo. A capacidade mínima do OpenSearch Serverless (OCU) é o item que mais cobra parado neste laboratório especificamente — confira o passo 4 antes de fechar o navegador.
Resumo: problema, peça e motivo
| Problema | Peça | Motivo |
|---|---|---|
| Resposta inventada, sem fonte | Bedrock Knowledge Bases (RetrieveAndGenerate) | só gera depois de recuperar, e devolve a citação junto da resposta |
| Documento real nunca chega ao modelo | S3 como fonte da base de conhecimento | o mesmo bucket que já guarda política e catálogo do L62, sem duplicar dado |
| Onde procurar o trecho certo | OpenSearch Serverless, gerenciado pela Knowledge Base | busca por similaridade vetorial, sem operar índice à mão |
| Tabela de garantia cortada ao meio | Chunking de tamanho fixo com sobreposição | o trecho recuperado carrega a linha inteira, não metade dela |
| "Melhorou" sem prova | Golden set de 40 perguntas, medido antes e depois | 35% para 92,5% — um número comparável entre versões, não uma impressão |
| Chamada sem controle de acesso | IAM com Resource específico do modelo e da base | nenhuma aplicação além da de atendimento invoca este modelo ou esta base |
Desafio — sem roteiro
O requisito
Adicione um documento à base que CONTRADIZ diretamente um documento já existente (ex: duas versões de uma política com datas diferentes dizendo coisas opostas) e observe o que o sistema faz quando perguntado sobre o tema.
Critério de aceite — executável, não "verifique se funciona"
A resposta do sistema para uma pergunta cujo tema tem os dois documentos conflitantes NÃO escolhe silenciosamente um dos dois sem avisar — ela cita as DUAS fontes (a citação é o contrato do laboratório) e, no mínimo, sinaliza a divergência, mesmo que a resposta final privilegie uma delas.
- Dica 1: Se os dois documentos tiverem data, o jeito mais simples de resolver o conflito é adicionar a data aos metadados de cada chunk e instruir o prompt a preferir o mais recente EXPLICITAMENTE — mas isso só funciona se a citação também mostrar a data, senão o usuário não consegue auditar a escolha.
- Dica 2: Teste com uma pergunta que force os DOIS documentos a aparecerem entre os top-k resultados da busca — se o retriever só traz um dos dois, o teste não está avaliando o comportamento de conflito, está testando busca.
- Dica 3: Uma resposta que ignora o conflito e cita só uma fonte, mesmo que a informação esteja certa, falha o critério — o requisito é sobre TRANSPARÊNCIA do conflito, não sobre acertar qual documento é o vigente.
Lembrete de limpeza
Todo recurso que este desafio criar entra no mesmo `terraform destroy` do laboratório — nada fica para trás cobrando sozinho.
Perguntas frequentes
❓ RAG substitui o fine-tuning do modelo com os documentos da Cadência?
❓ Por que a resposta do LLM direto parecia tão confiante mesmo estando errada?
❓ Chunking de 500 tokens com 20% de sobreposição serve para qualquer documento?
❓ O que acontece quando a pergunta do atendente não tem resposta em nenhum documento?
❓ Knowledge Bases usa sempre OpenSearch Serverless por baixo?
❓ Por que a latência subiu de 700 ms para 1,6 s com RAG, e isso é um problema?
❓ Por que medir num golden set de só 40 perguntas, e não em toda pergunta possível?
Fixando
Depois de implantar o RAG, a taxa de acerto no golden set subiu de 35% para 92,5%, mas não chegou a 100%. Os 3 erros residuais medidos vieram de tabela cortada no limite do chunk e de um documento duplicado (versão antiga ainda no bucket). O que isso mostra sobre RAG?
No golden set de 40 perguntas, uma recebeu a resposta "não encontrei essa informação nos documentos da Cadência" — e essa resposta foi classificada como ACERTO, não erro. Por que uma recusa em responder conta como taxa de acerto?
Próximo laboratório
Próximo passo: L84 (onde guardar o vetor) e L85 (recuperação híbrida e reranking)
Este laboratório fixou o banco vetorial (OpenSearch Serverless, gerenciado pela Knowledge Base) para isolar a pergunta "RAG funciona?". O L84 volta exatamente a essa decisão e mede as quatro opções — Knowledge Bases/OpenSearch, OpenSearch provisionado, S3 Vectors e Aurora pgvector — no mesmo acervo da Cadência, com recall, latência e custo comparados lado a lado. Depois, o L85 ataca os 3 erros residuais medidos aqui pela raiz: busca híbrida (léxica + vetorial) com reranking, para reduzir os casos em que a recuperação traz o trecho errado, não o trecho nenhum.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…