Pular para conteúdo

Fluxo: Escalação

Visão Geral

O sistema realiza escalação de conversas trocando o modo de atendimento de IA para Manual. Isso pode acontecer de várias formas:

  1. Decisão da IA: A IA decide escalar durante o processamento da mensagem (via ShouldEscalate na resposta estruturada)
  2. Falha na IA: Quando a API do LLM falha (timeout, erro, parse), a conversa é auto-escalada silenciosamente — nenhuma mensagem é enviada ao WhatsApp
  3. Cap de respostas atingido: Quando o limite de respostas IA por conversa é atingido (MaxAiResponsesPerConversation)
  4. Créditos esgotados: Quando os créditos de IA do tenant acabam (check pré-IA ou consumo pós-IA)
  5. Troca automática: Quando um agente é desativado/arquivado ou a configuração de IA de uma instância é desligada

Em todos os casos, a conversa muda Mode de AI para Manual, sinalizando que um operador humano deve assumir. Cada escalação gera uma mensagem de sistema interna visível apenas no portal Ciba.

Escalação pela IA

Quando a IA determina que a conversa precisa ser atendida por um humano:

  1. ApplyAiDecisionsStep chama conversation.SwitchToManualMode()
  2. Mode muda para Manual, ModeChangedAt é registrado
  3. Mensagem de sistema criada (SystemEventType.EscalatedByAi, actor = nome do agente)
  4. Notificação SignalR é enviada ao portal

Escalação por Falha na IA

Quando a API do LLM falha (timeout, erro HTTP, resposta vazia), o InvokeAiServiceStep auto-escala silenciosamente:

  1. context.Conversation.SwitchToManualMode() — muda para manual
  2. context.SkipAiResponse = true + context.AiResponse = null — impede envio ao WhatsApp
  3. context.Escalated = true — garante notificação SignalR de escalação
  4. Mensagem de sistema criada (SystemEventType.AiFailure)

Comportamento anterior: enviava ErrorMessage do agente ao WhatsApp. Comportamento atual: nenhuma mensagem é enviada ao cliente. Escalação silenciosa.

Escalação por Cap de Respostas

Quando o limite de respostas IA por conversa é atingido (CheckAiResponseCapStep):

  1. context.Conversation.SwitchToManualMode() — muda para manual
  2. context.SkipAiResponse = true — pula geração de IA
  3. context.Escalated = true — garante notificação SignalR
  4. Mensagem de sistema criada (SystemEventType.AiCapReached)

Escalação por Créditos Esgotados

Ocorre em dois pontos do pipeline:

Pré-IA (CheckSubscriptionCreditsStep): - Verifica saldo antes de gerar resposta - Se sem créditos: SwitchToManualMode(), SkipAiResponse = true, Escalated = true - Mensagem de sistema: SystemEventType.CreditsExhausted

Pós-IA (ConsumeSubscriptionCreditStep): - Após consumir o último crédito (balance == 0) - ConversationModeSwitcher.SwitchToManualAsync() — bulk-switch de todas as conversas IA do tenant - Conversa tracked é sincronizada manualmente (SwitchToManualMode() + Escalated = true) - Mensagem de sistema: SystemEventType.CreditsExhausted

Mensagens de Sistema

Toda escalação gera uma mensagem de sistema (MessageRole.System) que:

  • É visível apenas no portal Ciba (nunca enviada ao WhatsApp)
  • Nunca incluída no histórico enviado à IA
  • Persistida em PersistMessagesStep com CreatedAt sempre após mensagens do usuário/IA (+100ms offset)
  • Notificada via SignalR para exibição em tempo real
  • Inclui ActorName quando aplicável (nome do agente IA ou do operador)

Escalação Automática por Desativação

Visão Geral

Além da decisão da IA, o sistema também realiza troca automática de modo (AI para Manual) quando um agente é desativado/arquivado ou quando a configuração de IA de uma instância é desligada. Isso garante que nenhuma conversa fique "presa" em modo IA sem um agente ou configuração ativa para respondê-la.

Cenários

Cenário Filtro Handler
Agente desativado (IsActive: true -> false) Conversas do agente UpdateAgentHandler
Agente arquivado (delete/archive) Conversas do agente DeleteAgentHandler
Instância com StartsWithAI desligado Conversas da instância UpdateInstanceHandler

Serviço: IConversationModeSwitcher

A lógica compartilhada está centralizada no serviço IConversationModeSwitcher (ConversationModeSwitcher):

Task<int> SwitchToManualAsync(
    Expression<Func<Conversation, bool>> filter,
    Guid tenantId,
    CancellationToken ct = default);

Comportamento: 1. Busca conversas que atendem ao filtro E estão com Status = Active E Mode = AI 2. Executa ExecuteUpdateAsync para alterar Mode para Manual e registrar ModeChangedAt 3. Envia notificação de escalação via SignalR para cada conversa afetada (NotifyConversationEscalatedAsync) 4. Retorna o total de conversas alteradas

Consistência Transacional

A troca de modo acontece DEPOIS do SaveChangesAsync da operação principal:

1. Handler persiste a alteração no agente/instância (SaveChangesAsync)
2. Somente após sucesso, chama _modeSwitcher.SwitchToManualAsync(...)
3. SwitchToManualAsync usa ExecuteUpdateAsync (operação atômica separada)

