chatwoot-develop/.context/planos/jasmine_brain_plan.md

404 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plano Técnico Evolutivo: Cérebro da Jasmine (SDR Agent)
**Versão:** 1.1 (Revisada)
**Data:** 2025-12-27
**Status:** Aprovado para Implementação
---
## Contexto Atual (Já Implementado)
| Componente | Status | Descrição |
| -------------------------------- | ------ | --------------------------------------------------------- |
| `Jasmine::Collection` | ✅ | Coleções com visibilidade (private/shared) |
| `Jasmine::Document` | ✅ | Documentos com status (pending/processing/indexed/failed) |
| `Jasmine::DocumentChunk` | ✅ | Chunks com embeddings vetoriais (pgvector) |
| `Jasmine::EmbeddingService` | ✅ | Chunking + OpenAI embeddings |
| `Jasmine::SemanticSearchService` | ✅ | Busca vetorial com **cosine distance** (`<=>`) |
| `Jasmine::InboxConfig` | ✅ | Configurações por inbox |
| UI Dashboard | ✅ | Gestão de coleções e documentos |
> **Importante:** O backend usa **cosine distance** (quanto menor, mais similar). Threshold padrão = 0.35.
---
## 1⃣ Arquitetura do Cérebro da Jasmine
### Diagrama de Camadas
```
┌─────────────────────────────────────────────────────────────────┐
│ MENSAGEM DO CLIENTE │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Jasmine::BrainService │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ IntentDetector → Detectar intenção (customizável) │ │
│ │ StrategyDecider → Decidir: Direto | RAG | (Tool) │ │
│ │ PromptAssembler → Montar contexto final │ │
│ │ StateUpdater → Atualizar estado do lead │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ SYSTEM │ │ PLAYBOOK │ │ ESTADO │
│ PROMPT │ │ SDR │ │ DO LEAD │
│ (fixo, │ │ (editável │ │ (memória │
│ curto) │ │ por inbox) │ │ curta) │
└─────────────┘ └─────────────────┘ └─────────────┘
▼ (OBRIGATÓRIO para preços/políticas)
┌─────────────────┐
│ BASE VETORIAL │
│ (RAG) │
│ Preços, FAQs, │
│ Políticas... │
└─────────────────┘
```
### O que entra em cada camada
| Camada | Conteúdo | Tamanho | Frequência |
| ------------------ | -------------------------------- | ----------- | ----------- |
| **System Prompt** | Identidade, tom, regras | ~500 tokens | Raro |
| **Playbook SDR** | Script de vendas | ~800 tokens | Ocasional |
| **Estado do Lead** | Nome, etapa, qualificação | ~200 tokens | Cada msg |
| **Histórico** | **Últimas 2-3 mensagens apenas** | ~300 tokens | Cada msg |
| **RAG** | Preços, políticas, FAQs | Ilimitado | Condicional |
### Regra de Ouro: Fonte da Verdade
> ⚠️ **REGRA CRÍTICA:**
>
> Se houver contexto RAG válido (abaixo do threshold de distance), a resposta **DEVE** se basear **exclusivamente** nesse conteúdo.
>
> É **PROIBIDO** complementar com conhecimento externo do modelo para:
>
> - Preços
> - Políticas
> - Regras operacionais
> - Horários
> - Disponibilidade
---
## 2⃣ Modelagem Mínima Necessária
### Alterações em `jasmine_inbox_configs`
```ruby
# Campos existentes (manter)
:enabled # boolean
:settings # jsonb
# Novos campos (adicionar)
:system_prompt # text - Prompt base
:playbook_prompt # text - Script SDR editável
:rag_distance_threshold # float, default 0.35 - Distance máxima (menor = mais similar)
:rag_max_results # integer, default 3
:model # string, default 'gpt-4o-mini'
:temperature # float, default 0.7
:intent_keywords # jsonb - Dicionário de intenções customizado por inbox
```
### Estado do Lead/Conversa
Usar `conversation.custom_attributes['jasmine_state']`:
```ruby
{
"stage": "qualification",
"qualified": false,
"collected_info": {
"name": "João",
"company": "Acme"
},
"rag_queries_count": 2,
"last_intent": "price_question",
"updated_at": "2025-12-27T10:00:00Z"
}
```
**Limites e Limpeza:**
- Tamanho máximo: 4KB
- Limpar ao fechar conversa (resolved/pending)
- Se `rag_queries_count > 5` na mesma conversa: mudar estratégia
---
## 3⃣ Serviço Central: `Jasmine::BrainService`
### Componentes Lógicos (mesmo arquivo no V1)
```ruby
class Jasmine::BrainService
# Componente 1: Detectar intenção
class IntentDetector
# Usa dicionário customizado por inbox
end
# Componente 2: Decidir estratégia
class StrategyDecider
# RAG obrigatório vs opcional
end
# Componente 3: Montar prompt
class PromptAssembler
# Limite de histórico: 2-3 mensagens
end
# Componente 4: Atualizar estado
class StateUpdater
# Limite de tamanho: 4KB
end
end
```
### Fluxo Passo a Passo
```
1. CARREGAR CONFIGURAÇÕES
└─ inbox.jasmine_config
2. CARREGAR ESTADO DO LEAD
└─ conversation.custom_attributes['jasmine_state']
3. DETECTAR INTENÇÃO (IntentDetector)
│ Usa dicionário customizado por inbox:
│ {
│ "price_question": ["preço", "valor", "quanto custa", "quanto fica",
│ "tabela", "promoção", "pernoite"],
│ "info_request": ["como funciona", "detalhe", "explica", "suíte"],
│ "policy": ["horário", "check-in", "política", "regra"],
│ "greeting": ["oi", "olá", "bom dia"],
│ "objection": ["caro", "não sei", "preciso pensar"]
│ }
└─ Retorna: intent_type + confidence
4. DECIDIR ESTRATÉGIA (StrategyDecider)
├─ RAG OBRIGATÓRIO quando:
│ • intenção = price_question
│ • intenção = info_request
│ • intenção = policy
│ (Mesmo que LLM "ache que sabe")
├─ RAG OPCIONAL:
│ • intenção = objection (buscar argumentos)
│ • intenção = general
└─ SEM RAG:
• intenção = greeting
• confirmações simples ("ok", "entendi")
5. BUSCAR RAG (se necessário)
│ SemanticSearchService.search(
│ query: message.content,
│ inbox: inbox,
│ limit: rag_max_results,
│ threshold: rag_distance_threshold # Cosine distance!
│ )
└─ Se rag_queries_count > 5:
mudar para "Vou confirmar com a equipe"
6. MONTAR PROMPT (PromptAssembler)
│ [System Prompt]
│ [Playbook SDR]
│ [Estado do Lead]
│ [Contexto RAG] ← SE PRESENTE, É FONTE DA VERDADE
│ "Use APENAS estas informações para responder..."
│ [Histórico: ÚLTIMAS 2-3 MENSAGENS APENAS]
│ [Mensagem Atual]
7. CHAMAR LLM
└─ RubyLLM.chat(prompt, model: config.model)
8. ATUALIZAR ESTADO (StateUpdater)
│ - Incrementar rag_queries_count
│ - Salvar nova etapa
│ - Verificar limite de 4KB
└─ Se conversation fechada: limpar estado
9. RETORNAR RESPOSTA
```
---
## 4⃣ Regras de Uso da Base Vetorial (RAG)
### RAG OBRIGATÓRIO
| Intenção | Motivo |
| ---------------- | --------------------------- |
| `price_question` | Evitar alucinação de preços |
| `info_request` | Dados factuais do produto |
| `policy` | Regras operacionais |
### RAG PROIBIDO
| Situação | Motivo |
| ----------------------------- | ---------------- |
| Saudação ("oi", "olá") | Desperdiça busca |
| Confirmação ("ok", "entendi") | Não há pergunta |
### Tratamento de Resultados
```
SE distance > threshold (resultado fraco):
└─ IGNORAR resultado
└─ Responder: "Vou verificar isso com a equipe e já retorno."
SE nenhum resultado:
└─ Mesma resposta de "resultado fraco"
SE múltiplos resultados válidos:
└─ Concatenar top N
└─ LLM sintetiza (mas não inventa)
SE resultado válido (distance ≤ threshold):
└─ Incluir no contexto como FONTE DA VERDADE
└─ LLM responde APENAS com base nesse conteúdo
```
### Proteção contra Loops
```
SE rag_queries_count > 5 na mesma conversa:
└─ Parar de buscar RAG
└─ Responder: "Deixa eu confirmar esses detalhes com a equipe."
└─ (Opcional) Escalar para humano
```
---
## 5⃣ Playbook SDR (Conceito)
O **Playbook** é texto curto (~500-800 palavras) que define o script de vendas.
### Estrutura
```markdown
## Objetivo
Qualificar leads e agendar visita/reserva.
## Etapas
1. ABERTURA: Cumprimentar, perguntar nome
2. DESCOBERTA: Entender necessidade
3. QUALIFICAÇÃO: Data, horário, preferências
4. APRESENTAÇÃO: Mostrar opções relevantes
5. OBJEÇÕES: Tratar dúvidas
6. FECHAMENTO: Confirmar reserva
## Perguntas Obrigatórias
- Qual seu nome?
- Para quando seria?
- Quantas pessoas?
- Prefere período diurno ou noturno?
## Objeções Comuns
- "Está caro" → Mostrar valor/diferencial
- "Preciso pensar" → Oferecer info adicional
## Regras
- NUNCA inventar preços
- SEMPRE usar preços da base de conhecimento
- Se não souber, perguntar à equipe
```
---
## 6⃣ Preparação para Ferramentas (Plano Futuro)
> ⚠️ **NÃO implementar agora. Apenas preparar arquitetura.**
### Regra de Precedência
```
Tools críticas SEMPRE têm precedência sobre resposta livre.
Exemplo: Se existe tool "verificar_disponibilidade":
1. BrainService detecta intenção = availability_check
2. ANTES de chamar LLM, executa tool
3. Resultado da tool entra no contexto
4. LLM formula resposta com dado real
```
### Arquitetura Preparada
O `StrategyDecider` já deve prever:
```ruby
def decide
case intent
when :availability_check
:execute_tool # ← FUTURO
when :price_question
:rag_required
when :greeting
:direct_response
end
end
```
---
## 7⃣ Critérios de Aceite
| # | Critério | Prioridade |
| --- | ------------------------------------------ | ----------- |
| 1 | System prompt ≤ 500 tokens | Alta |
| 2 | Playbook editável por inbox | Alta |
| 3 | RAG obrigatório para preços/políticas | **Crítico** |
| 4 | Threshold em **distance** (não similarity) | Alta |
| 5 | Histórico limitado a 2-3 mensagens | Alta |
| 6 | Estado do lead ≤ 4KB, limpar ao fechar | Média |
| 7 | Proteção contra loops (rag_queries > 5) | Média |
| 8 | Intenções customizáveis por inbox | Média |
| 9 | Fonte da verdade: RAG prevalece sobre LLM | **Crítico** |
| 10 | Arquitetura não bloqueia tools futuras | Média |
---
## Próximos Passos (Após Aprovação)
1. **Migration:** Adicionar campos em `jasmine_inbox_configs`
2. **Service:** Criar `Jasmine::BrainService` com componentes separados
3. **Intent Keywords:** Seed inicial de keywords por tipo de negócio
4. **UI:** Campos para System Prompt, Playbook, Threshold
5. **Hook:** Integrar com incoming messages
6. **Testes:** Specs para cada decisão
---
## Changelog
| Versão | Data | Mudanças |
| ------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1.0 | 2025-12-27 | Versão inicial |
| 1.1 | 2025-12-27 | Ajustes: distance threshold, componentes separados, dicionário customizável, RAG obrigatório, fonte da verdade, limites de histórico/estado, proteção loops |
**Status: APROVADO PARA IMPLEMENTAÇÃO**