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)
308 309 310 |
# File 'app/services/semantic_search_service.rb', line 308 def self.available? ContentEmbedding.exists? end |
.find_all(query, types: nil, limit: 10) ⇒ Array<ApplicationRecord>
Multi-type search returning records directly
300 301 302 |
# File 'app/services/semantic_search_service.rb', line 300 def self.find_all(query, types: nil, limit: 10) new(query, types: types, limit: limit).search.map { |r| r[:record] } end |
.find_article_faqs(query, limit: 10) ⇒ Array<Article>
Find FAQ articles matching a query
249 250 251 |
# File 'app/services/semantic_search_service.rb', line 249 def self.find_article_faqs(query, limit: 10) new(query, types: ['faqs'], limit: limit).search.map { |r| r[:record] } end |
.find_article_press(query, limit: 10) ⇒ Array<Article>
Find press articles matching a query
289 290 291 |
# File 'app/services/semantic_search_service.rb', line 289 def self.find_article_press(query, limit: 10) new(query, types: ['press'], limit: limit).search.map { |r| r[:record] } end |
.find_article_procedures(query, limit: 10) ⇒ Array<Article>
Find procedure articles matching a query
269 270 271 |
# File 'app/services/semantic_search_service.rb', line 269 def self.find_article_procedures(query, limit: 10) new(query, types: ['procedures'], limit: limit).search.map { |r| r[:record] } end |
.find_article_technical(query, limit: 10) ⇒ Array<Article>
Find technical articles matching a query
259 260 261 |
# File 'app/services/semantic_search_service.rb', line 259 def self.find_article_technical(query, limit: 10) new(query, types: ['technical'], limit: limit).search.map { |r| r[:record] } end |
.find_article_training(query, limit: 10) ⇒ Array<Article>
Find training articles matching a query
279 280 281 |
# File 'app/services/semantic_search_service.rb', line 279 def self.find_article_training(query, limit: 10) new(query, types: ['training'], limit: limit).search.map { |r| r[:record] } end |
.find_articles(query, limit: 10) ⇒ Array<Article>
Find articles (all types) matching a query
199 200 201 |
# File 'app/services/semantic_search_service.rb', line 199 def self.find_articles(query, limit: 10) new(query, types: %w[posts articles], limit: limit).search.map { |r| r[:record] } end |
.find_images(query, limit: 10) ⇒ Array<Image>
Find images matching a query
169 170 171 |
# File 'app/services/semantic_search_service.rb', line 169 def self.find_images(query, limit: 10) new(query, types: ['images'], limit: limit).search.map { |r| r[:record] } end |
.find_pages(query, limit: 10) ⇒ Array<SiteMap>
Find pages matching a query
219 220 221 |
# File 'app/services/semantic_search_service.rb', line 219 def self.find_pages(query, limit: 10) new(query, types: ['pages'], limit: limit).search.map { |r| r[:record] } end |
.find_posts(query, limit: 10) ⇒ Array<Post>
Find blog posts matching a query
189 190 191 |
# File 'app/services/semantic_search_service.rb', line 189 def self.find_posts(query, limit: 10) new(query, types: ['posts'], limit: limit).search.map { |r| r[:record] } end |
.find_products(query, limit: 10) ⇒ Array<Item, ProductLine>
Find products (items and product lines) matching a query
209 210 211 |
# File 'app/services/semantic_search_service.rb', line 209 def self.find_products(query, limit: 10) new(query, types: ['products'], limit: limit).search.map { |r| r[:record] } end |
.find_publications(query, limit: 10) ⇒ Array<Item>
Find publications/PDFs matching a query
239 240 241 |
# File 'app/services/semantic_search_service.rb', line 239 def self.find_publications(query, limit: 10) new(query, types: ['publications'], limit: limit).search.map { |r| r[:record] } end |
.find_reviews(query, limit: 10) ⇒ Array<ReviewsIo>
Find customer reviews matching a query
229 230 231 |
# File 'app/services/semantic_search_service.rb', line 229 def self.find_reviews(query, limit: 10) new(query, types: ['reviews'], limit: limit).search.map { |r| r[:record] } end |
.find_showcases(query, limit: 10) ⇒ Array<Showcase>
Find showcases matching a query
159 160 161 |
# File 'app/services/semantic_search_service.rb', line 159 def self.find_showcases(query, limit: 10) new(query, types: ['showcases'], limit: limit).search.map { |r| r[:record] } end |
.find_videos(query, limit: 10) ⇒ Array<Video>
Find videos matching a query
179 180 181 |
# File 'app/services/semantic_search_service.rb', line 179 def self.find_videos(query, limit: 10) new(query, types: ['videos'], limit: limit).search.map { |r| r[:record] } end |
.search(query, **options) ⇒ Array<Hash>
Class-level search convenience method
149 150 151 |
# File 'app/services/semantic_search_service.rb', line 149 def self.search(query, **) new(query, ).search end |
.stats ⇒ Hash
Get embedding statistics
316 317 318 |
# File 'app/services/semantic_search_service.rb', line 316 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 135 136 137 138 139 140 141 |
# 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 = if hybrid ContentEmbedding.hybrid_search( query, limit: limit * 3, types: search_types, locale: locale, exclude_sensitive: exclude_sensitive ) else ContentEmbedding.semantic_search( query, limit: limit * 3, types: search_types, locale: locale, exclude_sensitive: exclude_sensitive ) end 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 |