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/wavaudio/webm
Retry Policy¶
- 2 retries com backoff exponencial (4s, 8s)
- Retry em erros transientes e rate limit (429)
Fluxo¶
- Áudio recebido via webhook
- Mídia baixada do Evolution API (base64)
- Convertido para bytes e salvo no R2
- Enviado ao Whisper API para transcrição
- Texto transcrito salvo em
Message.ExtractedText - 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/jpegimage/pngimage/gifimage/webp
Fluxo¶
- Imagem recebida via webhook
- Mídia baixada do Evolution API (base64)
- Salva no R2
- Enviada ao Claude Vision com prompt de descrição
- Descrição textual salva em
Message.ExtractedText - 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:
Implementação¶
A lógica de limite fica no ProcessMediaStep da pipeline de agregação:
- Coleta todas as mensagens com mídia do batch
- Seleciona as N mais recentes (por ordem reversa) para extração
- Para cada mensagem, chama
MediaProcessingService.ProcessAsynccom o flagskipExtraction - 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¶
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
PlaygroundDescribeImageHandlerePlaygroundTranscribeHandler - Upload é fire-and-forget tolerante: se falhar, retorna
nulle 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