Lab 89 — Custo e latência de GenAI
O problema, e a empresa que o tem
A Cadência é a mesma equipe do L01, do L13 e do L80: catálogo cacheado, réplica indexada, e os quatro vazamentos de ML já corrigidos. Nos últimos dois meses, mais duas peças entraram em produção: um RAG que responde dúvida de política interna citando a fonte, e um agente que executa ações simples de suporte sozinho. As duas usam o Bedrock. A fatura de token, que era de R$ 6.950 por mês antes das duas entrarem no ar, fechou o mês seguinte em R$ 41.800 — seis vezes mais, sem seis vezes mais valor entregue.
O time abre o Cost Explorer esperando achar um bug óbvio e não acha. Cada chamada ao Bedrock está correta: o RAG recupera o trecho certo, o agente decide a ação certa, a classificação de ticket acerta a categoria. O problema não é nenhuma chamada errada — é que três decisões de engenharia nunca foram tomadas de propósito. Primeiro: o RAG manda o MESMO contexto grande — os documentos recuperados, em média 3.500 tokens — de novo a cada pergunta parecida, porque nada reaproveita o que já foi enviado. Segundo: toda tarefa usa o modelo mais caro e mais potente disponível, inclusive classificar a categoria de um ticket, que não precisa do mesmo modelo que escreve uma resposta longa. Terceiro: o job que gera descrição para os produtos do catálogo — 5 mil itens por execução — chama o Bedrock um produto de cada vez, de forma síncrona, na mesma capacidade que atende pergunta interativa.
Nenhum dos três vazamentos exige trocar de modelo nem retreinar nada — é a mesma lição do L80 aplicada a um domínio novo: o dinheiro não vaza porque o serviço é caro, vaza porque três decisões de infraestrutura nunca foram tomadas. E o mecanismo de cache que resolve o primeiro vazamento já existe na conta — é o ElastiCache Redis que o L13 construiu para o catálogo, aplicado agora a uma segunda fonte de verdade: a resposta de um modelo de linguagem.
A fatura dobra nas próximas duas entradas em produção se ninguém mexer
Um agente novo e um segundo caso de RAG já estão no roadmap do próximo trimestre. Sem cache de contexto, sem roteamento por tarefa e sem modo batch, cada novo consumidor do Bedrock soma fatura no MESMO padrão caro que gerou os R$ 41.800 — a curva não é linear, é composta: mais RAG, mais agente, mais contexto repetido, mais chamada no modelo mais caro. O problema não vai se resolver sozinho porque nenhuma chamada individual está errada.
O que este laboratório NÃO é
Não é sobre trocar de modelo para um mais barato e pior, nem sobre limitar o RAG a respostas curtas para economizar token — isso reduziria custo destruindo o motivo de o RAG e o agente existirem. Também não é sobre re-treinar nada: a disciplina de medir antes de comprometer já é o L59, aplicada ao SageMaker no L80; aqui ela é aplicada ao Bedrock, com as ferramentas específicas de GenAI — cache de prompt, roteamento por tarefa, modo batch.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com uma medição concreta na seção de implantação, não com a sensação de ter entendido.
- Explicar por que um RAG que recupera o trecho certo pode mesmo assim ter fatura de token alta.
- Diferenciar cache de RESPOSTA (ElastiCache, chave exata) de cache de PROMPT nativo do Bedrock (prefixo repetido, dentro da mesma chamada).
- Implementar cache-aside no ElastiCache para respostas e contexto de RAG repetidos, reaproveitando o padrão de TTL e invalidação do L13.
- Usar `cachePoint` na Converse API do Bedrock para reduzir o custo do contexto grande reenviado em chamadas diferentes.
- Rotear a chamada para o modelo certo por tipo de tarefa, e medir a qualidade antes de trocar, não só o preço.
- Migrar um job de geração em lote do modo síncrono para o Bedrock batch, com o desconto do modo lote.
- Configurar Budgets segmentado por tarefa/consumidor, não por uma linha agregada de "Bedrock".
- Provar redução de custo por resposta sem regressão de qualidade medida, com número nas duas pontas.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Modelo de cobrança do Bedrock (on-demand) | AIF-C01 | cobrança por token de entrada e de saída, por chamada, sem contrato | token de entrada geralmente custa menos que o de saída, e o contexto grande do RAG entra como token de ENTRADA a cada chamada |
| Cache de prompt (`cachePoint`) | AIF-C01 | o RAG marca o bloco de contexto recuperado como reaproveitável entre chamadas próximas | cache de prompt reduz o custo do PREFIXO repetido dentro da janela de cache do provedor — é diferente de cachear a resposta inteira |
| Roteamento de modelo por tarefa | AIF-C01, MLA-C01 | classificação de ticket vai para um modelo leve; geração de resposta longa vai para um modelo maior | custo e latência crescem com o tamanho do modelo; a tarefa, não o hype, decide qual usar |
| Bedrock em modo batch (`CreateModelInvocationJob`) | AIF-C01, MLS-C01 | as 5 mil descrições de produto saem da fila interativa e entram no modo lote | o modo batch processa em janela de horas com desconto sobre o preço on-demand — não serve consumidor que precisa de resposta imediata |
| On-demand vs provisioned throughput | AIF-C01 | por que este laboratório NÃO usa provisioned throughput | provisioned throughput reserva capacidade por um período mínimo — faz sentido para volume alto e previsível; o consumo da Cadência ainda varia demais |
| Cache-aside aplicado a resposta de modelo | SAA-C03, AIF-C01 | ElastiCache na frente do Bedrock, reaproveitando exatamente o padrão do L13 | a decisão de TTL de um cache de resposta de IA é a mesma pergunta do L13: quanto tempo você tolera uma resposta desatualizada |
Onde isto costuma ser cobrado errado
A questão clássica descreve uma fatura de Bedrock alta e oferece "trocar para um modelo mais barato" como a resposta óbvia. Na maioria dos cenários reais — e nos três deste laboratório — a causa não é a ESCOLHA de modelo isolada: é reenviar o mesmo contexto sem cache, usar um modelo por hábito em vez de por tarefa, e processar trabalho não-interativo na mesma capacidade cara e síncrona do trabalho interativo.
Requisitos, e como cada um muda o desenho
A coluna da direita é a rastreabilidade: toda peça da arquitetura de produção aparece nela. Peça que não aparecesse teria sido retirada antes deste texto existir.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Custo por resposta reduzido, com número medido | sem valor absoluto — comprovado por medição antes e depois | obriga instrumentar custo por CHAMADA antes de qualquer mudança, não só a fatura consolidada do mês |
| Taxa de resposta citável do RAG não pode regredir | medida do L83, dentro da margem de ruído já observada | o cache de resposta e o de prompt não podem alterar o CONTEÚDO da resposta, só evitar reprocessamento |
| Acurácia de classificação do ticket não pode regredir | medida contra golden set, antes de trocar de modelo | obriga medir o modelo leve contra o caro no MESMO conjunto antes de rotear qualquer tráfego real |
| Job de produto não pode competir com tráfego interativo | 5 mil itens não podem atrasar resposta de chat nem de ticket | obriga sair da fila síncrona e entrar no modo batch, fora do caminho que serve usuário |
| Resposta cacheada não pode ficar velha além do tolerável | decisão por tipo de conteúdo — política interna muda raramente, catálogo muda todo dia | obriga TTL diferente por tipo de conteúdo, não um valor único para tudo que passa pelo cache |
| Custo rastreável por consumidor | tag obrigatória em toda chamada nova: chat, ticket ou lote | obriga Budgets e CloudWatch segmentados por tag, não uma linha agregada de "Bedrock" |
| Acesso ao modelo mais caro é restrito | só o caminho de geração de resposta pode invocá-lo | obriga IAM com `Resource` do ARN do modelo, não `bedrock:InvokeModel` aberto para qualquer papel de execução |
O requisito mais fácil de confundir com os outros dois
"Reduzir custo por resposta" e "reduzir a fatura do mês" parecem o mesmo objetivo e não são: a fatura pode CRESCER porque o volume de uso legítimo cresceu, e ainda assim o custo por resposta ter caído — é exatamente o que acontece neste laboratório. Medir só a fatura total esconderia uma redução real atrás de mais gente usando o sistema.
Arquitetura mínima: um caminho só, para as três tarefas
Este é o desenho que a Cadência tem hoje. É literalmente implantável — nenhuma das três chamadas está incorreta — e é por isso que o defeito passa despercebido: o RAG responde certo, o ticket é classificado certo, o produto ganha descrição certa. O problema não é nenhuma resposta errada; é que a mesma função atende as três tarefas com o mesmo modelo, sem cache e sem distinguir o que pode esperar.
- → pergunta em linguagem natural, com contexto de política interna
- → texto do ticket — só a categoria importa
- → dados de um produto por chamada, 5 mil vezes seguidas
- → busca os trechos relevantes, toda pergunta de novo
- → devolve os mesmos documentos grandes, sem cache algum
- → contexto completo mais a tarefa, seja qual for
- → resposta, cobrada no preço do modelo mais caro
- → custo de token somado, sem separar por consumidor
- Fora da AWS
- Compute
- IA e machine learning
- Gestão e governança
Chat, ticket e job de produto entram pela mesma função e saem pelo mesmo modelo caro — o contexto do RAG é reenviado inteiro a cada chamada parecida, e o job de 5 mil produtos disputa a mesma capacidade da pergunta interativa. Percorra os passos: o defeito não é nenhuma chamada individual, é a ausência de três decisões que a arquitetura de produção adiciona.
- Três tarefas diferentes entram pela mesma porta. Pergunta ao RAG, classificação de ticket e descrição de produto chegam na mesma função, que não olha o tipo de tarefa antes de decidir o que fazer.
- O RAG busca o mesmo contexto de novo, a cada pergunta parecida. A base de conhecimento devolve os mesmos documentos grandes toda vez que uma pergunta semelhante chega — nada no caminho guarda o que já foi recuperado e enviado antes.
- Classificar um ticket paga o preço de gerar um texto longo. A mesma chamada que gera uma resposta de política interna de várias frases também decide se um ticket é urgente — o preço do modelo é o mesmo nos dois casos, embora a tarefa seja muito mais simples na segunda.
- O job de 5 mil produtos roda um de cada vez, na fila interativa. Sem um modo separado para trabalho não-interativo, cada uma das 5 mil descrições dispara uma chamada síncrona que compete pela mesma capacidade que atende chat e ticket em tempo real.
- Cada chamada cobra o contexto inteiro de novo, mesmo repetido. Não existe reaproveitamento entre chamadas: o mesmo bloco de 3.500 tokens de contexto do RAG é cobrado como token de entrada NOVO em cada pergunta parecida, mesmo quando nada nele mudou.
- A fatura sobe, e ninguém sabe qual dos três consumidores pesa mais. O Cost Explorer mostra o total de Bedrock crescendo mês a mês, mas sem tag por consumidor não há como saber se o RAG, o ticket ou o lote de produtos é o que mais pesa na conta.
- Por que alguém constrói assim. Reaproveitar a mesma função e o mesmo modelo para as três tarefas pareceu mais simples do que decidir, tarefa por tarefa, o que cada uma realmente precisa — e nada quebra, então nada chama atenção até a fatura fechar o mês.
#!/usr/bin/env bash
# medir_custo_atual.sh -- confere o que ninguem tinha medido: custo POR CHAMADA,
# nao so a fatura consolidada do mes.
aws ce get-cost-and-usage \
--time-period Start=2026-07-01,End=2026-08-01 \
--granularity MONTHLY \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Bedrock"]}}' \
--metrics UnblendedCost --output table
python3 - <<'PY'
fatura_mes = 41800.0 # R$, medido no Cost Explorer
chamadas_interativas = 180_000
itens_lote = 10_000
total_chamadas = chamadas_interativas + itens_lote
custo_por_resposta = fatura_mes / total_chamadas
print(f'custo medio por resposta: R$ {custo_por_resposta:.4f}')
# Sem tag por consumidor, este numero e uma MEDIA -- nao diz se o RAG,
# o ticket ou o lote pesa mais. E o proximo defeito que este laboratorio corrige.
PY
Nada aqui quebra — é por isso que ninguém percebeu em seis semanas
O RAG responde certo, o ticket é classificado certo, o produto ganha descrição certa. Não existe alarme para "isto está caro e não precisava estar" — só existe fatura, um mês depois, seis vezes maior. O mesmo padrão de custo silencioso do L80, agora em tokens em vez de horas de GPU.
Arquitetura para produção
A topologia muda, não só o rótulo. Três peças novas atacam os três vazamentos, cada uma exatamente onde o diagrama anterior mostrou o defeito: um roteador decide o modelo pela tarefa antes de qualquer chamada; um cache no ElastiCache responde antes de o Bedrock ser acionado; e o job de produtos sai da fila interativa e entra no modo batch, com o desconto do modo lote.
- → pergunta em linguagem natural, sem mudança perceptível
- → texto do ticket a classificar
- → identifica a tarefa antes de montar a chamada
- → consulta a chave normalizada da pergunta ou do contexto
- → HIT: resposta ou contexto já prontos, sem chamar o Bedrock
- → MISS de tarefa curta: classificação de ticket
- → MISS de tarefa longa: geração com contexto do RAG, com cachePoint
- → grava resposta e contexto no cache, com TTL por tipo de conteúdo
- → replicação Multi-AZ dentro do grupo
- → AUTH token, renovado em runtime
- → dispara o job com os itens pendentes do dia
- → entrada: dados dos 10 mil produtos do mês
- → saída: descrição gerada, gravada em lote
- → custo e latência, tag ticket
- → custo e latência, tag chat
- → custo por item processado em lote, tag lote
- → alimenta o orçamento por tag, não só o total consolidado
- Fora da AWS
- Compute
- Conceito de arquitetura
- Banco de dados
- Segurança e identidade
- IA e machine learning
- Integração de apps
- Armazenamento
- Gestão e governança
O roteador decide o modelo pela tarefa antes de qualquer chamada; o cache responde antes do Bedrock ser acionado; o job de produtos nunca entra na fila interativa. Percorra os passos: cada peça nova ataca exatamente um dos três vazamentos que a arquitetura mínima expôs, e o orçamento por tag prova a redução por componente.
- O roteador decide o modelo antes de qualquer chamada. A tarefa já é conhecida no momento em que a requisição chega — classificar ticket ou responder com o RAG — e o roteador mapeia isso para um modelo específico, sem nenhum modelo decidindo sozinho qual modelo usar.
- Primeiro pergunta ao cache, não ao modelo. Antes de qualquer chamada ao Bedrock, o roteador consulta o Redis pela chave normalizada da pergunta ou do contexto — um HIT devolve a resposta sem gastar um token sequer.
- Um MISS vai para o modelo certo da tarefa, nunca para o mais caro por padrão. Classificação de ticket, que é curta e bem definida, vai para o modelo leve; geração de resposta longa do RAG, que precisa de mais capacidade de raciocínio, vai para o modelo potente — a diferença de preço entre os dois é a alavanca que mais depende de acertar essa divisão.
- A chamada cara grava no cache com TTL por tipo de conteúdo. Resposta sobre política interna, que muda raramente, recebe TTL de horas; contexto de um produto específico, que muda todo dia, recebe TTL de minutos — o mesmo raciocínio de "quanto tempo você tolera estar errado" do L13, aplicado a um domínio novo.
- O job de produtos nunca entra na fila interativa. O agendador dispara o job de lote fora do horário de pico, direto no modo batch do Bedrock — os 10 mil itens do mês processam numa janela de horas, sem competir um segundo sequer com uma pergunta de chat ou um ticket chegando.
- Cada modelo e o lote reportam custo separado, por tag. O painel do CloudWatch separa custo e latência por tarefa — chat, ticket, lote — em vez de uma linha só de "Bedrock", e é essa separação que permite culpar o componente certo quando a fatura se move.
- O orçamento alarma por componente, antes da fatura consolidada. Um orçamento por tag de consumidor detecta se o ticket, sozinho, começar a crescer acima do esperado — sem esperar o fechamento do mês para descobrir qual dos três está pesando.
O ajuste com maior efeito por chamada evitada
Das três alavancas, o cache de resposta no ElastiCache é o que mais reduz custo por unidade de esforço: uma chave bem desenhada e um TTL por tipo de conteúdo eliminam a chamada ao Bedrock inteira, não apenas reduzem o preço dela. Roteamento e lote reduzem o custo de cada chamada que ainda acontece — o cache reduz o NÚMERO de chamadas.
Como funciona ponta a ponta: da pergunta ao Bedrock, e de volta
O caminho mais fácil de entender errado é achar que basta comparar o texto da pergunta letra por letra. Não é — duas perguntas com o mesmo significado e texto diferente ("qual o prazo de reembolso" e "em quantos dias sai o reembolso") geram chaves diferentes com uma normalização simples, e isso é uma limitação conhecida deste desenho, não um bug escondido.
Trecho da chamada Converse API com cachePoint no bloco de contexto do RAG. O bloco de sistema e o contexto recuperado ficam ANTES do cachePoint -- e sao eles que se tornam mais baratos em chamadas proximas que reusam o mesmo prefixo. A pergunta do usuario, que muda a cada chamada, fica DEPOIS.
{
"modelId": "anthropic.claude-sonnet-4-5-...",
"system": [
{ "text": "Você responde dúvidas de política interna da Cadência, citando a fonte." }
],
"messages": [
{
"role": "user",
"content": [
{ "text": "<contexto recuperado do RAG, ~3.500 tokens>" },
{ "cachePoint": { "type": "default" } },
{ "text": "Qual o prazo de reembolso para pedido cancelado?" }
]
}
]
}
Por que o cachePoint fica DEPOIS do contexto, não antes da pergunta
O cache de prompt do provedor funciona por PREFIXO: ele reaproveita tudo que vem antes do `cachePoint` quando uma chamada próxima repete o mesmo início exato. Colocar o marcador depois do contexto recuperado, e antes da pergunta variável do usuário, é o que garante que o contexto — a parte grande e repetida — seja a parte reaproveitada, e a pergunta — a parte que muda sempre — nunca invalide o cache por engano.
As decisões, e o que se perde em cada uma
📋 Reduzir o custo por resposta do Bedrock na Cadência, com RAG e agente já em produção, sem regredir a taxa de resposta citável nem a acurácia de classificação de ticket, e sem atrasar nenhuma resposta interativa.
Nenhuma das três alavancas exige modelo novo nem retreino: o cache de resposta evita a chamada inteira quando a pergunta se repete; o cache de prompt reduz o custo do contexto grande quando a pergunta é nova mas o contexto é o mesmo; o roteamento paga o preço certo pela tarefa certa; e o modo batch tira o trabalho não-interativo da capacidade cara e síncrona. As quatro juntas atacam os três vazamentos sem tocar em nenhuma resposta que já está correta.
Alt: Trocar todo o sistema para um único modelo mais barato — reduziria custo de toda chamada, inclusive as que precisam da capacidade do modelo potente — a qualidade da resposta do RAG cairia junto, e o requisito de não regredir a taxa de resposta citável seria quebrado de propósito
Alt: Cache semântico via embedding, em vez de chave normalizada — juntaria perguntas com o mesmo sentido e texto diferente, elevando a taxa de acerto além dos ~52% medidos — mas exige infraestrutura de busca vetorial adicional (o assunto do L84), fora do escopo deste laboratório
Alt: Provisioned throughput em vez de on-demand — eliminaria a variação de preço por chamada com uma taxa fixa — mas exige compromisso de capacidade mínima que o volume ainda instável da Cadência (RAG e agente com menos de três meses em produção) não justifica ainda, e é a mesma lição do L59 sobre medir antes de comprometer
Alt: Mover a classificação de ticket para o modo batch — aproveitaria o desconto do modo lote — mas o ticket precisa de resposta em segundos para decidir o roteamento da fila de suporte, e o modo batch processa em janela de horas; resolve custo e quebra o requisito de latência
| Decisão | Escolha | Alternativa considerada | Motivo | O que se perde |
|---|---|---|---|---|
| Cache de resposta repetida | ElastiCache Redis, cache-aside com chave normalizada | cache semântico via embedding | reaproveita a infraestrutura e a disciplina de TTL do L13, sem componente novo | perguntas com mesmo sentido e texto diferente não batem no cache — taxa de acerto fica em ~52%, não mais alta |
| Cache do contexto grande do RAG | cachePoint nativo do Bedrock no prefixo do contexto | reenviar o contexto sempre, sem marcação | reduz o custo do token de entrada repetido mesmo quando a pergunta final é nova | a janela de reaproveitamento do provedor é curta — não substitui o cache de resposta para perguntas distantes no tempo |
| Modelo por tarefa | roteamento determinístico por tipo de requisição, medido contra golden set | um classificador de complexidade decidindo o modelo em tempo real | decisão simples, testável antes de ir para produção, sem depender de outro modelo para decidir | tarefa nova, ainda não mapeada, cai num modelo padrão até alguém medir e adicionar a regra |
| Job de produtos | Bedrock batch, disparado por agendador, fora da fila interativa | manter síncrono, só com instância maior | desconto do modo lote e zero disputa com tráfego interativo | resultado só fica pronto na janela de horas do job, não em segundos |
| TTL do cache | por tipo de conteúdo — horas para política, minutos para produto | TTL único para tudo que passa pelo cache | reflete quanto tempo cada tipo de conteúdo tolera ficar desatualizado | mais uma decisão de configuração por tipo de conteúdo, em vez de um valor só |
Construir: cache de resposta e contexto no ElastiCache
A infraestrutura é a mesma do L13 — grupo de replicação Multi-AZ, AUTH token via Secrets Manager, `volatile-lru`. O que muda é o DOMÍNIO da chave e o TTL, que agora dependem do tipo de conteúdo, não de um único catálogo de produtos.
// RespostaCacheService.cs -- cache-aside para resposta de IA, reaproveitando
// o mesmo grupo de replicacao Multi-AZ do L13. O que muda e o dominio da
// chave e o TTL, que dependem do TIPO de conteudo, nao de um catalogo so.
public sealed class RespostaCacheService
{
private readonly IConnectionMultiplexer _redis;
// TTL por tipo de conteudo -- a mesma pergunta do L13 ("quanto tempo voce
// tolera estar errado"), respondida diferente para cada dominio.
private static readonly Dictionary<TipoConteudo, TimeSpan> TtlPorTipo = new()
{
[TipoConteudo.PoliticaInterna] = TimeSpan.FromHours(6), // muda raramente
[TipoConteudo.ContextoProduto] = TimeSpan.FromMinutes(15), // muda todo dia
[TipoConteudo.ClassificacaoTicket] = TimeSpan.FromMinutes(2), // quase nunca repete
};
public RespostaCacheService(IConnectionMultiplexer redis) => _redis = redis;
public async Task<string?> ObterAsync(string pergunta, TipoConteudo tipo)
{
var chave = ChaveNormalizada(pergunta, tipo);
var db = _redis.GetDatabase();
var valor = await db.StringGetAsync(chave);
return valor.HasValue ? valor.ToString() : null; // null = MISS, chama o Bedrock
}
public async Task GravarAsync(string pergunta, TipoConteudo tipo, string resposta)
{
var chave = ChaveNormalizada(pergunta, tipo);
var db = _redis.GetDatabase();
await db.StringSetAsync(chave, resposta, TtlPorTipo[tipo]);
}
// Normalizacao SIMPLES: minusculas, espacos redundantes fora, pontuacao
// fora. Nao junta perguntas com o MESMO SENTIDO e texto diferente -- essa
// limitacao e conhecida e esta na secao de decisoes, nao escondida.
private static string ChaveNormalizada(string pergunta, TipoConteudo tipo)
{
var normalizada = Regex.Replace(pergunta.Trim().ToLowerInvariant(), @"[^\w\s]|\s+", " ").Trim();
var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(normalizada)))[..16];
return $"ia:{tipo}:{hash}";
}
}
public enum TipoConteudo { PoliticaInterna, ContextoProduto, ClassificacaoTicket }
Resposta cacheada além do TTL é resposta errada com aparência de certa
Se a política de reembolso mudar e o TTL de 6 horas ainda não tiver expirado, o RAG cita uma fonte que já não é mais verdadeira — e a resposta parece tão confiável quanto uma correta, porque vem no mesmo formato com citação. É por isso que conteúdo que pode mudar sem aviso (preço, prazo, política) precisa de TTL curto o bastante para o risco ser aceitável, mesmo perdendo parte do ganho de cache — a mesma decisão do L13, com uma consequência mais séria aqui: resposta errada de IA parece mais autoritativa que preço errado de catálogo.
Construir: o roteador de modelo por tarefa
O roteador não é um modelo decidindo qual modelo usar — é uma tabela de decisão simples, testável, versionada como código. A tarefa já é conhecida no momento em que a requisição chega.
// RoteadorDeModelo.cs -- mapeamento deterministico de tarefa para modelo.
// NAO e um modelo decidindo qual modelo usar: e uma tabela versionada,
// testavel com o golden set antes de qualquer mudanca ir para producao.
public sealed class RoteadorDeModelo
{
private static readonly Dictionary<TipoTarefa, string> ModeloPorTarefa = new()
{
// Classificacao curta: modelo leve, medido no golden set antes de rotear
// trafego real -- diferenca de acuracia medida em 0,6 ponto percentual
// contra o modelo caro, dentro da margem aceita pelo time de suporte.
[TipoTarefa.ClassificarTicket] = "amazon.nova-micro-v1:0",
// Geracao longa com contexto do RAG: modelo potente, com cachePoint no
// contexto recuperado -- ver ConverseComCache.
[TipoTarefa.ResponderComRag] = "anthropic.claude-sonnet-4-5-...",
};
public string ModeloPara(TipoTarefa tarefa)
{
// Tarefa nova, ainda nao mapeada, cai no modelo potente por seguranca --
// e fica registrada para medicao antes de ganhar uma linha propria aqui.
if (!ModeloPorTarefa.TryGetValue(tarefa, out var modelo))
{
_logger.LogWarning("Tarefa {Tarefa} sem modelo mapeado -- usando padrao", tarefa);
return ModeloPorTarefa[TipoTarefa.ResponderComRag];
}
return modelo;
}
private readonly ILogger<RoteadorDeModelo> _logger;
public RoteadorDeModelo(ILogger<RoteadorDeModelo> logger) => _logger = logger;
}
public enum TipoTarefa { ClassificarTicket, ResponderComRag }
Medir antes de rotear, não depois
Trocar o modelo de uma tarefa sem medir a qualidade contra um golden set antes é a mesma armadilha do L59 aplicada a modelo em vez de instância: o preço cai imediatamente e é visível; a regressão de qualidade demora a aparecer e é medida em produção, tarde demais. O roteador só ganhou a linha de `ClassificarTicket` depois de duas semanas comparando os dois modelos no mesmo conjunto de tickets já resolvidos.
Construir: o job de produtos em modo batch
O Bedrock batch não tem um recurso declarativo de "job" no provedor Terraform — criar o job é uma chamada de API, igual ao padrão do L59 para a compra do Savings Plan: o que É infraestrutura declarável (buckets, papel de execução, agendador) entra em Terraform; o disparo do job entra como código que chama a API, porque não existe operação inversa nem estado a rastrear.
# lote-produtos.tf -- o que E declaravel: buckets, papel, agendador. O job
# em si (CreateModelInvocationJob) e uma chamada de API, nao um recurso.
resource "aws_s3_bucket" "lote_ia" {
bucket = "${var.projeto}-lote-descricao-produto"
}
resource "aws_s3_bucket_lifecycle_configuration" "lote_ia" {
bucket = aws_s3_bucket.lote_ia.id
rule {
id = "expirar-saida-processada"
status = "Enabled"
filter { prefix = "saida/" }
# 30 dias bastam: a descricao gerada e promovida para o catalogo em
# producao logo apos o job -- o objeto aqui e so o resultado bruto.
expiration { days = 30 }
}
}
resource "aws_iam_role" "bedrock_batch" {
name = "${var.projeto}-bedrock-batch"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow", Action = "sts:AssumeRole"
Principal = { Service = "bedrock.amazonaws.com" }
}]
})
}
# Resource especifico dos DOIS lados -- entrada e saida -- e nada alem deles.
data "aws_iam_policy_document" "bedrock_batch" {
statement {
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.lote_ia.arn}/entrada/*"]
}
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.lote_ia.arn}/saida/*"]
}
}
resource "aws_scheduler_schedule" "disparar_lote_produtos" {
name = "${var.projeto}-disparar-lote-produtos"
group_name = "default"
flexible_time_window { mode = "OFF" }
# Fora do horario comercial -- o job nunca compete com a fila interativa,
# e a janela de horas do modo batch cabe folgada de madrugada.
schedule_expression = "cron(0 2 * * ? *)"
target {
arn = aws_lambda_function.criar_job_lote.arn
role_arn = aws_iam_role.scheduler_invoca_lambda.arn
}
}
// CriarJobLote.cs -- dispara o CreateModelInvocationJob. Chamada de API,
// nao recurso do Terraform: nao ha 'destroy' que desfaca um job em andamento,
// e o job termina sozinho -- nao ha estado para o provedor rastrear.
public async Task<string> CriarJobAsync(CancellationToken ct)
{
var request = new CreateModelInvocationJobRequest
{
JobName = $"descricao-produto-{DateTime.UtcNow:yyyyMMdd}",
ModelId = "anthropic.claude-haiku-4-5-...", // tarefa curta, modelo leve tambem no lote
RoleArn = _roleArn,
InputDataConfig = new() { S3InputDataConfig = new() {
S3Uri = $"s3://{_bucket}/entrada/" } },
OutputDataConfig = new() { S3OutputDataConfig = new() {
S3Uri = $"s3://{_bucket}/saida/" } },
};
var resposta = await _bedrock.CreateModelInvocationJobAsync(request, ct);
_logger.LogInformation("Job de lote criado: {JobArn}, ~10 mil itens, janela de horas",
resposta.JobArn);
return resposta.JobArn;
}
Construir: orçamento por consumidor, não por serviço
Um orçamento sobre "Bedrock" agregado teria disparado o alarme só depois da fatura já ter seis vezes o valor normal. Um orçamento por tag detecta o componente errado crescendo sozinho, semanas antes.
# orcamento-por-tag.tf -- alarma por CONSUMIDOR, nao pela fatura consolidada
# de Bedrock -- e a diferenca entre descobrir em semanas e descobrir no fim do mes.
resource "aws_budgets_budget" "bedrock_chat" {
name = "${var.projeto}-bedrock-chat"
budget_type = "COST"
limit_amount = "8000" # R$, baseado no volume medido pos-cache
limit_unit = "USD"
time_unit = "MONTHLY"
cost_filter {
name = "TagKeyValue"
values = ["user:Consumidor$chat"]
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.email_finops]
}
}
# Mesma forma, filtro diferente -- repetido para "ticket" e "lote". Tres
# orcamentos pequenos avisam qual componente cresceu; um orcamento so, grande,
# so avisa QUE algo cresceu, sem dizer o que.
Quebrar de propósito: quatro falhas das três alavancas
As três alavancas — cache, roteador e lote — reduziram a fatura em 67%. Cada uma introduziu uma forma nova de falhar em silêncio, e as quatro abaixo são as que aparecem. Nenhuma derruba o serviço: três delas devolvem a fatura ao valor anterior ou pioram a resposta, sem nenhum erro no log.
| Falha injetada | Como injetar | Sintoma enganoso | Diagnóstico correto |
|---|---|---|---|
| Chave de cache sem a dimensão do lojista | Remover o identificador do lojista da composição da chave e fazer dois lojistas diferentes fazerem a mesma pergunta | A taxa de acerto do cache MELHORA visivelmente, e o custo por resposta cai mais ainda. O painel de eficiência fica ótimo | O segundo lojista recebeu a resposta calculada com o contexto do primeiro. Onde a política é igual para todos, ninguém percebe; onde ela varia por contrato, isto é vazamento entre inquilinos, servido com citação e confiança. Ganho súbito de taxa de acerto sem mudança de conteúdo é sinal de chave larga demais, não de cache eficiente. A dimensão que separa inquilinos é obrigatória na chave, mesmo quando "quase sempre" dá na mesma |
| Tarefa desconhecida chegando ao roteador | Enviar uma requisição com um tipo de tarefa que a tabela de decisão não prevê | Tudo continua funcionando, com qualidade boa. Nada no log de erro | O caso padrão do roteador manda para o modelo mais caro — é a escolha segura em qualidade e a cara em fatura. Um tipo de tarefa novo, introduzido por outro time, faz a economia do roteador evaporar aos poucos, sem nenhum evento. O caso padrão precisa EMITIR MÉTRICA, não só decidir: "requisições que caíram no padrão" é o alarme que protege a alavanca |
| ElastiCache indisponível | Bloquear o grupo de segurança do Redis e observar o comportamento do caminho de resposta | Ou o serviço inteiro para de responder, ou ele responde normalmente e a fatura triplica no dia | Os dois resultados são o mesmo defeito: a decisão de falhar aberto ou fechado nunca foi tomada de propósito. Cache de RESPOSTA deve falhar aberto — indisponibilidade de cache não pode virar indisponibilidade de produto — mas falhar aberto sem um teto de gasto transforma um incidente de infraestrutura num incidente de fatura. As duas peças andam juntas: `fail-open` no código, orçamento por tag com alarme no mesmo dia |
| Job de lote concluído pela metade | Interromper o job de produtos no meio e deixar o pipeline seguir | O job aparece como finalizado, e o catálogo tem descrição nova em boa parte dos itens | "Finalizado" no modo lote significa que o job terminou, não que todo item de entrada virou um item de saída. A conferência é de CONTAGEM: itens no manifesto de entrada contra registros no arquivo de saída. Sem essa comparação, o catálogo fica num estado misto que ninguém consegue nomear — e a próxima execução, se não souber o que já foi feito, paga de novo pelo que já estava pronto |
A primeira falha é a que muda de categoria
Três destas custam dinheiro. A primeira custa confiança: uma resposta correta entregue ao inquilino errado é incidente de privacidade, não de FinOps, e chega ao jurídico antes de chegar ao painel. Toda alavanca de economia que REUSA trabalho entre requisições precisa responder, por escrito, a pergunta "reusar entre quem?".
A Cadência aplicou cache-aside no ElastiCache para as respostas do RAG, mas a taxa de acerto ficou em apenas 12%, bem abaixo do esperado. Investigando, o time percebe que a MESMA pergunta de negócio é digitada de formas diferentes por usuários diferentes ('qual o prazo de reembolso', 'em quantos dias sai o reembolso', 'reembolso demora quanto tempo'). Qual é a explicação mais provável para a taxa de acerto baixa?
Segurança: quem pode invocar o modelo caro
Sem `Resource` restrito ao ARN do modelo, qualquer papel de execução com `bedrock:InvokeModel` pode chamar o modelo mais caro para qualquer tarefa — inclusive um bug de deploy que reverta o roteador sem ninguém perceber.
# iam-bedrock.tf -- Resource especifico do ARN do modelo: o papel que
# classifica ticket NAO tem permissao de invocar o modelo potente.
data "aws_iam_policy_document" "invocar_modelo_leve" {
statement {
effect = "Allow"
actions = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
resources = ["arn:aws:bedrock:${var.regiao}::foundation-model/amazon.nova-micro-v1:0"]
}
}
data "aws_iam_policy_document" "invocar_modelo_potente" {
statement {
effect = "Allow"
actions = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
resources = ["arn:aws:bedrock:${var.regiao}::foundation-model/anthropic.claude-sonnet-4-5-*"]
}
}
# O papel do classificador de ticket usa SO a primeira policy. Ele nao
# consegue chamar o modelo potente mesmo que o codigo da aplicacao tente --
# o roteador errar vira erro de permissao, nao fatura inesperada.
`bedrock:InvokeModel` sem `Resource` específico é o roteador virando sugestão
Com `"Resource": "*"`, o roteador de modelo é só uma DECISÃO DE CÓDIGO — nada impede um bug, um rollback malfeito ou uma dependência atualizada de reverter silenciosamente para o modelo mais caro em toda tarefa, inclusive classificação de ticket. Restringir o `Resource` ao ARN do modelo por papel de execução transforma a decisão de roteamento em um limite técnico: o papel do classificador de ticket simplesmente não tem permissão de invocar o modelo potente, mesmo que o código tente.
Observabilidade: as perguntas que o painel tem de responder
| Pergunta que o painel responde | Métrica | Alarme / limiar inicial |
|---|---|---|
| Quanto o cache está evitando de chamada? | taxa de acerto do ElastiCache, por tipo de conteúdo | alarma se cair abaixo de 30% por 2 horas — sinal de TTL curto demais ou chave mal desenhada |
| O roteamento está indo para o modelo certo? | % de chamadas por modelo, por tag de tarefa | alarma se `ClassificarTicket` passar a usar o modelo potente acima de 5% das chamadas |
| O lote terminou na janela esperada? | duração do job de `CreateModelInvocationJob` | alarma se o job não completar em 6 horas |
| A qualidade se manteve depois da mudança? | taxa de resposta citável do RAG; acurácia de classificação contra golden set | revisão semanal, comparando com a linha de base medida antes das três alavancas |
| Algum componente está crescendo sozinho? | custo por tag de consumidor (Budgets) | alarma acima de 80% do orçamento mensal daquele componente |
Taxa de acerto do cache e taxa de HIT do cachePoint são sinais diferentes
A primeira mede quantas chamadas ao Bedrock foram evitadas por completo (ElastiCache). A segunda mede, DENTRO das chamadas que ainda aconteceram, quanto do token de entrada foi cobrado com desconto por reaproveitar o prefixo (cachePoint nativo do provedor). Confundir as duas leva a superestimar o efeito de uma alavanca lendo o número da outra.
Escala: 10, 10 mil, 1 milhão, e o pico do lançamento
| Ordem de grandeza | O que muda | O que quebra primeiro sem ajuste |
|---|---|---|
| 10 perguntas/dia | cache mal aquecido; quase toda chamada é MISS | nada quebra — é o regime em que o cache ainda não paga a complexidade que adiciona |
| 10 mil perguntas/dia | cache aquecido, taxa de acerto próxima da medida (~52%) | TTL mal calibrado por tipo de conteúdo começa a custar caro em volume |
| 1 milhão de perguntas/dia | um nó de Redis não sustenta o throughput de comandos | precisa de `cluster mode enabled` com sharding — o mesmo limite do L13, agora batendo mais cedo por volume de IA |
| Pico de lançamento de produto | job de lote precisa rodar fora da janela normal, com mais itens | sem margem na janela do agendador, o job de madrugada pode não terminar antes do horário comercial |
| Falha de AZ | réplica de cache é promovida automaticamente | sem Multi-AZ, a queda do nó de cache manda 100% do tráfego para chamada direta ao Bedrock, no pior momento |
Custo: a fatura antes e depois, com número
É o mesmo tipo de disciplina do L59 e do L80 aplicado a GenAI: medir antes de mudar, medir depois para provar, com o mesmo consumidor observado nas duas pontas. A diferença aqui é que o volume de uso CRESCEU entre as duas medições — a redução não veio de usar menos, veio de pagar menos por cada uso.
| Métrica | Antes (mês do pico) | Depois (3 semanas com as 3 alavancas) |
|---|---|---|
| Fatura mensal de Bedrock | R$ 41.800 | R$ 15.100 (queda de 64%) |
| Volume total de chamadas/mês | 190.000 (180 mil interativas + 10 mil em lote) | 210.000 (uso real cresceu, não caiu) |
| Custo médio por resposta | R$ 0,2200 | R$ 0,0719 (queda de 67%) |
| Taxa de acerto do cache de resposta | 0% (sem cache) | 52%, medida após 2 semanas de aquecimento |
| % de chamadas de classificação no modelo leve | 0% | 38% do volume interativo, com IAM restringindo o modelo potente para esse papel |
| Itens processados via Bedrock batch | 0 (tudo síncrono) | 10.000/mês, com desconto do modo lote sobre o preço on-demand |
| Taxa de resposta citável do RAG (L83) | 91,4% | 91,1% (dentro da margem de ruído já observada) |
| Acurácia de classificação de ticket (golden set) | 94,2% (modelo caro) | 93,6% (modelo leve — diferença de 0,6 ponto percentual) |
A comparação que justifica o laboratório
R$ 41.800 caindo para R$ 15.100 com MAIS volume, não menos, é a prova de que nenhuma das três alavancas exigiu usar o sistema de menos: elas reduziram o custo de cada resposta, não a quantidade de respostas entregues. A queda de 0,3 ponto percentual na taxa de resposta citável e de 0,6 ponto na acurácia de classificação ficam dentro da margem de ruído já observada — é redução de custo, não degradação disfarçada de economia, a mesma checagem que o L80 já exigia para SageMaker.
Well-Architected nos seis pilares
| Pilar | Situação antes | Risco | Melhoria aplicada | Prioridade |
|---|---|---|---|---|
| Excelência operacional | sem tag por consumidor; ninguém sabia o que pesava na fatura | média | tag obrigatória + painel segmentado por chat/ticket/lote | alta |
| Segurança | `bedrock:InvokeModel` sem `Resource` específico | alta — reversão silenciosa para o modelo caro | IAM por ARN de modelo, por papel de execução | alta |
| Confiabilidade | job de lote síncrono competia com tráfego interativo | média — latência do chat degradava durante o job | modo batch, fora da janela de horário comercial | média |
| Eficiência de performance | mesmo modelo para tarefas de complexidade muito diferente | baixa — funciona, só é ineficiente | roteamento por tarefa, medido contra golden set | média |
| Otimização de custo | fatura seis vezes maior sem seis vezes mais valor | alta — capital desperdiçado todo mês | cache + roteamento + lote + Budgets por tag | alta |
| Sustentabilidade | token de entrada repetido sem necessidade, todo GPU-mês do provedor | baixa, mas real em escala | cache evita reprocessar o mesmo contexto no provedor | baixa |
Evolução em níveis: do modelo único até o acervo inteiro em lote
Cada nível responde a mesma pergunta: o que muda quando o volume ou a ambição cresce, e o que essa mudança troca.
Um modelo, sem cache, chamada síncrona para as três tarefas — o estado da Cadência antes deste laboratório.Cache manual esporádico, sem TTL disciplinado, roteamento decidido caso a caso pelo desenvolvedor.Cache de resposta no ElastiCache + cache de prompt nativo (`cachePoint`) + roteamento por tarefa medido + Bedrock batch + Budgets por tag.Múltiplas equipes usando o mesmo Bedrock reaproveitam o mesmo cluster de cache, com namespace por time.Cota de Bedrock por time, chargeback por centro de custo — o assunto do L98.Não é mais um job de 10 mil produtos por mês — é o catálogo inteiro, classificado e enriquecido continuamente em modo batch, sem ninguém esperando. É o L95.A ordem não é opcional, e o motivo é concreto
Medir a qualidade ANTES de rotear, cachear com TTL disciplinado ANTES de aumentar volume, e só então escalar para lote em todo o acervo — inverter essa ordem repete o erro que abriu este laboratório: decisão de infraestrutura tomada por hábito, sem medição, que só aparece na fatura meses depois.
Onde IA entra nesta arquitetura, e onde ela seria só mais uma fatura
Este é um laboratório sobre o custo de IA, o que torna a pergunta desconfortável: vale usar IA para gerenciar IA? Em dois dos três pontos, não vale — e o motivo é o mesmo em ambos.
| Ideia | Vale a pena? | Por quê |
|---|---|---|
| Um modelo decidindo qual modelo usar, no lugar da tabela de decisão | Não | Acrescenta uma chamada de modelo para economizar uma chamada de modelo, e a chamada acrescentada acontece em TODA requisição enquanto a economizada acontece em algumas. Pior: transforma uma decisão auditável e testável em linha de código numa decisão que ninguém consegue explicar depois. A tarefa já é conhecida no momento em que a requisição chega — quando a informação já está na mão, inferir é retrocesso |
| Cache semântico: reaproveitar resposta de pergunta parafraseada | Sim, e é o próximo passo natural | A alavanca de cache atual só acerta em texto idêntico, e "qual o prazo de garantia?" e "quanto tempo dura a garantia?" pagam duas vezes pela mesma resposta. Comparar o embedding da pergunta nova com o das perguntas já respondidas eleva a taxa de acerto sem tocar no modelo de geração. O custo é um embedding por pergunta — ordens de grandeza mais barato que a geração que ele evita. A armadilha é o limiar de similaridade: alto demais não economiza, baixo demais responde a pergunta errada com convicção, e a única forma de calibrar é contra o golden set do L83 |
| Prever a fatura do mês a partir da série histórica de consumo | Não, mas por outro motivo | Não é que não funcione — é que o AWS Budgets já faz previsão sobre a série de custo, sem você construir nada, e o orçamento por tag deste laboratório já entrega o alarme antes do estouro. Construir um previsor próprio aqui é reimplementar um recurso gerenciado que já está ligado |
O critério que separa as três linhas
IA agrega quando a entrada é ambígua e a saída tolera aproximação — é o caso do cache semântico, onde "parecido o bastante" é exatamente a pergunta. IA não agrega quando a informação já é determinística (o tipo da tarefa) nem quando um serviço gerenciado já resolve (a previsão de orçamento). O erro que esta banda inteira existe para evitar é usar o modelo por hábito, no lugar onde um `switch` de três linhas decide melhor, mais barato e de forma auditável.
Anti-padrões deste laboratório
| Erro | Por que alguém faz isso | Sintoma em produção | Forma correta |
|---|---|---|---|
| Usar sempre o modelo mais caro e potente | é o modelo que aparece primeiro em qualquer exemplo de documentação; escolher por tarefa dá trabalho de medir e comparar | fatura sobe proporcional ao volume, sem relação com a complexidade real de cada tarefa | roteador determinístico por tipo de tarefa, medido contra golden set antes de ir para produção |
| Não configurar cache de prompt nem de resposta | parece "só mais uma flag opcional"; ninguém sente o efeito até a fatura fechar o mês | o mesmo contexto grande é cobrado como token novo em toda chamada parecida | cachePoint no prefixo repetido + cache de resposta no ElastiCache para perguntas idênticas |
| Rodar trabalho em lote de forma síncrona | é a forma mais simples de escrever o loop — chamar o modelo uma vez por item, sem esperar job assíncrono | o job compete pela mesma capacidade da pergunta interativa, e pode degradar latência do chat | Bedrock batch, disparado por agendador, fora da janela de tráfego interativo |
| Cachear sem TTL claro, ou não cachear por medo de resposta velha | "IA tem que ser sempre fresca" vira desculpa para não decidir um TTL | ou taxa de acerto zero (sem cache) ou resposta desatualizada sem ninguém saber por quanto tempo | TTL por tipo de conteúdo, decidido pela mesma pergunta do L13: quanto tempo você tolera estar errado |
| Tratar Budgets como configuração de billing, não de engenharia | parece trabalho de FinOps, não de quem escreve o código, então ninguém prioriza | a fatura sobe seis vezes antes de alguém abrir o Cost Explorer para investigar | Budgets por tag de consumidor, configurado junto com o primeiro deploy da funcionalidade |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Correção |
|---|---|---|---|
| Taxa de acerto do cache abaixo de 20% | perguntas com o mesmo sentido chegam com texto muito diferente entre si | amostrar 50 chaves MISS e comparar o texto original das perguntas | ampliar a normalização (remover acento, sinônimo comum) antes de considerar cache semântico |
| Fatura caiu, mas latência do chat subiu | roteamento mandou tráfego para o modelo leve numa tarefa que precisava do potente | comparar latência por tag de modelo no CloudWatch, não só o agregado | revisar o mapeamento do roteador para aquela tarefa específica |
| Job de lote não termina na janela de 6 horas | volume do mês cresceu além do medido quando a janela foi dimensionada | conferir o tamanho do lote de entrada contra o histórico dos últimos 3 meses | dividir em dois jobs, ou ampliar a janela do agendador |
| Resposta cacheada contradiz uma política que acabou de mudar | TTL do tipo de conteúdo está maior que o tempo real de validade daquela informação | checar o timestamp de gravação da chave contra a data da mudança de política | reduzir o TTL daquele tipo de conteúdo, ou invalidar a chave explicitamente na publicação da mudança |
| Acurácia do modelo leve caiu depois de semanas estável | o padrão dos tickets mudou (categoria nova, texto diferente) e o golden set não reflete mais o tráfego real | reamostrar o golden set com tickets recentes e comparar acurácia de novo | atualizar o golden set periodicamente, não só na primeira medição |
A pergunta que resolve metade destes casos
"Este número está segmentado por tag de consumidor, ou é o agregado de chat, ticket e lote misturados?" — boa parte dos sintomas confusos deste laboratório desaparece assim que a métrica é olhada por componente, porque um problema real num consumidor pequeno se esconde dentro da média de um consumidor grande.
Limpeza: o que o destroy não leva
`terraform destroy` remove o grupo de replicação do ElastiCache, o agendador, os papéis IAM e o orçamento — mas não remove tudo que este laboratório cria.
#!/usr/bin/env bash
# limpeza.sh -- ordem que evita cobranca depois do destroy.
set -euo pipefail
# 1) Confirma que nenhum job de Bedrock batch ainda esta rodando -- destroy
# do papel IAM no meio de um job em andamento nao cancela o job.
aws bedrock list-model-invocation-jobs --status-equals InProgress
# 2) Esvazia o bucket de lote ANTES do destroy -- bucket com objeto
# bloqueia a remocao, e o destroy para no meio, deixando o resto criado.
aws s3 rm "s3://${PROJETO}-lote-descricao-produto" --recursive
terraform destroy
# 3) O grupo de logs do Lambda tem retencao configurada, mas so PARA DE
# RECEBER log novo apos o destroy -- log ja gravado ainda ocupa espaco ate
# a retencao expirar. Conferir manualmente se ficou algo com retencao alta.
aws logs describe-log-groups --log-group-name-prefix "/${PROJETO}"
| Recurso | O destroy remove? | O que fica, e por quê |
|---|---|---|
| Grupo de replicação ElastiCache | sim | nada — mas se `snapshot_retention_limit` estiver configurado, o snapshot final continua existindo e cobrando armazenamento |
| Job de Bedrock batch em andamento | não é afetado pelo destroy do papel | o job continua até terminar sozinho; remover o papel IAM no meio pode falhar o job, não cancelá-lo |
| Objetos no bucket de lote (S3) | não — bucket com objeto bloqueia o destroy | entrada e saída do job ficam gravadas até serem removidas explicitamente ou expirarem pela regra de ciclo de vida |
| Grupo de logs do Lambda | sim, o grupo em si | log já ingerido antes do destroy conta para a retenção configurada, não some junto com o grupo |
O job de lote não tem "cancelar" barato
Diferente de um recurso do Terraform, um `CreateModelInvocationJob` em andamento não tem operação de rollback limpa — ele processa até o fim ou até ser explicitamente parado, e itens já processados são cobrados de qualquer forma. Rodar `terraform destroy` sem checar jobs em andamento primeiro é o mesmo erro que o L59 aponta para Savings Plans: tratar como reversível uma ação que não é.
Resumo: problema, peça e motivo
| Problema | Peça que resolve | Motivo |
|---|---|---|
| RAG reenvia o mesmo contexto grande a cada pergunta parecida | cachePoint nativo do Bedrock, no prefixo do contexto | reduz o custo do token de entrada repetido dentro da janela de reaproveitamento do provedor |
| Pergunta idêntica ou muito parecida chega de novo | ElastiCache Redis, cache-aside com chave normalizada e TTL por tipo de conteúdo | elimina a chamada ao Bedrock inteira, não só reduz o preço dela |
| Toda tarefa usa o modelo mais caro, inclusive as simples | roteador determinístico por tarefa, medido contra golden set | paga o preço certo pela complexidade certa, sem depender de outro modelo para decidir |
| Job de 10 mil itens roda síncrono, competindo com tráfego interativo | Bedrock batch, disparado por agendador fora do horário de pico | desconto do modo lote e zero disputa com chat ou ticket |
| Ninguém sabe qual consumidor pesa mais na fatura | tag obrigatória + Budgets segmentado por chat/ticket/lote | alarma por componente, semanas antes do fechamento do mês |
| Roteador pode ser revertido silenciosamente para o modelo caro | IAM com `Resource` restrito ao ARN do modelo, por papel de execução | transforma a decisão de roteamento em limite técnico, não em confiança de código |
- Cache de prompt nativo (cachePoint) marcado no contexto recuperado, antes da pergunta variável
- ElastiCache Redis Multi-AZ com AUTH via Secrets Manager, chave normalizada, TTL por tipo de conteúdo
- Roteador de modelo por tarefa, medido contra golden set antes de qualquer troca de tráfego real
- Job de descrição de produto migrado para Bedrock batch, disparado fora do horário de pico
- IAM restringindo `bedrock:InvokeModel` por ARN de modelo, por papel de execução
- Budgets e CloudWatch segmentados por tag de consumidor — chat, ticket, lote
Perguntas frequentes
❓ Cache de resposta no ElastiCache substitui o cache de prompt do Bedrock?
❓ Por que a taxa de acerto do cache ficou em 52% e não mais alta?
❓ O roteador de modelo por tarefa não deveria usar IA para decidir o modelo?
❓ Migrar a classificação de ticket para o modo batch não economizaria ainda mais?
❓ Por que o TTL do cache é diferente para cada tipo de conteúdo?
❓ Restringir o IAM por ARN de modelo não deixa mais rígido adicionar um modelo novo?
Fixando
O time da Cadência decide rotear toda tarefa de classificação — incluindo classificar ticket como urgente ou não urgente — para o modelo mais barato disponível, sem medir a acurácia antes de trocar. Depois de duas semanas em produção, o time de suporte reporta que tickets urgentes estão sendo classificados como não urgentes com mais frequência que antes. O que esse cenário demonstra sobre a alavanca 'modelo por tarefa'?
A Cadência considera mover a classificação de tickets — feita em tempo real, com resposta esperada em até 2 segundos — para o Bedrock batch, atraída pelo desconto de custo do modo lote. Por que essa mudança quebraria o requisito do consumidor, mesmo com o desconto sendo real?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L13 (cache-aside com ElastiCache), L59 (medir antes de comprometer) e L80 (onde o custo de ML vaza) concluídos; RAG do L83 e agente do L87 já em produção na conta fictícia |
| Conhecimentos adquiridos | cache de resposta e contexto com TTL por tipo de conteúdo; cache de prompt nativo do Bedrock (cachePoint); roteamento de modelo por tarefa medido contra golden set; migração de job síncrono para Bedrock batch; Budgets e IAM segmentados por consumidor de IA |
| Limitação que fica | o cache de resposta usa chave normalizada, não semântica — perguntas com o mesmo sentido e texto diferente continuam gerando MISS; um cache por embedding é evolução fora do escopo, tratada no L84 |
| Próximo exemplo recomendado | L95 — enriquecimento em lote do acervo inteiro, que aplica a mesma disciplina de medir antes de escalar ao catálogo completo, não só a 10 mil produtos por mês |
| Também haverá uso destes conceitos em | L91 (atendimento por voz, onde latência elimina modelo antes mesmo de custo), L98 (cota e chargeback multi-time sobre o mesmo Bedrock) |
Documentação oficial consultada: Amazon Bedrock pricing — modelo de cobrança por token de entrada e saída, e o desconto do modo batch; Prompt caching for Amazon Bedrock — o mecanismo de `cachePoint`, os modelos suportados e a janela de reaproveitamento do prefixo; CreateModelInvocationJob — parâmetros de entrada e saída do modo batch; documentação de `aws_budgets_budget` e do grupo de replicação do ElastiCache, já detalhada no L13. Valores em reais aparecem por decisão editorial deste módulo, como medição do cenário fictício da Cadência — não são preço publicado pela AWS; confira o AWS Pricing Calculator e o Cost Explorer da sua própria conta antes de projetar redução semelhante.
O que não foi verificado, e você deve conferir na sua conta
A taxa de acerto de 52%, o custo por resposta e os valores em reais são medições do cenário de exemplo desta Cadência fictícia — meça o padrão real de repetição de pergunta da sua aplicação antes de projetar um ganho semelhante. A disponibilidade de `cachePoint` varia por modelo e por região; confirme no console quais modelos da sua conta suportam prompt caching antes de assumir o desconto. Preço do modo batch e limites de tamanho de job também merecem checagem na documentação atual do Bedrock antes de dimensionar um job em produção.
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…