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 ──▶ ProcessMessageHandler           │
│                                                                             │
│                                    ┌────────────────────────────────────┐   │
│                                    │  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) │         │
│                    └─────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Transcrição de Áudio

Configuração

appsettings.json:

"Transcription": {
  "Enabled": true,
  "Provider": "openai",
  "Model": "whisper-1",
  "DefaultLanguage": "pt",
  "TimeoutSeconds": 60
}

Campo Descrição Padrão
Enabled Habilita/desabilita transcrição true
Provider Nome do provider em Llm.Providers openai
Model Modelo do Whisper whisper-1
DefaultLanguage Idioma padrão (ISO 639-1) pt
TimeoutSeconds Timeout para áudios 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 5 Máximo de mídias com extração por batch
MaxAudioBytesForTranscription 2 MB (2.097.152 bytes) Tamanho máximo de áudio para transcrição Whisper

Comportamento: - As 5 mídias mais recentes do batch recebem extração de texto (OCR ou transcrição) - 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 são configuráveis via AppConfig (seção App do appsettings.json)

Configuração

appsettings.json:

"App": {
  "MaxMediaExtractionsPerBatch": 5,
  "MaxAudioBytesForTranscription": 2097152
}

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 método BuildChatMessages combina Content e ExtractedText para montar o contexto:

private static string BuildMessageContent(Message m)
{
    // Para mídia com texto extraído, combinar caption + extracted
    if (!string.IsNullOrEmpty(m.ExtractedText))
    {
        return string.IsNullOrEmpty(m.Content)
            ? m.ExtractedText
            : $"{m.Content}\n\n{m.ExtractedText}";
    }

    return m.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 Descrição
Ciba.Infrastructure/Services/Transcription/IAudioTranscriptionService.cs Interface de transcrição
Ciba.Infrastructure/Services/Transcription/WhisperTranscriptionService.cs Implementação Whisper
Ciba.Infrastructure/Services/Transcription/TranscriptionOptions.cs Configuração
Ciba.Infrastructure/Services/Transcription/TranscriptionResult.cs DTO de resultado
Ciba.Infrastructure/Services/Transcription/NoOpTranscriptionService.cs Implementação desabilitada
Ciba.Api/Features/Webhook/ProcessMessage/ProcessMessageHandler.cs Orquestração do processamento
Ciba.Api/Features/Webhook/Pipelines/MessageAggregationPipeline/ProcessMediaStep.cs Limites de extração por batch
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 Configuração de limites (MaxMediaExtractionsPerBatch, MaxAudioBytesForTranscription)

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