fix(captain-memory): strict taxonomy definitions in ExtractionService prompt

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) <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-04-19 07:44:26 -03:00
parent 6ecafd30c6
commit 4becfd0a57

View File

@ -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 ( 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:<id>' 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 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" ( 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 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 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:<id>` 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