Origem: D:/Documents/Github/ForgeLM Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 1
499 lines
17 KiB
Markdown
499 lines
17 KiB
Markdown
# 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<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
|
|
|
|
```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<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
|
|
|
|
```typescript
|
|
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)?
|