DEV Community

Juan Felipe Voltolini
Juan Felipe Voltolini

Posted on

Amazon Nova 2 Sonic no Amazon Bedrock: o que funcionou, o que quebrou e o que aprendi nesta POC

TL;DR: Um dia após o lançamento do Nova 2 Sonic no re:Invent 2025, implementei duas arquiteturas: uma versão batch (Lambda + API Gateway + S3) e uma versão streaming (ECS Fargate + WebSocket persistente). Os maiores problemas reais foram rota /ws via CloudFront na stack streaming, credenciais expirando no ECS sem ContainerCredentialsResolver, e turn detection só ficando confiável com fala humana + silêncio enviado em tempo real. A versão final usa CloudFront só para frontend e wss://api.dominio.com/ws direto no ALB.


O que é o Amazon Nova 2 Sonic?

O Amazon Nova 2 Sonic (amazon.nova-2-sonic-v1:0) é o modelo speech-to-speech da AWS, lançado no re:Invent 2025 em dezembro e disponível via Amazon Bedrock. Diferente de pipelines tradicionais que encadeiam STT, LLM e TTS, o Nova 2 Sonic faz tudo em um único modelo: recebe áudio de voz humana e responde com áudio sintetizado, mantendo contexto conversacional.

Nota temporal: Esta POC foi desenvolvida em dezembro de 2025, logo após o lançamento no re:Invent. Algumas limitações e comportamentos descritos aqui podem ter sido atualizados desde então. Consulte a documentação oficial para informações mais recentes.

O diferencial? Turn Detection nativo: o modelo detecta automaticamente quando o usuário parou de falar. Na prática desta POC, ainda usei VAD leve no frontend para decidir quando enviar stopRecording (batch) ou endAudio (streaming), e deixei o modelo fechar o turno no backend.

Por que testei tão rápido?

Trabalho como Software Engineer focado em GenAI na Dati, uma consultoria parceira AWS. Quando a AWS lança um serviço novo, queremos ser os primeiros a entender suas capacidades e limitações reais, não apenas o que diz na documentação.

O modelo foi anunciado, e no dia seguinte eu já estava com as mãos na massa.

Arquitetura v1: Lambda + API Gateway (Batch)

A primeira versão seguiu o caminho mais simples possível:

Browser → API Gateway (WebSocket) → Lambda → S3 (chunks) → Bedrock Nova 2 Sonic
Enter fullscreen mode Exit fullscreen mode

Como funcionava:

  1. Frontend captura áudio via AudioWorklet (16kHz, 16-bit, mono)
  2. Chunks enviados via WebSocket para Lambda
  3. Lambda armazena chunks no S3
  4. Quando o usuário para de falar, Lambda combina os chunks e envia para o Bedrock
  5. Bedrock processa e responde com áudio (24kHz)
  6. Lambda retorna resposta via WebSocket

O SDK Experimental

O Nova 2 Sonic usa streaming bidirecional, que não está disponível no boto3 padrão. É preciso usar o SDK experimental:

from aws_sdk_bedrock_runtime.client import BedrockRuntimeClient
from aws_sdk_bedrock_runtime.models import (
    InvokeModelWithBidirectionalStreamInputChunk,
    BidirectionalInputPayloadPart,
)
Enter fullscreen mode Exit fullscreen mode

A API funciona com eventos JSON tipados que você envia e recebe pelo stream:

# Iniciar sessão com turn detection
await send_event({
    "event": {
        "sessionStart": {
            "inferenceConfiguration": {
                "maxTokens": 1024,
                "topP": 0.9,
                "temperature": 0.7
            },
            "turnDetectionConfiguration": {
                "endpointingSensitivity": "MEDIUM"
            }
        }
    }
})
Enter fullscreen mode Exit fullscreen mode

A sequência de eventos é: sessionStartpromptStartcontentStart (system prompt) → textInputcontentEndcontentStart (audio) → audioInput (chunks) → contentEndpromptEnd.

Resultado da v1

Funcionou, mas com latência de 2-5 segundos. Aceitável para uma POC, mas longe de uma conversa natural.

Arquitetura v2: ECS Fargate + Streaming (Tempo Real)

Para reduzir a latência para ~200-500ms, migrei para ECS Fargate com conexão WebSocket persistente:

Browser → ALB → ECS Fargate (FastAPI) ↔ Bedrock (stream bidirecional persistente)
Enter fullscreen mode Exit fullscreen mode

