chatwoot-develop/enterprise/app/jobs/captain/intent_classification_job.rb
2026-01-19 19:26:23 -03:00

88 lines
3.0 KiB
Ruby

module Captain
class IntentClassificationJob < ApplicationJob
queue_as :default
CATEGORIES = %w[
valores
disponibilidade
localizacao
checkin_checkout
pet_friendly
cancelamento
cafe_da_manha
estacionamento
pagamento
outros
].freeze
def perform(conversation_id, message_content)
conversation = Conversation.find_by(id: conversation_id)
return unless conversation
# Prevent labeling if already labeled recently (optional optimization, skipping for MVP to ensure accuracy)
# For MVP, we classify every user message to capture the flow, or we could limit to the first few.
# Let's classify every message that is substantial enough.
return if message_content.to_s.strip.length < 5
intent = classify_intent(message_content)
return unless intent.present? && CATEGORIES.include?(intent)
label_name = "duvida:#{intent}"
# Add label if not present
unless conversation.labels.exists?(name: label_name)
conversation.labels << Label.find_or_create_by(title: label_name, account_id: conversation.account_id)
Rails.logger.info "[IntentClassification] Applied label '#{label_name}' to conversation #{conversation.id}"
end
rescue StandardError => e
Rails.logger.error "[IntentClassification] Failed to classify: #{e.message}"
end
private
def classify_intent(text)
# We use a simple prompt for the LLM
prompt = <<~PROMPT
Classifique a mensagem do usuário em UMA das seguintes categorias:
#{CATEGORIES.join(', ')}
Se não se encaixar claramente, responda 'outros'.
Responda APENAS com o nome da categoria.
Mensagem: "#{text}"
PROMPT
# Using the existing LLM infrastructure
# We create a temporary safe agent config or just use direct LLM call if possible.
# Since we are inside Captain, we can try to use RubyLLM direct client if configured,
# or fallback to the conversation's assistant if available.
# For simplicity and robustness in this specific codebase context, let's use the OpenAI client wrapper directly
# if available via the Agents gem or RubyLLM configuration already set up.
messages = [{ role: 'user', content: prompt }]
# Robust API Key fetching for background jobs
api_key = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_API_KEY')&.value
api_key ||= ENV.fetch('OPENAI_API_KEY', nil)
# Strip eventual image suffixes if present (reuse sanitization logic)
api_key = api_key.to_s.gsub(/\.(png|jpg|jpeg|gif|webp|svg|@2x|@3x).*$/i, '').strip
client = OpenAI::Client.new(access_token: api_key)
response = client.chat(
parameters: {
model: 'gpt-4o-mini', # Cost effective
messages: messages,
temperature: 0.0,
max_tokens: 10
}
)
content = response.dig('choices', 0, 'message', 'content')
content&.strip&.downcase
end
end
end