87 lines
3.2 KiB
Ruby
87 lines
3.2 KiB
Ruby
module Conversations
|
|
class ClusterJob < ApplicationJob
|
|
queue_as :low_priority
|
|
|
|
def perform(account_id, days_back = 7)
|
|
account = Account.find(account_id)
|
|
|
|
# 1. Busca conversas recentes que já foram processadas pela IA
|
|
start_date = days_back.days.ago.beginning_of_day
|
|
|
|
# Labels que queremos agrupar (todas da taxonomia)
|
|
Conversations::AutoLabelJob::TAXONOMY.keys.each do |label|
|
|
# Busca perguntas dessa categoria
|
|
# Note: estamos queryng o campo JSONB additional_attributes
|
|
account.conversations
|
|
.where('created_at >= ?', start_date)
|
|
.where("additional_attributes ->> 'ai_canonical_question' IS NOT NULL")
|
|
.tagged_with(label)
|
|
.pluckArel::Nodes::InfixOperation.new('->>', Arel::Nodes::SqlLiteral.new('additional_attributes'), Arel::Nodes::SqlLiteral.new("'ai_canonical_question'"))
|
|
|
|
# O pluck acima pode ser complexo dependendo do adapter, vamos simplificar:
|
|
questions = account.conversations
|
|
.where('created_at >= ?', start_date)
|
|
.where("additional_attributes ->> 'ai_canonical_question' IS NOT NULL")
|
|
.tagged_with(label)
|
|
.map { |c| c.additional_attributes['ai_canonical_question'] }
|
|
|
|
next if questions.empty?
|
|
|
|
# 2. Chama LLM para agrupar
|
|
clusters = cluster_questions_with_llm(label, questions)
|
|
|
|
# 3. Salva no banco
|
|
save_clusters(account, label, clusters, start_date.to_date)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def cluster_questions_with_llm(label, questions_list)
|
|
prompt = <<~PROMPT
|
|
Atue como um analista de dados especialista em atendimento ao cliente.
|
|
Abaixo está uma lista de dúvidas reais de clientes sobre o tópico "#{label}".
|
|
|
|
Sua tarefa:
|
|
1. Agrupar dúvidas semânticamente idênticas.
|
|
2. Criar uma "Pergunta Padrão" clara que represente o grupo.
|
|
3. Contar quantas vezes cada dúvida apareceu.
|
|
|
|
Lista de Dúvidas:
|
|
#{questions_list.map { |q| "- #{q}" }.join("\n")}
|
|
|
|
Retorne APENAS um JSON:
|
|
[
|
|
{ "question": "Pergunta Padrão 1", "count": 10 },
|
|
{ "question": "Pergunta Padrão 2", "count": 5 }
|
|
]
|
|
PROMPT
|
|
|
|
model = ENV.fetch('JASMINE_LLM_MODEL', 'gpt-4o-mini')
|
|
chat = RubyLLM.chat(model: model).with_temperature(0.0)
|
|
response = chat.ask(prompt)
|
|
|
|
clean_response = response.content.gsub('```json', '').gsub('```', '').strip
|
|
JSON.parse(clean_response)
|
|
rescue StandardError => e
|
|
Rails.logger.error "[ClusterJob] Failed to cluster for label #{label}: #{e.message}"
|
|
[]
|
|
end
|
|
|
|
def save_clusters(account, label, clusters, date)
|
|
# Limpa clusters anteriores dessa data/label para reprocessamento
|
|
FrequentQuestion.where(account: account, label: label, cluster_date: date).destroy_all
|
|
|
|
clusters.each do |cluster|
|
|
FrequentQuestion.create!(
|
|
account: account,
|
|
label: label,
|
|
question_text: cluster['question'],
|
|
occurrence_count: cluster['count'],
|
|
cluster_date: date
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|