Pular para conteúdo

Processamento de Mídia

Visão Geral

O sistema processa automaticamente mídias recebidas via WhatsApp, extraindo informações textuais para enriquecer o contexto das conversas com IA:

Tipo de Mídia Processamento Resultado
Áudio/Voz Transcrição via OpenAI Whisper Texto transcrito
Imagem Descrição via Claude Vision Descrição textual da imagem
Vídeo Não processado Apenas armazena
Documento Não processado Apenas armazena
Sticker Não processado Apenas armazena

Arquitetura

┌─────────────────────────────────────────────────────────────────────────────┐
│  Recebimento de Mídia                                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  WhatsApp ──▶ Evolution API ──▶ Webhook ──▶ ProcessMediaStep                │
│                                                                             │
│                                    ┌────────────────────────────────────┐   │
│                                    │  1. Baixa mídia via Evolution API  │   │
│                                    │  2. Salva no Cloudflare R2         │   │
│                                    │  3. Processa conforme tipo         │   │
│                                    └─────────────┬──────────────────────┘   │
│                                                  │                          │
│                           ┌──────────────────────┼──────────────────────┐   │
│                           ▼                      ▼                      ▼   │
│                    ┌──────────────┐      ┌──────────────┐      ┌──────────┐│
│                    │    Áudio     │      │    Imagem    │      │  Outros  ││
│                    │              │      │              │      │          ││
│                    │   Whisper    │      │Claude Vision │      │   N/A    ││
│                    │   (OpenAI)   │      │ (Anthropic)  │      │          ││
│                    └──────┬───────┘      └──────┬───────┘      └──────────┘│
│                           │                     │                          │
│                           ▼                     ▼                          │
│                    ┌─────────────────────────────────────────────┐         │
│                    │  ExtractedText                               │         │
│                    │  (Texto extraído salvo na entidade Message) │         │
│                    └─────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Custo de Créditos

O processamento de mídia consome mais créditos do que respostas textuais, refletindo o custo maior das APIs de IA envolvidas:

Operação Créditos Descrição
Texto (Chat) 1 Resposta textual padrão
Imagem (Vision) 3 Análise via Claude Vision
Áudio (Transcription) 3 Transcrição via OpenAI Whisper

Custos configurados em CreditCostConfig (classe estática): Text = 1, Image = 3, Audio = 3.

Veja subscription-enforcement.md para detalhes sobre o fluxo de consumo de créditos.


Flags de Análise de Mídia por Instância

Cada instância WhatsApp possui flags que controlam se mídias são processadas:

Flag Default Descrição
AllowImageAnalysis true Habilita descrição de imagem via Claude Vision
AllowAudioAnalysis true Habilita transcrição de áudio via Whisper

Comportamento quando desabilitado: - A instância envia uma resposta padrão (canned response) informando que não processa aquele tipo de mídia - Nenhum crédito é consumido - A mídia é salva normalmente no R2 (apenas a extração de texto é ignorada) - Útil para instâncias que não precisam processar mídia, reduzindo custos

Configurável via endpoints de atualização da instância. Veja instances.md.


Transcrição de Áudio

Configuracao

A configuracao de transcricao e definida via classe estatica com campos const (hardcoded em tempo de compilacao), nao via appsettings.json:

TranscriptionConfig (Ciba.Infrastructure/Configuration/TranscriptionConfig.cs):

public static class TranscriptionConfig
{
    public const string SectionName = "Transcription";
    public const bool Enabled = true;
    public const string Provider = "openai";
    public const string Model = "whisper-1";
    public const string DefaultLanguage = "pt";
    public const int TimeoutSeconds = 60;
}
Campo Descricao Valor
Enabled Habilita/desabilita transcricao true
Provider Provider de transcricao openai
Model Modelo do Whisper whisper-1
DefaultLanguage Idioma padrao (ISO 639-1) pt
TimeoutSeconds Timeout para audios longos 60

Formatos Suportados

  • audio/ogg (WhatsApp voice messages)
  • audio/mpeg (MP3)
  • audio/mp4 (M4A)
  • audio/wav
  • audio/webm

Retry Policy

  • 2 retries com backoff exponencial (4s, 8s)
  • Retry em erros transientes e rate limit (429)

Fluxo

  1. Áudio recebido via webhook
  2. Mídia baixada do Evolution API (base64)
  3. Convertido para bytes e salvo no R2
  4. Enviado ao Whisper API para transcrição
  5. Texto transcrito salvo em Message.ExtractedText
  6. Claude recebe o texto transcrito no contexto

