Files

17 KiB
Raw Permalink Blame History

Sprint M2 e M3 — Próximos passos do motor newwhats e plugin Alemão

Contexto: este documento congela o estado em 2026-04-26 após conclusão das Sprints Fase A, Fase B e M1 do motor. Serve para retomar o trabalho sem precisar reler todo o histórico.


📍 Estado atual (o que já está em produção)

Fluxo end-to-end de uma mensagem WhatsApp

WhatsApp ──► motor newwhats (PID 30387, porta 8008)
                    │
                    └─► webhook → alemão (PID 28386, porta 4001)
                            │
                            ├─► [Fase A] smart-router
                            │     ├─ FAQ determinístico (horário, endereço, entrega, pagamento, menu)
                            │     ├─ Lookup direto: cotação #N, pedido #N, PED-XXX, "meus pedidos"
                            │     └─ ~80% das msgs respondidas SEM LLM (0 tokens)
                            │
                            └─► [Fase B] se smart-router miss:
                                  ├─ context-builder enxuto (intencoes, contexto_temporal, consulta_mencionada, produtos_buscados, carrinho_abandonado)
                                  ├─ systemExtra com header [DADOS_REAIS] (grounding rule)
                                  ├─ brain do alemão filtrado por tags (core + intent)
                                  └─ chama motor /api/ext/v1/secretaria/ask
                                        │
                                        ▼
                                  [M1] motor ProtocolEngine
                                        ├─ brain compacto 2018 chars (template OpenAI)
                                        ├─ injeta DATA + PROTOCOLO + brain + contextData + systemExtra + summary
                                        ├─ callAI com max_tokens=250 + cache_control Anthropic
                                        ├─ telemetria gravada em sec_messages.usage_tokens
                                        └─ sumariza a cada 10 trocas com modelo barato

Configuração de chaves (importante)

Onde O que
Dragonfly/Redis key plugin:config:secretaria OpenAI, Gemini, Anthropic, default_provider — fonte da verdade
PostgreSQL sec_agents (motor) Agente "Ana", provider=openai, model=gpt-4o-mini, max_tokens=250
PostgreSQL sec_brain_nodes (motor) 6 nodes template OpenAI (~2000 chars)
PostgreSQL sector_brain_nodes (alemão, sector_id=1) 10 nodes filtráveis por tag (~3400 chars total)
.env do motor NÃO é fonte de OPENAI_API_KEY — é Redis. O que está em .env é ignorado pelo plugin secretaria

Tabelas críticas

Alemão (alemaoconveniencias DB):

  • sector_brain_nodes — brain do bot por intent
  • nw_router_metrics — telemetria local vs LLM
  • nw_auto_replies — log de respostas (smart-router e LLM)
  • nw_event_logs — eventos do motor recebidos

Motor (newwhats DB):

  • sec_agents — agentes IA
  • sec_brain_nodes — brain do motor (com usage_tokens, provider_used, model_used agora)
  • sec_messages — histórico de conversa por protocolo
  • sec_conversations — protocolos abertos
  • sec_brain_nodes_backup_2026_04_26 — backup pré-M1

Métricas de economia já medidas

Cenário Tokens antes Tokens depois Δ
"em que fase esta a cotação #1" ~2000 0 (smart-router) 100%
"qual o horário?" ~2000 0 (smart-router) 100%
"oi tudo bem?" (LLM) — 1ª chamada ~2000 1041 input + 16 output 47%
"oi tudo bem?" (LLM) — 2ª chamada na mesma conversa ~2000 46 efetivos (1024 cache) 97%

🚀 Sprint M2 — Function Calling Externo (callback HTTP)

Objetivo

Permitir que plugins satélites (como o Alemão) registrem tools customizadas no motor. O LLM decide quando chamar; o motor faz HTTP callback no plugin para executar.

Por quê

  • Tokens 60% adicional: hoje injetamos consulta_mencionada, produtos_buscados, etc. upfront no contexto. Com tools, só vai quando o LLM decide pedir
  • Tools versionadas no plugin: cada satélite define suas tools sem mexer no motor
  • Reuso do smart-router: as funções já existem em /sistema-nuvem/plugins/newwhats/smart-router.js (tryLocalReply) — viram a implementação das tools
  • Multi-tenant nato: motor vira gateway LLM puro, plugins ficam responsáveis pelos seus dados

