# 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** ✅