Pular para conteúdo

Fluxo de mensagem

Do webhook à resposta

O entry point público é webhook_handler.app.lambda_handler, configurado em WebhookHandlerFunction no template.yaml. GET realiza o handshake Meta; POST lê o corpo bruto, valida a assinatura quando aplicável e traduz o envelope por core.integrations.whatsapp.adapter. O resultado interno contém telefone, tipo, conteúdo, instância, horário e ID do provider.

sequenceDiagram
    participant WA as WhatsApp
    participant WH as Webhook
    participant DB as DynamoDB
    participant Q as SQS FIFO
    participant OR as Orchestrator
    participant AI as AgentInvoker / OpenAI
    WA->>WH: POST envelope Meta
    WH->>WH: Validar assinatura e traduzir todas as mensagens
    WH->>DB: Registrar receipts + latest por telefone
    WH->>DB: Classificar texto como agent_routed
    WH->>Q: phone_number + message_id
    WH->>DB: Registrar dispatched_at
    WH-->>WA: 202 queued
    Q->>OR: Wake-up do telefone
    OR->>DB: Ler pending_message_ids em ordem atômica
    OR->>DB: Resolver ACTIVE, sessão e histórico
    OR->>AI: Prompt + histórico da sessão + packages
    AI-->>OR: JSON + mensagem
    OR->>DB: Verificar latest, ownership e persistir ação
    OR->>WA: Enviar texto permitido
    OR->>DB: Marcar receipt completed_at
    OR-->>Q: batchItemFailures vazio

O 202 confirma que a mensagem foi encaminhada, não que a OpenAI respondeu ou o WhatsApp entregou o texto. Callbacks Meta que não geram mensagens inbound não passam por esse fluxo de IA; consulte WhatsApp.

Classificação antes da fila

_handle_payload() aplica esta precedência, após deduplicação:

Ordem Condição Resultado
1 /encerrar-debug reconhecido Invalida turnos pendentes, fecha sessão pelo helper de debug, registra e responde
2 Remetente na whitelist efetiva Comandos de operador ou ajuda; pode emitir link de acesso
3 Sessão HANDOFF_PENDING ou HUMAN Append condicional no inbox real; nenhuma IA
4 Conteúdo não textual enquanto bot controla Mensagem fixa pedindo texto; não chama IA
5 Texto controlado pelo bot mark_agent_routed, SQS FIFO e mark_dispatched

O claim try_claim_sync() impede que dois handlers concorrentes executem a mesma rota síncrona. Se o append do inbox perde uma corrida de estado e não era duplicata, o webhook libera o claim, recarrega o roteamento uma vez e pode devolver 409 conversation_state_changed. Uma falha no envio da confirmação de fila humana pode devolver 503 human_inbox_ack_failed, permitindo recuperação.

Dados ao longo do caminho

Etapa Contrato Identificador preservado
Meta → adapter entry[].changes[].value.messages[] messages[].id
Registro InboundTurn(phone_number, message_id, content, received_at, instance) ID do provider; UUID local se ausente
SQS JSON com phone_number, message_id Corpo não contém o texto completo
Log inbound Message.inbound_text(...) message_id e provider_message_id
IA AgentInvoker.invoke(...) Histórico filtrado por session_id
Ação ParsedLLMResponseActionResult Código s, metadados e atualizações
Entrega ProcessOutcome(reply, state_after, superseded) Log outbound associado ao conjunto inbound

Exemplo sintético da mensagem SQS:

{"phone_number":"<TELEFONE_FICTICIO>","message_id":"wamid.EXEMPLO_001"}

O texto real é recuperado do receipt no DynamoDB. Um evento SQS para a mensagem B pode drenar A e B na ordem do registro atômico, independentemente da ordem em que chamadas de envio SQS chegaram.

Mensagens rápidas e contexto

Se A e B chegam antes de A concluir, A continua registrada no histórico, mas seu resultado pode ser marcado superseded. A execução de B recebe o histórico da mesma sessão, incluindo o que o cliente escreveu em A; não deve repetir uma resposta antiga. O marcador agent_routed_at impede que mídia, comandos ou inbox humano sejam drenados acidentalmente como texto de IA.

O fluxo atual não espera uma janela de agregação. O buffer legado ainda concatena textos com \n quando o consumidor recebe registros de implantação anterior; isso é compatibilidade, não o comportamento do POST atual.

Persistência versus entrega

O caso de uso retorna o texto e grava a resposta; a Lambda envia pelo WhatsAppClient. Se o envio falha, o registro permanece pendente e aparece em batchItemFailures. Na próxima tentativa, _pending_outbound_reply() procura resposta existente e reenvia sem nova chamada da IA. A validade é novamente comparada com a última entrada, sessão e estado. Respostas antigas não são reproduzidas depois de mudança de sessão ou takeover humano.

Não há confirmação transacional entre DynamoDB e Meta: uma queda após envio e antes de marcar conclusão pode exigir diagnóstico de duplicidade. IDs estáveis, logs e checagens reduzem o risco, mas não são garantia de entrega exatamente uma vez.

Diagnóstico e testes

Siga provider_message_id → receipt → session_id → log outbound, evitando expor conteúdo pessoal. Eventos úteis: webhook_received, orchestrator_event, immediate_turns_done, process_immediate_turn_failed, buffer_already_flushed e logs de envio WhatsApp.

tests/unit/lambdas/test_webhook_handler.py cobre assinatura, lote, roteamento, duplicação e atendimento humano. tests/unit/lambdas/test_orchestrator.py cobre ordem FIFO independente do wake-up, supersessão, reenvio sem segunda chamada LLM e takeover. Para mudanças, preserve os casos test_fifo_wakeup_processes_registered_order_even_when_newer_event_arrives_first e test_failed_current_immediate_turn_replays_without_second_llm_call.

Veja também contratos, persistência e atendimento humano.