Class: SemanticSearchService
- Inherits:
-
Object
- Object
- SemanticSearchService
- Defined in:
- app/services/semantic_search_service.rb
Overview
Service for performing semantic search across all embeddable content types.
Uses pgvector for efficient vector similarity search with HNSW indexing.
Constant Summary collapse
- CONTENT_TYPE_MAPPING =
Map user-friendly type names to model class names
{ 'showcases' => 'Showcase', 'showcase' => 'Showcase', 'images' => 'Image', 'image' => 'Image', 'videos' => 'Video', 'video' => 'Video', 'posts' => 'Post', 'post' => 'Post', 'articles' => 'Article', 'article' => 'Article', 'products' => %w[Item ProductLine], 'product' => %w[Item ProductLine], 'items' => 'Item', 'item' => 'Item', 'product_lines' => 'ProductLine', 'product_line' => 'ProductLine', 'pages' => 'SiteMap', 'page' => 'SiteMap', 'reviews' => 'ReviewsIo', 'review' => 'ReviewsIo', 'publications' => 'Item', 'publication' => 'Item', 'pdfs' => 'Item', 'pdf' => 'Item', 'manuals' => 'Item', 'manual' => 'Item', 'datasheets' => 'Item', 'datasheet' => 'Item', # Article STI subtypes 'faqs' => 'ArticleFaq', 'faq' => 'ArticleFaq', 'technical' => 'ArticleTechnical', 'training' => 'ArticleTraining', 'procedures' => 'ArticleProcedure', 'procedure' => 'ArticleProcedure', # Support case data (sensitive — requires exclude_sensitive: false) 'support_notes' => 'Activity', 'support_communications' => 'Communication' }.freeze
- TYPE_DISPLAY_NAMES =
Human-readable names for result formatting
{ 'Showcase' => 'Showcase', 'Image' => 'Image', 'Video' => 'Video', 'Post' => 'Blog Post', 'Article' => 'Article', 'ProductLine' => 'Product Line', 'SiteMap' => 'Page', 'ReviewsIo' => 'Customer Review', 'Item' => 'Publication/PDF', 'Activity' => 'Support Note', 'Communication' => 'Communication' }.freeze
Instance Attribute Summary collapse
-
#exclude_sensitive ⇒ Object
readonly
Returns the value of attribute exclude_sensitive.
-
#limit ⇒ Object
readonly
Returns the value of attribute limit.
-
#locale ⇒ Object
readonly
Returns the value of attribute locale.
-
#query ⇒ Object
readonly
Returns the value of attribute query.
-
#types ⇒ Object
readonly
Returns the value of attribute types.
Class Method Summary collapse
-
.available? ⇒ Boolean
Check if semantic search is available (has embeddings).
-
.find_all(query, types: nil, limit: 10) ⇒ Array<ApplicationRecord>
Multi-type search returning records directly.
-
.find_article_faqs(query, limit: 10) ⇒ Array<Article>
Find FAQ articles matching a query.
-
.find_article_press(query, limit: 10) ⇒ Array<Article>
Find press articles matching a query.
-
.find_article_procedures(query, limit: 10) ⇒ Array<Article>
Find procedure articles matching a query.
-
.find_article_technical(query, limit: 10) ⇒ Array<Article>
Find technical articles matching a query.
-
.find_article_training(query, limit: 10) ⇒ Array<Article>
Find training articles matching a query.
-
.find_articles(query, limit: 10) ⇒ Array<Article>
Find articles (all types) matching a query.
-
.find_images(query, limit: 10) ⇒ Array<Image>
Find images matching a query.
-
.find_pages(query, limit: 10) ⇒ Array<SiteMap>
Find pages matching a query.
-
.find_posts(query, limit: 10) ⇒ Array<Post>
Find blog posts matching a query.
-
.find_products(query, limit: 10) ⇒ Array<Item, ProductLine>
Find products (items and product lines) matching a query.
-
.find_publications(query, limit: 10) ⇒ Array<Item>
Find publications/PDFs matching a query.
-
.find_reviews(query, limit: 10) ⇒ Array<ReviewsIo>
Find customer reviews matching a query.
-
.find_showcases(query, limit: 10) ⇒ Array<Showcase>
Find showcases matching a query.
-
.find_videos(query, limit: 10) ⇒ Array<Video>
Find videos matching a query.
-
.search(query, **options) ⇒ Array<Hash>
Class-level search convenience method.
-
.stats ⇒ Hash
Get embedding statistics.
Instance Method Summary collapse
-
#initialize(query, options = {}) ⇒ SemanticSearchService
constructor
A new instance of SemanticSearchService.
-
#search(hybrid: true) ⇒ Array<Hash>
Perform semantic search.
Constructor Details
#initialize(query, options = {}) ⇒ SemanticSearchService
Returns a new instance of SemanticSearchService.
83 84 85 86 87 88 89 |
# File 'app/services/semantic_search_service.rb', line 83 def initialize(query, = {}) @query = query.to_s.strip @limit = .fetch(:limit, 10) @types = normalize_types([:types]) @locale = .fetch(:locale, 'en') @exclude_sensitive = .fetch(:exclude_sensitive, true) end |
Instance Attribute Details
#exclude_sensitive ⇒ Object (readonly)
Returns the value of attribute exclude_sensitive.
74 75 76 |
# File 'app/services/semantic_search_service.rb', line 74 def exclude_sensitive @exclude_sensitive end |
#limit ⇒ Object (readonly)
Returns the value of attribute limit.
74 75 76 |
# File 'app/services/semantic_search_service.rb', line 74 def limit @limit end |
#locale ⇒ Object (readonly)
Returns the value of attribute locale.
74 75 76 |
# File 'app/services/semantic_search_service.rb', line 74 def locale @locale end |
#query ⇒ Object (readonly)
Returns the value of attribute query.
74 75 76 |
# File 'app/services/semantic_search_service.rb', line 74 def query @query end |
#types ⇒ Object (readonly)
Returns the value of attribute types.
74 75 76 |
# File 'app/services/semantic_search_service.rb', line 74 def types @types end |
Class Method Details
.available? ⇒ Boolean
Check if semantic search is available (has embeddings)
302 303 304 |
# File 'app/services/semantic_search_service.rb', line 302 def self.available? ContentEmbedding.exists? end |
.find_all(query, types: nil, limit: 10) ⇒ Array<ApplicationRecord>
Multi-type search returning records directly
294 295 296 |
# File 'app/services/semantic_search_service.rb', line 294 def self.find_all(query, types: nil, limit: 10) new(query, types: types, limit: limit).search.pluck(:record) end |
.find_article_faqs(query, limit: 10) ⇒ Array<Article>
Find FAQ articles matching a query
243 244 245 |
# File 'app/services/semantic_search_service.rb', line 243 def self.find_article_faqs(query, limit: 10) new(query, types: ['faqs'], limit: limit).search.pluck(:record) end |
.find_article_press(query, limit: 10) ⇒ Array<Article>
Find press articles matching a query
283 284 285 |
# File 'app/services/semantic_search_service.rb', line 283 def self.find_article_press(query, limit: 10) new(query, types: ['press'], limit: limit).search.pluck(:record) end |
.find_article_procedures(query, limit: 10) ⇒ Array<Article>
Find procedure articles matching a query
263 264 265 |
# File 'app/services/semantic_search_service.rb', line 263 def self.find_article_procedures(query, limit: 10) new(query, types: ['procedures'], limit: limit).search.pluck(:record) end |
.find_article_technical(query, limit: 10) ⇒ Array<Article>
Find technical articles matching a query
253 254 255 |
# File 'app/services/semantic_search_service.rb', line 253 def self.find_article_technical(query, limit: 10) new(query, types: ['technical'], limit: limit).search.pluck(:record) end |
.find_article_training(query, limit: 10) ⇒ Array<Article>
Find training articles matching a query
273 274 275 |
# File 'app/services/semantic_search_service.rb', line 273 def self.find_article_training(query, limit: 10) new(query, types: ['training'], limit: limit).search.pluck(:record) end |
.find_articles(query, limit: 10) ⇒ Array<Article>
Find articles (all types) matching a query
193 194 195 |
# File 'app/services/semantic_search_service.rb', line 193 def self.find_articles(query, limit: 10) new(query, types: %w[posts articles], limit: limit).search.pluck(:record) end |
.find_images(query, limit: 10) ⇒ Array<Image>
Find images matching a query
163 164 165 |
# File 'app/services/semantic_search_service.rb', line 163 def self.find_images(query, limit: 10) new(query, types: ['images'], limit: limit).search.pluck(:record) end |
.find_pages(query, limit: 10) ⇒ Array<SiteMap>
Find pages matching a query
213 214 215 |
# File 'app/services/semantic_search_service.rb', line 213 def self.find_pages(query, limit: 10) new(query, types: ['pages'], limit: limit).search.pluck(:record) end |
.find_posts(query, limit: 10) ⇒ Array<Post>
Find blog posts matching a query
183 184 185 |
# File 'app/services/semantic_search_service.rb', line 183 def self.find_posts(query, limit: 10) new(query, types: ['posts'], limit: limit).search.pluck(:record) end |
.find_products(query, limit: 10) ⇒ Array<Item, ProductLine>
Find products (items and product lines) matching a query
203 204 205 |
# File 'app/services/semantic_search_service.rb', line 203 def self.find_products(query, limit: 10) new(query, types: ['products'], limit: limit).search.pluck(:record) end |
.find_publications(query, limit: 10) ⇒ Array<Item>
Find publications/PDFs matching a query
233 234 235 |
# File 'app/services/semantic_search_service.rb', line 233 def self.find_publications(query, limit: 10) new(query, types: ['publications'], limit: limit).search.pluck(:record) end |
.find_reviews(query, limit: 10) ⇒ Array<ReviewsIo>
Find customer reviews matching a query
223 224 225 |
# File 'app/services/semantic_search_service.rb', line 223 def self.find_reviews(query, limit: 10) new(query, types: ['reviews'], limit: limit).search.pluck(:record) end |
.find_showcases(query, limit: 10) ⇒ Array<Showcase>
Find showcases matching a query
153 154 155 |
# File 'app/services/semantic_search_service.rb', line 153 def self.find_showcases(query, limit: 10) new(query, types: ['showcases'], limit: limit).search.pluck(:record) end |
.find_videos(query, limit: 10) ⇒ Array<Video>
Find videos matching a query
173 174 175 |
# File 'app/services/semantic_search_service.rb', line 173 def self.find_videos(query, limit: 10) new(query, types: ['videos'], limit: limit).search.pluck(:record) end |
.search(query, **options) ⇒ Array<Hash>
Class-level search convenience method
142 143 144 145 |
# File 'app/services/semantic_search_service.rb', line 142 def self.search(query, **) hybrid = .delete(:hybrid) { true } new(query, ).search(hybrid: hybrid) end |
.stats ⇒ Hash
Get embedding statistics
310 311 312 |
# File 'app/services/semantic_search_service.rb', line 310 def self.stats ContentEmbedding.group(:embeddable_type).count end |
Instance Method Details
#search(hybrid: true) ⇒ Array<Hash>
Perform semantic search
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/services/semantic_search_service.rb', line 106 def search(hybrid: true) return [] if query.blank? search_types, sti_type_filters = parse_types_with_filters # Gemini unified (cross-modal) space — text + images ranked together. unified_method = hybrid ? :unified_hybrid_search : :unified_search = ContentEmbedding.public_send( unified_method, query, limit: limit * 3, types: search_types, locale: locale, exclude_sensitive: exclude_sensitive ) if sti_type_filters.any? = .select do |emb| next true unless emb. == 'Article' article = emb. next false unless article sti_type_filters.include?(article.class.name) end end format_results() end |