Descrição de Imagem

Configuração

A descrição de imagem usa o LLM configurado (Claude Vision). Não requer configuração adicional.

Formatos Suportados

  • image/jpeg
  • image/png
  • image/gif
  • image/webp

Fluxo

  1. Imagem recebida via webhook
  2. Mídia baixada do Evolution API (base64)
  3. Salva no R2
  4. Enviada ao Claude Vision com prompt de descrição
  5. Descrição textual salva em Message.ExtractedText
  6. Claude recebe a descrição no contexto

Prompt de Descrição

Sem caption:

"Descreva esta imagem de forma concisa em português (máximo 2-3 frases). Foque no conteúdo principal."

Com caption:

"O usuário enviou esta imagem com a legenda: \"{caption}\". Descreva o conteúdo da imagem de forma concisa em português (máximo 2-3 frases)."


Limites de Extração de Mídia

Extração por Batch

O sistema limita a quantidade de mídias que passam por extração de texto (OCR/transcrição) em cada batch de agregação para controlar custos de IA:

Configuração Valor Padrão Descrição
MaxMediaExtractionsPerBatch 3 Padrão de mídias com extração por batch (configurável por instância via MaxMediaPerMessage, 1-10)
MaxAudioBytesForTranscription 2 MB (2.097.152 bytes) Tamanho máximo de áudio para transcrição Whisper

Comportamento: - As mídias mais recentes do batch recebem extração (OCR ou transcrição), limitadas por WhatsAppInstance.MaxMediaPerMessage (padrão 3, máximo 10) - Mídias além do limite são salvas normalmente no R2, mas sem extração - Áudios acima de 2 MB são salvos no R2 mas não transcritos (~2 MB de OGG/Opus corresponde a ~5 min de áudio) - Ambos os limites sao definidos como constantes na classe estatica AppConfig

Configuracao

AppConfig (Ciba.Infrastructure/Configuration/AppConfig.cs) e uma classe estatica com campos const (hardcoded em tempo de compilacao), nao configuravel via appsettings.json:

public static class AppConfig
{
    // ... outros campos ...
    public const int MaxMediaExtractionsPerBatch = 3; // fallback quando instância não tem MaxMediaPerMessage
    public const int MaxAudioBytesForTranscription = 2 * 1024 * 1024; // ~2 MB
}

Implementação

A lógica de limite fica no ProcessMediaStep da pipeline de agregação:

  1. Coleta todas as mensagens com mídia do batch
  2. Seleciona as N mais recentes (por ordem reversa) para extração
  3. Para cada mensagem, chama MediaProcessingService.ProcessAsync com o flag skipExtraction
  4. Mensagens fora do limite recebem skipExtraction = true (salva no R2 sem OCR/transcrição)

Processamento de Mídia no Playground

Visão Geral

O Playground do agente permite testar processamento de mídia (imagens e áudio) diretamente pelo portal administrativo, sem necessidade de enviar mensagens via WhatsApp.

Endpoint: describe-image

POST /api/agents/{agentId}/playground/describe-image

Realiza OCR/descrição de imagem via Claude Vision, retornando o texto extraído para uso no chat do playground.

Handler: PlaygroundDescribeImageHandler

Fluxo: 1. Frontend envia imagem em base64 + MIME type + caption opcional 2. Handler executa em paralelo: - Descrição da imagem via IImageDescriptionService (Claude Vision) - Upload da imagem no Cloudflare R2 via PlaygroundMediaHelper 3. Retorna a descrição textual e a URL da mídia no R2 4. Frontend usa o texto extraído como contexto antes de enviar ao LLM

PlaygroundMediaHelper

Helper compartilhado (PlaygroundMediaHelper) centraliza o upload de mídia do playground para o R2:

  • Usado por PlaygroundDescribeImageHandler e PlaygroundTranscribeHandler
  • Upload é fire-and-forget tolerante: se falhar, retorna null e o processamento continua
  • Armazena mídias no path de playground do tenant/agente via IFileStorageService.SavePlaygroundMediaAsync

Fluxo no Frontend

┌─────────────────────────────────────────────────────┐
│  Playground (AgentPlaygroundTab)                     │
├─────────────────────────────────────────────────────┤
│                                                      │
│  Usuário anexa imagem                                │
│       │                                              │
│       ▼                                              │
│  POST /describe-image (base64 + mimeType)           │
│       │                                              │
│       ├──▶ Claude Vision (OCR/descrição)            │
│       └──▶ R2 Upload (paralelo)                     │
│       │                                              │
│       ▼                                              │
│  Texto extraído retornado ao frontend                │
│       │                                              │
│       ▼                                              │
│  Frontend inclui texto no contexto do chat           │
│  POST /chat (mensagens com texto extraído)          │
│                                                      │
└─────────────────────────────────────────────────────┘