A diferença fundamental: sem buffering em S3 no backend. O áudio do microfone vai direto pro Bedrock, e a resposta volta direto pro browser. No fluxo streaming, o frontend ainda usa VAD leve para sinalizar fim da fala (endAudio), e o backend envia silêncio em tempo real para garantir que o turn detection funcione de forma consistente.

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()

    client = NovaSonicStreamingClient()
    await client.start_session()

    # Tasks paralelas: browser ↔ Bedrock
    receive_task = asyncio.create_task(handle_browser_audio(websocket, client))
    send_task = asyncio.create_task(handle_bedrock_responses(websocket, client))

    await asyncio.wait([receive_task, send_task], return_when=asyncio.FIRST_COMPLETED)
Enter fullscreen mode Exit fullscreen mode

Infraestrutura completa com CDK: VPC, 2 tasks Fargate (ARM64/Graviton), ALB com idle timeout de 1 hora, auto-scaling 2-10 instâncias.

Estrutura da POC

  • Handler batch: fluxo startRecordingaudioChunkstopRecording com chunks no S3.
  • Server streaming: WebSocket persistente em /ws com tasks paralelas browser/Bedrock.
  • Client streaming: ContainerCredentialsResolver no ECS + send_silence_for_turn_detection(2.0) com pacing de 20ms.
  • Infra CDK: CloudFront apenas para frontend e WebSocket direto no ALB HTTPS (api.dominio.com).

Perrengue 1: rota /ws via CloudFront falhando na prática

Algumas boas horas perdidas aqui.

Sintoma: WebSocket conectava, mas áudio não era processado. Testando direto no ALB, funcionava perfeitamente.

Investigação:

# Via CloudFront → FALHA
curl -H "Connection: Upgrade" -H "Upgrade: websocket" \
  "https://d2rnu2entck3mk.cloudfront.net/ws"
# HTTP/2 404

# Direto no ALB → FUNCIONA
curl -H "Connection: Upgrade" -H "Upgrade: websocket" \
  "http://alb-dns.us-east-1.elb.amazonaws.com/ws"
# HTTP/1.1 101 Switching Protocols ✅
Enter fullscreen mode Exit fullscreen mode

Causa raiz (nesta POC): na stack nova_sonic_streaming_stack.py, a rota /ws passando pelo CloudFront ficou inconsistente (404 no handshake e sem tráfego de áudio estável). O problema desapareceu quando o WebSocket passou a conectar direto no ALB HTTPS da stack de produção.

Solução: Separar o tráfego: CloudFront serve apenas o frontend estático, e o WebSocket conecta diretamente no ALB via HTTPS com certificado ACM.

# CloudFront → S3 (frontend)
# ALB com HTTPS → ECS (WebSocket)
# Domínio: dominio.com (frontend) + api.dominio.com (WebSocket)
Enter fullscreen mode Exit fullscreen mode

Lição: Entenda a camada de rede antes de debugar a aplicação. Neste caso, o gargalo estava no caminho CloudFront → ALB para /ws, não na lógica de áudio.

Perrengue 2: O modelo ignora áudio sintético

Este foi o mais frustrante. Resolvi o WebSocket, tudo conectava perfeitamente, mas o Bedrock simplesmente não respondia.

Evidência:

[Test] Received: usageEvent (22 textTokens)
[Test] Received: usageEvent (157 speechTokens)  ← BEDROCK RECEBEU O ÁUDIO!
[Test] Reader error: ValidationException: Timed out waiting for audio bytes (59 seconds)
Enter fullscreen mode Exit fullscreen mode

O modelo recebia o áudio, contava tokens, mas nunca respondia. Testei com tons sintéticos, silêncio, ondas senoidais. Nada.

Descoberta: no meu fluxo, tons/silêncio sintéticos não acionavam a resposta com confiabilidade. Com microfone real + silêncio em tempo real, a resposta passou a chegar de forma consistente.

Solução: Testar com microfone real. Parece óbvio em retrospecto, mas quando você está debugando infraestrutura, tende a automatizar testes; aqui, áudio sintético não foi confiável para validar turn detection.

O loop infinito que me custou tempo:

  1. Pensei que o problema era CloudFront → Criei stack com HTTPS direto
  2. WebSocket funcionou, mas Bedrock não respondia → Pensei que era auth
  3. Auth OK, mas não respondia → Pensei que era formato do áudio
  4. Formato OK, mas não respondia → Descobri que precisa de fala humana real

Comprei até um domínio ($3/ano) e criei uma stack completa de produção antes de perceber que o problema era fundamentalmente diferente do que eu imaginava.

Lição: Leia a documentação completa antes de debugar infraestrutura. A AWS menciona que o turn detection detecta "non-verbal cues, pauses, hesitations", o que implica que precisa de fala humana, mas não diz explicitamente.

Perrengue 3: Credenciais expirando no ECS

