From f1753f08b247653cdbb36999126752ce33c233dd Mon Sep 17 00:00:00 2001 From: aakashb95 Date: Wed, 22 Jul 2026 23:28:53 +0530 Subject: [PATCH] fix(captain): match approved FAQs across languages --- .../captain/llm/conversation_faq_service.rb | 15 +++------------ .../captain/llm/conversation_faq_service_spec.rb | 13 ++++++++----- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/enterprise/app/services/captain/llm/conversation_faq_service.rb b/enterprise/app/services/captain/llm/conversation_faq_service.rb index 5ace3d772..28e2b5654 100644 --- a/enterprise/app/services/captain/llm/conversation_faq_service.rb +++ b/enterprise/app/services/captain/llm/conversation_faq_service.rb @@ -14,9 +14,6 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService language.to_s.tr('-', '_').split('_').first.downcase end - def self.account_language_for(account) - normalize_language(account.locale.presence || I18n.default_locale.to_s) - end private_class_method :normalize_language def initialize(assistant, conversation) @@ -44,7 +41,7 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService def route_candidate(faq) embedding = embedding_service.get_embedding(candidate_text(faq)) - return discard_observation(faq) if matching_record(approved_faqs_for_language, faq, embedding) + return discard_observation(faq) if matching_record(approved_faqs, faq, embedding) return discard_observation(faq) if matching_record(dismissed_suggestions_for_language, faq, embedding) suggestion = matching_record(open_suggestions_for_language, faq, embedding) @@ -134,10 +131,8 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService assistant.faq_suggestions.where(account_id: conversation.account_id).dismissed.by_language(faq_language) end - def approved_faqs_for_language - return assistant.responses.approved if faq_language == account_language - - assistant.responses.none + def approved_faqs + assistant.responses.approved end def candidate_text(faq) @@ -197,10 +192,6 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService @faq_language ||= self.class.language_for(conversation) end - def account_language - @account_language ||= self.class.account_language_for(conversation.account) - end - def language_name(language) ISO_639.find(language)&.english_name&.downcase || 'english' 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 4a028406a..51e070055 100644 --- a/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb +++ b/spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb @@ -399,8 +399,9 @@ RSpec.describe Captain::Llm::ConversationFaqService do end end - context 'when a similar approved FAQ uses the account language' do + context 'when a similar approved FAQ uses another language' do let(:sample_faqs) { [{ 'question' => 'Como ativo o recurso?', 'answer' => 'Ative nas configuracoes.' }] } + let(:match_response) { instance_double(RubyLLM::Message, content: { same_faq: true }.to_json) } before do create(:captain_assistant_response, assistant: captain_assistant, account: captain_assistant.account, @@ -408,14 +409,16 @@ RSpec.describe Captain::Llm::ConversationFaqService do embedding: embedding_one) conversation.update!(additional_attributes: { conversation_language: 'pt-BR' }) allow(embedding_service).to receive(:get_embedding).and_return(embedding_one) + allow(mock_chat).to receive(:ask) do |input| + input.start_with?('{') ? match_response : mock_response + end end - it 'does not discard a candidate in another language' do + it 'deduplicates against the approved FAQ' do expect do service.generate_suggestions - end.to change(captain_assistant.faq_suggestions, :count).by(1) - expect(Captain::FaqObservation.discarded.count).to be_zero - expect(captain_assistant.faq_suggestions.last.language).to eq('pt') + end.to change(Captain::FaqObservation.discarded, :count).by(1) + expect(captain_assistant.faq_suggestions.count).to be_zero end end