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:
- Decisão da IA: A IA decide escalar durante o processamento da mensagem (via
ShouldEscalatena resposta estruturada) - Falha na IA: Quando a API do LLM falha (timeout, erro, parse), a conversa é auto-escalada silenciosamente — nenhuma mensagem é enviada ao WhatsApp
- Cap de respostas atingido: Quando o limite de respostas IA por conversa é atingido (
MaxAiResponsesPerConversation) - Créditos esgotados: Quando os créditos de IA do tenant acabam (check pré-IA ou consumo pós-IA)
- 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:
ApplyAiDecisionsStepchamaconversation.SwitchToManualMode()Modemuda paraManual,ModeChangedAté registrado- Mensagem de sistema criada (
SystemEventType.EscalatedByAi, actor = nome do agente) - 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:
context.Conversation.SwitchToManualMode()— muda para manualcontext.SkipAiResponse = true+context.AiResponse = null— impede envio ao WhatsAppcontext.Escalated = true— garante notificação SignalR de escalação- 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):
context.Conversation.SwitchToManualMode()— muda para manualcontext.SkipAiResponse = true— pula geração de IAcontext.Escalated = true— garante notificação SignalR- 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
PersistMessagesStepcomCreatedAtsempre após mensagens do usuário/IA (+100ms offset) - Notificada via SignalR para exibição em tempo real
- Inclui
ActorNamequando 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¶
- Operador clica em "Transferir para IA" no header da conversa
- Dialog aparece com duas opções:
- Enviar para IA e responder agora (pré-selecionado se a última mensagem não-sistema é do contato)
- Enviar para IA e aguardar próxima mensagem
- Operador confirma
TransferConversationHandler:- Valida se o agente está ativo (retorna erro 409 se desativado ou inexistente)
- Valida créditos de IA disponíveis (retorna erro se sem créditos)
- Troca modo para AI + cria mensagem de sistema
ModeChangedToAi - Se
TriggerImmediateResponse = true: agenda job TickerQ imediato viaIDeferredAiResponseScheduler.ScheduleImmediateAsync() - TickerQ executa o pipeline
DeferredAiResponse(~1-2s): - Valida conversa → carrega histórico → gera resposta IA → envia WhatsApp → persiste → notifica SignalR
- 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