# Arquitetura: Modo Paralelo Multi-Agente com Síntese ## Visão Geral Sistema onde múltiplos agentes (2-16) processam o prompt simultaneamente, e um sintetizador consolida todas as respostas em uma resposta final otimizada. ## Estrutura de Dados ### Estado do Sistema ```typescript // Substitui agenticMode: boolean → agentCount: number (0 = desativado) type AgentCount = 0 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16; // Agente individual no modo paralelo interface ParallelAgent { id: string; // "agent-1", "agent-2", etc. name: string; // Nome exibido no card role: string; // "Crítico", "Criativo", "Técnico", etc. systemPrompt: string; // Prompt específico deste agente status: "idle" | "streaming" | "completed" | "error"; content: string; // Resposta acumulada (streaming) streamChunks: string[]; // Chunks para renderização em tempo real tokensPerSecond: number | null; error?: string; } // Estado completo da revisão paralela interface ParallelReviewState { messageId: number; // ID da mensagem sendo processada agents: ParallelAgent[]; // Lista de agentes (2-16) // Barreira de sincronia allAgentsCompleted: boolean; // true quando TODOS terminaram completedAt: number | null; // Timestamp de conclusão do último agente // Sintetizador synthesizerStatus: "waiting" | "streaming" | "completed" | "error" | "skipped"; synthesizerContent: string; // Resposta final sendo gerada // Estado geral status: "running" | "completed" | "error"; startedAt: number; completedAt: number | null; } ``` ## Fluxo de Execução ### 1. Inicialização (ChatView) ```typescript // Antes de enviar mensagem const agentCount = useAgentStore((s) => s.agentCount); // 2-16 ou 0 // Se agentCount > 0, não envia para o LLM principal diretamente // Em vez disso, inicia o modo paralelo if (agentCount > 0) { await parallelOrchestrator.startParallelReview( messageId, userMessage, agentCount, context ); } else { // Fluxo normal await sendToLLM(userMessage); } ``` ### 2. Orquestração Paralela ```typescript class ParallelOrchestrator { async startParallelReview( messageId: number, userPrompt: string, agentCount: number, context: Context ): Promise { // 1. Gerar N agentes dinamicamente const agents = this.generateAgents(agentCount, userPrompt); // 2. Inicializar estado const state: ParallelReviewState = { messageId, agents: agents.map((a) => ({ ...a, status: "idle", content: "" })), allAgentsCompleted: false, completedAt: null, synthesizerStatus: "waiting", synthesizerContent: "", status: "running", startedAt: Date.now(), completedAt: null, }; // 3. Iniciar TODOS os agentes em paralelo const agentPromises = agents.map((agent, index) => this.runAgentStreaming(agent, index, userPrompt, context, state) ); // 4. BARREIRA: Aguardar TODOS terminarem await Promise.all(agentPromises); state.allAgentsCompleted = true; state.agentsCompletedAt = Date.now(); // 5. Chamar sintetizador (só depois da barreira) await this.runSynthesizer(userPrompt, state, context); state.status = "completed"; state.completedAt = Date.now(); } private async runAgentStreaming( agent: AgentDefinition, index: number, userPrompt: string, context: Context, state: ParallelReviewState ): Promise { state.agents[index].status = "streaming"; const response = await fetch(`${context.server}/v1/chat/completions`, { method: "POST", headers: { /* ... */ }, body: JSON.stringify({ model: context.model, messages: [ { role: "system", content: agent.systemPrompt }, { role: "user", content: userPrompt } ], stream: true, // Stream para atualização em tempo real }), }); // Processar stream e atualizar state.agents[index].content em tempo real for await (const chunk of response.body) { const content = extractContent(chunk); state.agents[index].content += content; state.agents[index].streamChunks.push(content); this.notifyUpdate(state); // Callback para UI } state.agents[index].status = "completed"; } private async runSynthesizer( originalPrompt: string, state: ParallelReviewState, context: Context ): Promise { state.synthesizerStatus = "streaming"; // Construir contexto com TODAS as respostas dos agentes const synthesizerPrompt = this.buildSynthesizerPrompt( originalPrompt, state.agents ); const response = await fetch(`${context.server}/v1/chat/completions`, { method: "POST", headers: { /* ... */ }, body: JSON.stringify({ model: context.model, messages: [ { role: "system", content: SYNTHESIZER_SYSTEM_PROMPT }, { role: "user", content: synthesizerPrompt } ], stream: true, }), }); // Stream da resposta final for await (const chunk of response.body) { const content = extractContent(chunk); state.synthesizerContent += content; this.notifyUpdate(state); } state.synthesizerStatus = "completed"; // Salvar no DB await this.saveFinalResponse(state); } } ``` ## Geração Dinâmica de Agentes ```typescript function generateAgents(count: number, userPrompt: string): AgentDefinition[] { // Especialistas base que variam conforme o contexto const baseRoles = [ { name: "Analista", focus: "Analisar profundamente o problema" }, { name: "Criativo", focus: "Gerar soluções inovadoras e fora da caixa" }, { name: "Crítico", focus: "Identificar falhas, riscos e edge cases" }, { name: "Prático", focus: "Focar em implementação e viabilidade" }, { name: "Especialista", focus: "Aprofundar em detalhes técnicos" }, { name: "Simplificador", focus: "Explicar de forma clara e acessível" }, { name: "Histórico", focus: "Considerar contexto e precedentes" }, { name: "Futurista", focus: "Projetar implicações de longo prazo" }, ]; // Detectar tipo de pergunta para especialização const isCode = /```|function|class|const|let|var|import|export/.test(userPrompt); const isCreative = /ideia|criar|inventar|imagine|story|história/i.test(userPrompt); const isTechnical = /como funciona|explica|por que/i.test(userPrompt); // Selecionar roles apropriados const selectedRoles = selectRoles(baseRoles, count, { isCode, isCreative, isTechnical }); return selectedRoles.map((role, i) => ({ id: `agent-${i + 1}`, name: role.name, role: role.focus, systemPrompt: buildAgentPrompt(role, i + 1, count), })); } function buildAgentPrompt(role: Role, index: number, total: number): string { return `Você é o ${role.name} (${index}/${total}) em um painel de especialistas. SUA FUNÇÃO ESPECÍFICA: ${role.focus} REGRAS: 1. Responda à pergunta do usuário APENAS da perspectiva do seu papel 2. Não se preocupe em ser completo - outros especialistas cobrirão outros ângulos 3. Seja específico e aprofunde no que seu papel exige 4. Mantenha resposta concisa (máx 500 tokens) para facilitar síntese posterior 5. Use markdown quando apropriado Você é o especialista #${index} de ${total}. Foque em contribuir sua perspectiva única.`; } ``` ## Prompt do Sintetizador ```typescript const SYNTHESIZER_SYSTEM_PROMPT = `Você é um sintetizador master. Sua função é consolidar múltiplas perspectivas de especialistas em uma resposta única, otimizada e completa. ENTRADAS QUE VOCÊ RECEBERÁ: 1. Pergunta original do usuário 2. Respostas de N especialistas (cada um com perspectiva diferente) SUA TAREFA: 1. Analisar todas as contribuições dos especialistas 2. Identificar pontos de concordância e complementaridade 3. Resolver contradições de forma equilibrada 4. Criar uma resposta final que: - Seja mais completa que qualquer resposta individual - Mantenha o melhor de cada perspectiva - Tenha fluxo natural e coeso - Seja precisa e bem fundamentada ESTRUTURA DA RESPOSTA FINAL: - Comece com uma resposta direta à pergunta - Desenvolva com detalhes relevantes das perspectivas combinadas - Use exemplos práticos quando apropriado - Mantenha o tom apropriado ao contexto NÃO mencione que esta é uma síntese de múltiplos agentes. A resposta deve parecer uma única resposta coesa e natural.`; function buildSynthesizerPrompt(originalPrompt: string, agents: ParallelAgent[]): string { const agentResponses = agents .map((a, i) => `=== ESPECIALISTA ${i + 1}: ${a.name} ===\n${a.content}\n`) .join("\n"); return `PERGUNTA ORIGINAL:\n${originalPrompt}\n\n${agentResponses}`; } ``` ## UI Components ### 1. AgentCountSelector (substitui AgenticToggle) ```typescript interface AgentCountSelectorProps { value: AgentCount; // 0-16, onde 0 = desativado onChange: (count: AgentCount) => void; } // Slider de 0 a 16, com: // - 0 = "Modo Normal" (label) // - 2-16 = "Modo Multi-Agente: N agentes" // - Marcadores visuais em 2, 4, 8, 16 ``` ### 2. ParallelAgentsPanel (dentro de MessageBubble) ```typescript interface ParallelAgentsPanelProps { state: ParallelReviewState; onAgentClick?: (agentId: string) => void; } // Layout: Grid de cards (2 colunas em mobile, 3-4 em desktop) // Cada card mostra: // - Nome do agente + ícone/avatar // - Status (streaming dots, check, error) // - Preview do conteúdo (scrollable) // - Progresso visual (se streaming) // Seção separada para sintetizador: // - Mostra só depois de allAgentsCompleted = true // - Indicador "Sintetizando..." com animação // - Streaming da resposta final ``` ### 3. Estado no ChatView ```typescript const [agentCount, setAgentCount] = useState(0); const [parallelStates, setParallelStates] = useState>(new Map()); // Quando uma mensagem é criada no modo paralelo: const handleParallelStart = (messageId: number, state: ParallelReviewState) => { setParallelStates((prev) => new Map(prev).set(messageId, state)); }; // Atualizações em tempo real via callback: const handleParallelUpdate = (messageId: number, state: ParallelReviewState) => { setParallelStates((prev) => new Map(prev).set(messageId, state)); }; ``` ## Hook useParallelReview ```typescript function useParallelReview() { const [states, setStates] = useState>(new Map()); const orchestratorRef = useRef(null); const startParallelReview = useCallback(async ( messageId: number, userPrompt: string, agentCount: number, context: Context ) => { orchestratorRef.current = new ParallelOrchestrator({ onUpdate: (state) => { setStates((prev) => new Map(prev).set(messageId, state)); } }); await orchestratorRef.current.start(messageId, userPrompt, agentCount, context); }, []); const getStateForMessage = useCallback((messageId: number) => { return states.get(messageId) || null; }, [states]); return { startParallelReview, getStateForMessage }; } ``` ## Modificações Necessárias ### Arquivos a Modificar 1. **`src/stores/agentStore.ts`** (novo) - Estado global: `agentCount: AgentCount` - Persistir no localStorage 2. **`src/components/AgentCountSelector.tsx`** (novo) - Slider 0-16 com labels - Tooltip explicando modo multi-agente 3. **`src/lib/parallel-orchestrator.ts`** (novo) - Lógica completa descrita acima - Promise.all para paralelismo - Barreira de sincronia - Sintetizador 4. **`src/components/ParallelAgentsPanel.tsx`** (novo) - Grid de cards de agentes - Visualização de streaming - Seção do sintetizador 5. **`src/components/ChatView.tsx`** - Substituir toggle por AgentCountSelector - Condicional: se agentCount > 0, usa parallel-orchestrator - Passar parallelState para MessageBubble 6. **`src/components/MessageBubble.tsx`** - Prop opcional: `parallelState?: ParallelReviewState` - Se presente, renderiza ParallelAgentsPanel - Senão, comportamento normal 7. **`src/hooks/useParallelReview.ts`** (novo) - Hook para gerenciar estado - Interface com orchestrator ### Arquivos a Deprecar/Manter - **`src/lib/review-orchestrator.ts`**: Manter (modo sequencial ainda útil) - **`src/hooks/useSpecialistReview.ts`**: Manter para compatibilidade - **`src/components/forgeagent/ReviewProgress.tsx`**: Manter para modo sequencial ## Fluxo Visual Esperado ``` [Input Area] Slider: [0====|====4====|====8====|====12====|====16] Label: "Modo Multi-Agente: 5 agentes" [Usuário envia mensagem] [MessageBubble aparece] ┌─────────────────────────────────────────┐ │ 🤖 Processando com 5 especialistas... │ ├─────────────────────────────────────────┤ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │Analista │ │ Criativo│ │ Crítico │ │ │ │ ●●● │ │ ●●● │ │ ●●● │ │ │ │Streaming│ │Streaming│ │ Aguarde │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ ┌─────────┐ ┌─────────┐ │ │ │ Prático │ │Especiali│ │ │ │ ●●● │ │ ✓ │ │ │ │Streaming│ │Completo │ │ │ └─────────┘ └─────────┘ │ ├─────────────────────────────────────────┤ │ ⏳ Aguardando todos os agentes... │ └─────────────────────────────────────────┘ [Todos completam] [MessageBubble atualiza] ┌─────────────────────────────────────────┐ │ ✓ Análise de 5 especialistas completa │ ├─────────────────────────────────────────┤ │ [Cards colapsados ou em abas] │ │ ├─ Analista [Ver ▼] │ │ ├─ Criativo [Ver ▼] │ │ ├─ Crítico [Ver ▼] │ │ ├─ Prático [Ver ▼] │ │ └─ Especialista[Ver ▼] │ ├─────────────────────────────────────────┤ │ 🤖 Sintetizando resposta final... │ │ [Streaming da resposta consolidada] │ └─────────────────────────────────────────┘ [Síntese completa] [MessageBubble final] ┌─────────────────────────────────────────┐ │ [Resposta final sintetizada] │ │ │ │ [Conteúdo completo e otimizado] │ │ │ ├─────────────────────────────────────────┤ │ 💡 Baseado em análise de 5 especialistas│ │ [Ver detalhes ▼] │ └─────────────────────────────────────────┘ ``` ## Considerações de Performance 1. **N requisições simultâneas**: Pode sobrecarregar o servidor local - Mitigação: Limitar a 8 agentes por padrão - Ou implementar batching/semaforo 2. **Memória**: N streams simultâneas - Mitigação: Manter só últimos N chunks visíveis - Descartar chunks antigos da UI 3. **Tempo total**: max(tempo_agentes) + tempo_sintetizador - Mitigação: Usar modelo mais rápido para agentes - Modelo melhor só para sintetizador ## Próximos Passos ### Fase 1: UI e Estado 1. Criar agentStore com persistência 2. Criar AgentCountSelector component 3. Integrar no ChatView (substituir toggle) ### Fase 2: Orquestrador Paralelo 1. Criar parallel-orchestrator.ts 2. Implementar geração dinâmica de agentes 3. Promise.all com streaming simultâneo 4. Barreira de sincronia ### Fase 3: UI de Agentes 1. Criar ParallelAgentsPanel 2. Grid de cards responsivo 3. Streaming em tempo real 4. Collapse/expand de cards ### Fase 4: Sintetizador 1. Prompt do sintetizador 2. Integração no orchestrator 3. Streaming da resposta final 4. Salvamento no DB Quer que eu comece implementando a Fase 1 (UI do seletor)?