From 68f60b06728c76b1b36ed59dad981194b3d37972 Mon Sep 17 00:00:00 2001 From: aakashb95 Date: Thu, 16 Jul 2026 15:34:52 +0530 Subject: [PATCH] refactor(captain): extract FAQ conversation content --- .../llm/conversation_faq_content_service.rb | 66 +++++++++++++++++++ .../captain/llm/conversation_faq_service.rb | 58 +--------------- .../llm/conversation_faq_service_spec.rb | 6 +- 3 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 enterprise/app/services/captain/llm/conversation_faq_content_service.rb diff --git a/enterprise/app/services/captain/llm/conversation_faq_content_service.rb b/enterprise/app/services/captain/llm/conversation_faq_content_service.rb new file mode 100644 index 000000000..c26c75cfc --- /dev/null +++ b/enterprise/app/services/captain/llm/conversation_faq_content_service.rb @@ -0,0 +1,66 @@ +class Captain::Llm::ConversationFaqContentService + def initialize(assistant, conversation) + @assistant = assistant + @conversation = conversation + end + + def generate + [ + 'Business Context:', + JSON.pretty_generate(business_context), + "Conversation ID: ##{conversation.display_id}", + "Channel: #{conversation.inbox.channel.name}", + 'Message History:', + conversation_messages + ].join("\n") + end + + private + + attr_reader :assistant, :conversation + + def conversation_messages + messages = conversation + .messages + .where(message_type: %i[incoming outgoing], private: false) + .order(created_at: :asc) + + return "No messages in this conversation\n" if messages.empty? + + messages.filter_map { |message| format_message(message) }.join + end + + def format_message(message) + return unless source_message?(message) + + message_content = message.content_for_llm + return if message_content.blank? + + sender = human_support_reply?(message) ? 'Support Agent' : 'User' + "#{sender}: #{message_content}\n" + end + + def source_message?(message) + return true if message.incoming? && message.sender_type == 'Contact' + + human_support_reply?(message) + end + + def human_support_reply?(message) + return false unless message.outgoing? + return false if message.content_attributes['automation_rule_id'].present? + return false if message.additional_attributes['campaign_id'].present? + + message.sender_type == 'User' || message.content_attributes['external_echo'].present? + end + + def business_context + { + product_name: assistant.config['product_name'], + assistant_description: assistant.description, + instructions: assistant.config['instructions'], + response_guidelines: assistant.response_guidelines, + guardrails: assistant.guardrails + }.compact + end +end diff --git a/enterprise/app/services/captain/llm/conversation_faq_service.rb b/enterprise/app/services/captain/llm/conversation_faq_service.rb index db418f3f8..b84ab45d6 100644 --- a/enterprise/app/services/captain/llm/conversation_faq_service.rb +++ b/enterprise/app/services/captain/llm/conversation_faq_service.rb @@ -9,7 +9,7 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService super(feature: LLM_FEATURE, account: conversation.account, fallback_model: Llm::Models.default_model_for(LLM_FEATURE)) @assistant = assistant @conversation = conversation - @content = conversation_faq_content + @content = Captain::Llm::ConversationFaqContentService.new(assistant, conversation).generate @embedding_service = Captain::Llm::EmbeddingService.new(account_id: conversation.account_id) end @@ -23,52 +23,6 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService attr_reader :content, :conversation, :assistant, :embedding_service - def conversation_faq_content - [ - 'Business Context:', - JSON.pretty_generate(business_context), - "Conversation ID: ##{conversation.display_id}", - "Channel: #{conversation.inbox.channel.name}", - 'Message History:', - conversation_faq_messages - ].join("\n") - end - - def conversation_faq_messages - messages = conversation - .messages - .where(message_type: %i[incoming outgoing], private: false) - .order(created_at: :asc) - - return "No messages in this conversation\n" if messages.empty? - - messages.filter_map { |message| format_conversation_faq_message(message) }.join - end - - def format_conversation_faq_message(message) - return unless faq_source_message?(message) - - message_content = message.content_for_llm - return if message_content.blank? - - sender = human_support_reply?(message) ? 'Support Agent' : 'User' - "#{sender}: #{message_content}\n" - end - - def faq_source_message?(message) - return true if message.incoming? && message.sender_type == 'Contact' - - human_support_reply?(message) - end - - def human_support_reply?(message) - return false unless message.outgoing? - return false if message.content_attributes['automation_rule_id'].present? - return false if message.additional_attributes['campaign_id'].present? - - message.sender_type == 'User' || message.content_attributes['external_echo'].present? - end - def no_human_interaction? conversation.first_reply_created_at.nil? end @@ -217,16 +171,6 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService Captain::Llm::ConversationFaqPromptsService.generator(language_name(faq_language)) end - def business_context - { - product_name: assistant.config['product_name'], - assistant_description: assistant.description, - instructions: assistant.config['instructions'], - response_guidelines: assistant.response_guidelines, - guardrails: assistant.guardrails - }.compact - end - def faq_language @faq_language ||= normalize_language(conversation.language.presence || conversation.account.locale.presence || I18n.default_locale.to_s) end diff --git a/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb b/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb index 554900979..e247f6020 100644 --- a/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb +++ b/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb @@ -255,13 +255,11 @@ RSpec.describe Captain::Llm::ConversationFaqService do context 'when a similar approved FAQ uses the account language' do let(:sample_faqs) { [{ 'question' => 'Como ativo o recurso?', 'answer' => 'Ative nas configuracoes.' }] } - let!(:existing_response) do + + before do create(:captain_assistant_response, assistant: captain_assistant, account: captain_assistant.account, question: 'How do I enable the feature?', answer: 'Turn it on in settings.', embedding: embedding_one) - end - - before do conversation.update!(additional_attributes: { conversation_language: 'pt-BR' }) allow(embedding_service).to receive(:get_embedding).and_return(embedding_one) end