# Análise: Boilerplate Secretária Virtual × NewWhats v2.2 > Análise técnica real — comparando o boilerplate enviado com a arquitetura atual do motor NewWhats > e identificando o que cada Frente v2.2 ganha (ou não) com esse padrão. --- ## TL;DR — Veredicto em 3 linhas O boilerplate implementa **Tool Use** (IA chama funções em tempo real). O motor atual implementa **Knowledge Node** (dados pré-carregados no system prompt). **São complementares, não concorrentes — e o motor está faltando o Tool Use.** --- ## 1. O que o boilerplate faz certo ### ✅ Segurança: patientId vem da sessão, nunca da IA ```javascript // patientId sempre da sessão — a IA nunca pode sobrescrever isso async function executeTool(toolName, toolInput, patientId) { ... } // No endpoint: const patient = req.session.patient // ← fonte da verdade const { reply } = await runAgentLoop(messages, patient) ``` **Impacto:** A IA pode pedir "cancele o agendamento 999" de outro paciente, mas o `WHERE patient_id = ?` garante que só o paciente da sessão é afetado. Isso é o padrão correto de segurança com tool use. ### ✅ IA nunca toca o banco — executa via função intermediária A IA solicita `{ name: 'cancel_appointment', input: { appointment_id: 42 } }`. O servidor executa. A IA nunca tem credenciais, nunca faz queries diretas. ### ✅ Agent loop: multi-step tool chains ```javascript while (true) { const response = await anthropic.messages.create({ tools: TOOLS, messages }) if (response.stop_reason === 'end_turn') return reply if (response.stop_reason === 'tool_use') { // executa todas as tools solicitadas // devolve resultados e volta ao loop messages.push({ role: 'user', content: toolResults }) } } ``` Isso permite: `list_doctors` → apresenta ao usuário → `check_availability` → `create_appointment` em uma única conversa, sem o usuário perceber que são 3 chamadas de API. ### ✅ Confirmação antes de ação destrutiva O system prompt exige confirmação explícita antes de cancelar. E o `cancel_appointment` re-verifica `WHERE patient_id = ?` como segunda camada de segurança. ### ✅ System prompt como "contrato" da IA As regras absolutas são claras, específicas e mensuráveis: - "NUNCA confirma agendamento sem check_availability" - "NUNCA discute temas fora do escopo" - "NUNCA pede CPF ou dados bancários" --- ## 2. O que está faltando / gaps do boilerplate ### ❌ Zero prompt caching Cada request reconstrói o system prompt + histórico do zero. Com Claude, o system prompt idêntico entre requests ativa cache automático de prefix. O boilerplate não usa `cache_control: { type: "ephemeral" }` nos blocos de sistema. **Custo real:** Um system prompt de 800 tokens × 100 conversas/dia = 80.000 tokens de input pagos inteiramente toda vez. Com cache, ~90% vira cache hit (5× mais barato). ### ❌ Histórico em RAM (express-session em memória) ```javascript req.session.messages = updatedMessages.slice(-50) ``` - Reiniciar o servidor = perder todas as conversas ativas - Não escala horizontalmente (sem Redis/banco) - Slice(-50) é bruto — 50 mensagens podem ser 40.000 tokens se forem longas **O motor faz certo:** persiste em `sec_conversations` + `sec_messages` no banco. ### ❌ Sem fallback de provider Se a API key da Anthropic falhar → a secretária morre. O motor tem a chain `openai → gemini → anthropic → ollama`. ### ❌ Sem limite de iterações no agent loop ```javascript while (true) { ... } // pode rodar para sempre ``` Se a IA entrar em loop (chama tool → recebe erro → chama tool de novo), nunca sai. Falta: `if (iteration > MAX_ITER) throw new Error('Loop de ferramentas excedido')` ### ❌ Sem multi-tenant Uma instância, um banco de dados de clínica. Não serve para SaaS. O motor tem `tenantId` em todas as queries e usa `ext_chat_id = {tenantId}:{chatId}`. ### ❌ Sem sumarização automática Histórico só cresce (limitado por slice bruto). O motor sumariza a cada 10 trocas e descarta mensagens antigas, mantendo o contexto leve. ### ❌ Sem webhook — só polling request/response O boilerplate assume que o humano digita no browser. Para WhatsApp, o fluxo é invertido: o motor recebe webhook e precisa chamar o runAgentLoop de forma assíncrona, depois enviar a resposta proativamente. --- ## 3. A diferença arquitetural central ``` BOILERPLATE MOTOR ATUAL (newwhats) ───────────────────────────── ────────────────────────────── Pattern: Tool Use (runtime) Pattern: Knowledge Node (RAG) IA decide quando buscar dados → Dados pré-carregados no prompt Queries em tempo real por conv → sync-knowledge.js roda periodicamente +1-3 chamadas API por mensagem → Contexto maior, 0 chamadas extras Dados sempre frescos → Dados podem ter até 1h de delay Conhece dados do CLIENTE → Conhece catálogo GERAL da empresa ``` ### O que cada abordagem responde bem | Pergunta do cliente | Knowledge Node | Tool Use | |---|---|---| | "Quais planos do clube odonto?" | ✅ (sync-knowledge) | funciona, mas desnecessário | | "Quais parceiros aceitam meu plano?" | ✅ (sync-knowledge) | funciona | | "Tenho promoção ativa?" | ✅ (sync-knowledge) | funciona | | **"Quais são meus números da rifa?"** | ❌ **não sabe** | ✅ lookup por telefone | | **"Meu plano está ativo?"** | ❌ **não sabe** | ✅ lookup por CPF | | **"Minha candidatura foi aprovada?"** | ❌ **não sabe** | ✅ lookup por CPF | | **"Quando é meu próximo vencimento?"** | ❌ **não sabe** | ✅ lookup por CPF | **Conclusão:** O motor conhece o catálogo do Alemão, mas é cego para dados individuais do cliente. O boilerplate mostra exatamente o que falta. --- ## 4. Mapeamento por Frente v2.2 ### Frente 1 — Gerenciamento de Números ← **não usa o boilerplate** O boilerplate é para chat de clínica, não para gerenciar instâncias WhatsApp. Porém o padrão de segurança (`phone from webhook, never from AI`) é o mesmo princípio que usaremos na auto-resposta: o número de origem vem do evento, nunca da IA. **Status:** em implementação (modais AddNumber/EditNumber já adicionados no WhatsAppInbox.tsx). --- ### Frente 2 — Auto-resposta ← **o boilerplate É o que falta** O `runAgentLoop` + `executeTool` é exatamente o núcleo que o webhook-receiver precisa. **Hoje:** ```javascript // webhook-receiver.js — handleMessageNew async function handleMessageNew(data) { await execute(`INSERT INTO nw_event_logs ...`) // FIM — nunca responde } ``` **Com padrão do boilerplate:** ```javascript async function handleMessageNew(data) { await execute(`INSERT INTO nw_event_logs ...`) const msg = data.message if (msg.fromMe) return // nunca responder a si mesmo // 1. Verifica role do número (notificacoes = nunca auto-responde) const numInfo = await getNwNumber(data.instanceId) if (!numInfo || numInfo.role === 'notificacoes') return if (auto_reply_mode === 'off') return // 2. Identifica cliente pelo telefone const phone = data.chatId.replace('@s.whatsapp.net', '') const clientCtx = await fetch(`http://localhost:3000/nw/cliente?telefone=${phone}`) // 3. Busca/cria conversa no motor — usa ext_chat_id = chatId const conv = await motorFetch('POST', '/secretaria/ask', { chatId: data.chatId, message: msg.body, contactName: msg.pushName ?? phone, }) // motor.brain.chat() já executa o agent loop internamente // 4. Envia resposta via motor await motorFetch('POST', `/inbox/${data.chatId}/send`, { text: conv.reply }, useIntegKey) } ``` **Mas há um problema crítico detectado:** O `/secretaria/ask` do motor chama `brain.chat()` que é **somente texto** — não tem tool use. A IA não pode buscar dados do cliente em tempo real. Quando o cliente perguntar "meus números da rifa" a Ana vai responder "acesse o site" porque não tem como saber. --- ### Frente 3 — Sync Knowledge ← **complementar ao boilerplate** O boilerplate usa o banco direto (tool `list_doctors`). O sync-knowledge.js usa o banco para construir o system prompt. São estratégias diferentes para o mesmo problema: ``` Boilerplate approach: sync-knowledge approach: list_doctors como tool → médicos/parceiros no knowledge node resultado por request → resultado atualizado de hora em hora +tokens de tool call → +tokens no system prompt sempre atualizado → atraso de até 1h ``` **Para o Alemão, o melhor é:** - `sync-knowledge` para catálogo (planos, parceiros, promoções, vagas) ← já feito - tool use para dados do cliente (tickets, memberships, candidaturas) ← falta **Status:** botão de sync no admin ainda não implementado (simples, próximo passo). --- ### Frente 4 — Escalação ← **o boilerplate mostra o anti-padrão** O boilerplate usa system prompt para definir regras de escalação: ``` "NUNCA realize ações destrutivas sem confirmação" ``` O motor já tem o nó `escalation` com o mesmo conceito, porém mais estruturado. O que falta no motor (e o boilerplate não resolve) é o **lado operacional**: detectar `[ESCALAR]` na resposta da IA e notificar o humano no painel. O boilerplate não implementa escalação porque assume atendimento 100% humano de qualquer forma (login no browser = humano já presente). Para WhatsApp assíncrono, escalação é crítica. --- ### Frente 5 — Métricas ← **o boilerplate tem zero** Apenas `console.log` dos tool calls. Sem persistência, sem dashboard. O `nw_event_logs` já é a base certa para métricas. Quando a auto-resposta for implementada, adicionar `nw_auto_replies` resolve as métricas. --- ## 5. O gap mais crítico identificado: Motor sem Tool Use O motor atual em `brain.ts`: ```typescript private async callAI(...): Promise { ... } // Retorna sempre string — sem suporte a tool_use ``` Todos os providers são chamados sem `tools: []`. O motor nunca vai receber `stop_reason: 'tool_use'` porque nunca passa ferramentas para a IA. **Para a Ana ser realmente útil individualmente, o motor precisa evoluir:** ```typescript // Hoje: brain.chat() → string const reply = await brain.chat(convId, message) // Precisaria: brain.chat() com tool loop (como o boilerplate) interface Tool { name: string; description: string; input_schema: object } interface ToolExecutor { (name: string, input: any, context: ChatContext): Promise } async chat(convId: string, message: string, tools?: Tool[], executor?: ToolExecutor): Promise ``` As ferramentas que a Ana precisaria para o Alemão: ```javascript const ALEMAO_TOOLS = [ { name: 'buscar_cliente', description: 'Busca dados do cliente pelo telefone. Retorna tickets da rifa, clubes ativos e candidaturas.', input_schema: { type: 'object', properties: { telefone: { type: 'string' } }, required: ['telefone'] } }, { name: 'buscar_clube', description: 'Retorna plano ativo do cliente, dependentes e parceiros credenciados.', input_schema: { type: 'object', properties: { cpf: { type: 'string' } }, required: ['cpf'] } }, { name: 'buscar_candidaturas', description: 'Retorna candidaturas do cliente com status atual.', input_schema: { type: 'object', properties: { cpf: { type: 'string' } }, required: ['cpf'] } }, ] // Executor: chama as rotas /nw/* já existentes no satélite ``` Essas rotas `/nw/*` **já existem** no satélite. O problema é que a Ana não consegue chamá-las. --- ## 6. Resumo executivo — O que fazer com isso ### Aproveitar do boilerplate para v2.2 | O quê | Onde aplicar | Prioridade | |---|---|---| | Agent loop (while + tool_use) | webhook-receiver.js (Frente 2) | 🔴 Alta | | Security pattern (phone from webhook only) | webhook-receiver.js (Frente 2) | 🔴 Alta | | Max iterations guard | webhook-receiver.js anti-loop | 🔴 Alta | | Tool definitions ALEMAO_TOOLS | motor brain.ts (evolução futura) | 🟡 Média | | Confirmação antes de ação destrutiva | já no nó escalation do motor | ✅ Já existe | ### NÃO aproveitar do boilerplate | O quê | Motivo | |---|---| | express-session em memória | motor já usa banco (correto) | | Histórico slice(-50) | motor já sumariza (correto) | | Single provider | motor já tem fallback chain (correto) | | MySQL | projeto usa PostgreSQL | | Sem multi-tenant | motor já tem tenantId (correto) | ### Sequência correta para Frente 2 ``` 1. webhook-receiver.js: verificar role do número (notificacoes = skip) 2. webhook-receiver.js: chamar POST /api/secretaria/ask (motor) → motor já faz: lookup/cria conversa, chama brain.chat() → brain.chat() responde com texto (sem tool use por ora) 3. webhook-receiver.js: enviar reply via POST /api/ext/v1/inbox/:chatId/send 4. Resultado: Ana responde perguntas gerais (catálogo) mas não dados individuais 5. (Evolução): adicionar tool use no brain.ts → Ana passa a responder "seus tickets são 003, 047, 112" → Usando as rotas /nw/* já existentes como tool executors ``` A Frente 2 básica (sem tool use) já resolve 70% dos casos de uso. A evolução com tool use resolve os 30% restantes (dados pessoais do cliente). --- ## 7. Arquitetura alvo — Sistema completo ``` ┌─────────────────────────────────────┐ │ WhatsApp (cliente) │ └──────────────┬──────────────────────┘ │ mensagem ▼ ┌─────────────────────────────────────┐ │ Motor NewWhats (webhook) │ │ POST /api/webhook/newwhats │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ webhook-receiver.js │ │ 1. verifica role do número │ │ 2. verifica auto_reply_mode │ │ 3. cooldown / anti-loop │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ POST /api/secretaria/ask │ │ Motor: cria/retoma conversa │ │ brain.chat(convId, message) │ └──────────────┬──────────────────────┘ │ [futuro: tool use] ┌───────────────────────►│◄───────────────────────┐ │ │ │ ┌─────────▼──────────┐ ┌────────▼──────────┐ ┌────────▼──────────┐ │ Knowledge Nodes │ │ /nw/cliente │ │ /nw/clube │ │ (sync-knowledge) │ │ /nw/candidaturas │ │ /nw/sorteio │ │ planos, parceiros │ │ /nw/vagas │ │ /nw/promocoes │ │ promoções, vagas │ │ (dados pessoais) │ │ (dados pessoais) │ └────────────────────┘ └───────────────────┘ └───────────────────┘ (RAG) (Tool Use) (Tool Use) dados estáticos dados individuais dados individuais ``` O boilerplate mostra como construir a metade direita do diagrama. O motor já tem a metade esquerda funcionando. **Frente 2 une as duas.**