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
This commit is contained in:
1 parent
f2481cf7e6
commit
4f293dcb3b
34 files changed
+7131
-255
No files matched your search
@@ -0,0 +1,446 @@
|
||||
# Arquitetura: Multi-Agentes Especialistas para ForgeLM
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Sistema de especialistas que analisam e refinam iterativamente as respostas do assistente, utilizando a infraestrutura ForgeAgent existente.
|
||||
|
||||
## 1. Fluxo Conceitual
|
||||
|
||||
```
|
||||
Usuário → ChatView → API Local → Resposta Inicial
|
||||
↓
|
||||
┌───────────────┼───────────────┐
|
||||
↓ ↓ ↓
|
||||
[Especialista 1] [Especialista 2] [Especialista 3]
|
||||
CodeReviewer StyleEditor FactChecker
|
||||
↓ ↓ ↓
|
||||
└───────────────┴───────────────┘
|
||||
↓
|
||||
Resposta Refinada
|
||||
↓
|
||||
ChatView UI
|
||||
```
|
||||
|
||||
## 2. Estrutura de Dados
|
||||
|
||||
### SpecialistDefinition (Configuração)
|
||||
```typescript
|
||||
interface SpecialistDefinition {
|
||||
id: string; // "code-reviewer", "style-editor"
|
||||
name: string; // "Code Reviewer"
|
||||
title: string; // "Revisor de Código"
|
||||
systemPrompt: string; // Prompt específico do especialista
|
||||
activationRules: ActivationRule[]; // Quando ativar
|
||||
modelTier: "fast" | "quality"; // Qual modelo usar
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
}
|
||||
|
||||
type ActivationRule =
|
||||
| { type: "always" }
|
||||
| { type: "contains-code"; languages?: string[] }
|
||||
| { type: "length-threshold"; minChars: number }
|
||||
| { type: "content-pattern"; regex: string }
|
||||
| { type: "manual-toggle"; toggleId: string };
|
||||
```
|
||||
|
||||
### ReviewCycle (Execução)
|
||||
```typescript
|
||||
interface ReviewCycle {
|
||||
id: string; // UUID da revisão
|
||||
originalMessageId: number; // ID da mensagem original
|
||||
originalContent: string; // Resposta inicial do assistente
|
||||
specialists: SpecialistReview[];
|
||||
finalContent: string; // Resposta após todas as revisões
|
||||
status: "running" | "completed" | "error";
|
||||
startedAt: number;
|
||||
completedAt?: number;
|
||||
}
|
||||
|
||||
interface SpecialistReview {
|
||||
specialistId: string;
|
||||
status: "pending" | "thinking" | "completed" | "error";
|
||||
thinking?: string; // Chain of thought
|
||||
suggestions: Suggestion[];
|
||||
applied: boolean; // Se as sugestões foram aplicadas
|
||||
}
|
||||
|
||||
interface Suggestion {
|
||||
type: "improvement" | "correction" | "addition" | "deletion";
|
||||
original?: string; // Texto original
|
||||
replacement?: string; // Texto sugerido
|
||||
explanation: string; // Por que mudar
|
||||
confidence: number; // 0.0 - 1.0
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Componentes Principais
|
||||
|
||||
### A. SpecialistRegistry (src/lib/specialist-registry.ts)
|
||||
Registro central de especialistas disponíveis.
|
||||
|
||||
```typescript
|
||||
class SpecialistRegistry {
|
||||
private specialists: Map<string, SpecialistDefinition> = new Map();
|
||||
|
||||
register(def: SpecialistDefinition): void;
|
||||
unregister(id: string): void;
|
||||
getActiveForContent(content: string, context: Context): SpecialistDefinition[];
|
||||
getAll(): SpecialistDefinition[];
|
||||
}
|
||||
```
|
||||
|
||||
### B. ReviewOrchestrator (src/lib/review-orchestrator.ts)
|
||||
Orquestra o ciclo de revisão.
|
||||
|
||||
```typescript
|
||||
class ReviewOrchestrator {
|
||||
async startReview(
|
||||
messageId: number,
|
||||
content: string,
|
||||
context: MessageContext
|
||||
): Promise<ReviewCycle>;
|
||||
|
||||
private async runSpecialist(
|
||||
cycle: ReviewCycle,
|
||||
specialist: SpecialistDefinition,
|
||||
currentContent: string
|
||||
): Promise<SpecialistReview>;
|
||||
|
||||
private applySuggestions(content: string, review: SpecialistReview): string;
|
||||
|
||||
onProgress(callback: (cycle: ReviewCycle) => void): void;
|
||||
onComplete(callback: (cycle: ReviewCycle) => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
### C. SpecialistService (Backend/Tauri)
|
||||
Comandos Rust para executar revisões sem bloquear a UI.
|
||||
|
||||
```rust
|
||||
#[tauri::command]
|
||||
async fn run_specialist_review(
|
||||
specialist_id: String,
|
||||
content: String,
|
||||
context: MessageContext,
|
||||
server: String,
|
||||
model: String,
|
||||
) -> Result<SpecialistReview, String>;
|
||||
```
|
||||
|
||||
## 4. Integração ao ChatView
|
||||
|
||||
### Modo Síncrono vs Assíncrono
|
||||
|
||||
**Opção A: Assíncrono (Recomendado)**
|
||||
- Resposta inicial aparece imediatamente
|
||||
- Especialistas processam em background
|
||||
- Badge/indicator mostra "Melhorando..."
|
||||
- Quando completo, mensagem é atualizada com versão refinada
|
||||
- Toggle "Agentic" controla se ativa especialistas
|
||||
|
||||
**Opção B: Síncrono**
|
||||
- Aguarda todos os especialistas antes de mostrar
|
||||
- Mais lento mas garante qualidade máxima
|
||||
- Mostra progresso em tempo real (SpecialistCards)
|
||||
|
||||
### Proposta: Híbrido com Streaming
|
||||
|
||||
```typescript
|
||||
// No ChatView
|
||||
const [reviewCycle, setReviewCycle] = useState<ReviewCycle | null>(null);
|
||||
|
||||
// Após receber resposta completa
|
||||
if (agenticMode) {
|
||||
const cycle = await reviewOrchestrator.startReview(
|
||||
messageId,
|
||||
responseContent,
|
||||
{ chatId, previousMessages }
|
||||
);
|
||||
setReviewCycle(cycle);
|
||||
|
||||
// Mostra mensagem inicial imediatamente
|
||||
// Atualiza quando especialistas completam
|
||||
cycle.onProgress((updated) => {
|
||||
setReviewCycle({ ...updated });
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### UI Components
|
||||
|
||||
1. **ReviewProgressIndicator** - Badge mostrando "3/5 especialistas completos"
|
||||
2. **ReviewDiffViewer** - Mostra diff entre original e refinado
|
||||
3. **SpecialistSidebar** - Panel mostrando thinking de cada especialista
|
||||
4. **SuggestionAcceptReject** - Controles para aceitar/rejeitar sugestões
|
||||
|
||||
## 5. Especialistas Padrão
|
||||
|
||||
### 1. CodeReviewer
|
||||
```typescript
|
||||
{
|
||||
id: "code-reviewer",
|
||||
name: "Code Reviewer",
|
||||
title: "Revisor de Código",
|
||||
activationRules: [{ type: "contains-code" }],
|
||||
systemPrompt: `Você é um revisor de código sênior. Analise o código fornecido e sugira melhorias em:
|
||||
- Eficiência e performance
|
||||
- Legibilidade e clareza
|
||||
- Boas práticas e padrões
|
||||
- Possíveis bugs ou edge cases
|
||||
|
||||
Formato de resposta (JSON):
|
||||
{
|
||||
"thinking": "sua análise passo a passo",
|
||||
"suggestions": [
|
||||
{
|
||||
"type": "improvement",
|
||||
"original": "código original",
|
||||
"replacement": "código melhorado",
|
||||
"explanation": "por que melhorar",
|
||||
"confidence": 0.95
|
||||
}
|
||||
]
|
||||
}`
|
||||
}
|
||||
```
|
||||
|
||||
### 2. StyleEditor
|
||||
```typescript
|
||||
{
|
||||
id: "style-editor",
|
||||
name: "Style Editor",
|
||||
title: "Editor de Estilo",
|
||||
activationRules: [{ type: "length-threshold", minChars: 200 }],
|
||||
systemPrompt: `Você é um editor de texto profissional. Melhore a clareza e fluidez do texto mantendo o significado original.
|
||||
|
||||
Foque em:
|
||||
- Clareza e concisão
|
||||
- Fluxo e coesão entre frases
|
||||
- Tom apropriado ao contexto
|
||||
- Eliminação de redundâncias
|
||||
|
||||
Formato de resposta (JSON):
|
||||
{
|
||||
"thinking": "análise do estilo",
|
||||
"suggestions": [...]
|
||||
}`
|
||||
}
|
||||
```
|
||||
|
||||
### 3. FactChecker
|
||||
```typescript
|
||||
{
|
||||
id: "fact-checker",
|
||||
name: "Fact Checker",
|
||||
title: "Verificador de Fatos",
|
||||
activationRules: [{ type: "content-pattern", regex: "\b(sabe|sabia|fato|verdade|estudo|pesquisa|dado|estatística)\b" }],
|
||||
systemPrompt: `Você é um verificador de fatos. Identifique afirmações factuais no texto e avalie:
|
||||
- Se são factualmente corretas (conhecimento até 2024)
|
||||
- Se precisam de citação ou fonte
|
||||
- Se há nuances ou contexto faltante
|
||||
|
||||
NÃO invente informações. Marque como "não verificável" se incerto.
|
||||
|
||||
Formato de resposta (JSON):
|
||||
{
|
||||
"thinking": "análise factual",
|
||||
"suggestions": [
|
||||
{
|
||||
"type": "correction",
|
||||
"original": "afirmação incorreta",
|
||||
"replacement": "versão corrigida ou com ressalvas",
|
||||
"explanation": "por que precisa correção",
|
||||
"confidence": 0.85
|
||||
}
|
||||
]
|
||||
}`
|
||||
}
|
||||
```
|
||||
|
||||
### 4. ToneAdapter
|
||||
```typescript
|
||||
{
|
||||
id: "tone-adapter",
|
||||
name: "Tone Adapter",
|
||||
title: "Adaptador de Tom",
|
||||
activationRules: [{ type: "always" }],
|
||||
systemPrompt: `Você adapta o tom da resposta ao contexto da conversa.
|
||||
|
||||
Analise o histórico recente e ajuste:
|
||||
- Formalidade (técnico vs casual)
|
||||
- Entusiasmo (neutro vs animado)
|
||||
- Detalhamento (resumo vs aprofundado)
|
||||
- Personalização (menção a fatos do usuário)
|
||||
|
||||
Formato de resposta (JSON):
|
||||
{
|
||||
"thinking": "análise do contexto e tom adequado",
|
||||
"suggestions": [...]
|
||||
}`
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Fluxo de Execução
|
||||
|
||||
```typescript
|
||||
// 1. Usuário envia mensagem
|
||||
// 2. Recebe resposta do assistente
|
||||
// 3. Se agenticMode ativo:
|
||||
|
||||
async function processWithSpecialists(messageId: number, content: string) {
|
||||
// Determina quais especialistas ativar
|
||||
const activeSpecialists = specialistRegistry.getActiveForContent(content, context);
|
||||
|
||||
// Cria ciclo de revisão
|
||||
const cycle: ReviewCycle = {
|
||||
id: generateUUID(),
|
||||
originalMessageId: messageId,
|
||||
originalContent: content,
|
||||
specialists: activeSpecialists.map(s => ({
|
||||
specialistId: s.id,
|
||||
status: "pending",
|
||||
suggestions: [],
|
||||
applied: false
|
||||
})),
|
||||
finalContent: content, // Começa com original
|
||||
status: "running",
|
||||
startedAt: Date.now()
|
||||
};
|
||||
|
||||
// Executa sequencialmente (para builds contextuais)
|
||||
let currentContent = content;
|
||||
for (const specialist of activeSpecialists) {
|
||||
updateSpecialistStatus(cycle, specialist.id, "thinking");
|
||||
|
||||
const review = await runSpecialist(specialist, currentContent, context);
|
||||
|
||||
updateSpecialistStatus(cycle, specialist.id, "completed", review);
|
||||
|
||||
// Aplica sugestões automaticamente (pode ser manual no futuro)
|
||||
currentContent = applySuggestions(currentContent, review);
|
||||
cycle.finalContent = currentContent;
|
||||
|
||||
notifyProgress(cycle);
|
||||
}
|
||||
|
||||
cycle.status = "completed";
|
||||
cycle.completedAt = Date.now();
|
||||
|
||||
// Atualiza mensagem no DB com versão refinada
|
||||
await updateMessage(messageId, cycle.finalContent, cycle);
|
||||
|
||||
return cycle;
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Persistência
|
||||
|
||||
### Tabela: specialist_reviews
|
||||
```sql
|
||||
CREATE TABLE specialist_reviews (
|
||||
id TEXT PRIMARY KEY,
|
||||
message_id INTEGER NOT NULL,
|
||||
original_content TEXT NOT NULL,
|
||||
final_content TEXT NOT NULL,
|
||||
specialists_json TEXT NOT NULL, -- SpecialistReview[]
|
||||
status TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
completed_at INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
### Tabela: specialist_definitions (configuração do usuário)
|
||||
```sql
|
||||
CREATE TABLE specialist_definitions (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
system_prompt TEXT NOT NULL,
|
||||
activation_rules_json TEXT NOT NULL,
|
||||
is_enabled BOOLEAN DEFAULT 1,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
);
|
||||
```
|
||||
|
||||
## 8. Configuração do Usuário
|
||||
|
||||
### Settings UI
|
||||
- Lista de especialistas (drag-drop para reordenar)
|
||||
- Toggle individual para cada especialista
|
||||
- Editor de system prompts customizados
|
||||
- Regras de ativação configuráveis
|
||||
|
||||
### Padrões do Sistema
|
||||
```typescript
|
||||
const DEFAULT_SPECIALISTS: SpecialistDefinition[] = [
|
||||
toneAdapter, // Sempre ativo
|
||||
codeReviewer, // Só com código
|
||||
styleEditor, // Textos longos
|
||||
factChecker, // Quando detecta factualidade
|
||||
];
|
||||
```
|
||||
|
||||
## 9. Otimizações de Performance
|
||||
|
||||
1. **Caching**: Especialistas memorizam contexto entre mensagens
|
||||
2. **Lazy Loading**: Só carrega especialistas quando necessário
|
||||
3. **Parallel where safe**: Especialistas independentes rodam em paralelo
|
||||
4. **Early Exit**: Se confiança < threshold, pula para o próximo
|
||||
5. **Model Tier**: Especialistas usam modelos menores/fasters quando possível
|
||||
|
||||
## 10. Considerações de UX
|
||||
|
||||
### Quando Mostrar
|
||||
- ✅ Sempre mostrar resultado original primeiro
|
||||
- ✅ Mostrar progresso discreto (badge, não modal)
|
||||
- ✅ Permitir ver thinking de cada especialista (expansível)
|
||||
- ✅ Permitir toggle on/off global (já existe AgenticToggle)
|
||||
- ✅ Permitir ver diff original vs refinado
|
||||
|
||||
### Quando NÃO Usar
|
||||
- ❌ Não bloquear a UI esperando especialistas
|
||||
- ❌ Não mostrar erros de especialista ao usuário (fail silently)
|
||||
- ❌ Não processar mensagens muito curtas (< 50 chars)
|
||||
- ❌ Não processar se modelo indisponível
|
||||
|
||||
## 11. MVP - Fase 1
|
||||
|
||||
Implementação mínima viável:
|
||||
|
||||
1. **2 especialistas hardcoded**: ToneAdapter (sempre) + CodeReviewer (se código)
|
||||
2. **Execução sequencial síncrona** (simplifica UI)
|
||||
3. **Aplicação automática** de sugestões
|
||||
4. **UI mínima**: Badge "Melhorando..." → "Melhorado ✓" + SpecialistCard expansível
|
||||
5. **Sem persistência** de review cycles (só resultado final)
|
||||
|
||||
### Arquivos a Criar (Fase 1)
|
||||
```
|
||||
src/
|
||||
lib/
|
||||
specialist-definitions.ts # Especialistas padrão
|
||||
review-orchestrator.ts # Lógica de orquestração
|
||||
components/
|
||||
forgeagent/
|
||||
ReviewProgress.tsx # Badge de progresso
|
||||
ReviewDiffViewer.tsx # Diff original vs refinado
|
||||
hooks/
|
||||
useSpecialistReview.ts # Hook para gerenciar ciclo
|
||||
```
|
||||
|
||||
### Arquivos a Modificar
|
||||
```
|
||||
src/components/ChatView.tsx # Integrar ao fluxo de resposta
|
||||
src/components/MessageBubble.tsx # Mostrar badge de revisão
|
||||
src/prompts/specialists/ # Prompts de cada especialista
|
||||
```
|
||||
|
||||
## 12. Próximos Passos
|
||||
|
||||
1. ✅ Validar arquitetura com o usuário
|
||||
2. Implementar Fase 1 (MVP)
|
||||
3. Testar com diferentes modelos (Qwen3 0.8B, 2B, 4B)
|
||||
4. Ajustar prompts baseado em resultados
|
||||
5. Adicionar especialistas adicionais
|
||||
6. Implementar modo paralelo assíncrono
|
||||
7. Adicionar configuração via UI Settings
|
||||
@@ -0,0 +1,498 @@
|
||||
# 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)?
|
||||
Reference in new issue
Block a user