Tools a implementar (lado Alemão)

Tool Endpoint Argumentos Retorno
consultar_pedido POST /api/ai-tools/consultar_pedido { id_or_protocolo: string } { status, fase, pagamento, entrega, total }
buscar_produto POST /api/ai-tools/buscar_produto { query: string, max?: number } [{ nome, preco, sku, estoque }]
meus_pedidos POST /api/ai-tools/meus_pedidos { cpf?: string, telefone?: string } [{ protocolo, status, data, total }]
info_loja POST /api/ai-tools/info_loja { topico: 'horario'|'endereco'|'entrega'|'pagamento' } { texto: string }
criar_carrinho POST /api/ai-tools/criar_carrinho { telefone, itens: [{ sku, qtd }] } { cotacao_id, total }
confirmar_recebimento POST /api/ai-tools/confirmar_recebimento { protocolo } { ok: bool }

Trabalho no motor (plugins/secretaria/)

  1. Schema novo em sec_agents ou sec_external_tools:

    CREATE TABLE sec_external_tools (
      id UUID PRIMARY KEY,
      agent_id UUID REFERENCES sec_agents(id),
      tenant_id TEXT NOT NULL,
      name VARCHAR(80) NOT NULL,
      description TEXT NOT NULL,
      parameters JSONB NOT NULL,           -- JSON Schema para args
      callback_url TEXT NOT NULL,
      auth_header_name VARCHAR(100) DEFAULT 'x-nw-key',
      auth_value TEXT,                     -- integration_key do plugin
      timeout_ms INT DEFAULT 5000,
      enabled BOOLEAN DEFAULT TRUE,
      created_at TIMESTAMPTZ DEFAULT NOW()
    );
    
  2. API admin do motor para CRUD de tools externas:

    • GET /api/secretaria/agents/:id/external-tools
    • POST /api/secretaria/agents/:id/external-tools
    • DELETE /api/secretaria/external-tools/:id
  3. Adicionar external_http no tools.ts (resolveTools): quando tool é externa, gera ToolDef com execute que faz fetch para callback_url

  4. Bridge no ProtocolEngine.chat():

    const externalTools = await this.db('sec_external_tools')
      .where({ agent_id: agent.id, enabled: true })
    const externalToolDefs = externalTools.map(t => ({
      name: t.name, description: t.description, parameters: t.parameters,
      execute: async (args, ctx) => {
        const res = await fetch(t.callback_url, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', [t.auth_header_name]: t.auth_value },
          body: JSON.stringify({ args, chatId: ctx.extChatId, conversationId: ctx.conversationId })
        })
        return res.json()
      }
    }))
    
  5. API ext do motor: POST /api/ext/v1/sec/tools para o plugin satélite registrar tools com sua integration_key

Trabalho no Alemão (sistema-nuvem/)

  1. Novo arquivo plugins/newwhats/ai-tools.js com handlers que reutilizam smart-router:

    router.post('/api/ai-tools/consultar_pedido', verifyMotorAuth, async (req, res) => {
      const { id_or_protocolo } = req.body.args
      const data = await lookupCotacaoOuPedido(parseIntent(id_or_protocolo))
      res.json(data ?? { error: 'não encontrado' })
    })
    
  2. Boot do motor (alemão): registrar suas tools no startup

  3. Remover injection de consulta_mencionada, produtos_buscados do context-builder (vira on-demand)

  4. Manter smart-router para o caminho de 0 tokens (resposta direta sem LLM)

Critérios de sucesso M2

  • Tools registradas no motor via API
  • LLM consegue chamar consultar_pedido quando usuário pergunta sobre pedido
  • Latência adicional do callback < 200ms
  • Tokens médio por chamada cai de ~1000 para ~400
  • Smart-router continua respondendo o caso óbvio antes de chegar ao LLM

Estimativa

  • Motor: 4-6h (schema + API + bridge no ProtocolEngine)
  • Alemão: 3-4h (handlers + integração)
  • Testes + ajustes: 2h
  • Total: ~10h de trabalho focado

🎤 Sprint M3 — Whisper + Vision (mídia)

Objetivo

