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¶
- Timeout de Requisição HTTP
Cliente → POST /webchat/message ↓ (aguarda 5-30 segundos) ↓ Agente processando... ↓ Possível timeout (30-60s típico) ← Response JSON - Load balancers típicos: 30s timeout
- Navegadores: 30-120s dependendo
- Cloud Run: 60s default (max 3600s)
-
Risco: Requisições longas podem dar timeout
-
UX Ruim - Sem Feedback Intermediário
- Usuário vê apenas "loading..." por 10-30 segundos
- Não sabe se está processando ou travou
-
Sem indicação de progresso (ferramentas sendo chamadas)
-
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 - Não aproveita streaming do ADK Runner
- Memória: acumula resposta completa
-
Latência: usuário só vê após conclusão total
-
Concorrência Limitada
- Cada requisição mantém conexão HTTP aberta
- FastAPI/uvicorn: limite de workers
-
100 usuários simultâneos = 100 conexões HTTP bloqueadas
-
Sem Suporte a Eventos Assíncronos
- Não pode enviar notificações proativas
- Não pode atualizar mensagens já enviadas
- 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
WebSocketAdapterbásico - [ ] Implementar
ConnectionManagersimples - [ ] 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:
useWebSockethook - 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).