Skip to content

Análise de Performance: WebChat Adapter

Problemas Identificados e Arquitetura de Solução

Data: 31/01/2026
Status: 📋 Análise Técnica - Não Implementado


🔍 Análise do Problema Atual

WebChat Adapter (HTTP Síncrono)

✅ Vantagens

  • ✅ Implementação simples
  • ✅ Compatível com qualquer cliente HTTP
  • ✅ Sem necessidade de bibliotecas especiais
  • ✅ Funciona através de proxies/load balancers

❌ Problemas de Performance

  1. Timeout de Requisição HTTP
    Cliente → POST /webchat/message
             ↓ (aguarda 5-30 segundos)
             ↓ Agente processando...
             ↓ Possível timeout (30-60s típico)
             ← Response JSON
    
  2. Load balancers típicos: 30s timeout
  3. Navegadores: 30-120s dependendo
  4. Cloud Run: 60s default (max 3600s)
  5. Risco: Requisições longas podem dar timeout

  6. UX Ruim - Sem Feedback Intermediário

  7. Usuário vê apenas "loading..." por 10-30 segundos
  8. Não sabe se está processando ou travou
  9. Sem indicação de progresso (ferramentas sendo chamadas)

  10. Sem Streaming de Resposta

    # Atual: acumula tudo, retorna no final
    response_text = ""
    async for event in runner.run_async(...):
        response_text += part.text
    return {"response": response_text}  # Tudo de uma vez
    

  11. Não aproveita streaming do ADK Runner
  12. Memória: acumula resposta completa
  13. Latência: usuário só vê após conclusão total

  14. Concorrência Limitada

  15. Cada requisição mantém conexão HTTP aberta
  16. FastAPI/uvicorn: limite de workers
  17. 100 usuários simultâneos = 100 conexões HTTP bloqueadas

  18. Sem Suporte a Eventos Assíncronos

  19. Não pode enviar notificações proativas
  20. Não pode atualizar mensagens já enviadas
  21. Cliente precisa fazer polling para atualizações

🎯 Soluções Arquiteturais

Opção 1: WebSocket Adapter (RECOMENDADO)

Arquitetura

┌─────────────────────────────────────────────────────────────┐
│                      CLIENTE (Frontend)                     │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  WebSocket Client                                     │  │
│  │  - Conexão persistente bidirecional                  │  │
│  │  - Recebe eventos em tempo real                      │  │
│  │  - Envia mensagens assíncronas                       │  │
│  └──────────────────────────────────────────────────────┘  │
└──────────────────────┬──────────────────────────────────────┘
                       │ WebSocket Connection
                       │ ws://api.com/ws/chat
                       ↓
┌─────────────────────────────────────────────────────────────┐
│                   SERVIDOR (Backend)                        │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  WebSocketAdapter                                     │  │
│  │  - ConnectionManager (gerencia conexões)             │  │
│  │  - EventStreamer (streaming de eventos)              │  │
│  │  - MessageHandler (processa mensagens)               │  │
│  └──────────────────┬───────────────────────────────────┘  │
│                     │                                        │
│  ┌──────────────────▼───────────────────────────────────┐  │
│  │  ADK Runner (Streaming)                              │  │
│  │  async for event in runner.run_async():              │  │
│  │    - Tool call start → ws.send("tool_start")         │  │
│  │    - Tool result → ws.send("tool_result")            │  │
│  │    - Text chunk → ws.send("text_chunk")              │  │
│  │    - Complete → ws.send("message_complete")          │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Fluxo de Mensagem

// 1. Cliente conecta
const ws = new WebSocket('ws://localhost:8080/ws/chat');

// 2. Cliente envia mensagem
ws.send(JSON.stringify({
  type: 'message',
  user_id: 'user123',
  session_id: 'session456',
  message: 'Quero viajar para Paris'
}));

// 3. Servidor responde com eventos em tempo real
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  switch(data.type) {
    case 'message_start':
      showTypingIndicator();
      break;

    case 'tool_call':
      showToolFeedback(data.tool_name); // "🔍 Buscando produtos..."
      break;

    case 'text_chunk':
      appendText(data.text); // Streaming: "Olá! Que ót..."
      break;

    case 'message_complete':
      hideTypingIndicator();
      break;

    case 'error':
      showError(data.message);
      break;
  }
};

Tipos de Eventos (Protocol)

// Cliente → Servidor
interface ClientMessage {
  type: 'message' | 'ping' | 'disconnect';
  user_id: string;
  session_id?: string;
  message?: string;
  metadata?: Record<string, any>;
}