Aceitar áudio e imagem do cliente. Brasileiro manda muito áudio no WhatsApp; ignorar isso é deixar metade da experiência na mesa.

Whisper — transcrição de áudio

Onde: motor newwhats, hook em MessageHandler quando msg.type === 'AUDIO'.

Fluxo:

WhatsApp Audio → motor baixa via Baileys → ffmpeg converte para mp3
                                                │
                                                ▼
                              OpenAI Whisper (whisper-1) com `language: pt`
                                                │
                                                ▼
                              texto entra no fluxo normal de message.new
                                                │
                                                ▼
                              webhook envia ao alemão como TEXT

Custo: $0.006/minuto. Áudio típico de WhatsApp 30s = 0.003 = R 0,015. Insignificante.

Implementação:

// plugins/secretaria ou novo plugin "transcribe"
async function transcribeAudio(filePath: string, openaiKey: string): Promise<string> {
  const form = new FormData()
  form.append('file', fs.createReadStream(filePath))
  form.append('model', 'whisper-1')
  form.append('language', 'pt')
  const res = await fetch('https://api.openai.com/v1/audio/transcriptions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${openaiKey}` },
    body: form,
  })
  const data = await res.json()
  return data.text
}

Onde plugar no motor: MessageHandler.processIncoming() — antes de salvar messages, se for AUDIO, transcrever e usar o texto como body (manter referência ao áudio original em mediaUrl).

Vision — entender imagens

Casos de uso reais Alemão:

  1. Comprovante de Pix → IA confirma valor + horário, marca pedido como pago
  2. Foto de produto ("é esse aqui que vocês têm?") → IA identifica produto
  3. Foto de receita → fora do escopo desta loja, mas vale para futuros tenants

Como:

  • gpt-4o-mini aceita visão nativamente (messages[].content com image_url)
  • Encoda imagem como base64 ou URL pública
  • Envia junto com a mensagem do usuário

Custo: vision adiciona ~85 tokens por imagem 512x512. ~R$ 0,001 por img. Trivial.

Implementação no motor (callProvider OpenAI):

