80 lines
2.4 KiB
Ruby
Executable File
80 lines
2.4 KiB
Ruby
Executable File
class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseTool
|
|
LOW_CONFIDENCE_DISTANCE = 0.55
|
|
|
|
def self.name
|
|
'search_documentation'
|
|
end
|
|
description 'Search and retrieve documentation from knowledge base'
|
|
|
|
param :query, desc: 'Search Query', required: true
|
|
|
|
def initialize(assistant, user: nil, conversation: nil)
|
|
@conversation = conversation
|
|
super(assistant, user: user)
|
|
end
|
|
|
|
def execute(*args, **params)
|
|
actual_params = resolve_params(args, params)
|
|
query = actual_params[:query]
|
|
Rails.logger.info { "#{self.class.name}: #{query}" }
|
|
|
|
responses = assistant.responses.approved.search(query)
|
|
|
|
log_results(query, responses)
|
|
|
|
return 'No FAQs found for the given query' if responses.empty? || low_confidence?(responses.first)
|
|
|
|
responses.map { |response| format_response(response) }.join
|
|
end
|
|
|
|
private
|
|
|
|
def log_results(query, responses)
|
|
request_id = extract_request_id
|
|
conversation_id = @conversation&.id
|
|
response_count = responses.length
|
|
scores = responses.map { |response| result_score_payload(response) }
|
|
top_response = responses.first
|
|
top_snippet = top_response&.answer.to_s.strip[0, 200]
|
|
low_confidence = top_response&.neighbor_distance.to_f > LOW_CONFIDENCE_DISTANCE if top_response
|
|
|
|
Rails.logger.info(
|
|
"[CAPTAIN][search_documentation] request_id=#{request_id} conversation_id=#{conversation_id} query=#{query.inspect} " \
|
|
"count=#{response_count} results=#{scores} low_confidence=#{low_confidence} top_snippet=#{top_snippet.inspect}"
|
|
)
|
|
end
|
|
|
|
def low_confidence?(response)
|
|
return false unless response.respond_to?(:neighbor_distance)
|
|
|
|
response.neighbor_distance.to_f > LOW_CONFIDENCE_DISTANCE
|
|
end
|
|
|
|
def result_score_payload(response)
|
|
{
|
|
id: response.id,
|
|
distance: response.neighbor_distance
|
|
}
|
|
end
|
|
|
|
def extract_request_id
|
|
return RequestStore.store[:request_id] if defined?(RequestStore) && RequestStore.store[:request_id].present?
|
|
|
|
Thread.current[:request_id] || 'unknown'
|
|
end
|
|
|
|
def format_response(response)
|
|
formatted_response = "
|
|
Question: #{response.question}
|
|
Answer: #{response.answer}
|
|
"
|
|
if response.documentable.present? && response.documentable.try(:external_link)
|
|
formatted_response += "
|
|
Source: #{response.documentable.external_link}
|
|
"
|
|
end
|
|
|
|
formatted_response
|
|
end
|
|
end
|