Files
mercado.clube67.com/docs/analise-boilerplate-v22.md
T

16 KiB
Raw Blame History

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

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

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

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

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:

// webhook-receiver.js — handleMessageNew
async function handleMessageNew(data) {
  await execute(`INSERT INTO nw_event_logs ...`)
  // FIM — nunca responde
}

Com padrão do boilerplate:

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:

private async callAI(...): Promise<string> { ... }
// 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:

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

async chat(convId: string, message: string, tools?: Tool[], executor?: ToolExecutor): Promise<string>

As ferramentas que a Ana precisaria para o Alemão:

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.