From 4becfd0a57849b515de469ce4a99952f3d7e55b1 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Sun, 19 Apr 2026 07:44:26 -0300 Subject: [PATCH] fix(captain-memory): strict taxonomy definitions in ExtractionService prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real-world test revealed the LLM extractor (gpt-4o-mini) was using type labels too loosely: a customer's QUESTION about parking ("tem estacionamento?") was classified as 'reclamacao'. Similarly cortesia generica ("obrigado") was becoming 'feedback_positivo', and transactional events (CPF informed, reservation made) were becoming memories when they should be ignored. Rewrote build_prompt with: - Per-type strict definition (what it IS) - YES/NO examples for each of the 9 types, with the most common pitfalls explicitly shown as NO - 7 absolute rules, including: questions are never complaints, generic courtesy is never feedback, agent actions are never customer memory, transactional events are not long-term facts - Confidence threshold guidance (>=0.9 only if totally explicit, 0.7-0.89 for strong inference, <0.7 discard) - "If in doubt, discard — quality > quantity. Most transactional conversations should return empty facts list" Existing 9 specs still pass (stub call_llm, so prompt changes don't affect unit test assertions). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../contact_memories/extraction_service.rb | 95 +++++++++++++++---- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/enterprise/app/services/captain/contact_memories/extraction_service.rb b/enterprise/app/services/captain/contact_memories/extraction_service.rb index f87bb9abd..baab96c02 100644 --- a/enterprise/app/services/captain/contact_memories/extraction_service.rb +++ b/enterprise/app/services/captain/contact_memories/extraction_service.rb @@ -36,28 +36,91 @@ class Captain::ContactMemories::ExtractionService def build_prompt <<~PROMPT - Você é um analista que extrai FATOS MEMORÁVEIS de uma conversa de WhatsApp entre um hóspede e um hotel. + Você é um analista conservador que extrai apenas FATOS MEMORÁVEIS de uma conversa de WhatsApp entre um hóspede e um hotel. Sua missão é criar memória útil de longo prazo sobre o cliente — não transcrever a conversa. - Taxonomia (SÓ use estes tipos, caso contrário descarte o fato): - #{Captain::ContactMemory::MEMORY_TYPES.join(', ')} + ## TAXONOMIA ESTRITA - Para cada fato, retorne JSON com: - - memory_type (um dos tipos acima) - - content (frase curta, português, max 1000 chars) - - evidence (trecho LITERAL da conversa que sustenta o fato — obrigatório) - - confidence (0.0 a 1.0) - - scope ('global' na maioria dos casos; 'unit:' só se o fato for operacional de uma unidade específica) + Use apenas estes 9 tipos. Cada tipo tem definição precisa + exemplos do que SIM e do que NÃO é. - Regras INVIOLÁVEIS: - 1. Se não houver evidência textual clara, NÃO extraia o fato. - 2. Máximo 5 fatos por conversa. Extraia só os realmente memoráveis. - 3. Se a conversa não tem nada memorável, retorne {"facts": []}. - 4. Nunca invente fatos. Se em dúvida, descarte. + 1. **preferencia** — algo que o cliente EXPLICITAMENTE PREFERE (suíte, horário, estilo, preço, comida). + SIM: "Prefiro sempre a Stilo com hidro" + SIM: "Gosto de chegar tarde, depois das 22h" + NÃO: "Quero reservar uma suíte" (é pedido pontual, não preferência recorrente) + NÃO: "Escolheu a Alexa dessa vez" (foi uma escolha, não preferência declarada) + + 2. **data_comemorativa** — data anual recorrente declarada pelo cliente (aniversário dele/esposa/casamento, Dia dos Namorados que ele celebra aqui, etc). + SIM: "É nosso aniversário de casamento dia 14/02" + SIM: "Aniversário da minha esposa em 3/8" + NÃO: "Quero reservar dia 20/04" (é data da reserva, não data comemorativa) + NÃO: "Ontem foi meu aniversário" (só comemora aqui se mencionar que TRADICIONALMENTE vem) + + 3. **vinculo_social** — relacionamento social duradouro com outra pessoa mencionada (esposa, marido, filho, amigo, colega). + SIM: "Sempre venho com minha esposa Mariana" + SIM: "Indicado pelo Márcio, meu amigo" + NÃO: "Obrigado" (não é vínculo) + NÃO: "Expressou gratidão ao hotel" (isso é gentileza, não vínculo social) + + 4. **padrao_comportamental** — padrão OBSERVADO/DECLARADO em múltiplas visitas ou que ele menciona como hábito. + SIM: "Sempre chego tarde, entre 23h e meia-noite" + SIM: "Costumo ficar só o pernoite" + NÃO: "Vou chegar às 22h hoje" (é horário pontual dessa reserva) + + 5. **reclamacao** — queixa EXPLÍCITA sobre algo que desagradou/frustrou/causou problema, com sentimento negativo claro. + SIM: "O ar-condicionado estava barulhento demais, não dormi direito" + SIM: "Achei péssimo o atendimento da recepcionista X" + NÃO: "Tem estacionamento?" (é PERGUNTA, não reclamação) + NÃO: "Vocês aceitam pet?" (é dúvida) + NÃO: "Poderia ter chegado mais cedo" (é observação, sem queixa) + + 6. **feedback_positivo** — elogio EXPLÍCITO sobre pessoa, serviço ou experiência específica. + SIM: "A Dona Cida do café é maravilhosa, sempre muito atenciosa" + SIM: "Foi a melhor estadia que tive, amei a limpeza" + NÃO: "Obrigado" ou "Agradeceu o atendimento" (cortesia genérica, não elogio memorável) + NÃO: "Foi incentivado a aproveitar o aniversário" (isso é resposta do atendente, não fala do cliente) + + 7. **restricao** — restrição médica, alérgica, dietética ou de mobilidade que afeta hospedagem. + SIM: "Sou alérgico a amendoim" + SIM: "Não posso subir escadas, preciso de quarto térreo" + NÃO: "O hotel não aceita pet" (é regra do hotel, não restrição do cliente) + NÃO: "Hoje vou comer só frutas" (preferência pontual, não restrição médica) + + 8. **vinculo_comercial** — relação comercial/profissional declarada que afeta tratamento (funcionário de empresa parceira, influenciador, agente de viagem, desconto corporativo). + SIM: "Sou funcionário da Caixa, tenho desconto corp" + SIM: "Trabalho com turismo, venho avaliar o hotel" + NÃO: "Fez uma reserva para X" (é reserva, não vínculo comercial) + NÃO: "Já veio 3 vezes" (frequência não é vínculo comercial) + + 9. **contexto_pessoal** — fato pessoal relevante que afeta o atendimento (profissão, local onde mora, situação de vida). + SIM: "Sou caminhoneiro, viajo sempre, preciso hospedagem rápida" + SIM: "Moro em Luziânia, venho no fim de semana" + NÃO: "Me chamo Rodrigo" (nome vai nos dados cadastrais, não é memória) + NÃO: "Informou CPF e email" (dados cadastrais, não memória) + NÃO: "Está interessado em reservar" (intenção da conversa, não perfil) + + ## REGRAS ABSOLUTAS (violação = FATO DESCARTADO) + + 1. **Evidência OBRIGATÓRIA**: cada fato precisa de um trecho LITERAL da conversa. Se não tem trecho claro, não extraia. + 2. **Perguntas/dúvidas NÃO são reclamação nem memória**: se o cliente fez uma pergunta ("tem X?", "aceita Y?"), isso é informação que ele queria, não fato sobre ele. + 3. **Cortesia genérica NÃO é feedback**: "obrigado", "tá bom", "ok" NÃO viram feedback_positivo. + 4. **Eventos pontuais da conversa atual NÃO são memória**: "reservou para tal dia", "escolheu Alexa", "informou CPF" — isso é registro de transação, não fato memorável sobre o cliente. + 5. **Ações do atendente NÃO são memória do cliente**: se o bot "incentivou X" ou "ofereceu Y", isso descreve o atendente, não o cliente. Ignore. + 6. **Máximo 5 fatos por conversa**. Se há dúvida entre extrair ou não, DESCARTE. Qualidade > quantidade. + 7. **Se a conversa não tem NADA realmente memorável**, retorne `{"facts": []}`. Isso é o comportamento normal e esperado da maioria das conversas transacionais. + + ## FORMATO DE SAÍDA + + JSON: `{"facts": [{...}, ...]}` onde cada fato tem: + - `memory_type`: um dos 9 tipos acima + - `content`: frase curta em português, 3ª pessoa ("Prefere Stilo com hidro"), max 1000 chars + - `evidence`: trecho LITERAL da conversa que prova o fato (copiar aspas do cliente) + - `confidence`: 0.0 a 1.0 (use ≥0.9 só se for totalmente explícito, 0.7-0.89 se for forte inferência, <0.7 descarte) + - `scope`: `global` na maioria dos casos. Use `unit:` apenas para reclamação/feedback sobre algo específico de UMA unidade. + + ## CONVERSA A ANALISAR - Conversa: #{formatted_messages} - Retorne JSON no formato: {"facts": [{...}, ...]} + Retorne JSON puro, nada além disso. PROMPT end