Essa ordem garante que: - Se o save da entidade falhar, nenhuma conversa é afetada - A troca de modo é uma operação independente após a persistência principal

Fluxo

Agente desativado / Instância StartsWithAI=false
┌──────────────────┐
│ SaveChangesAsync │  (persiste agente/instância)
└──────┬───────────┘
       │ Sucesso
┌──────────────────┐
│ SwitchToManual   │  (busca conversas AI ativas)
└──────┬───────────┘
┌──────────────────┐
│ ExecuteUpdate    │  (Mode = Manual, ModeChangedAt = now)
└──────┬───────────┘
┌──────────────────┐
│ SignalR Notify   │  (para cada conversa afetada)
└──────────────────┘

Arquivos Relacionados

Arquivo Descrição
Ciba.Api/Services/IConversationModeSwitcher.cs Interface do serviço
Ciba.Api/Services/ConversationModeSwitcher.cs Implementação
Ciba.Api/Features/Agents/Delete/DeleteAgentHandler.cs Troca ao arquivar agente
Ciba.Api/Features/Agents/Update/UpdateAgentHandler.cs Troca ao desativar agente
Ciba.Api/Features/Instances/Update/UpdateInstanceHandler.cs Troca ao desligar StartsWithAI

Ações Manuais com Mensagens de Sistema

Ações do operador no portal também geram mensagens de sistema:

Ação SystemEventType ActorName
Transferir para Manual ModeChangedToManual Nome do operador (JWT)
Transferir para IA ModeChangedToAi Nome do operador (JWT)
Resolver conversa ResolvedByOperator Nome do operador (JWT)

Handlers: TransferConversationHandler, ResolveConversationHandler


Transferir para IA com Resposta Imediata

Ao transferir uma conversa de Manual para IA, o operador pode optar por disparar uma resposta imediata da IA — sem aguardar uma nova mensagem do contato. Isso evita que o contato fique em "limbo" quando já enviou uma mensagem que ficou sem resposta durante o modo Manual.

Fluxo

  1. Operador clica em "Transferir para IA" no header da conversa
  2. Dialog aparece com duas opções:
  3. Enviar para IA e responder agora (pré-selecionado se a última mensagem não-sistema é do contato)
  4. Enviar para IA e aguardar próxima mensagem
  5. Operador confirma
  6. TransferConversationHandler:
  7. Valida se o agente está ativo (retorna erro 409 se desativado ou inexistente)
  8. Valida créditos de IA disponíveis (retorna erro se sem créditos)
  9. Troca modo para AI + cria mensagem de sistema ModeChangedToAi
  10. Se TriggerImmediateResponse = true: agenda job TickerQ imediato via IDeferredAiResponseScheduler.ScheduleImmediateAsync()
  11. TickerQ executa o pipeline DeferredAiResponse (~1-2s):
  12. Valida conversa → carrega histórico → gera resposta IA → envia WhatsApp → persiste → notifica SignalR
  13. Job criado com SkipAvailabilityCheck = true (ignora horário de atendimento, pois o operador solicitou explicitamente)

Arquivos Relacionados

Arquivo Descrição
Ciba.Api/Features/Conversations/Transfer/TransferConversationHandler.cs Valida agente ativo, créditos e agenda resposta imediata
Ciba.Api/Features/Instances/DeferredAiResponse/IDeferredAiResponseScheduler.cs Interface com ScheduleImmediateAsync
Ciba.Api/Features/Instances/DeferredAiResponse/DeferredAiResponseScheduler.cs Cancela job existente e cria novo com SkipAvailabilityCheck
Ciba.Web/Pages/Conversations/TransferToAiDialog.razor Dialog com opções de resposta imediata
Ciba.Web/Pages/Conversations/ConversationChat.razor.cs Método TransferMode com lógica de default inteligente

Pipeline DeferredAiResponse — Mensagens de Sistema

O pipeline DeferredAiResponse (usado para respostas fora do horário e respostas imediatas) gera mensagens de sistema semelhantes ao pipeline de agregação:

Step SystemEventType Quando
CheckDeferredSubscriptionCreditsStep Sem créditos; chama context.Stop() (pipeline para completamente)
CheckDeferredAiResponseCapStep AiCapReached Limite de respostas atingido
InvokeDeferredAiStep AiFailure Falha na API do LLM, auto-escalação
ApplyDeferredDecisionsStep EscalatedByAi IA decide escalar
ApplyDeferredDecisionsStep ResolvedByAi IA decide resolver

Exceção: CheckDeferredSubscriptionCreditsStep — Este step apenas verifica créditos e, se insuficientes, chama context.Stop() para interromper o pipeline por completo. Ele não seta SkipAiResponse, não cria mensagens de sistema e não chama SwitchToManualMode(). O pipeline simplesmente para sem enviar nada ao WhatsApp.

Para os demais steps, context.SkipAiResponse = true é usado (em vez de Stop() ou AddError()) para que o pipeline continue até PersistDeferredMessageStep e NotifyDeferredStep, garantindo que as mensagens de sistema sejam persistidas e notificadas via SignalR.


Observações

  • Conversa escalada continua recebendo mensagens normalmente
  • Operador pode atender a conversa no portal (modo Manual)
  • Troca automática por desativação afeta todas as conversas ativas em modo AI do agente/instância
  • Toda escalação/resolução gera mensagem de sistema visível no portal para auditoria