# 🏗️ Arquitetura de Integração - Speech-to-Speech com Avatar ## 📋 Visão Geral Integração de componentes independentes para criar um sistema de conversação em tempo real com avatar: - **Whisper** (STT - Speech-to-Text) - **LLM** (Gemma/GPT - Processamento de linguagem) - **TTS** (FishAudio - Text-to-Speech) - **MuseTalk** (Geração de vídeo do avatar) - **WebRTC** (Transporte de vídeo/áudio) ## 🎯 Objetivos ✅ **Componentes independentes** - Desenvolvimento e deploy separados ✅ **Baixa latência** - < 500ms de resposta total ✅ **Escalabilidade** - Cada componente pode escalar independentemente ✅ **Manutenibilidade** - Fácil atualização de cada parte ## 🔧 Arquitetura Recomendada: Microserviços ``` ┌─────────────────────────────────────────────────────────────────┐ │ USUÁRIO │ │ (Navegador Web) │ └────────────────────────┬────────────────────────────────────────┘ │ WebRTC ▼ ┌─────────────────────────────────────────────────────────────────┐ │ GATEWAY / ORQUESTRADOR │ │ (FastAPI/Node.js) │ │ - Gerencia sessões WebRTC │ │ - Roteia requisições para microserviços │ │ - Mantém estado da conversação │ └─────┬───────┬───────┬───────┬────────────────────────────────┘ │ │ │ │ │ │ │ │ gRPC/WebSocket/HTTP ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────┐ ┌─────┐ ┌──────────┐ │ Whisper │ │ LLM │ │ TTS │ │ MuseTalk │ │ Service │ │Svc │ │Svc │ │ Service │ │ │ │ │ │ │ │ │ │ Port: │ │Port:│ │Port:│ │ Port: │ │ 5001 │ │5002 │ │5003 │ │ 5004 │ └─────────┘ └─────┘ └─────┘ └──────────┘ ``` ## 📊 Fluxo de Dados (Speech-to-Speech) ``` 1. 🎤 Usuário fala └─> WebRTC captura áudio └─> Gateway recebe │ 2. 🔊 Speech-to-Text (Whisper) └─> Gateway → Whisper Service (gRPC) └─> Whisper retorna texto └─> Latência: ~50-150ms │ 3. 🧠 LLM Processing (Gemma/GPT) └─> Gateway → LLM Service (HTTP/gRPC) └─> LLM retorna resposta (streaming) └─> Latência: ~200-500ms │ 4. 🗣️ Text-to-Speech (FishAudio) └─> Gateway → TTS Service (WebSocket) └─> TTS retorna áudio (streaming) └─> Latência: ~100-300ms │ 5. 🎬 Avatar Generation (MuseTalk) └─> Gateway → MuseTalk Service (gRPC) └─> MuseTalk gera vídeo (streaming) └─> Latência: ~100-200ms │ 6. 📹 Video Delivery (WebRTC) └─> Gateway → WebRTC └─> Usuário recebe vídeo/áudio └─> Latência: ~50-150ms ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ TOTAL: ~500-1300ms (pode ser reduzido com streaming) ``` ## 🚀 Estratégias para Reduzir Latência ### 1. **Streaming em Pipeline** ⚡ Ao invés de esperar cada etapa terminar completamente, inicie a próxima assim que houver dados parciais: ```python # ❌ SEQUENCIAL (alto latência) texto = await whisper.transcribe(audio) # 150ms resposta = await llm.generate(texto) # 500ms audio_tts = await tts.synthesize(resposta) # 300ms video = await musetalk.generate(audio_tts) # 200ms # Total: 1150ms # ✅ STREAMING (baixa latência) async for texto_parcial in whisper.transcribe_stream(audio): async for resposta_parcial in llm.generate_stream(texto_parcial): async for audio_chunk in tts.synthesize_stream(resposta_parcial): async for video_frame in musetalk.generate_stream(audio_chunk): await webrtc.send_frame(video_frame) # Primeira palavra: ~300ms # Latência percebida muito menor! ``` ### 2. **Cache Inteligente** 💾 ```python # Cache de embeddings do LLM llm_cache = { "Olá, como vai?": cached_response_embedding, "Qual é seu nome?": cached_response_embedding, } # Cache de áudio TTS (frases comuns) tts_cache = { "Olá!": audio_bytes, "Sim": audio_bytes, "Não": audio_bytes, } # Cache de vídeo MuseTalk (idle animations) musetalk_cache = { "idle": video_frames_loop, "thinking": video_frames_loop, } ``` ### 3. **Processamento Paralelo** 🔄 ```python import asyncio # Processar múltiplas partes simultaneamente async def process_speech(audio): # Iniciar todas as tarefas em paralelo whisper_task = asyncio.create_task(whisper.transcribe(audio)) # Assim que whisper terminar, iniciar LLM texto = await whisper_task # LLM e preparação do MuseTalk em paralelo llm_task = asyncio.create_task(llm.generate(texto)) musetalk_prep_task = asyncio.create_task(musetalk.prepare()) # Aguardar ambos resposta, _ = await asyncio.gather(llm_task, musetalk_prep_task) # TTS audio_tts = await tts.synthesize(resposta) # MuseTalk (já preparado) video = await musetalk.generate(audio_tts) return video ``` ### 4. **Pré-computação** 🎯 ```python # Pré-carregar modelos na inicialização class Services: def __init__(self): # Carregar tudo na memória self.whisper = WhisperModel.load() self.llm = LLMModel.load() self.tts = TTSModel.load() self.musetalk = MuseTalkModel.load() # Pré-gerar frames de "idle" self.idle_animation = self.musetalk.generate_idle_loop() # Warmup (primeira inferência é sempre mais lenta) self.whisper.transcribe(dummy_audio) self.llm.generate("test") self.tts.synthesize("test") ``` ## 🔌 Protocolos de Comunicação ### Escolha do Protocolo por Serviço | Serviço | Protocolo | Justificativa | |---------|-----------|---------------| | **Gateway ↔ Cliente** | WebRTC | Baixa latência, P2P, suporte a vídeo/áudio | | **Gateway ↔ Whisper** | gRPC | Binário, rápido, suporte a streaming | | **Gateway ↔ LLM** | gRPC + Streaming | Streaming de tokens, baixa latência | | **Gateway ↔ TTS** | WebSocket | Streaming de áudio, bidirecional | | **Gateway ↔ MuseTalk** | gRPC | Streaming de frames, binário eficiente | ### Exemplo: Interface gRPC para MuseTalk ```protobuf // musetalk.proto syntax = "proto3"; service MuseTalkService { // Gerar vídeo a partir de áudio (streaming) rpc GenerateVideo(stream AudioChunk) returns (stream VideoFrame) {} // Obter animação idle rpc GetIdleAnimation(IdleRequest) returns (stream VideoFrame) {} } message AudioChunk { bytes audio_data = 1; int32 sample_rate = 2; int32 chunk_index = 3; } message VideoFrame { bytes frame_data = 1; int64 timestamp_ms = 2; int32 frame_index = 3; } ``` ## 🏛️ Arquitetura de Deployment ### Opção 1: Containers Docker (Recomendado) ```yaml # docker-compose.yml version: '3.8' services: gateway: build: ./gateway ports: - "8080:8080" - "9000:9000" # WebRTC depends_on: - whisper - llm - tts - musetalk environment: - WHISPER_URL=whisper:5001 - LLM_URL=llm:5002 - TTS_URL=tts:5003 - MUSETALK_URL=musetalk:5004 whisper: build: ./services/whisper ports: - "5001:5001" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] llm: build: ./services/llm ports: - "5002:5002" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] tts: build: ./services/tts ports: - "5003:5003" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] musetalk: build: ./services/musetalk ports: - "5004:5004" deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] redis: image: redis:alpine ports: - "6379:6379" ``` ### Opção 2: Kubernetes (Produção/Escala) ```yaml # k8s-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: musetalk-service spec: replicas: 3 selector: matchLabels: app: musetalk template: metadata: labels: app: musetalk spec: containers: - name: musetalk image: your-registry/musetalk:latest ports: - containerPort: 5004 resources: limits: nvidia.com/gpu: 1 env: - name: MODEL_PATH value: /models/musetalk --- apiVersion: v1 kind: Service metadata: name: musetalk-service spec: selector: app: musetalk ports: - protocol: TCP port: 5004 targetPort: 5004 type: ClusterIP ``` ## 📁 Estrutura de Projeto Recomendada ``` avatar-conversation-system/ ├── gateway/ # Orquestrador principal │ ├── main.py │ ├── websocket_handler.py │ ├── session_manager.py │ └── requirements.txt │ ├── services/ │ ├── whisper/ # STT Service │ │ ├── server.py │ │ ├── model_loader.py │ │ ├── Dockerfile │ │ └── requirements.txt │ │ │ ├── llm/ # LLM Service │ │ ├── server.py │ │ ├── model_loader.py │ │ ├── Dockerfile │ │ └── requirements.txt │ │ │ ├── tts/ # TTS Service │ │ ├── server.py │ │ ├── model_loader.py │ │ ├── Dockerfile │ │ └── requirements.txt │ │ │ └── musetalk/ # Avatar Service │ ├── server.py │ ├── model_loader.py │ ├── Dockerfile │ └── requirements.txt │ ├── shared/ # Código compartilhado │ ├── proto/ # gRPC proto files │ │ ├── whisper.proto │ │ ├── llm.proto │ │ ├── tts.proto │ │ └── musetalk.proto │ │ │ └── utils/ │ ├── logger.py │ ├── metrics.py │ └── cache.py │ ├── docker-compose.yml ├── k8s/ # Kubernetes configs │ ├── deployment.yaml │ ├── service.yaml │ └── ingress.yaml │ └── README.md ``` ## 💡 Gateway/Orquestrador - Código Exemplo ```python # gateway/main.py from fastapi import FastAPI, WebSocket from fastapi.responses import HTMLResponse import asyncio import grpc # Imports dos clientes gRPC from services.whisper import whisper_pb2, whisper_pb2_grpc from services.llm import llm_pb2, llm_pb2_grpc from services.tts import tts_pb2, tts_pb2_grpc from services.musetalk import musetalk_pb2, musetalk_pb2_grpc app = FastAPI() class ConversationOrchestrator: def __init__(self): # Conexões com microserviços self.whisper_channel = grpc.aio.insecure_channel('whisper:5001') self.whisper_client = whisper_pb2_grpc.WhisperServiceStub(self.whisper_channel) self.llm_channel = grpc.aio.insecure_channel('llm:5002') self.llm_client = llm_pb2_grpc.LLMServiceStub(self.llm_channel) self.tts_channel = grpc.aio.insecure_channel('tts:5003') self.tts_client = tts_pb2_grpc.TTSServiceStub(self.tts_channel) self.musetalk_channel = grpc.aio.insecure_channel('musetalk:5004') self.musetalk_client = musetalk_pb2_grpc.MuseTalkServiceStub(self.musetalk_channel) async def process_speech_stream(self, audio_stream): """ Pipeline de streaming completo """ # 1. STT (Whisper) async for text_chunk in self.whisper_client.TranscribeStream(audio_stream): # 2. LLM (streaming de tokens) async for response_token in self.llm_client.GenerateStream(text_chunk): # 3. TTS (streaming de áudio) async for audio_chunk in self.tts_client.SynthesizeStream(response_token): # 4. MuseTalk (streaming de frames) async for video_frame in self.musetalk_client.GenerateVideo(audio_chunk): # 5. Enviar para WebRTC yield video_frame orchestrator = ConversationOrchestrator() @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() try: while True: # Receber áudio do cliente audio_data = await websocket.receive_bytes() # Processar em pipeline async for video_frame in orchestrator.process_speech_stream([audio_data]): # Enviar frame de vídeo de volta await websocket.send_bytes(video_frame.frame_data) except Exception as e: print(f"Error: {e}") finally: await websocket.close() @app.get("/") async def get(): # Retornar cliente WebRTC HTML return HTMLResponse(open("client.html").read()) ``` ## 🎯 Métricas de Latência Alvo | Componente | Latência Alvo | Como Alcançar | |------------|---------------|---------------| | **Whisper** | < 100ms | GPU, batching, modelo otimizado | | **LLM** | < 300ms | Streaming, cache, modelo menor | | **TTS** | < 150ms | GPU, streaming, cache de frases comuns | | **MuseTalk** | < 150ms | GPU, pré-computação, streaming | | **WebRTC** | < 100ms | Codec otimizado, servidor próximo | | **TOTAL** | **< 500ms** | Pipeline streaming | ## 🔥 Otimizações Adicionais ### 1. **Quantização de Modelos** ```python # Reduzir tamanho e latência dos modelos from transformers import AutoModelForCausalLM import torch # Carregar modelo em FP16 (metade do tamanho) model = AutoModelForCausalLM.from_pretrained( "model-name", torch_dtype=torch.float16, device_map="auto" ) # Ou INT8 (ainda menor) model = AutoModelForCausalLM.from_pretrained( "model-name", load_in_8bit=True, device_map="auto" ) ``` ### 2. **Batching Dinâmico** ```python # Agrupar múltiplas requisições para processar em batch class BatchProcessor: def __init__(self, max_batch_size=8, max_wait_ms=50): self.queue = [] self.max_batch_size = max_batch_size self.max_wait_ms = max_wait_ms async def process(self, input_data): # Adicionar à fila future = asyncio.Future() self.queue.append((input_data, future)) # Se batch está cheio ou timeout, processar if len(self.queue) >= self.max_batch_size: await self._process_batch() else: asyncio.create_task(self._wait_and_process()) return await future async def _process_batch(self): if not self.queue: return batch = self.queue[:self.max_batch_size] self.queue = self.queue[self.max_batch_size:] # Processar batch inputs = [item[0] for item in batch] results = await model.process_batch(inputs) # Retornar resultados for (_, future), result in zip(batch, results): future.set_result(result) ``` ### 3. **Health Checks e Circuit Breakers** ```python from circuitbreaker import circuit @circuit(failure_threshold=5, recovery_timeout=60) async def call_llm_service(text): try: response = await llm_client.generate(text, timeout=2.0) return response except grpc.aio.AioRpcError as e: # Fallback para resposta pré-definida return "Desculpe, estou tendo problemas técnicos." ``` ## 📊 Monitoramento ```python from prometheus_client import Counter, Histogram # Métricas latency_histogram = Histogram( 'service_latency_seconds', 'Latência de cada serviço', ['service'] ) requests_counter = Counter( 'service_requests_total', 'Total de requisições por serviço', ['service', 'status'] ) # Uso with latency_histogram.labels(service='whisper').time(): result = await whisper_client.transcribe(audio) requests_counter.labels(service='whisper', status='success').inc() ``` ## 🎯 Resumo ### Vantagens da Arquitetura de Microserviços ✅ **Desenvolvimento independente** - Cada equipe/pessoa pode trabalhar em um serviço ✅ **Deploy independente** - Atualizar um serviço sem afetar outros ✅ **Escalabilidade granular** - Escalar apenas o serviço que precisa ✅ **Tecnologias diferentes** - Cada serviço pode usar a stack mais adequada ✅ **Resiliência** - Falha em um serviço não derruba todo o sistema ✅ **Testabilidade** - Testar cada componente isoladamente ### Desvantagens ⚠️ **Complexidade** - Mais componentes para gerenciar ⚠️ **Latência de rede** - Comunicação entre serviços adiciona latência ⚠️ **Debugging** - Rastrear problemas através de múltiplos serviços ### Quando Usar Microserviços - ✅ Sistema grande com múltiplos desenvolvedores - ✅ Componentes que precisam escalar independentemente - ✅ Necessidade de diferentes tecnologias/linguagens - ❌ Sistema pequeno/MVP (monolito é mais simples) --- **Próximos Passos:** 1. Implementar Gateway/Orquestrador básico 2. Criar interfaces gRPC para cada serviço 3. Dockerizar cada componente 4. Testar latência end-to-end 5. Otimizar gargalos identificados