// Servidor → Cliente
type ServerEvent = 
  | { type: 'connected'; session_id: string; }
  | { type: 'message_start'; message_id: string; }
  | { type: 'tool_call'; tool_name: string; description: string; }
  | { type: 'tool_result'; tool_name: string; success: boolean; }
  | { type: 'text_chunk'; text: string; }
  | { type: 'message_complete'; message_id: string; full_text: string; }
  | { type: 'error'; code: string; message: string; }
  | { type: 'pong'; timestamp: number; };

Vantagens do WebSocket

  • Streaming Real-Time: Texto aparece conforme gerado
  • Feedback Intermediário: Mostra ferramentas sendo executadas
  • Conexão Persistente: Sem overhead de HTTP handshake
  • Bidirecional: Servidor pode enviar notificações proativas
  • Baixa Latência: ~1-10ms vs 50-200ms HTTP
  • Suporta Timeout Longo: Sem problema com respostas de 60s+
  • Eficiente: Uma conexão TCP vs múltiplas requisições HTTP

Desafios do WebSocket

  • ⚠️ Complexidade: Mais código para gerenciar conexões
  • ⚠️ Stateful: Precisa gerenciar estado de conexões
  • ⚠️ Escalabilidade: Conexões persistentes consomem memória
  • ⚠️ Load Balancing: Precisa de sticky sessions ou Redis pub/sub
  • ⚠️ Proxy Support: Alguns proxies/firewalls bloqueiam WebSocket
  • ⚠️ Reconexão: Cliente precisa lidar com desconexões

Opção 2: Server-Sent Events (SSE) Adapter

Arquitetura

Cliente → POST /sse/chat (inicia conversa, retorna stream_id)
        ← { "stream_id": "abc123" }

Cliente → EventSource('/sse/stream/abc123')
        ← data: {"type": "text_chunk", "text": "Olá"}
        ← data: {"type": "text_chunk", "text": "! Como"}
        ← data: {"type": "message_complete"}

Vantagens SSE

  • Unidirecional: Servidor → Cliente (suficiente para chat)
  • HTTP Padrão: Funciona através de proxies
  • Auto-reconexão: Built-in no EventSource API
  • Simples: Mais fácil que WebSocket
  • Streaming: Resposta em chunks
  • Compatível: Funciona com HTTP/1.1 e HTTP/2

Desvantagens SSE

  • Unidirecional: Cliente → Servidor precisa de HTTP POST separado
  • Texto Apenas: Precisa serializar JSON em texto
  • Conexões Persistentes: 6 conexões máx por domínio (HTTP/1.1)
  • Sem Suporte Binário: Apenas texto/eventos

Opção 3: HTTP Long Polling

Arquitetura

Cliente → POST /chat/message (inicia processamento)
        ← { "message_id": "msg123", "status": "processing" }

Cliente → GET /chat/message/msg123/stream (polling)
        ← { "status": "processing", "progress": 30% }

Cliente → GET /chat/message/msg123/stream
        ← { "status": "processing", "progress": 60%, "tool": "buscar_produtos" }

Cliente → GET /chat/message/msg123/stream
        ← { "status": "complete", "response": "..." }

Vantagens Polling

  • Simples: Apenas HTTP REST
  • Compatível: Funciona em qualquer ambiente
  • Stateless: Fácil de escalar

Desvantagens Polling

  • Ineficiente: Múltiplas requisições HTTP
  • Latência: Delay entre polls (500ms-2s típico)
  • Overhead: Muitas requisições HTTP vazias
  • Recursos: Consome mais CPU/rede

📊 Comparação das Soluções

Característica HTTP Síncrono (Atual) WebSocket SSE Long Polling
Streaming ⚠️ Simulado
Latência Alta (blocking) Muito Baixa Baixa Média
Bidirecional ⚠️ Via POST
Complexidade Baixa Alta Média Média
Escalabilidade Alta (stateless) Média (stateful) Média Alta
Proxy Support ⚠️ Alguns bloqueiam
Overhead Médio Baixo Baixo Alto
Timeout Risk ✅ Alto ❌ Não ❌ Não ⚠️ Médio
UX Feedback ✅ Excelente ✅ Bom ⚠️ OK
Implementação ✅ Simples ❌ Complexo ⚠️ Médio ⚠️ Médio
Mobile Support
Reconexão N/A Manual ✅ Auto Manual

🏗️ Arquitetura Proposta: WebSocket Adapter

Componentes

1. WebSocketAdapter

class WebSocketAdapter(MessagingAdapter):
    """
    Adapter WebSocket para chat em tempo real com streaming.

    Características:
    - Conexões persistentes
    - Streaming de eventos do ADK Runner
    - Feedback em tempo real (tool calls)
    - Gerenciamento de sessões
    """

    def __init__(self, config):
        self.connection_manager = ConnectionManager()
        self.event_streamer = EventStreamer()

    async def handle_connection(self, websocket: WebSocket, user_id: str):
        """Gerencia lifecycle da conexão WebSocket"""

    async def stream_runner_events(self, websocket, runner, message):
        """Converte eventos do ADK Runner para WebSocket events"""

