fix(captain): filter FAQ signals with business context
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user