Class: Ragdoll::SearchEngine

Inherits:
Object
  • Object
show all
Defined in:
app/services/ragdoll/search_engine.rb

Instance Method Summary collapse

Constructor Details

#initialize(embedding_service, config_service: nil) ⇒ SearchEngine

Returns a new instance of SearchEngine.



7
8
9
10
# File 'app/services/ragdoll/search_engine.rb', line 7

def initialize(embedding_service, config_service: nil)
  @embedding_service = embedding_service
  @config_service = config_service || Ragdoll::ConfigurationService.new
end

Instance Method Details

#search_documents(query, options = {}) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'app/services/ragdoll/search_engine.rb', line 12

def search_documents(query, options = {})
  search_config = @config_service.search_config
  limit = options[:limit] || search_config[:max_results]
  threshold = options[:threshold] || search_config[:similarity_threshold]
  filters = options[:filters] || {}

  # Generate embedding for the query
  query_embedding = @embedding_service.generate_embedding(query)
  return [] if query_embedding.nil?

  # Search using ActiveRecord models
  Ragdoll::Embedding.search_similar(query_embedding,
                                   limit: limit,
                                   threshold: threshold,
                                   filters: filters)
end

#search_similar_content(query_or_embedding, options = {}) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/services/ragdoll/search_engine.rb', line 29

def search_similar_content(query_or_embedding, options = {})
  start_time = Time.current
  search_config = @config_service.search_config
  limit = options[:limit] || search_config[:max_results]
  threshold = options[:threshold] || search_config[:similarity_threshold]
  filters = options[:filters] || {}
  
  # Extract keywords option and normalize
  keywords = options[:keywords] || []
  keywords = Array(keywords).map(&:to_s).reject(&:empty?)
  
  # Extract tracking options
  session_id = options[:session_id]
  user_id = options[:user_id]
  track_search = options.fetch(:track_search, true)

  if query_or_embedding.is_a?(Array)
    # It's already an embedding
    query_embedding = query_or_embedding
    query_string = options[:query] # Should be provided when passing embedding directly
  else
    # It's a query string, generate embedding
    query_string = query_or_embedding
    query_embedding = @embedding_service.generate_embedding(query_string)
    return [] if query_embedding.nil?
  end

  # Add keywords to filters if provided
  if keywords.any?
    filters[:keywords] = keywords
  end

  # Search using ActiveRecord models with statistics
  # Try enhanced search first, fall back to original if it fails
  begin
    search_response = Ragdoll::Embedding.search_similar_with_stats(query_embedding,
                                                                  limit: limit,
                                                                  threshold: threshold,
                                                                  filters: filters)
    results = search_response[:results]
    statistics = search_response[:statistics]
  rescue NoMethodError, PG::SyntaxError => e
    # Fall back to original search method if enhanced version fails
    puts "Warning: Enhanced search failed (#{e.message}), using fallback" if ENV["RAGDOLL_DEBUG"]
    results = Ragdoll::Embedding.search_similar(query_embedding,
                                               limit: limit,
                                               threshold: threshold,
                                               filters: filters)
    statistics = nil
  end
  
  execution_time = ((Time.current - start_time) * 1000).round
  
  # Record search if tracking enabled and we have a query string
  if track_search && query_string && !query_string.empty?
    begin
      # Format results for search recording
      search_results = results.map do |result|
        {
          embedding_id: result[:embedding_id] || result[:id],
          similarity: result[:similarity] || result[:similarity_score] || 0.0
        }
      end
      
      search_type = keywords.any? ? "semantic_with_keywords" : "semantic"
      
      Ragdoll::Search.record_search(
        query: query_string,
        query_embedding: query_embedding,
        results: search_results,
        search_type: search_type,
        filters: filters,
        options: { limit: limit, threshold: threshold, keywords: keywords },
        execution_time_ms: execution_time,
        session_id: session_id,
        user_id: user_id
      )
    rescue => e
      # Log error but don't fail the search
      puts "Warning: Search tracking failed: #{e.message}" if ENV["RAGDOLL_DEBUG"]
    end
  end

  # Return results with statistics for better user feedback
  {
    results: results,
    statistics: statistics,
    execution_time_ms: execution_time
  }
end