Lab 91 — Atendimento com voz, prazo de resposta e saída para humano
O problema, e a empresa que o tem
A Cadência é a mesma equipe do L83 e do L89: o RAG de atendimento que cita a política de devolução, garantia e frete está em produção há dois meses, respondendo 91,1% das perguntas do golden set corretamente, com o custo por resposta já reduzido em 67%. O time de atendimento pediu o passo óbvio: levar o mesmo RAG para o canal de voz da central, usando o Amazon Connect que já roteia as ligações e o Transcribe para converter fala em texto. Alguém plugou a mesma função "responder-com-citacao" do L83 atrás do fluxo de contato — mesma base de conhecimento, mesmo modelo potente, mesma chamada RetrieveAndGenerate.
Funcionalmente, funciona: a transcrição sai certa, o RAG recupera o trecho certo, a resposta cita a fonte certa. O piloto rodou duas semanas em duas lojas parceiras, com 800 ligações por dia. E foi nessas duas semanas que apareceu o defeito que nenhum teste de texto detectaria: um cliente ligou perguntando o prazo de garantia de um produto elétrico — a mesma pergunta sobre material elétrico que o L83 usa como exemplo canônico —, e a pausa entre o fim da pergunta e o início da resposta durou 4,1 segundos. O cliente, achando que a ligação tinha caído, repetiu a pergunta duas vezes; o Connect registrou a chamada como abandonada aos 38 segundos, antes de a resposta certa — a mesma que o RAG já cita corretamente por texto — chegar a ser reproduzida.
Em texto, 4 segundos de espera é irrelevante: o atendente lê outra coisa enquanto a sugestão carrega. Em voz, ao telefone, silêncio acima de 1 a 2 segundos já soa como ligação caída — é um padrão de comportamento medido em contact center antes de qualquer modelo de linguagem existir, e não é negociável ajustando o prompt. O problema deste laboratório não é a qualidade da resposta — o RAG do L83 já resolveu isso — é que latência deixou de ser uma característica de qualidade e virou um requisito que elimina modelo antes mesmo de a resposta ser avaliada. O modelo potente que o L83 escolheu por qualidade pode ser exatamente a escolha ERRADA aqui, mesmo respondendo melhor — porque uma resposta melhor que chega tarde demais nunca chega a ser ouvida.
O silêncio que o chat nunca teve
O silêncio de 3 a 4 segundos ao telefone não é desconforto — é abandono de chamada. O padrão de contact center considera qualquer pausa acima de 1 a 2 segundos como sinal de ligação caída, e o cliente reage repetindo a pergunta ou desligando — os dois pioram a métrica que o time está tentando melhorar. Um caso público de contact center com Bedrock e Connect (DoorDash) e outro de IA de voz em produção com 15 mil chamadas/dia (EFS Networks) — ambos citados no catálogo de soluções de IA na AWS como S1 e S2 — chegam à mesma conclusão por caminhos diferentes: modelo pequeno e rápido vence modelo grande quando a latência é o requisito, e escalonamento para humano é caminho de primeira classe, não exceção residual.
O que este laboratório NÃO é
Não é sobre trocar o RAG por outro — a base de conhecimento e a taxa de resposta citável são as mesmas do L83, medidas no mesmo golden set. Não é sobre reduzir custo — isso já foi resolvido no L89, e as duas alavancas (cache e roteamento por tarefa) são reaproveitadas aqui, adaptadas para sessão de ligação em vez de pergunta isolada. Este laboratório é especificamente sobre o requisito que só existe em voz: um prazo de resposta que, se não for cumprido, tem de virar uma transição real e medida para um humano — não um fallback silencioso.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com uma medição na seção de implantação, não com a sensação de que a ligação "ficou mais rápida".
- Explicar por que o modelo escolhido por qualidade no L83 pode ser a escolha ERRADA em voz, mesmo respondendo melhor.
- Configurar transcrição em streaming no Transcribe, processando a fala antes de o cliente terminar de falar.
- Implementar um orçamento de latência de 2,5 s como restrição de design, não como meta aspiracional.
- Adaptar o cache de prompt e de contexto do L89 para uma chave por sessão de ligação, com TTL da duração da chamada.
- Rotear a chamada de voz para um modelo escolhido pela velocidade medida, com a queda de qualidade quantificada contra o golden set do L83.
- Instrumentar um escalonamento para humano com dois gatilhos — prazo estourado e confiança abaixo do limiar — carregando o contexto da ligação, não uma transferência muda.
- Medir p50 e p99 de latência ponta a ponta, e a taxa de escalonamento, como as duas provas centrais deste laboratório.
- Restringir por IAM qual papel de execução pode invocar o modelo rápido do canal de voz, separado do modelo potente do canal de texto.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Latência como requisito não funcional que ELIMINA opção de modelo | AIF-C01, SAP-C02 | o modelo mais capaz do RAG de texto (L83) é descartado para o canal de voz por não caber no orçamento de 2,5 s | em cenário com prazo de resposta em tempo real, a pergunta certa não é "qual modelo responde melhor", é "qual modelo responde melhor DENTRO do prazo" |
| Amazon Connect + Transcribe em streaming | SAP-C02, AIF-C01 | transcrição parcial processada token a token, antes do fim da fala do cliente | streaming reduz a latência PERCEBIDA processando enquanto o áudio ainda chega, não depois |
| Cache de contexto por sessão vs. cache de resposta por chave normalizada | AIF-C01 | Redis com chave por ID de contato da ligação, TTL da duração da chamada — diferente da chave global do L89 | por que a chave de voz não pode ser a mesma chave normalizada de texto: o mesmo trecho de contexto muda de valor dentro de uma ligação e perde valor fora dela |
| Escalonamento humano (human-in-the-loop) como parte do SLA | SAP-C02 | fila do Connect recebendo transferência com contexto anexado quando o sistema não cumpre prazo ou confiança | escalonamento instrumentado com gatilho medido é diferente de "desculpe, não entendi" seguido de desligamento |
| Trade-off custo/qualidade versus latência na escolha de modelo | AIF-C01, MLA-C01 | acurácia do modelo rápido medida contra o modelo potente no mesmo golden set do L83, com a diferença declarada como aceitável | decisão de modelo em produção pesa latência tanto quanto qualidade — a prova cobra os dois lados da balança, não só o preço por token |
Onde isto costuma ser cobrado errado
A questão clássica descreve um contact center com fila alta e resposta inconsistente, e oferece "usar o modelo mais capaz disponível" como a resposta que parece óbvia. Na maioria dos cenários de voz — e no deste laboratório — essa é a resposta ERRADA: o requisito de tempo real elimina a opção antes de a qualidade entrar na comparação. O erro de raciocínio mais comum é tratar "modelo melhor" como sinônimo de "resposta melhor para o usuário" — são coisas diferentes quando o usuário está ao telefone esperando em silêncio.
Requisitos, e como cada um muda o desenho
A coluna da direita é a rastreabilidade: toda peça nova 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 |
|---|---|---|
| Resposta falada cabe em até 2,5 s desde o fim da fala do cliente | 2,5 s, medido do fim do áudio de entrada ao início do áudio de resposta | obriga transcrição em streaming, cache de contexto por ligação e geração em streaming — não há tempo para esperar o texto completo do RAG de página inteira |
| O modelo elegível é o que cumpre o prazo, não o mais capaz | só entram na lista modelos com p99 de geração medido dentro do orçamento restante após transcrição e busca | elimina o modelo potente do RAG de texto (L83) da lista de opções deste canal, mesmo ele respondendo melhor em avaliação isolada |
| Escalonamento para humano não pode ser silencioso | toda transferência carrega o motivo do gatilho e o contexto da ligação | obriga instrumentar o gatilho (prazo estourado OU confiança abaixo do limiar) e anexar transcrição e trechos recuperados à fila — não apenas transferir a chamada muda |
| Cache de contexto não pode vazar entre ligações diferentes | chave por ID de contato do Connect, TTL igual à duração média de uma chamada | obriga chave de cache específica da SESSÃO, ao contrário da chave normalizada global de pergunta usada no atendimento por texto do L89 |
| Nenhum ponto único de falha derruba uma ligação em andamento | Multi-AZ no grupo de cache de contexto | obriga grupo de replicação com promoção automática — uma falha de nó não pode silenciar a resposta no meio de uma chamada |
| Acesso ao modelo restrito por canal | o papel do orquestrador de voz só pode invocar o modelo rápido aprovado para este canal | obriga IAM com `Resource` do ARN do modelo específico — um bug ou rollback não pode reverter silenciosamente para o modelo lento do RAG de texto |
| Taxa de resposta citável do RAG (L83) não pode regredir por causa da voz | medida do L83, dentro de margem declarada de até 5 pontos percentuais | obriga que os trechos recuperados, mesmo mais curtos para caber no prazo, continuem citáveis — não é permitido cortar qualidade sem medir quanto foi cortado |
O requisito mais fácil de confundir com os outros
"Resposta melhor" e "resposta a tempo" parecem o mesmo objetivo e não são: o modelo potente do L83 produz a resposta com maior taxa de acerto medida — e ainda assim é a escolha errada aqui, porque uma resposta melhor que chega no sexto segundo já perdeu o cliente que desligou no quarto. Otimizar só a qualidade da resposta, sem o requisito de tempo, escolheria sistematicamente o modelo errado para este canal.
Arquitetura mínima: o RAG de texto plugado na voz, sem orçamento de latência
Este é o desenho que a Cadência colocou em produção no piloto — a arquitetura do L83, sem nenhuma mudança, atrás de um canal novo. É implantável de verdade e tecnicamente correta: a transcrição sai certa, o RAG recupera o trecho certo, a resposta cita a fonte certa. O defeito não está em nenhuma peça — está na ausência de uma decisão que nunca foi tomada: quanto tempo o cliente pode esperar em silêncio.
- → pergunta falada sobre política, prazo ou pedido
- → áudio da ligação, encaminhado só depois de o cliente parar de falar
- → texto completo da pergunta, pronto
- → busca os mesmos trechos do canal de texto, sem calibrar quantidade
- → trechos recuperados, no mesmo volume do RAG de texto
- → contexto completo mais a pergunta, sem streaming
- → resposta gerada por inteiro, só então enviada adiante
- → texto da resposta pronta para sintetizar em áudio
- → resposta falada, depois de toda a cadeia ter terminado
- Fora da AWS
- Integração de apps
- IA e machine learning
- Compute
É o piloto real da Cadência: Transcribe espera o cliente terminar de falar, o RAG busca com o mesmo k do canal de texto, e o modelo potente gera a resposta inteira antes de qualquer áudio voltar. Percorra os passos: o defeito não é nenhuma chamada errada, é que cada etapa soma tempo sem que ninguém tenha decidido um orçamento — e o cliente ouve silêncio do início ao fim.
- O cliente fala, e o Connect não mede nada ainda. A chamada chega e é encaminhada ao fluxo de atendimento — nenhum temporizador começa a contar neste ponto, porque nenhuma etapa seguinte tem um orçamento declarado.
- O Transcribe espera o cliente terminar de falar por completo. Sem streaming, a transcrição só começa a ser processada depois que o Connect detecta silêncio na fala do cliente — todo o tempo da fala em si já é tempo que a resposta ainda não começou a ser preparada.
- A função reaproveita o RAG de texto, sem adaptar nada. O texto transcrito chega à mesma função do L83, que monta a mesma consulta e não sabe que está atendendo um canal onde cada segundo de espera é ouvido como silêncio.
- O RAG busca o mesmo número de trechos do canal de texto. A base de conhecimento devolve o mesmo volume de contexto que funciona bem quando o atendente está lendo na tela — em voz, esse mesmo volume vira mais tokens para o modelo processar antes de começar a responder.
- O modelo potente gera a resposta inteira antes de devolver qualquer coisa. Sem streaming de geração, nada volta ao Connect até a resposta completa estar pronta — o modelo mais capaz do L83, que responde melhor mas mais devagar, está no caminho mais lento possível desta cadeia.
- A resposta chega, depois de o cliente já ter esperado em silêncio. Cada etapa somou tempo sem orçamento: fala completa, transcrição em lote, busca sem calibrar, geração sem streaming — o total medido no piloto ficou em p50 de 3,9 s e p99 de 5,8 s, muito acima do que um telefone tolera em silêncio.
- Por que alguém constrói assim. Reaproveitar a função inteira do RAG de texto pareceu o caminho mais rápido de lançar o piloto — e nada nela está tecnicamente errado. O defeito é a ausência de uma decisão: ninguém definiu um orçamento de latência antes de escolher modelo, volume de contexto ou modo de transcrição.
#!/usr/bin/env bash
# medir_latencia_atual.sh -- confere o que ninguem tinha medido: o tempo
# ponta a ponta de uma ligacao, nao so a taxa de acerto do RAG.
aws connect get-current-metric-data \
--instance-id "$CONNECT_INSTANCE_ID" \
--filters Channels=VOICE,Queues="$QUEUE_ID" \
--current-metrics Name=CONTACTS_IN_QUEUE,Unit=COUNT
python3 - <<'PY'
# Amostra de 200 ligacoes do piloto, latencia fim-da-fala ate inicio-do-audio.
amostras_ms = [3820, 4130, 3510, 5760, 3990, 4420, 3680, 5340] # trecho da amostra real
amostras_ms.sort()
p50 = amostras_ms[len(amostras_ms)//2]
p99_idx = max(0, int(len(amostras_ms) * 0.99) - 1)
print(f'p50 medido: {p50} ms')
print(f'orcamento declarado: 2500 ms')
# Sem orcamento declarado antes do piloto, nao havia contra o que comparar --
# e e exatamente esse orcamento que a arquitetura de producao introduz.
PY
Cada peça está certa, e o total ainda está errado
Nada aqui está tecnicamente errado — é por isso que passou pela revisão de código sem ninguém apontar o defeito. A transcrição está correta, a busca está correta, a citação está correta. O problema é que nenhuma dessas correções individuais soma um número contra um orçamento, e um sistema sem orçamento de latência declarado sempre vai, cedo ou tarde, estourar o tempo que um humano tolera em silêncio.
Arquitetura para produção: prazo como restrição de design, escalonamento como caminho de primeira classe
A topologia muda, não só o rótulo. Cinco peças novas atacam o mesmo problema em pontos diferentes: transcrição em streaming ganha tempo antes do fim da fala; um cache por sessão de ligação evita reprocessar contexto já visto na mesma chamada; um roteador escolhe o modelo pela restrição de latência medida, não pela nota de qualidade isolada; e um escalonamento instrumentado — não um fallback silencioso — assume quando o sistema não cumpre o prazo ou não tem confiança suficiente.
- → pergunta falada sobre política, prazo ou pedido
- → áudio da ligação, transmitido em streaming desde a primeira sílaba
- → texto parcial e final, entregue token a token
- → identifica a tarefa e inicia o temporizador de 2,5 s
- → consulta contexto já cacheado nesta ligação
- → HIT: contexto pronto, sem nova busca no RAG
- → MISS: busca poucos trechos, curtos e relevantes
- → trechos recuperados, calibrados para caber no orçamento restante
- → grava contexto da resposta para reaproveitar dentro da mesma ligação
- → replicação Multi-AZ dentro do grupo de cache
- → resposta gerada em streaming, token a token
- → AUTH token do Redis, renovado em runtime
- → áudio de resposta sintetizado, dentro do prazo medido
- → resposta falada ao cliente, com latência publicada
- → escalonamento quando o prazo estoura ou a confiança cai abaixo do limiar
- → transferência de chamada com transcrição e trechos recuperados anexados
- → publica p50, p99 e o motivo de cada escalonamento
- Fora da AWS
- Integração de apps
- IA e machine learning
- Compute
- Conceito de arquitetura
- Banco de dados
- Segurança e identidade
- Gestão e governança
Cada peça nova rastreia a um requisito da tabela: o cache por sessão existe porque a ligação repete contexto que a pergunta isolada de texto não repetia; o roteador existe porque o modelo certo para voz não é o modelo certo para texto; e a fila de escalonamento existe porque o prazo estourado não pode terminar em silêncio. Percorra os passos: a decisão central não está em nenhum nó isolado, está na ORDEM em que o temporizador é consultado antes de cada etapa custosa.
- Transcribe entrega texto em streaming, ganhando tempo antes do fim da fala. O cliente ainda está falando quando os primeiros tokens de transcrição já chegam ao orquestrador — é tempo de processamento que a versão mínima simplesmente descartava esperando o silêncio completo.
- O orquestrador dispara o temporizador de 2,5 s no mesmo instante. Assim que a fala termina, o roteador já sabe quanto tempo resta — e cada etapa seguinte é decidida em função desse orçamento restante, não de um tempo ilimitado.
- Primeiro pergunta ao cache DESTA ligação, não ao RAG. Se o cliente já perguntou algo relacionado antes na mesma chamada, o contexto pode já estar em cache por ID de contato — um HIT elimina a busca vetorial inteira, a etapa mais cara do orçamento.
- Um MISS busca poucos trechos curtos, nunca o volume do canal de texto. O k de recuperação é calibrado para caber no orçamento restante — é menos contexto do que o RAG de texto do L83 usa, uma perda deliberada e medida, não um corte por acidente.
- O modelo é escolhido pela velocidade medida, não pela capacidade máxima. O roteador nunca considera o modelo potente do L83 para este canal — a lista de opções já foi filtrada pelo requisito de latência antes de qualquer comparação de qualidade acontecer.
- Dentro do prazo, a resposta volta sintetizada por voz. A geração em streaming permite que o áudio comece a ser reproduzido antes mesmo de o modelo terminar de gerar a frase inteira — o cliente ouve o início da resposta enquanto o fim ainda está sendo processado.
- Fora do prazo ou com confiança baixa, escalona com o contexto anexado. O temporizador ou o limiar de confiança dispara a transferência para a fila humana — a transcrição e os trechos já recuperados vão junto, então o atendente continua a conversa em vez de recomeçá-la, e o motivo do escalonamento fica registrado no CloudWatch.
Por que sobra margem para decidir, não só para responder
A soma das quatro etapas no melhor caminho (cache HIT, modelo rápido) fica em torno de 950 ms a 1,4 s — bem dentro do orçamento de 2,5 s, com margem suficiente para o próprio temporizador decidir escalonar antes de o cliente perceber qualquer demora. É essa margem, não o modelo em si, que permite ao sistema errar a favor do humano em vez de a favor do silêncio.
Como funciona ponta a ponta: da fala ao áudio de resposta, ou à fila humana
O caminho mais fácil de entender errado é achar que o escalonamento é um "plano B" acionado só quando algo quebra. Não é — o gatilho de escalonamento é avaliado em TODA ligação, no mesmo ponto do fluxo em que a resposta seria enviada, e a decisão entre as duas saídas é parte do caminho normal, não uma exceção.
// Payload que o orquestrador publica no CloudWatch a cada decisão --
// resposta falada OU escalonamento, sempre com o motivo explícito.
{
"evento": "DECISAO_FIM_DE_LIGACAO",
"timestamp": "2026-08-08T15:41:22.118Z",
"contatoId": "c-9f3a7b21",
"transcricaoParcial": "qual o prazo de garantia de um produto elétrico",
"orcamentoMs": 2500,
"latenciaAcumuladaMs": 1180,
"cacheContextoHit": false,
"modeloEscolhido": "amazon.nova-lite-v1:0",
"trechosRecuperados": 2,
"similaridadeMinima": 0.81,
"decisao": "RESPOSTA_FALADA",
"motivoEscalonamento": null,
"_comentario": "decisao so vira ESCALONAMENTO quando latenciaAcumuladaMs"
" ultrapassa o orcamento OU similaridadeMinima fica abaixo"
" de 0.70 -- os dois gatilhos sao avaliados sempre, nao so"
" quando algo falha."
}
Prazo e confiança são gatilhos independentes, não um substituindo o outro
Os dois gatilhos de escalonamento — prazo e confiança — são avaliados INDEPENDENTEMENTE, e qualquer um dos dois basta para escalonar. Uma resposta rápida mas com trecho recuperado de baixa similaridade escalona mesmo cumprindo o prazo; uma resposta com trecho excelente mas que ultrapassa o orçamento escalona mesmo sendo, em tese, uma resposta boa. Nenhum dos dois caminhos é uma falha do sistema — os dois são o sistema funcionando como projetado.
As decisões, e o que se perde em cada uma
📋 Cadência precisa levar o RAG de atendimento (L83) para o canal de voz, com resposta em até 2,5 s desde o fim da fala do cliente, e um cliente que desliga diante de silêncio — sem regredir a taxa de resposta citável do RAG abaixo de uma margem aceitável.
Nenhuma peça exige treinar modelo novo: streaming ganha tempo processando em paralelo com a fala; o roteador troca a pergunta "qual modelo responde melhor" por "qual modelo responde melhor DENTRO do prazo"; o cache por sessão evita reprocessar contexto dentro da mesma ligação — diferente do cache de pergunta normalizada do L89, que não faz sentido numa conversa contínua; e o escalonamento transforma o estouro de prazo de falha silenciosa em transição medida.
Alt: Manter o modelo potente do L83, só otimizando o prompt — prompt mais enxuto reduz tokens de entrada, mas não muda a velocidade de geração do modelo em si — a latência do modelo potente sozinha já se aproxima do orçamento de 2,5 s antes de qualquer outra etapa somar tempo
Alt: Aumentar o prazo de resposta para 5 segundos, para caber o modelo potente — resolve o problema trocando o requisito, não a arquitetura — um cliente ao telefone não tolera 5 segundos de silêncio, e essa mudança repete a mesma armadilha que gerou a fila alta do problema original
Alt: Fallback silencioso: se o modelo demorar, desligar e pedir para ligar de novo — parece simples de implementar, mas é escalonamento invisível para o cliente — ele não sabe que foi desligado de propósito, e a métrica de abandono deixa de distinguir "desistiu" de "foi desligado pelo sistema"
Alt: Usar sempre o modelo mais rápido disponível, mesmo em texto — ganharia velocidade em toda chamada, mas perderia a qualidade que o modelo potente ainda entrega bem no canal de texto, onde não há o mesmo requisito de tempo real — a decisão certa é por CANAL, não substituir um modelo pelo outro globalmente
| Decisão | Escolha | Alternativa considerada | Motivo | O que se perde |
|---|---|---|---|---|
| Modo de transcrição | Transcribe streaming, processando desde a primeira sílaba | transcrição em lote, só depois do silêncio completo | ganha os 200 a 400 ms que a versão em lote descarta esperando o fim da fala inteira | streaming custa por minuto de forma similar ao modo em lote, mas exige o cliente WebSocket em vez de uma chamada síncrona simples |
| Chave de cache de contexto | por ID de contato da ligação, TTL da duração da chamada | chave normalizada global do L89, reaproveitada sem mudança | uma ligação é uma conversa contínua — o mesmo contexto pode ser reutilizado várias vezes dentro da chamada, o que a chave global de pergunta isolada não capturava | o cache não se acumula entre ligações diferentes do mesmo cliente — cada chamada nova começa com cache vazio, por desenho |
| Modelo para o canal de voz | roteamento determinístico por restrição de latência, medido contra o golden set do L83 | deixar o mesmo modelo potente do RAG de texto responder também por voz | elimina da lista de opções qualquer modelo cujo p99 de geração não caiba no orçamento restante, antes de comparar qualidade | o modelo rápido responde com acurácia mensuravelmente menor que o potente no mesmo golden set — a perda é o preço declarado da restrição de tempo |
| Gatilho de escalonamento | dois gatilhos independentes — prazo estourado OU confiança abaixo do limiar | só um gatilho de tempo, ignorando a confiança da resposta recuperada | responder dentro do prazo com um trecho de baixa relevância seria pior que escalonar — o cliente prefere esperar um pouco mais na fila do que ouvir uma resposta errada rápida | mais um parâmetro para calibrar e monitorar — o limiar de similaridade precisa de revisão periódica contra o golden set, como qualquer threshold |
| Volume de contexto recuperado (k) | reduzido e calibrado para caber no orçamento restante após transcrição | manter o mesmo k do canal de texto, sem calibrar | cada trecho a mais soma tokens de entrada e tempo de geração — em voz, esse custo é medido em segundos de silêncio, não só em tokens cobrados | menos contexto por chamada pode não cobrir perguntas mais complexas — essas ficam mais propensas ao escalonamento por confiança baixa, por desenho |
A dívida que este laboratório não paga
Este laboratório não resolve o cache semântico por embedding — a chave por sessão de ligação continua sendo uma chave exata de ID de contato, não um cache que reconheça duas perguntas com o mesmo sentido dentro da mesma chamada. Também não resolve interrupção do cliente no meio da resposta (barge-in) — o Connect suporta o recurso, mas orquestrá-lo junto com o temporizador de escalonamento é uma composição que fica fora do escopo declarado aqui, candidata a um laboratório futuro da série.
Construir: transcrição em streaming e o temporizador de 2,5 s
O fluxo de contato do Connect encaminha a chamada para uma função Lambda que abre um stream de Transcribe e inicia o temporizador no mesmo instante em que a fala do cliente termina — é esse instante, não o início da ligação, que é o marco zero do orçamento de 2,5 s.
# voz-orquestrador.tf -- funcao que abre o stream de Transcribe e
# publica o instante de fim de fala como marco zero do orcamento.
resource "aws_lambda_function" "orquestrador_voz" {
function_name = "${var.projeto}-orquestrador-voz"
runtime = "dotnet8"
handler = "OrquestradorVoz::OrquestradorVoz.Function::HandlerAsync"
role = aws_iam_role.orquestrador_voz.arn
timeout = 8 # folga sobre os 2,5 s do orcamento -- nao e o mesmo numero
memory_size = 512
environment {
variables = {
ORCAMENTO_MS = "2500"
KNOWLEDGE_BASE_ID = aws_bedrockagent_knowledge_base.politica_atendimento.id
REDIS_ENDPOINT = aws_elasticache_replication_group.contexto_ligacao.primary_endpoint_address
}
}
}
resource "aws_connect_contact_flow" "atendimento_voz_rag" {
instance_id = var.connect_instance_id
name = "atendimento-voz-rag"
type = "CONTACT_FLOW"
# O conteudo real e um JSON exportado do designer visual do Connect --
# aqui, o essencial e que o bloco "Invoke AWS Lambda function" chama o
# orquestrador, e o bloco seguinte confere o atributo "escalonar" para
# decidir entre reproduzir audio ou transferir para a fila humana.
content = file("${path.module}/fluxos/atendimento-voz-rag.json")
}
resource "aws_cloudwatch_log_group" "orquestrador_voz" {
name = "/aws/lambda/${aws_lambda_function.orquestrador_voz.function_name}"
retention_in_days = 30
}
// OrquestradorVoz.cs -- abre o stream de Transcribe e marca o instante
// de fim de fala como o marco zero do orcamento de 2,5 s.
public sealed class OrquestradorVoz
{
private const int OrcamentoMs = 2500;
private readonly TranscribeStreamingClient _transcribe;
private readonly Stopwatch _relogio = new();
public async Task<ResultadoLigacao> ProcessarAsync(Stream audioEntrada, CancellationToken ct)
{
var requisicao = new StartStreamTranscriptionRequest
{
LanguageCode = LanguageCode.PtBR,
MediaSampleRateHertz = 8000,
MediaEncoding = MediaEncoding.Pcm,
};
var resposta = await _transcribe.StartStreamTranscriptionAsync(requisicao, ct);
var textoParcial = new StringBuilder();
await foreach (var evento in resposta.TranscriptResultStream.WithCancellation(ct))
{
if (evento is TranscriptEvent te)
{
foreach (var resultado in te.Transcript.Results)
{
// Resultado PARCIAL: ja processavel, mas ainda pode mudar.
// Nao inicia o temporizador -- so o resultado FINAL marca o
// fim de fala, que e o marco zero real do orcamento.
if (!resultado.IsPartial)
{
textoParcial.Append(resultado.Alternatives[0].Transcript);
_relogio.Restart(); // marco zero: 2500 ms comecam a contar aqui
}
}
}
}
return await ContinuarComOrcamentoAsync(textoParcial.ToString(), ct);
}
private Task<ResultadoLigacao> ContinuarComOrcamentoAsync(string texto, CancellationToken ct)
{
// MsRestante e consultado antes de CADA etapa custosa a seguir --
// cache, busca no RAG, geracao -- para decidir se ainda ha tempo
// ou se e hora de escalonar direto, sem tentar mais nada.
int msRestante = OrcamentoMs - (int)_relogio.ElapsedMilliseconds;
return DecidirProximaEtapaAsync(texto, msRestante, ct);
}
private Task<ResultadoLigacao> DecidirProximaEtapaAsync(string texto, int msRestante, CancellationToken ct)
=> throw new NotImplementedException("segue no roteador de modelo");
}
O relógio começa no silêncio do cliente, não na chamada inteira
O marco zero do orçamento é o FIM da fala do cliente, não o início da ligação nem o início da transcrição. Um erro comum é começar a contar do início da chamada — isso penaliza clientes que falam frases longas com o mesmo orçamento de quem faz uma pergunta curta, quando na verdade o que importa é quanto tempo o sistema leva para responder DEPOIS que o cliente parou de falar.
Construir: cache de contexto por sessão de ligação
A infraestrutura reaproveita o grupo de replicação Multi-AZ do L89 — mesmo padrão de AUTH via Secrets Manager, mesma promoção automática de réplica. O que muda é o DOMÍNIO da chave: em vez de uma chave normalizada global de pergunta, a chave aqui é o ID de contato da ligação, e o TTL é a duração esperada de uma chamada, não horas.
// ContextoLigacaoCacheService.cs -- cache-aside por ID de contato do
// Connect, nao pela chave normalizada global de pergunta do L89. Uma
// ligacao e uma conversa continua: o mesmo contexto pode valer para
// mais de uma pergunta DENTRO da mesma chamada.
public sealed class ContextoLigacaoCacheService
{
private readonly IConnectionMultiplexer _redis;
// TTL curto e fixo -- diferente do TTL por tipo de conteudo do L89,
// porque aqui o que importa e a duracao da LIGACAO, nao o quanto o
// CONTEUDO tolera ficar desatualizado.
private static readonly TimeSpan TtlLigacao = TimeSpan.FromMinutes(10);
public ContextoLigacaoCacheService(IConnectionMultiplexer redis) => _redis = redis;
public async Task<string?> ObterContextoAsync(string contatoId, string topico)
{
var chave = $"voz:{contatoId}:{topico}";
var db = _redis.GetDatabase();
var valor = await db.StringGetAsync(chave);
return valor.HasValue ? valor.ToString() : null; // null = MISS, busca no RAG
}
public async Task GravarContextoAsync(string contatoId, string topico, string contextoRecuperado)
{
var chave = $"voz:{contatoId}:{topico}";
var db = _redis.GetDatabase();
await db.StringSetAsync(chave, contextoRecuperado, TtlLigacao);
// TTL renova a cada gravacao -- uma ligacao ativa mantem o contexto
// vivo; uma ligacao encerrada libera a chave em ate 10 minutos,
// sem exigir limpeza explicita ao fim da chamada.
}
}
Por que a chave carrega tópico, não só o ID da ligação
O `topico` na chave existe porque uma ligação pode conter mais de um assunto — o cliente pergunta sobre devolução e, na mesma chamada, sobre frete. Uma chave só por `contatoId` misturaria os dois contextos e devolveria um HIT para um assunto que não é o que está sendo perguntado agora; a mesma armadilha que uma chave grande demais causaria em qualquer cache-aside.
Construir: o roteador que escolhe o modelo pela restrição de latência
Diferente do roteador do L89, que escolhe por TIPO de tarefa, este roteador escolhe por CANAL — e a lista de modelos elegíveis para voz já vem filtrada por latência medida antes de qualquer comparação de qualidade acontecer.
// RoteadorPorLatencia.cs -- filtra por restricao de tempo ANTES de
// considerar qualidade. O modelo potente do L83 nunca entra na lista
// de opcoes deste canal, porque seu p99 medido nao cabe no orcamento.
public sealed class RoteadorPorLatencia
{
// p99 de geracao medido em producao, em ms, por modelo -- atualizado
// pela suite de avaliacao continua, nao decidido uma vez e esquecido.
private static readonly Dictionary<string, int> P99PorModeloMs = new()
{
["amazon.nova-lite-v1:0"] = 780,
["anthropic.claude-haiku-4-5-..."] = 910,
["anthropic.claude-sonnet-4-5-..."] = 1480, // o modelo potente do L83
};
public string EscolherModelo(int msRestanteAposRecuperacao)
{
// So entram na lista os modelos cujo p99 CABE no que sobrou do
// orcamento depois de transcricao e busca -- comparar qualidade
// entre modelos que nem cabem no prazo e tempo perdido.
var elegiveis = P99PorModeloMs
.Where(m => m.Value <= msRestanteAposRecuperacao)
.OrderByDescending(m => m.Value) // entre os elegiveis, o MAIS
.ToList(); // capaz que ainda cabe no prazo
if (elegiveis.Count == 0)
{
// Nenhum modelo cabe no tempo restante -- nao e hora de arriscar
// uma chamada que provavelmente vai estourar; e hora de escalonar.
throw new OrcamentoEsgotadoException(msRestanteAposRecuperacao);
}
return elegiveis.First().Key;
}
}
public sealed class OrcamentoEsgotadoException : Exception
{
public OrcamentoEsgotadoException(int msRestante)
: base($"Nenhum modelo cabe nos {msRestante} ms restantes -- escalonar.") { }
}
Entre os elegíveis, ainda vence o mais capaz — não o mais rápido
O roteador prefere, ENTRE os modelos elegíveis, o mais capaz — não automaticamente o mais rápido. Se sobrar orçamento suficiente depois da recuperação, um modelo com p99 de 910 ms e melhor qualidade é preferível a um de 780 ms com qualidade menor: a restrição de latência elimina OPÇÕES, não escolhe automaticamente a mais rápida entre as que restaram.
Construir: escalonamento instrumentado para humano
O Connect não recebe uma chamada de API para "transferir agora" — o orquestrador grava os atributos de contato (motivo, transcrição, trechos recuperados) e devolve o controle ao fluxo de contato, que decide para onde rotear com base nesses atributos. É o fluxo, não o código da Lambda, que executa a transferência.
# escalonamento.tf -- fila e perfil de roteamento que recebem a
# transferencia quando o orquestrador marca o atributo "escalonar".
resource "aws_connect_queue" "escalonamento_rag_voz" {
instance_id = var.connect_instance_id
name = "escalonamento-rag-voz"
description = "Fila para ligacoes que estouraram o prazo ou a confianca do RAG por voz"
hours_of_operation_id = var.hours_of_operation_id
}
resource "aws_connect_routing_profile" "atendentes_rag_voz" {
instance_id = var.connect_instance_id
name = "atendentes-rag-voz"
default_outbound_queue_id = aws_connect_queue.escalonamento_rag_voz.queue_id
media_concurrencies {
channel = "VOICE"
concurrency = 1
}
queue_configs {
channel = "VOICE"
priority = 1
queue_id = aws_connect_queue.escalonamento_rag_voz.queue_id
}
}
// GatilhoEscalonamento.cs -- avalia os DOIS gatilhos sempre, e grava
// os atributos que o fluxo de contato do Connect le para decidir rota.
public sealed class GatilhoEscalonamento
{
private const double LimiarSimilaridade = 0.70;
private readonly AmazonConnectClient _connect;
public async Task<DecisaoLigacao> AvaliarAsync(
string contatoId, int msRestante, double similaridadeTrecho,
string transcricao, string trechosRecuperados)
{
// Os dois gatilhos sao avaliados SEMPRE -- nenhum e "reserva" do
// outro. Qualquer um dos dois, sozinho, decide escalonar.
var estourouPrazo = msRestante <= 0;
var confiancaBaixa = similaridadeTrecho < LimiarSimilaridade;
if (!estourouPrazo && !confiancaBaixa)
return DecisaoLigacao.RespostaFalada;
var motivo = estourouPrazo ? "prazo_estourado" : "confianca_baixa";
// Contexto ANEXADO -- transcricao e trechos ja recuperados vao para
// os atributos do contato, para o atendente humano continuar a
// conversa em vez de pedir para o cliente repetir tudo.
await _connect.UpdateContactAttributesAsync(new UpdateContactAttributesRequest
{
InstanceId = _instanceId,
InitialContactId = contatoId,
Attributes = new Dictionary<string, string>
{
["escalonar"] = "true",
["motivoEscalonamento"] = motivo,
["transcricaoParcial"] = transcricao,
["contextoRecuperado"] = trechosRecuperados,
},
});
return DecisaoLigacao.Escalonar;
// O FLUXO de contato, nao esta funcao, e quem faz a transferencia --
// um bloco "Check contact attributes" no Connect le "escalonar" e
// roteia para a fila configurada em escalonamento.tf.
}
}
Transferir sem contexto é o mesmo silêncio, só que mais tarde
Escalonamento sem contexto anexado é fallback silencioso disfarçado de solução — a chamada é transferida, mas o atendente humano começa do zero, pedindo ao cliente para repetir uma pergunta que o sistema já tinha entendido. O ganho real do escalonamento instrumentado não é evitar o silêncio, é evitar que o cliente pague DUAS VEZES o custo de explicar o problema.
Quebrar de propósito: quatro falhas que só existem em voz
As quatro injeções abaixo não têm equivalente no canal de texto — e é exatamente por isso que estão aqui. O RAG do L83 passou dois meses em produção sem encontrar nenhuma delas, porque em texto o relógio não corre contra você e ninguém fala por cima de ninguém.
| Falha injetada | Como injetar | Sintoma enganoso | Diagnóstico correto |
|---|---|---|---|
| Pergunta complexa roteada para o modelo maior | Fazer uma pergunta que a tabela de decisão classifica como complexa, e medir o tempo até o primeiro áudio de resposta | A p95 do painel continua dentro do prazo. Só algumas ligações estouram, e elas se perdem na média | A média esconde o caso que importa. O orçamento de 2,5 s não é uma meta estatística: é o ponto em que o cliente conclui que a ligação caiu, e ele conclui isso na ligação DELE, não na p95. Em voz, a métrica que governa é a cauda — p99 e o percentual acima do prazo — e o roteador precisa de um teto de latência que force o modelo menor quando o maior não cabe no tempo, mesmo que a resposta fique pior |
| Silêncio longo com a sessão de transcrição aberta | Ficar 90 segundos sem falar depois de conectar, e comparar o custo daquela ligação com o de uma de mesma duração falada | O custo por ligação sobe sem que o volume de ligações ou de tokens tenha mudado. A investigação natural vai para o Bedrock, que é o item caro do desenho | Transcrição em streaming cobra pelo áudio transmitido enquanto a sessão está aberta, e silêncio é áudio. O cliente pensando, procurando a nota fiscal ou em espera continua gerando segundos faturáveis, e o Bedrock não tem nada com isso. Fechar a sessão em silêncio prolongado e reabrir ao detectar voz é a correção, e ela só aparece se alguém separar o custo por DURAÇÃO do custo por token no painel |
| Cliente falando por cima da resposta sintetizada | Começar a falar enquanto o sistema está reproduzindo a resposta, com o microfone aberto | A transcrição vira uma mistura confusa e o sistema responde a uma pergunta que ninguém fez. Parece falha de reconhecimento de fala | O reconhecimento está certo — ele transcreveu fielmente o que ouviu, que incluía a própria voz sintetizada do sistema. Sem interrupção tratada e sem supressão do áudio de saída na captura, o atendimento conversa consigo mesmo. É a falha mais característica de voz e a que não tem análogo nenhum em texto: o canal é full-duplex e o desenho de texto assume turnos |
| Escalonamento acionado sem humano disponível | Zerar a disponibilidade da fila de atendentes e forçar o caminho de escalonamento | A ligação fica em espera indefinida, com música. O painel de IA registra "escalonado com sucesso" e a taxa de sucesso do robô parece ótima | O escalonamento é uma transferência de responsabilidade, e o desenho tratou como se fosse um desfecho. Do ponto de vista do cliente, esperar para sempre é pior do que uma resposta imperfeita imediata. Precisa existir desfecho declarado quando não há humano: retorno de ligação agendado, registro de chamado com protocolo, ou a resposta do modelo com aviso explícito de limitação — e o painel precisa contar escalonamentos ATENDIDOS, não escalonamentos disparados |
A quarta falha é a que o painel esconde por construção
Métrica de sucesso definida no ponto errado do fluxo transforma um problema em um número bom. "Escalonou" só é sucesso se alguém do outro lado atendeu; "respondeu" só é sucesso se o cliente ainda estava na linha. Ao medir voz, ancore toda métrica no desfecho para o CLIENTE, não no evento que o seu componente emitiu.
Segurança: quem pode invocar o modelo rápido do canal de voz
Sem `Resource` restrito ao ARN do modelo, qualquer papel de execução com `bedrock:InvokeModelWithResponseStream` pode chamar o modelo potente do canal de texto a partir do orquestrador de voz — inclusive por um bug de configuração que reverta o roteador sem ninguém perceber, até a latência do canal de voz voltar a estourar o prazo.
# iam-bedrock-voz.tf -- o papel do orquestrador de voz SO pode invocar
# modelos da lista elegivel por latencia -- nunca o modelo potente do L83.
data "aws_iam_policy_document" "invocar_modelos_voz" {
statement {
effect = "Allow"
actions = ["bedrock:InvokeModelWithResponseStream"]
resources = [
"arn:aws:bedrock:${var.regiao}::foundation-model/amazon.nova-lite-v1:0",
"arn:aws:bedrock:${var.regiao}::foundation-model/anthropic.claude-haiku-4-5-*",
]
# O modelo potente (claude-sonnet-4-5) do L83 NAO aparece aqui -- o
# papel do orquestrador de voz simplesmente nao tem permissao de
# invoca-lo, mesmo que um bug no roteador tente.
}
}
resource "aws_iam_role_policy" "orquestrador_voz_bedrock" {
role = aws_iam_role.orquestrador_voz.id
policy = data.aws_iam_policy_document.invocar_modelos_voz.json
}
Sem `Resource` específico, o roteador vira sugestão, não regra
Sem `Resource` restrito ao modelo rápido, o roteador de latência é 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 potente do L83 em toda ligação de voz. Restringir o `Resource` por ARN de modelo, por papel de execução, transforma a decisão de roteamento em limite técnico: o orquestrador de voz simplesmente não tem permissão de invocar o modelo lento, mesmo que o código tente.
A Cadência lançou o piloto de atendimento por voz reaproveitando a função "responder-com-citacao" do L83, com o mesmo modelo potente que já responde 91,4% certo no golden set em texto. Depois de duas semanas, o time mede p99 de latência em 5,8 segundos e taxa de abandono de 34% nas perguntas específicas — mas nenhuma resposta individual estava tecnicamente errada. Qual é a explicação mais precisa para o problema?
Observabilidade: as perguntas que o painel tem de responder
| Pergunta que o painel responde | Métrica | Alarme / limiar inicial |
|---|---|---|
| O prazo de 2,5 s está sendo cumprido? | p50 e p99 de latência ponta a ponta, e por etapa (transcrição, busca, geração, síntese) | alarma se p99 ultrapassar 2,5 s por mais de 5 minutos seguidos |
| Quantas ligações escalonam, e por quê? | taxa de escalonamento, segmentada por motivo (prazo vs. confiança) | alarma se a taxa total passar de 20% — sinal de modelo ou limiar mal calibrado |
| A qualidade do modelo rápido está caindo? | acurácia/citabilidade contra o golden set do L83, por canal | revisão semanal, comparando com a linha de base medida no lançamento do piloto |
| O cache de contexto por ligação está sendo reaproveitado? | taxa de acerto do Redis, por sessão de contato | alarma se cair abaixo de 15% — sinal de que a maioria das ligações não repete tópico |
| O contexto chega ao atendente humano na transferência? | % de escalonamentos com atributos de contato preenchidos | alarma se ficar abaixo de 95% — o atendente está recomeçando a conversa do zero |
Um cache com taxa de acerto baixa pode estar funcionando exatamente como projetado
Taxa de acerto do cache por sessão e taxa de acerto do cache normalizado do L89 medem coisas diferentes, e comparar os dois números diretamente confunde: o cache por sessão tem taxa baixa POR DESENHO, porque a maioria das ligações não repete o mesmo tópico duas vezes — ele existe para as que repetem, não para toda ligação.
Escala: 10, 800, 8 mil ligações por dia, e a falha de AZ
| Ordem de grandeza | O que muda | O que quebra primeiro sem ajuste |
|---|---|---|
| 10 ligações/dia | cache por sessão quase sempre em MISS; latência dominada pela busca no RAG | nada quebra — é o regime de teste, antes de qualquer padrão de tráfego real aparecer |
| 800 ligações/dia (piloto atual) | volume suficiente para medir p50/p99 com confiança estatística | picos de horário comercial concentram ligações simultâneas, testando concorrência do orquestrador |
| 8 mil ligações/dia (todas as lojas) | um nó de Redis pode não sustentar o throughput de comandos por sessão | precisa de `cluster mode enabled` com sharding — o mesmo limite do L13/L89, batendo mais cedo por volume de sessões simultâneas |
| Pico sazonal (Black Friday) | fila de escalonamento humano pode saturar mais rápido que o volume de ligações sugere | sem capacidade extra na fila humana, o escalonamento vira o novo gargalo — o requisito de latência foi resolvido, mas a fila que recebe o excedente também precisa de capacidade |
| Falha de AZ | réplica de cache de contexto é promovida automaticamente | sem Multi-AZ, a queda do nó de cache manda 100% das ligações para busca completa no RAG, no pior momento — cada uma perdendo a margem de tempo que o cache dava |
Prova: latência medida contra o prazo, e o que se perde de qualidade
É o mesmo tipo de disciplina do L59, do L80 e do L89 aplicada a voz: medir antes de mudar, medir depois para provar, com o mesmo golden set nas duas pontas. A diferença aqui é que a prova tem DOIS lados que precisam ser lidos juntos — a latência caiu, mas a qualidade também caiu um pouco, e o requisito declarado é sobre os dois números, não só um.
| Métrica | Antes (arquitetura mínima) | Depois (arquitetura de produção) |
|---|---|---|
| p50 de latência ponta a ponta | 3,9 s | 1,7 s |
| p99 de latência ponta a ponta | 5,8 s | 2,4 s (dentro do orçamento de 2,5 s) |
| Taxa de abandono/repetição na pergunta específica | 34% das ligações sobre política específica | 6% das ligações sobre política específica |
| Taxa de escalonamento para humano | 0% (não existia) | 11,6% das ligações |
| Acurácia/citabilidade no golden set do L83 — modelo potente | 91,4% | 91,4% (mantida para o canal de texto, sem mudança) |
| Acurácia/citabilidade no golden set do L83 — modelo rápido (canal de voz) | não medido | 87,9% (queda de 3,5 pontos percentuais, dentro da margem de até 5 pontos declarada no requisito) |
| Taxa de acerto do cache de contexto por sessão | 0% (sem cache) | 18%, medida após uma semana — a maioria das ligações não repete tópico, por desenho |
Latência resolvida, qualidade medida — não escondida
A comparação que justifica o laboratório: p99 caindo de 5,8 s para 2,4 s com uma perda de qualidade de apenas 3,5 pontos percentuais, dentro da margem de até 5 pontos que o requisito declarou aceitável. Sem essa margem declarada ANTES da medição, a queda de qualidade seria uma surpresa desconfortável em vez de um trade-off assumido — a mesma disciplina do L80 e do L89 de medir os dois lados, não só o que melhorou.
Custo: o preço de uma ligação, e o item que domina a conta
A intuição trazida do canal de texto é que o modelo é o item caro. Em voz ela está errada, e a tabela abaixo mostra por quê: a ligação tem quatro dimensões que correm em paralelo com o relógio, e a mais cara de todas é a que acontece quando o robô desiste.
| Cenário | Volume | Onde o dinheiro vai | O que ninguém nota |
|---|---|---|---|
| Piloto — 2 lojas, 800 ligações por dia | Duas semanas, tráfego previsível em horário comercial | Telefonia e transcrição dominam, porque a duração média ainda é alta enquanto o fluxo não está afinado. Token é a menor fatia | A ligação abandonada custou tudo até o segundo em que o cliente desligou, e entregou zero. No piloto original, a ligação de 38 segundos que virou este laboratório pagou telefonia, transcrição e modelo para não resolver nada |
| Produção — 8 mil ligações por dia | Todas as lojas, com pico de duas horas no fim da tarde | A composição inverte conforme a taxa de escalonamento sobe: acima de um certo ponto, o custo de agente humano ultrapassa sozinho os outros quatro termos somados | Otimizar token nesta escala é otimizar o menor dos cinco termos. Reduzir um ponto percentual de escalonamento vale mais que qualquer economia de prompt — e é medível com o painel que este laboratório já constrói |
| Pico — campanha, com fila de atendentes saturada | Volume dobrado por alguns dias, sem aumentar o quadro de atendentes | O escalonamento deixa de ser uma válvula: não há humano para receber. O custo por ligação cai e a satisfação despenca junto | Custo por ligação caindo durante um pico é sinal ruim, não bom — significa que ligações estão terminando sem desfecho. Custo isolado de qualidade mede a coisa errada, e esta é a linha da tabela que mais engana num relatório mensal |
A conclusão que muda a prioridade de engenharia
Preço por minuto, por segundo de áudio, por caractere sintetizado e por 1.000 tokens muda por região e por modelo. Trate os números abaixo como ordem de grandeza para dimensionar a decisão, e confirme o valor vigente na página de preços de cada serviço antes de levar qualquer um a uma proposta. O número que decide aqui é o custo por ligação RESOLVIDA — não por ligação atendida, nem por token. Ele coloca a taxa de resolução no numerador da mesma fração em que está o custo, e é a única forma de comparar honestamente a IA com a fila humana que ela pretende aliviar.
Well-Architected nos seis pilares
| Pilar | Situação antes | Risco | Melhoria aplicada | Prioridade |
|---|---|---|---|---|
| Excelência operacional | nenhum orçamento de latência declarado; p99 medido só depois do piloto revelar o problema | alta — decisão de arquitetura tomada por hábito, sem métrica prévia | orçamento de 2,5 s declarado como requisito, medido por etapa no CloudWatch | alta |
| Segurança | papel de execução sem restrição de `Resource`, podendo invocar qualquer modelo | alta — reversão silenciosa para o modelo lento do canal de texto | IAM por ARN de modelo, restrito à lista elegível por latência | alta |
| Confiabilidade | sem escalonamento instrumentado; estouro de prazo terminava em silêncio ou desligamento | alta — cliente sem caminho de saída quando o sistema não responde a tempo | escalonamento com dois gatilhos, contexto anexado, fila dedicada | alta |
| Eficiência de performance | transcrição em lote e geração sem streaming, somando tempo sem sobreposição | alta — cada etapa síncrona soma ao total, sem nenhuma rodando em paralelo | streaming ponta a ponta: transcrição, busca com cache, geração | alta |
| Otimização de custo | modelo potente do L83 usado também em voz, sem medir se o custo por chamada compensava | média — não é o vazamento do L89, mas ainda paga por capacidade que o canal não usa por completo | modelo rápido, mais barato por token, escolhido pela mesma restrição que resolve latência | média |
| Sustentabilidade | reprocessamento de contexto repetido dentro da mesma ligação, sem cache | baixa, mas real em escala — cada MISS evitável é geração de token desperdiçada | cache por sessão evita reprocessar contexto já recuperado na mesma chamada | baixa |
Evolução em níveis: do piloto silencioso até a plataforma preditiva
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.
RAG de texto do L83, plugado direto no Connect via Transcribe em lote — o piloto real da Cadência, sem orçamento de latência.Streaming de transcrição adicionado, mas ainda sem cache por sessão nem roteamento por latência — só a etapa mais óbvia foi corrigida.Streaming ponta a ponta + cache por sessão + roteador por restrição de latência + escalonamento instrumentado com dois gatilhos.Texto e voz passam a compartilhar o mesmo namespace de cliente no cache, não apenas o mesmo RAG — uma pergunta feita por chat informa a próxima ligação do mesmo cliente.Cota de minutos de Bedrock por linha de negócio (voz, chat, agente autônomo), chargeback por centro de custo.O histórico de ligações — transcrição, latência por etapa, se escalonou e por quê — vira dado de treino para prever, ainda no início da ligação, a probabilidade de precisar de escalonamento. É o assunto do L96 e do L100.A ordem não é opcional, e o motivo é concreto
Medir latência por etapa ANTES de adicionar cache, calibrar o roteador com o golden set ANTES de rotear tráfego real, e só então considerar compartilhar contexto entre canais — inverter essa ordem repete o erro que abriu este laboratório: uma decisão de arquitetura tomada por hábito, sem medição, que só aparece quando um cliente já desligou.
Onde mais IA entra neste atendimento, e onde ela pioraria
Este laboratório já é feito de IA. A pergunta útil não é se cabe IA — é onde cabe MAIS, e as três linhas abaixo têm respostas diferentes.
| Ideia | Vale a pena? | Por quê |
|---|---|---|
| Detectar frustração na voz e antecipar o escalonamento | Sim, e é o ganho de maior valor desta lista | O sinal existe no áudio e não existe no texto: elevação de tom, repetição, interrupção. Como o custo do minuto humano domina a conta, antecipar o escalonamento de quem certamente vai escalar economiza os segundos de transcrição, modelo e síntese que seriam gastos para chegar ao mesmo lugar — e, mais importante, encurta o pior atendimento do dia. Exige medir contra desfecho real, senão vira escalonamento preventivo generalizado, que é o oposto do objetivo |
| Um modelo decidindo o teto de latência do roteador | Não | O teto de latência é uma restrição de produto, não uma previsão: 2,5 s é o ponto em que o cliente desiste, e esse número vem de medição de abandono, não de inferência. Colocar um modelo para adivinhar um limite que já se conhece acrescenta latência ao caminho crítico para decidir sobre latência |
| Sintetizar a voz com clonagem da voz de um atendente real | Tecnicamente sim, e é onde a decisão deixa de ser de engenharia | Funciona, e abre duas perguntas que este laboratório não resolve: o consentimento de quem cedeu a voz, e se o cliente sabe que está falando com um sistema. A segunda tende a ser exigência regulatória antes de ser escolha de produto. Trate como decisão de conformidade com prazo, não como funcionalidade de sprint |
Anti-padrões deste laboratório
| Erro | Por que alguém faz isso | Sintoma em produção | Forma correta |
|---|---|---|---|
| Reusar o modelo potente do RAG de texto para o canal de voz | parece consistente — mesmo modelo, mesma qualidade, um endpoint a menos para gerenciar — até o silêncio de 3 segundos no telefone quebrar a experiência que nunca quebrou no chat | p99 de latência acima de 5 segundos e taxa de abandono subindo nas perguntas específicas | roteador escolhe o modelo pela restrição de latência do canal, mesmo que isso signifique um modelo "menos capaz" na prova de qualidade isolada |
| Transcrever a ligação inteira antes de processar qualquer coisa | é a forma mais simples de integrar: espera acabar de falar, manda o texto pronto de uma vez | cada segundo de fala vira um segundo de latência somado depois, sem nenhum processamento acontecendo em paralelo | Transcribe streaming processa desde a primeira palavra, ganhando o tempo que a transcrição em lote desperdiça |
| Escalonar para humano sem contexto anexado | é a integração mais rápida — transferir a chamada e pronto — sem construir o payload de contexto | o cliente repete a pergunta inteira para o atendente humano, e o tempo total da ligação dobra | escalonamento carrega a transcrição e os trechos já recuperados para a fila, para o atendente continuar, não recomeçar |
| Medir só a fatura por minuto do Connect, sem medir latência por etapa | a fatura é o número que todo mundo já olha; instrumentar cada etapa da pipeline de voz dá mais trabalho | ninguém sabe se o gargalo é a transcrição, a busca ou a geração quando o prazo estoura | CloudWatch com latência publicada por etapa (transcrição, busca, geração, síntese), não só o tempo total |
| Escalonar só por timeout, ignorando confiança da resposta | medir tempo é fácil; medir confiança do trecho recuperado exige mais uma decisão de limiar | o sistema responde dentro do prazo com uma resposta de baixa relevância, em vez de escalonar por qualidade | dois gatilhos de escalonamento — prazo estourado E similaridade abaixo do limiar — não só um deles |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Correção |
|---|---|---|---|
| p99 acima de 2,5 s mesmo com o modelo rápido | o RAG está devolvendo mais trechos do que o orçamento permite, ou o cache de contexto está com taxa de acerto baixa nesta ligação | olhar o breakdown de latência por etapa no CloudWatch para esta chamada específica | reduzir k de trechos recuperados, ou revisar se a chave de cache da sessão está sendo reaproveitada corretamente |
| Taxa de escalonamento acima do esperado, mas o prazo está sendo cumprido | o limiar de similaridade está mais alto do que deveria, escalonando respostas que seriam aceitáveis | amostrar 30 escalonamentos por confiança baixa e revisar se a resposta gerada estava de fato errada | recalibrar o limiar de similaridade contra o golden set, não só por intuição |
| Cliente ouve silêncio antes mesmo do escalonamento | o temporizador de 2,5 s está contando certo, mas o fluxo do Connect não tem um prompt de espera curto configurado | revisar o contact flow: existe um bloco de prompt curto acionado logo após o início da espera? | adicionar um prompt de espera ("só um momento") disparado após ~1 s sem resposta, para não deixar silêncio puro |
| Modelo rápido responde errado numa pergunta que o modelo potente acertava | a diferença de capacidade entre os dois modelos, medida no golden set, é real para aquele tipo específico de pergunta | comparar a resposta específica nos dois modelos, com o mesmo trecho recuperado | se o padrão se repetir, mover aquele tipo de pergunta para escalonamento direto por baixa confiança, em vez de forçar o modelo rápido a cobrir tudo |
| Contexto da ligação não chega ao atendente humano | os atributos de contato não foram gravados antes da transferência, ou a chamada a UpdateContactAttributes falhou silenciosamente | checar o log do orquestrador para confirmar se a chamada de atualização de atributos retornou sucesso antes de marcar `escalonar=true` | tratar falha na gravação de atributos como motivo de retry, não seguir para a transferência sem confirmar que o contexto foi anexado |
A pergunta que resolve metade destes casos
"Este escalonamento saiu por prazo ou por confiança?" — boa parte dos sintomas confusos deste laboratório desaparece assim que o motivo é olhado separadamente, porque as duas causas pedem correções diferentes: prazo estourado aponta para uma etapa lenta na pipeline; confiança baixa aponta para o acervo do RAG ou para o limiar mal calibrado.
Limpeza: o que o destroy não leva
`terraform destroy` remove a função Lambda, o grupo de replicação do ElastiCache, a fila e o perfil de roteamento do Connect, e o papel IAM — 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 nao ha ligacao ativa presa na fila de escalonamento --
# destruir a fila com contato em andamento nao encerra a ligacao.
aws connect list-queues --instance-id "$CONNECT_INSTANCE_ID" \
--queue-types STANDARD
# 2) O fluxo de contato (contact flow) referencia a fila e o Lambda --
# desassociar o fluxo ANTES de destruir os recursos que ele chama,
# senao chamadas em andamento no meio da migracao falham em silencio.
terraform destroy
# 3) O grupo de logs do orquestrador tem retencao configurada, mas so
# PARA DE RECEBER log novo apos o destroy -- log ja gravado ainda
# ocupa espaco ate a retencao de 30 dias expirar.
aws logs describe-log-groups \
--log-group-name-prefix "/aws/lambda/${PROJETO}-orquestrador-voz"
| 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 |
| Fila e perfil de roteamento do Connect | sim, a definição | ligação em andamento na fila no momento do destroy não é encerrada — confira `list-queues`/`list-contacts` antes de destruir |
| Grupo de logs do orquestrador | sim, o grupo em si | log já ingerido antes do destroy conta para a retenção configurada, não some junto com o grupo |
| Contact flow do Connect | sim | se ele ainda for o fluxo padrão da fila de entrada, remover primeiro quebra ligações novas — desassocie antes de destruir |
Ligação em andamento não é revertida pelo destroy
Uma fila do Connect com ligação ativa não tem "cancelar" barato — diferente de um recurso puramente declarativo do Terraform, uma chamada de voz em andamento continua tocando até o cliente desligar ou o atendente encerrar, mesmo que a definição da fila já tenha sido destruída. Rodar `terraform destroy` sem checar ligações ativas primeiro é o mesmo erro que o L89 aponta para um job de lote em andamento: tratar como reversível uma ação que não é.
Resumo: problema, peça e motivo
| Problema | Peça que resolve | Motivo |
|---|---|---|
| RAG de texto plugado direto na voz, sem orçamento de latência | orçamento de 2,5 s declarado, medido por etapa no CloudWatch | transforma "parece rápido" em um número comparável contra o requisito real do canal |
| Transcrição espera o fim da fala para começar a processar | Transcribe streaming, processando desde a primeira sílaba | ganha os 200 a 400 ms que a transcrição em lote descarta esperando o silêncio completo |
| Modelo escolhido por qualidade em texto, reusado sem medir latência em voz | roteador que filtra modelos elegíveis pelo p99 medido, antes de comparar qualidade | elimina da lista de opções o modelo que não cabe no orçamento, mesmo respondendo melhor isoladamente |
| Contexto do RAG reprocessado a cada pergunta, mesmo dentro da mesma ligação | cache por sessão de ligação, chave por ID de contato e tópico, TTL da duração da chamada | evita nova busca vetorial quando a mesma ligação já recuperou contexto relacionado |
| Estouro de prazo termina em silêncio ou desligamento | escalonamento instrumentado com dois gatilhos — prazo e confiança — e contexto anexado | transforma a falha em transição planejada, medida e sem repetir a pergunta para o atendente humano |
| Reversão silenciosa para o modelo lento por bug ou rollback | IAM com `Resource` restrito ao ARN dos modelos elegíveis por latência | transforma a decisão de roteamento em limite técnico, não em confiança de código |
- Transcribe streaming processando desde a primeira sílaba da fala do cliente
- Orquestrador com temporizador de 2,5 s, marco zero no fim da fala
- Cache de contexto por sessão de ligação, chave por ID de contato e tópico
- Roteador que filtra modelos elegíveis pela restrição de latência, antes de comparar qualidade
- Escalonamento instrumentado com dois gatilhos (prazo e confiança), contexto anexado à fila humana
- IAM restringindo `bedrock:InvokeModelWithResponseStream` à lista de modelos elegíveis do canal de voz
- CloudWatch com latência publicada por etapa e taxa de escalonamento segmentada por motivo
Perguntas frequentes
❓ Por que o modelo potente do RAG de texto (L83) não serve também para voz?
❓ O roteador sempre escolhe o modelo mais rápido disponível?
❓ Escalonar para humano quando o prazo estoura não é admitir que o sistema falhou?
❓ Por que o cache de voz usa o ID da ligação, e não a chave normalizada do L89?
❓ A queda de acurácia do modelo rápido precisa ser corrigida antes de produção?
❓ Por que o marco zero do orçamento é o fim da fala, e não o início da ligação?
Fixando
No roteador de modelo por restrição de latência, o time decide que, entre os modelos cujo p99 cabe no orçamento restante, o roteador deve sempre escolher o de MENOR p99 (o mais rápido), em vez do mais capaz dentro dos elegíveis. Por que essa regra, embora simples, provavelmente desperdiça qualidade sem necessidade?
O time da Cadência propõe simplificar o escalonamento para usar só o gatilho de prazo (2,5 s), removendo o gatilho de confiança baseado na similaridade do trecho recuperado, para reduzir a complexidade do orquestrador. Qual cenário concreto mostra por que essa simplificação pioraria a experiência do cliente?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L83 (RAG com citação em produção, o golden set de 40 perguntas) e L89 (cache de prompt, roteamento de modelo por tarefa, medir antes de trocar) concluídos |
| Conhecimentos adquiridos | orçamento de latência como requisito de design; Transcribe em streaming; cache de contexto por sessão de ligação; roteamento de modelo por restrição de tempo, não só por tarefa; escalonamento instrumentado com dois gatilhos e contexto anexado; IAM segmentado por canal |
| Limitação que fica | o cache de contexto por sessão usa chave exata de tópico, não semântica — duas formulações diferentes do mesmo assunto dentro da mesma ligação continuam gerando MISS; interrupção do cliente no meio da resposta (barge-in) fica fora do escopo |
| Próximo exemplo recomendado | L100 — projeto final que integra os 99 laboratórios anteriores, incluindo o padrão de restrição de latência e escalonamento instrumentado deste módulo, num sistema completo com revisão Well-Architected e DR ensaiado |
| Também haverá uso destes conceitos em | L94 (busca de produto com IA, onde recuperação decide qualidade e geração só apresenta) e L96 (agente de operação, onde o mesmo princípio de escopo restrito por ferramenta aparece aplicado a diagnóstico) |
Documentação oficial consultada: Amazon Connect real-time contact analysis — atributos de contato e a integração com Lambda no fluxo de voz; Amazon Transcribe streaming — o protocolo de transcrição em tempo real e o formato de resultado parcial/final; Amazon Bedrock streaming with the Converse API — geração token a token e o efeito sobre o tempo até o primeiro byte de áudio; documentação de `aws_connect_queue` e `aws_connect_routing_profile` do provedor Terraform da AWS. O catálogo de soluções de IA na AWS (`docs/seo/CATALOGO_100_SOLUCOES_AWS_IA.md`) cita dois casos públicos que fundamentam o requisito de latência deste laboratório: um contact center com Bedrock e Connect (S1) e uma operação de IA de voz em produção processando 15 mil chamadas por dia (S2) — os números de latência e de volume da Cadência neste módulo são do cenário fictício, não desses casos públicos.
O que não foi verificado, e você deve conferir na sua conta
Os valores de p50/p99, taxa de escalonamento e taxa de acerto de cache são medições do cenário de exemplo desta Cadência fictícia — meça o padrão real de latência da sua conta antes de projetar um resultado semelhante. A disponibilidade de streaming varia por modelo e por região no Bedrock; confirme no console quais modelos da sua conta suportam `InvokeModelWithResponseStream` antes de assumir o comportamento descrito aqui. Preço de Connect e de Transcribe streaming merecem checagem no AWS Pricing Calculator antes de projetar custo 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…