From cf1fdff04b250044862e5863cc33e4429800dac4 Mon Sep 17 00:00:00 2001 From: aakashb95 Date: Tue, 14 Jul 2026 23:13:03 +0530 Subject: [PATCH] fix(captain): filter FAQ signals with business context --- .../llm/conversation_faq_prompts_service.rb | 1 + .../captain/llm/conversation_faq_service.rb | 29 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/enterprise/app/services/captain/llm/conversation_faq_prompts_service.rb b/enterprise/app/services/captain/llm/conversation_faq_prompts_service.rb index 0265c39a3..dd0df4a6c 100644 --- a/enterprise/app/services/captain/llm/conversation_faq_prompts_service.rb +++ b/enterprise/app/services/captain/llm/conversation_faq_prompts_service.rb @@ -6,6 +6,7 @@ class Captain::Llm::ConversationFaqPromptsService Only generate an FAQ when the conversation contains durable, reusable knowledge that would help many future customers. ## Source rules + - The input starts with trusted business context. Use it to reject conversations about other businesses or topics, but never use it as the source of an FAQ answer. - The conversation history contains only customer messages and human support agent messages. - Base every FAQ strictly on information stated in the human support agent messages. Do not infer, generalize, or add external knowledge. - A human support agent must state every fact used in the FAQ answer. Customer messages cannot supply missing answer facts. diff --git a/enterprise/app/services/captain/llm/conversation_faq_service.rb b/enterprise/app/services/captain/llm/conversation_faq_service.rb index aae67cd0d..e6bb15891 100644 --- a/enterprise/app/services/captain/llm/conversation_faq_service.rb +++ b/enterprise/app/services/captain/llm/conversation_faq_service.rb @@ -25,6 +25,8 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService def conversation_faq_content [ + 'Business Context:', + JSON.pretty_generate(business_context), "Conversation ID: ##{conversation.display_id}", "Channel: #{conversation.inbox.channel.name}", 'Message History:', @@ -74,9 +76,7 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService def route_candidate(faq) embedding = embedding_service.get_embedding(candidate_text(faq)) - if matching_record(approved_faqs_for_language, faq, embedding) - return discard_observation(faq) - end + return discard_observation(faq) if matching_record(approved_faqs_for_language, faq, embedding) suggestion = matching_record(open_suggestions_for_language, faq, embedding) suggestion ||= assistant.faq_suggestions.create!( @@ -96,10 +96,13 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService def likely_matches(relation, embedding) return [] unless relation.exists? - relation - .nearest_neighbors(:embedding, embedding, distance: 'cosine') - .limit(MATCH_LIMIT) - .select { |record| record.neighbor_distance < DISTANCE_THRESHOLD } + ApplicationRecord.transaction do + ApplicationRecord.connection.execute("SET LOCAL ivfflat.iterative_scan = 'relaxed_order'") + relation + .nearest_neighbors(:embedding, embedding, distance: 'cosine') + .limit(MATCH_LIMIT) + .select { |record| record.neighbor_distance < DISTANCE_THRESHOLD } + end end def same_faq?(candidate, existing_record) @@ -136,7 +139,7 @@ class Captain::Llm::ConversationFaqService < Llm::BaseAiService language: faq_language, status: :attached ) - suggestion.update!(source_count: suggestion.observations.attached.count) + suggestion.update!(source_count: suggestion.source_count + 1) observation end end @@ -214,6 +217,16 @@ 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