feat(captain/hermes): auto-react ambiente — 20% de chance em msgs neutras

Cobre o gap entre saudação e despedida: quando msg do cliente não bate
nenhum dos regex determinísticos (thanks/confirm/greet/farewell), rola
dado e reage com emoji discreto (😊/💕//💯/🤗) em ~1 a cada 5 msgs.

Filtros pra não reagir em momentos errados (ambient_eligible?):
- Tamanho 6..180 chars (evita "oi" e narrativas longas).
- Sem "?" (perguntas esperam resposta de texto, não emoji).
- Sem keywords de fluxo de reserva (cpf, valor, hora, data, suite).
- Não começa com número (CPF, telefone, valor).

Determinístico continua igual — agente parece mais vivo sem virar
robozinho que reage em tudo. Atende pedido do Rodrigo de "reagir 20%
da conversa, não só nas saudações".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-05-02 17:20:36 -03:00
parent 42ada8100e
commit 10fec1d2cb

View File

@ -22,6 +22,15 @@ class Captain::Hermes::AutoReactService
GREETING_REGEX = /\A(bom\s*dia|boa\s*tarde|boa\s*noite|oi|olá|ola|e\s*aí|hey|hi|hello)[\s.!,]*\z/i
FAREWELL_REGEX = /\A(tchau|até\s*(mais|logo|breve)|valeu\s*flw|flw|abraço|abraços|bjs|beijos)[\s.!,]*\z/i
# Reação "ambiente" — em msgs neutras que não bateram nenhum padrão
# acima, rola dado e reage com emoji discreto. Cobre o gap entre
# saudação e despedida pra agente parecer mais vivo (~1 a cada 5 msgs).
# Filtros em ambient_eligible? evitam reagir em momentos de fluxo
# crítico (cliente mandando dados de reserva, fazendo pergunta).
AMBIENT_EMOJIS = %w[😊 💕 ✨ 💯 🤗].freeze
AMBIENT_PROBABILITY = 0.20
AMBIENT_RESERVATION_KEYWORDS = /cpf|reserv|pix|valor|preço|preco|quanto|hor[áa]rio|dia\b|data\b|categori|suite|quart|chal[ée]/i
def self.maybe_react!(message)
new(message).maybe_react!
end
@ -73,10 +82,24 @@ class Captain::Hermes::AutoReactService
return '👍' if CONFIRMATION_REGEX.match?(text)
return '👋' if GREETING_REGEX.match?(text) && first_incoming_in_conversation?
return '❤️' if FAREWELL_REGEX.match?(text)
return AMBIENT_EMOJIS.sample if ambient_eligible?(text) && rand < AMBIENT_PROBABILITY
nil
end
# Mensagens "neutras" elegíveis pra reação ambiente: nem curtas demais
# (provavelmente saudação que já pega regex), nem longas (geralmente
# narrativa que pede atenção), sem ?, sem termos de fluxo de reserva
# (preço/cpf/data — cliente está esperando ação, não emoji).
def ambient_eligible?(text)
return false if text.length < 6 || text.length > 180
return false if text.include?('?')
return false if text.match?(AMBIENT_RESERVATION_KEYWORDS)
return false if text.match?(/\A\d/)
true
end
# Saudação só reage na PRIMEIRA mensagem da conversa pra não ficar
# forçado em conversa longa que retoma com "oi".
def first_incoming_in_conversation?