// Quando há anexo de imagem, mensagem do user vira:
{
  role: 'user',
  content: [
    { type: 'text', text: 'foto que ele mandou' },
    { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64}` } }
  ]
}

Boas práticas que devem entrar nesta sprint

  1. Detecção de comprovante Pix (regex em texto após OCR ou direto via vision):

    • Valor "R$ X,XX"
    • Data/hora
    • "Comprovante", "Pix enviado", "Transação"
    • Quando detectar → tool confirmar_pagamento(protocolo, valor) (parte da M2)
  2. Prevenção de áudio infinito: limitar duração max (60s? 90s?) — áudio de 5min vira cobrança e ruído.

  3. Salvar transcrição junto com o áudio em messages.transcript_text para auditoria.

Critérios de sucesso M3

  • Áudio recebido vira texto e flui normalmente
  • Imagem com comprovante Pix gera transcrição da info chave (valor, data)
  • Custo médio por mensagem com mídia < R$ 0,02
  • Mídia salva em storage (S3 ou local) com referência

Estimativa

  • Whisper: 3-4h (download via Baileys, ffmpeg, integração)
  • Vision: 2-3h (passar image_url no payload do callProvider)
  • Hooks no MessageHandler: 2h
  • Testes: 2h
  • Total: ~10h

💡 Outras melhorias mapeadas (backlog)

Robustez (Sprint M4)

  • Circuit breaker por provider: se OpenAI falhar 5x em 1min, pular esse provider por 30s
  • Streaming response: usar stream: true da API, enviar chunks pro WhatsApp (latência percebida menor)
  • Persist mensagens fechadas: hoje finalizeProtocol deleta sec_messages — mover para arquivo histórico em vez de apagar
  • Health check do motor: endpoint /health que valida providers (chave válida? respondem?) e expõe para monitoramento

Operação

  • Painel admin para editar brain_nodes (alemão e motor): hoje só via SQL ou Dragonfly. Deve haver UI no admin-app
  • Versão do prompt: tabela sec_brain_node_versions para rollback rápido se uma mudança quebrar conversas
  • A/B testing de prompts: 50% dos chats com brain v1, 50% com v2, medir qualidade
  • Dashboard de tokens no admin-app: gráfico diário de custo por tenant/agente
  • Limpeza de código morto: chatbot.service.ts + chatbot.repository.ts (Gemini standalone, sem uso). Toca 4 arquivos: MessageHandler, WhatsAppConnectionManager, chatbot.routes, chatbot.repository

Inteligência (Sprint M5+)

  • RAG com pgvector: embeddings dos brain_nodes e produtos. Busca semântica em vez de tag-matching. Próximo nível de relevância
  • Memória persistente por cliente (mem0-style): "Bruno sempre pede entrega à noite", "Cliente PJ fatura sempre dia 10"
  • Detector de intenção com modelo pequeno local (ex: paraphrase-multilingual-MiniLM via onnxruntime): roteamento sem LLM
  • Multi-agent: separar Aninha (atendimento) de outras personas (financeiro, suporte) com roteamento por contexto

Segurança / compliance

  • Rate limiting por chat: além do anti-loop atual (8 replies/h), proteção contra abuso
  • PII redaction: antes de enviar ao LLM, mascarar CPF/cartão se aparecer no histórico
  • Audit trail: logar prompts completos enviados ao LLM (criptografado, retention configurável) para casos de incidente
  • Termo de uso de IA: notificar cliente que está falando com IA na primeira mensagem

UX

  • Resposta proativa: notificar cliente sem ele perguntar (status pedido mudou, promo do produto que costuma comprar)
  • Carrinho via WhatsApp: usuário adiciona itens conversando, IA confirma e finaliza pedido
  • Reativação inteligente: cliente sumiu há 30 dias com carrinho aberto → mensagem personalizada
  • Multi-idioma automático: detectar idioma do cliente e responder no mesmo (ES, EN, PT)

Telemetria / observabilidade

  • OpenTelemetry traces em cada chamada LLM (provider, latência, tokens, sucesso)
  • Métricas Prometheus expostas em /metrics
  • Alertas: provider down, custo diário > X, taxa de erro > Y
  • Replays: poder "rerodar" uma conversa com prompt novo para ver se a resposta seria melhor

🛠️ Estado dos arquivos modificados (commits implícitos)

Alemão (/home/deploy/projetos/alemaoconveniencias.local/sistema-nuvem/)

  • plugins/newwhats/smart-router.js — NEW (Fase A)
  • plugins/newwhats/context-builder.js — modificado (Fases A, B)
  • plugins/newwhats/webhook-receiver.js — modificado (smart-router integration, brain filter)
  • routes/apiCotacoes.js — modificado (endpoint sem-resposta)
  • jobs/monitorMsgsSemResposta.js — NEW (sprint anterior)

Motor (/home/deploy/projetos/newwhats.local/)

  • plugins/secretaria/brain.ts — refatorado (M1: cache, max_tokens, telemetria, summarize barato)
  • plugins/secretaria/tools.ts — modificado (campo _telemetry no ToolContext)

DB

  • alemao.sector_brain_nodes — coluna tags TEXT[] adicionada, 10 nodes reescritos
  • alemao.nw_router_metrics — tabela nova
  • newwhats.sec_brain_nodes — 6 nodes reescritos (backup em sec_brain_nodes_backup_2026_04_26)
  • newwhats.sec_messages — colunas usage_tokens (jsonb), provider_used, model_used
  • newwhats.sec_agents — coluna max_tokens (default 250)

Redis

  • plugin:config:secretariadefault_provider mudou de gemini para openai, openai_key atualizada

📌 Como retomar

  1. Verificar serviços rodando:

    ss -tlnp | grep -E "8008|4001"   # 8008 = motor, 4001 = alemão
    
  2. Verificar telemetria:

    SELECT provider_used, COUNT(*), AVG((usage_tokens->>'total')::int) AS avg_tokens
    FROM sec_messages WHERE role='assistant' AND usage_tokens IS NOT NULL
    GROUP BY 1;
    
  3. Decidir próxima sprint:

    • M2 = mais ROI (tokens caem outros 60%, qualidade sobe)
    • M3 = mais UX (cliente brasileiro adora áudio/imagem)
    • M4 = robustez para escala (faça quando tiver volume)
  4. Sequência sugerida: M2 → M3 → M4 → M5 (RAG)


Documento gerado em 2026-04-26 após Sprint M1 concluída e validada end-to-end.