From f1d3a124d5c4fb11232525c79b01c7161791611a Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Sat, 2 May 2026 18:30:12 -0300 Subject: [PATCH] fix(hermes): debounce agrupava msgs de turns ANTIGOS (default_scope ASC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug raiz que causou todos os problemas de "Hermes repetindo info de turns anteriores" hoje: Message tem default_scope order(created_at: :asc). O combined_incoming_ content fazia .order(created_at: :desc) que GERA SQL com 2 orderings em conflito ("ORDER BY created_at ASC, created_at DESC") — no Postgres o ASC ganha quando a primeira coluna é igual. Resultado: last_real_outgoing virava a MAIS ANTIGA outgoing (a saudação inicial), não a mais recente. Aí o scope incoming agrupava TODAS as msgs do cliente desde o "Oi" da Juliana, juntando wifi+pet num turn só. Caso real conv 6064 (2026-05-02 21:18): T1: cliente "Preciso senha wifi" → Hermes "Prime2025" T2: cliente "Posso levar animais?" → debounce agrupou ["Preciso senha wifi", "Posso levar animais"] como se fossem MSGS DO MESMO TURN, mandou pro Hermes como "Cliente acabou de dizer: Preciso senha wifi.\nPosso levar animais?" → Hermes consultou faq_lookup com query "senha wifi e animais" → Resposta: "Senha Prime2025 e pode levar animais" Fix: usa .reorder(created_at: :desc) que sobrescreve default_scope, gerando SQL limpa com só "ORDER BY created_at DESC". Camadas 3 (strip linhas) e 4 (topic gating) que adicionei antes são paliativos válidos como defesa em profundidade, mas o problema raiz era esse default_scope. Co-Authored-By: Claude Opus 4.7 (1M context) --- enterprise/app/jobs/captain/hermes/outgoing_job.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/enterprise/app/jobs/captain/hermes/outgoing_job.rb b/enterprise/app/jobs/captain/hermes/outgoing_job.rb index aadb97604..1724afe41 100644 --- a/enterprise/app/jobs/captain/hermes/outgoing_job.rb +++ b/enterprise/app/jobs/captain/hermes/outgoing_job.rb @@ -55,17 +55,24 @@ class Captain::Hermes::OutgoingJob < ApplicationJob # Concatena texto de todas as msgs incoming entre a última resposta real # (não-reaction) do agente e a msg âncora. Retorna nil se só tem 1 msg # (pra dispatch usar message.content normal). + # + # Atenção: usa `reorder` em vez de `order` porque o model Message tem + # default_scope `order(created_at: :asc)` — sem reorder, a SQL final fica + # `ORDER BY created_at ASC, created_at DESC` e o ASC ganha. Resultado: + # last_real_outgoing virava a MAIS ANTIGA, agrupando msgs de turns + # passados (caso real: Hermes recebia "wifi+pet" colado mesmo em turns + # separados — visto em conv 6064 em 2026-05-02). def combined_incoming_content(conversation, anchor_message) last_real_outgoing = conversation.messages .where(message_type: :outgoing) .where("(content_attributes ->> 'is_reaction') IS NULL OR (content_attributes ->> 'is_reaction') != 'true'") - .order(created_at: :desc) + .reorder(created_at: :desc) .first scope = conversation.messages.where(message_type: :incoming).where('created_at <= ?', anchor_message.created_at) scope = scope.where('created_at > ?', last_real_outgoing.created_at) if last_real_outgoing - texts = scope.order(:created_at).pluck(:content).map(&:to_s).reject(&:blank?).uniq + texts = scope.reorder(created_at: :asc).pluck(:content).map(&:to_s).reject(&:blank?).uniq return nil if texts.size <= 1 Rails.logger.info("[Captain::Hermes::Debounce] agrupando #{texts.size} msgs do cliente em conv #{conversation.id}")