Files
ForgeLM/tauri-app/docs/PARALLEL_AGENT_ARCHITECTURE.md
T
evandro 4f293dcb3b wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/ForgeLM
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 1
2026-10-20 00:49:35 -03:00

17 KiB

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

// 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)

// 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

class ParallelOrchestrator {
  async startParallelReview(
    messageId: number,
    userPrompt: string,
    agentCount: number,
    context: Context
  ): Promise<void> {
    
    // 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<void> {
    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<void> {
    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

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

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)

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)

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

const [agentCount, setAgentCount] = useState<AgentCount>(0);
const [parallelStates, setParallelStates] = useState<Map<number, ParallelReviewState>>(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

function useParallelReview() {
  const [states, setStates] = useState<Map<number, ParallelReviewState>>(new Map());
  const orchestratorRef = useRef<ParallelOrchestrator | null>(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)?