Após ~12 horas rodando, o ECS parava de funcionar. As credenciais do Task Role expiravam e o SDK experimental não renovava automaticamente.

Solução: Usar ContainerCredentialsResolver em vez de EnvironmentCredentialsResolver:

from smithy_aws_core.identity.container import ContainerCredentialsResolver
from smithy_http.aio.aiohttp import AIOHTTPClient, AIOHTTPClientConfig

def _initialize_client(self):
    if is_running_in_ecs():
        # Auto-refresh de credenciais via ECS metadata endpoint
        http_client = AIOHTTPClient(client_config=AIOHTTPClientConfig())
        credentials_resolver = ContainerCredentialsResolver(http_client)
    else:
        # Desenvolvimento local
        credentials_resolver = EnvironmentCredentialsResolver()

    config = Config(
        endpoint_uri=f"https://bedrock-runtime.{self.region}.amazonaws.com",
        region=self.region,
        aws_credentials_identity_resolver=credentials_resolver,
    )
    self.client = BedrockRuntimeClient(config=config)
Enter fullscreen mode Exit fullscreen mode

O ContainerCredentialsResolver busca credenciais do ECS metadata endpoint e renova automaticamente quando estão perto de expirar. Sem ele, você precisa restartar as tasks periodicamente, o que é péssimo para conexões WebSocket de longa duração.

Lição: Sempre use o credentials resolver adequado para o ambiente de execução. O EnvironmentCredentialsResolver é para desenvolvimento local; em ECS, o ContainerCredentialsResolver é obrigatório para produção.

Perrengue #4: Silêncio para Turn Detection

Mesmo com fala humana real, o Nova 2 Sonic às vezes demorava a responder. O turn detection precisa de áudio contínuo, incluindo silêncio, para funcionar. Quando o frontend para de enviar chunks, o modelo simplesmente espera.

Solução: Enviar silêncio explícito após o usuário parar de falar:

async def send_silence_for_turn_detection(self, duration_seconds: float = 2.0):
    chunk_size = 640  # 20ms a 16kHz, 16-bit mono
    chunks_to_send = int(duration_seconds / 0.02)
    silence_chunk = bytes(chunk_size)  # Zeros = silêncio

    for i in range(chunks_to_send):
        await self.send_audio_chunk(silence_chunk)
        await asyncio.sleep(0.02)  # Real-time pacing!
Enter fullscreen mode Exit fullscreen mode

O asyncio.sleep(0.02) é crítico: o modelo espera áudio em tempo real. Se você enviar 2 segundos de silêncio instantaneamente, ele não interpreta corretamente.

O que a v2 ganhou

Depois de resolver todos os problemas, a stack final ficou robusta:

Aspecto Stack v1 (Lambda) Stack v2 (Produção)
Latência 2-5s ~200-500ms
WebSocket API Gateway WebSocket + Lambda (batch) Direto no ALB com HTTPS/WSS (streaming)
Segurança Básica WAF + Security Groups restritivos
Observabilidade Logs básicos Dashboard CloudWatch + Alarmes SNS
Credenciais Ambiente Lambda Auto-refresh via ContainerCredentialsResolver

Vozes usadas/testadas

Contexto Vozes
Batch (backend/) matthew (default da stack), com suporte no handler para camila, ricardo e leo
Streaming (backend-streaming/) tiffany (CDK produção) e camila (default no client)

A sensibilidade do turn detection (endpointingSensitivity) pode ser HIGH, MEDIUM ou LOW. Na stack atual, está MEDIUM em produção via CDK.

Conclusões

O que funciona bem:

  • Qualidade de voz: Respostas naturais com tiffany (stack) e camila (testes)
  • Turn Detection: Quando configurado corretamente, a detecção de fim de fala é impressionante
  • Custo por sessão: Na medição da POC, o custo por sessão foi competitivo
  • Streaming bidirecional: A latência com ECS fica excelente

O que precisa melhorar:

  • Documentação: Falta clareza sobre a necessidade de fala humana real para testes
  • SDK: Ainda é experimental, sem suporte no boto3 padrão
  • Testabilidade: Testes com áudio sintético não ficaram confiáveis nesta POC
  • CloudFront + WebSocket: A rota /ws via CloudFront não ficou estável no cenário implementado

Para quem vale a pena:

  • Assistentes de voz em atendimento ao cliente
  • Interfaces conversacionais em aplicações web
  • Chatbots internos de empresas (nosso caso com a DAI na Dati)
  • Qualquer cenário onde latência de resposta importa

Juan F. Voltolini, Software Engineer (GenAI) @ Dati
Dezembro 2025 (publicado em Março 2026)


Tags: aws, genai, braziliandevs, amazon-bedrock

Top comments (0)