Modelo de Dados

Message

Campo Tipo Descrição
Content string? Texto/caption original do usuário
ExtractedText string? Texto extraído (transcrição ou descrição)
MediaUrl string? URL da mídia no R2
MediaMimeType string? MIME type do arquivo
MediaFileName string? Nome do arquivo
Type MessageType Text, Image, Audio, etc.

Comportamento por Tipo

Tipo Content ExtractedText
Texto Texto do usuário null
Imagem sem caption "" ou null Descrição da imagem
Imagem com caption Caption do usuário Descrição da imagem
Áudio "" ou null Texto transcrito
Vídeo Caption (se houver) null
Documento Caption (se houver) null

Contexto para o LLM

O metodo ExtractMessageContent (em AiResponseService) combina Content e ExtractedText para montar o contexto enviado ao LLM:

private static string ExtractMessageContent(Message message)
{
    // Mensagens de midia do assistant (anexos enviados) -- nota sintetica
    if (message.Role == MessageRole.Assistant &&
        message.Type != MessageType.Text &&
        !string.IsNullOrEmpty(message.MediaFileName))
    {
        var label = message.Type switch { ... };
        return $"[Enviei {label}: {message.MediaFileName}]";
    }

    // Mensagens de midia do usuario
    if (message.Role == MessageRole.User && message.Type != MessageType.Text)
    {
        // Com extracao -- adicionar rotulo de origem
        if (!string.IsNullOrEmpty(message.ExtractedText))
        {
            var mediaType = message.Type switch { ... };
            return MessageContentFormatter.Format(message.Content, message.ExtractedText, mediaType);
        }
        // Sem extracao -- nota minima
        ...
    }

    return message.Content ?? string.Empty;
}

Exemplo - Imagem com caption:

O que é isso?

Uma foto de uma pizza margherita em uma caixa de papelão branca. A pizza tem molho de tomate, mozzarella derretida e folhas de manjericão fresco.


Arquivos Relacionados

Arquivo Descricao
Ciba.Infrastructure/Services/Transcription/IAudioTranscriptionService.cs Interface de transcricao
Ciba.Infrastructure/Services/Transcription/WhisperTranscriptionService.cs Implementacao Whisper
Ciba.Infrastructure/Configuration/TranscriptionConfig.cs Configuracao (classe estatica com constantes)
Ciba.Infrastructure/Services/Transcription/TranscriptionResult.cs DTO de resultado
Ciba.Infrastructure/Services/Transcription/NoOpTranscriptionService.cs Implementacao desabilitada
Ciba.Api/Features/Webhook/Pipelines/MessageAggregationPipeline/ProcessMediaStep.cs Processamento de midia no pipeline de agregacao
Ciba.Api/Features/Agents/Playground/PlaygroundDescribeImageHandler.cs OCR de imagem no playground
Ciba.Api/Features/Agents/Playground/PlaygroundMediaHelper.cs Helper de upload R2 para playground
Ciba.Infrastructure/Configuration/AppConfig.cs Configuracao de limites (classe estatica com constantes)
Ciba.Infrastructure/Configuration/CreditCostConfig.cs Custos de credito por tipo de midia (classe estatica)

Tratamento de Erros

Erro Tratamento
Transcrição falha Log warning, ExtractedText fica null
Descrição de imagem falha Log warning, ExtractedText fica null
Download de mídia falha Log warning, mídia não processada
Timeout Retry automático (Polly)
Rate limit (429) Retry com backoff exponencial

Monitoramento

Logs

// Transcrição bem-sucedida
_logger.LogInformation(
    "Audio transcribed for conversation {ConversationId}: {Duration}s, {TextLength} chars",
    conversationId, durationSeconds, textLength);

// Descrição bem-sucedida
_logger.LogInformation(
    "Image described for conversation {ConversationId}: {DescriptionLength} chars",
    conversationId, descriptionLength);

// Falhas
_logger.LogWarning(ex, "Failed to transcribe audio for conversation {ConversationId}", conversationId);
_logger.LogWarning(ex, "Failed to describe image for conversation {ConversationId}", conversationId);

Métricas

  • Tempo de transcrição por áudio
  • Taxa de sucesso/falha
  • Tamanho médio dos textos extraídos