14 KiB
14 KiB
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
# 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']:
{
"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 > 5na mesma conversa: mudar estratégia
3️⃣ Serviço Central: Jasmine::BrainService
Componentes Lógicos (mesmo arquivo no V1)
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
## 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:
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)
- Migration: Adicionar campos em
jasmine_inbox_configs - Service: Criar
Jasmine::BrainServicecom componentes separados - Intent Keywords: Seed inicial de keywords por tipo de negócio
- UI: Campos para System Prompt, Playbook, Threshold
- Hook: Integrar com incoming messages
- 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 ✅