Debounce e anti-overlap¶
Paciente no WhatsApp geralmente envia múltiplas mensagens seguidas:
Se cada mensagem dispara o LLM, o agente responde 4 vezes e bagunça a conversa. Solução: debounce.
Parâmetros¶
| Projeto | MESSAGE_DEBOUNCE_SECONDS |
|---|---|
| MEDCENTER | 2 |
| CLINFETO | 10 |
Clinfeto tem 10s porque pacientes idosas/grávidas digitam devagar. Medcenter 2s porque a conversa é mais objetiva (menu numerado).
Fluxo¶
flowchart TD
M1[Mensagem recebida] --> E[INSERT fila_mensagens<br/>phone + message_id + texto]
E --> S[sleep MESSAGE_DEBOUNCE_SECONDS]
S --> O{anti-overlap:<br/>sou a última mensagem<br/>desse telefone?}
O -->|não| SKIP[skip - outra thread processa]
O -->|sim| F[SELECT + DELETE toda a fila do telefone]
F --> C[Concatena textos com espaço]
C --> AG[run_agent com texto agregado]
Implementação¶
# workflow.py (resumo)
def clinfeto_workflow(payload):
# ... filtros iniciais ...
phone = session["contactPhoneNumber"]
message_id = payload["content"].get("messageId", "")
text = processar_conteudo(payload["content"]) # mídia -> texto
enqueue_message(phone, message_id, text)
time.sleep(MESSAGE_DEBOUNCE_SECONDS)
# Anti-overlap: se outra thread já está processando, a fila estará vazia
pending = fetch_and_clear_queue(phone)
if not pending:
return "skipped: another thread took over"
combined = " ".join(pending)
response = run_agent(
message=combined,
session_id=session_id,
phone_number=phone,
contact_name=session.get("contactName", ""),
)
# envio em partes...
Por que DELETE RETURNING¶
A leitura e deleção precisam ser atômicas. Se duas threads chegarem no mesmo telefone ao mesmo tempo:
A primeira thread pega todas as mensagens e as remove; a segunda thread
faz o SELECT e recebe [] → retorna sem processar.
Anti-overlap detalhado¶
Há outro caso: paciente manda mensagem, agente está respondendo (5s), e paciente manda mais uma mensagem. Sem anti-overlap, poderíamos responder duas vezes.
Com o pattern acima, a segunda mensagem:
- Vai para a fila.
sleep(debounce).- Tenta
fetch_and_clear_queue. - A primeira thread ainda está no LLM — a fila tem só a mensagem 2.
- Processa a 2 isoladamente (ok — se fosse muito próxima, o debounce teria juntado).
Edge cases¶
Mensagem durante o turn do agente¶
Se o paciente manda "nova mensagem" enquanto o agente está processando:
- Nova mensagem entra na fila.
- Após debounce, é processada em novo invoke com o mesmo
thread_id. - LangGraph pega o histórico e o agente continua.
Não há race no LLM — invoke é thread-safe (cada thread tem seu ctxvar).
Mensagem depois da transferência¶
Se o paciente manda após transfer_session:
ALLOWED_TEAMestatusfiltram no topo do workflow.- Sessão agora é da equipe humana; o agente ignora.
Debounce muito alto (> 30s)¶
Paciente pensa que ficou sem resposta e manda mensagens repetidas. Fila cresce. Não há problema técnico, mas UX é ruim. Não passar de ~15s.
Por que não usar fila Redis / Celery¶
Overkill para este volume. Postgres como fila é adequado até ~100 mensagens/minuto por instância. Se precisar escalar, aí sim.