2. ConnectionManager

class ConnectionManager:
    """
    Gerencia conexões WebSocket ativas.

    Responsabilidades:
    - Registrar/desregistrar conexões
    - Broadcast para múltiplas conexões (se necessário)
    - Heartbeat/keepalive
    - Cleanup de conexões órfãs
    """

    def __init__(self):
        self.active_connections: Dict[str, WebSocket] = {}
        self.user_sessions: Dict[str, str] = {}

    async def connect(self, user_id: str, websocket: WebSocket):
        """Adiciona nova conexão"""

    async def disconnect(self, user_id: str):
        """Remove conexão"""

    async def send_to_user(self, user_id: str, event: dict):
        """Envia evento para usuário específico"""

    async def heartbeat_loop(self):
        """Mantém conexões vivas com ping/pong"""

3. EventStreamer

class EventStreamer:
    """
    Converte eventos do ADK Runner para protocolo WebSocket.

    Mapeia:
    - Tool calls → tool_call events
    - Tool responses → tool_result events
    - Text parts → text_chunk events
    - Completion → message_complete event
    """

    async def stream_to_websocket(
        self,
        websocket: WebSocket,
        runner: Runner,
        message: IncomingMessage
    ):
        """Stream eventos em tempo real"""

        await self.send_event(websocket, {
            "type": "message_start",
            "message_id": generate_id()
        })

        async for event in runner.run_async(...):
            # Tool call
            if has_function_call(event):
                await self.send_event(websocket, {
                    "type": "tool_call",
                    "tool_name": event.function_call.name,
                    "description": get_tool_description(...)
                })

            # Text chunk
            if has_text(event):
                await self.send_event(websocket, {
                    "type": "text_chunk",
                    "text": event.text
                })

        await self.send_event(websocket, {
            "type": "message_complete",
            "full_text": accumulated_text
        })

4. Endpoint no Unified Bot

@app.websocket("/ws/chat/{user_id}")
async def websocket_chat_endpoint(
    websocket: WebSocket,
    user_id: str
):
    """
    Endpoint WebSocket para chat em tempo real.

    URL: ws://localhost:8080/ws/chat/user123
    """
    adapter = adapter_factory.get_adapter("websocket")
    await adapter.handle_connection(websocket, user_id)

🔄 Fluxo Completo de Mensagem

1. Conexão Inicial

// Cliente
const ws = new WebSocket('ws://localhost:8080/ws/chat/user123');

