# 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`:
   ```sql
   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()**:
   ```ts
   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:
   ```js
   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:**
```ts
// 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):**
```ts
// 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:secretaria` — `default_provider` mudou de `gemini` para `openai`, `openai_key` atualizada

---

## 📌 Como retomar

1. **Verificar serviços rodando:**
   ```bash
   ss -tlnp | grep -E "8008|4001"   # 8008 = motor, 4001 = alemão
   ```

2. **Verificar telemetria:**
   ```sql
   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.*