ws.onopen = () => {
  console.log('Conectado!');
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleEvent(data);
};
# Servidor
@app.websocket("/ws/chat/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str):
    await websocket.accept()
    await connection_manager.connect(user_id, websocket)

    # Envia confirmação
    await websocket.send_json({
        "type": "connected",
        "user_id": user_id,
        "session_id": get_or_create_session(user_id)
    })

    try:
        while True:
            data = await websocket.receive_json()
            await handle_client_message(user_id, data)
    except WebSocketDisconnect:
        await connection_manager.disconnect(user_id)

2. Envio de Mensagem + Streaming

// Cliente envia
ws.send(JSON.stringify({
  type: 'message',
  message: 'Quero viajar para Paris'
}));

// Cliente recebe (eventos em tempo real)
// [1ms] { type: 'message_start', message_id: 'msg123' }
// [100ms] { type: 'tool_call', tool_name: 'busca_produtos_tool', description: '🔍 Buscando produtos' }
// [2000ms] { type: 'tool_result', tool_name: 'busca_produtos_tool', success: true }
// [2100ms] { type: 'text_chunk', text: 'Olá' }
// [2150ms] { type: 'text_chunk', text: '! Encontrei' }
// [2200ms] { type: 'text_chunk', text: ' várias experiências' }
// [2250ms] { type: 'text_chunk', text: ' em Paris...' }
// [3000ms] { type: 'message_complete', full_text: 'Olá! Encontrei...' }
# Servidor processa
async def handle_client_message(user_id: str, data: dict):
    if data['type'] == 'message':
        websocket = connection_manager.get_connection(user_id)

        # Stream eventos do Runner
        await event_streamer.stream_to_websocket(
            websocket=websocket,
            runner=runner,
            message=IncomingMessage(
                platform="websocket",
                user_id=user_id,
                text=data['message'],
                ...
            )
        )

🚀 Plano de Implementação

Fase 1: Proof of Concept (1-2 dias)

  • [ ] Criar WebSocketAdapter básico
  • [ ] Implementar ConnectionManager simples
  • [ ] Endpoint /ws/chat/{user_id}
  • [ ] Cliente HTML mínimo com WebSocket
  • [ ] Testar streaming de texto

Fase 2: Event Streaming (1-2 dias)

  • [ ] Implementar EventStreamer
  • [ ] Mapear eventos do ADK Runner
  • [ ] Tool call events
  • [ ] Text chunk streaming
  • [ ] Error handling

Fase 3: Robustez (1-2 dias)

  • [ ] Heartbeat/keepalive (ping/pong)
  • [ ] Reconexão automática no cliente
  • [ ] Cleanup de conexões órfãs
  • [ ] Rate limiting
  • [ ] Autenticação (via token no header ou query param)

Fase 4: Escalabilidade (2-3 dias)

  • [ ] Redis Pub/Sub para múltiplas instâncias
  • [ ] Sticky sessions no load balancer
  • [ ] Métricas de conexões ativas
  • [ ] Monitoring e alertas

Fase 5: Produção (1-2 dias)

  • [ ] Testes de carga
  • [ ] Documentação completa
  • [ ] Exemplos de frontend (React, Vue, vanilla JS)
  • [ ] Deploy e validação

Total Estimado: 6-11 dias


📈 Métricas de Performance Esperadas

HTTP Síncrono (Atual)

Tempo total resposta: 10-30s
Feedback visual: Apenas loading spinner
Timeout risk: Alto (>30s)
Latência percebida: 10-30s (sem feedback)
Overhead rede: 1 request HTTP

WebSocket (Proposto)

Tempo total resposta: 10-30s (mesmo processamento)
Feedback visual: Real-time (tool calls, streaming)
Timeout risk: Baixo (conexão persistente)
Latência percebida: <1s (primeiro chunk aparece rápido)
Overhead rede: 1 conexão WS (mais eficiente)
UX: 10x melhor (streaming + feedback)

🎯 Recomendação Final

Para Produção Imediata: Manter HTTP + SSE

  • Implementar endpoint SSE paralelo ao HTTP atual
  • Cliente escolhe: HTTP (compatibilidade) ou SSE (streaming)
  • Menor complexidade que WebSocket
  • Melhor UX sem refatoração massiva

Para Médio Prazo: Migrar para WebSocket

  • Melhor performance
  • UX superior
  • Suporta casos de uso avançados:
  • Notificações proativas
  • Multi-dispositivo (sincronizar chat)
  • Typing indicators bidirecionais
  • Presença online

Estratégia Híbrida (IDEAL):

┌─────────────────────────────────────────┐
│  Manter os 3 Adapters                   │
├─────────────────────────────────────────┤
│  1. WebChatAdapter (HTTP)               │
│     - Compatibilidade máxima            │
│     - APIs simples                      │
│     - SEO bots, testes                  │
│                                          │
│  2. WebSocketAdapter (WS)               │
│     - Web apps modernos                 │
│     - Mobile apps                       │
│     - Desktop clients                   │
│                                          │
│  3. SSEAdapter (opcional)               │
│     - Fallback quando WS bloqueado      │
│     - Clientes que preferem HTTP/2      │
└─────────────────────────────────────────┘

Cliente detecta suporte e escolhe automaticamente:

if (supportsWebSocket && !behindRestrictiveProxy) {
    useWebSocket();
} else if (supportsSSE) {
    useSSE();
} else {
    useHTTP(); // fallback
}


🔐 Considerações de Segurança

WebSocket

  • ✅ Autenticação via token no handshake
  • ✅ Rate limiting por conexão
  • ✅ Validação de origem (CORS para WS)
  • ⚠️ DDoS: limitar conexões por IP
  • ⚠️ Memory exhaustion: timeout de inatividade

Exemplo de Auth

const token = getAuthToken();
const ws = new WebSocket(`ws://api.com/ws/chat?token=${token}`);
@app.websocket("/ws/chat")
async def websocket_endpoint(
    websocket: WebSocket,
    token: str = Query(...)
):
    user = await verify_token(token)
    if not user:
        await websocket.close(code=1008)  # Policy violation
        return

    await websocket.accept()
    # ... resto do código

📚 Referências Técnicas

FastAPI WebSocket

  • Docs: https://fastapi.tiangolo.com/advanced/websockets/
  • Exemplo: https://github.com/tiangolo/fastapi/blob/master/docs_src/websockets/tutorial001.py

Alternativas de Implementação

  • socketio: Abstração sobre WebSocket com fallbacks
  • channels: Para Django (não aplicável)
  • websockets library: Low-level Python WebSocket

Cliente JavaScript

  • Browser API: WebSocket
  • React: useWebSocket hook
  • Vue: vue-native-websocket

Conclusão: WebSocket é a solução ideal para chat em tempo real com melhor UX, mas requer mais complexidade. SSE é um meio-termo interessante. HTTP atual serve como fallback confiável.

Próximo passo: Decidir entre SSE (mais rápido) ou WebSocket (mais robusto).