Class: Prescient::Base Abstract
- Inherits:
-
Object
- Object
- Prescient::Base
- Defined in:
- lib/prescient/base.rb
Overview
Subclass and implement #generate_embedding, #generate_response, #health_check, and #validate_configuration!
Base class for all AI provider implementations
This abstract base class defines the common interface that all AI providers must implement. It provides shared functionality for text processing, context formatting, prompt building, and error handling.
Direct Known Subclasses
Provider::Anthropic, Provider::HuggingFace, Provider::Ollama, Provider::OpenAI
Instance Attribute Summary collapse
-
#options ⇒ Hash
readonly
Configuration options for this provider instance.
Instance Method Summary collapse
-
#apply_format_template(template, format_data) ⇒ Object
private
Apply format template with error handling.
-
#available? ⇒ Boolean
Check if the provider is currently available.
-
#build_format_data(item, config) ⇒ Object
private
Build format data from item fields.
-
#build_prompt(query, context_items = []) ⇒ String
protected
Build formatted prompt from query and context items.
-
#calculate_field_match_score(item_fields, config_fields) ⇒ Object
private
Calculate field matching score.
-
#clean_text(text) ⇒ String
protected
Clean and preprocess text for AI processing.
-
#default_context_configs ⇒ Object
protected
Minimal default context configuration - users should define their own contexts.
-
#default_prompt_templates ⇒ Hash
protected
Get default prompt templates.
-
#detect_context_type(item) ⇒ Object
private
Detect context type from item structure.
-
#extract_configured_fields(item, config) ⇒ Object
private
Extract fields configured for embeddings.
-
#extract_embedding_text(item, context_type = nil) ⇒ Object
protected
Extract text for embedding generation based on context configuration.
-
#extract_text_values(item) ⇒ Object
protected
Extract text values from hash, excluding non-textual fields.
-
#fallback_format_hash(item, format_data = nil) ⇒ Object
private
Fallback formatting for hash items.
-
#find_best_field_match(item_fields, context_configs) ⇒ Object
private
Find the best matching context configuration.
-
#format_context_item(item) ⇒ Object
protected
Generic context item formatting using configurable contexts.
-
#format_hash_item(item) ⇒ Object
private
Format a hash item using context configuration.
-
#generate_embedding(text, **options) ⇒ Array<Float>
abstract
Generate embeddings for the given text.
-
#generate_response(prompt, context_items = [], **options) ⇒ Hash
abstract
Generate text response for the given prompt.
-
#handle_errors { ... } ⇒ Object
protected
Handle and standardize errors from provider operations.
-
#health_check ⇒ Hash
abstract
Check the health and availability of the provider.
-
#initialize(**options) ⇒ Base
constructor
Initialize the provider with configuration options.
-
#match_context_by_fields(item, context_configs) ⇒ Object
private
Match context type based on configured field patterns.
-
#normalize_embedding(embedding, target_dimensions) ⇒ Array<Float>?
protected
Normalize embedding dimensions to match expected size.
-
#resolve_context_config(item, context_type) ⇒ Object
private
Resolve context configuration for an item.
-
#validate_configuration! ⇒ void
protected
Validate provider configuration.
Constructor Details
#initialize(**options) ⇒ Base
Initialize the provider with configuration options
41 42 43 44 |
# File 'lib/prescient/base.rb', line 41 def initialize(**) = validate_configuration! end |
Instance Attribute Details
#options ⇒ Hash (readonly)
Returns Configuration options for this provider instance.
31 32 33 |
# File 'lib/prescient/base.rb', line 31 def end |
Instance Method Details
#apply_format_template(template, format_data) ⇒ Object (private)
Apply format template with error handling
313 314 315 316 317 |
# File 'lib/prescient/base.rb', line 313 def apply_format_template(template, format_data) template % format_data rescue KeyError nil end |
#available? ⇒ Boolean
Check if the provider is currently available
93 94 95 96 97 |
# File 'lib/prescient/base.rb', line 93 def available? health_check[:status] == 'healthy' rescue StandardError false end |
#build_format_data(item, config) ⇒ Object (private)
Build format data from item fields
300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/prescient/base.rb', line 300 def build_format_data(item, config) format_data = {} fields_to_check = config[:fields].any? ? config[:fields] : item.keys.map(&:to_s) fields_to_check.each do |field| value = item[field] || item[field.to_sym] format_data[field.to_sym] = value if value end format_data end |
#build_prompt(query, context_items = []) ⇒ String (protected)
Build formatted prompt from query and context items
Creates a properly formatted prompt using configurable templates, incorporating context items when provided.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/prescient/base.rb', line 203 def build_prompt(query, context_items = []) templates = default_prompt_templates.merge([:prompt_templates] || {}) system_prompt = templates[:system_prompt] if context_items.empty? templates[:no_context_template] % { system_prompt: system_prompt, query: query, } else context_text = context_items.map.with_index(1) { |item, index| "#{index}. #{format_context_item(item)}" }.join("\n\n") templates[:with_context_template] % { system_prompt: system_prompt, context: context_text, query: query, } end end |
#calculate_field_match_score(item_fields, config_fields) ⇒ Object (private)
Calculate field matching score
362 363 364 365 366 367 |
# File 'lib/prescient/base.rb', line 362 def calculate_field_match_score(item_fields, config_fields) return 0 if config_fields.empty? matching_fields = (item_fields & config_fields).size matching_fields.to_f / config_fields.size end |
#clean_text(text) ⇒ String (protected)
Clean and preprocess text for AI processing
Removes excess whitespace, normalizes spacing, and enforces length limits suitable for most AI models.
160 161 162 163 |
# File 'lib/prescient/base.rb', line 160 def clean_text(text) # Limit length for most models text.to_s.gsub(/\s+/, ' ').strip.slice(0, 8000) end |
#default_context_configs ⇒ Object (protected)
Minimal default context configuration - users should define their own contexts
226 227 228 229 230 231 232 233 234 235 |
# File 'lib/prescient/base.rb', line 226 def default_context_configs { # Generic fallback configuration - works with any hash structure 'default' => { fields: [], # Will be dynamically determined from item keys format: nil, # Will use fallback formatting embedding_fields: [], # Will use all string/text fields }, } end |
#default_prompt_templates ⇒ Hash (protected)
Get default prompt templates
Provides standard templates for system prompts and context handling that can be overridden via provider options.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/prescient/base.rb', line 172 def default_prompt_templates { system_prompt: 'You are a helpful AI assistant. Answer questions clearly and accurately.', no_context_template: " %<system_prompt>s\n\n Question: %<query>s\n\n Please provide a helpful response based on your knowledge.\n TEMPLATE\n with_context_template: <<~TEMPLATE.strip,\n %<system_prompt>s Use the following context to answer the question. If the context doesn't contain relevant information, say so clearly.\n\n Context:\n %<context>s\n\n Question: %<query>s\n\n Please provide a helpful response based on the context above.\n TEMPLATE\n }\nend\n".strip, |
#detect_context_type(item) ⇒ Object (private)
Detect context type from item structure
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/prescient/base.rb', line 320 def detect_context_type(item) return 'default' unless item.is_a?(Hash) # Check for explicit type fields (user-defined) return item['type'].to_s if item['type'] return item['context_type'].to_s if item['context_type'] return item['model_type'].to_s.downcase if item['model_type'] # If no explicit type and user has configured contexts, try to match context_configs = [:context_configs] || {} return match_context_by_fields(item, context_configs) if context_configs.any? # Default fallback 'default' end |
#extract_configured_fields(item, config) ⇒ Object (private)
Extract fields configured for embeddings
282 283 284 285 286 |
# File 'lib/prescient/base.rb', line 282 def extract_configured_fields(item, config) return nil unless config[:embedding_fields]&.any? config[:embedding_fields].filter_map { |field| item[field] || item[field.to_sym] } end |
#extract_embedding_text(item, context_type = nil) ⇒ Object (protected)
Extract text for embedding generation based on context configuration
238 239 240 241 242 243 244 |
# File 'lib/prescient/base.rb', line 238 def (item, context_type = nil) return item.to_s unless item.is_a?(Hash) config = resolve_context_config(item, context_type) text_values = extract_configured_fields(item, config) || extract_text_values(item) text_values.join(' ').strip end |
#extract_text_values(item) ⇒ Object (protected)
Extract text values from hash, excluding non-textual fields
247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/prescient/base.rb', line 247 def extract_text_values(item) # Common fields to exclude from embedding text # TODO: configurable fields to exclude aside from the common ones below exclude_fields = ['id', '_id', 'uuid', 'created_at', 'updated_at', 'timestamp', 'version', 'status', 'active'] item.filter_map { |key, value| next if exclude_fields.include?(key.to_s.downcase) next unless value.is_a?(String) || value.is_a?(Numeric) next if value.to_s.strip.empty? value.to_s } end |
#fallback_format_hash(item, format_data = nil) ⇒ Object (private)
Fallback formatting for hash items
370 371 372 373 |
# File 'lib/prescient/base.rb', line 370 def fallback_format_hash(item, format_data = nil) # Fallback: join key-value pairs (format_data || item).map { |k, v| "#{k}: #{v}" }.join(', ') end |
#find_best_field_match(item_fields, context_configs) ⇒ Object (private)
Find the best matching context configuration
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/prescient/base.rb', line 344 def find_best_field_match(item_fields, context_configs) best_match = nil best_score = 0 context_configs.each do |context_type, config| next unless config[:fields]&.any? score = calculate_field_match_score(item_fields, config[:fields]) next unless score >= 0.5 && score > best_score best_match = context_type best_score = score end best_match end |
#format_context_item(item) ⇒ Object (protected)
Generic context item formatting using configurable contexts
262 263 264 265 266 267 268 |
# File 'lib/prescient/base.rb', line 262 def format_context_item(item) case item when Hash then format_hash_item(item) when String then item else item.to_s end end |
#format_hash_item(item) ⇒ Object (private)
Format a hash item using context configuration
289 290 291 292 293 294 295 296 297 |
# File 'lib/prescient/base.rb', line 289 def format_hash_item(item) config = resolve_context_config(item, nil) return fallback_format_hash(item) unless config[:format] format_data = build_format_data(item, config) return fallback_format_hash(item) unless format_data.any? apply_format_template(config[:format], format_data) || fallback_format_hash(item) end |
#generate_embedding(text, **options) ⇒ Array<Float>
Generate embeddings for the given text
This method must be implemented by subclasses to provide embedding generation functionality.
56 57 58 |
# File 'lib/prescient/base.rb', line 56 def (text, **) raise NotImplementedError, "#{self.class} must implement #generate_embedding" end |
#generate_response(prompt, context_items = [], **options) ⇒ Hash
Generate text response for the given prompt
This method must be implemented by subclasses to provide text generation functionality with optional context items.
74 75 76 |
# File 'lib/prescient/base.rb', line 74 def generate_response(prompt, context_items = [], **) raise NotImplementedError, "#{self.class} must implement #generate_response" end |
#handle_errors { ... } ⇒ Object (protected)
Handle and standardize errors from provider operations
Wraps provider-specific operations and converts common exceptions into standardized Prescient error types while preserving existing Prescient errors.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/prescient/base.rb', line 123 def handle_errors yield rescue Prescient::Error # Re-raise Prescient errors without wrapping raise rescue Net::ReadTimeout, Net::OpenTimeout => e raise Prescient::ConnectionError, "Request timeout: #{e.message}" rescue Net::HTTPError => e raise Prescient::ConnectionError, "HTTP error: #{e.message}" rescue JSON::ParserError => e raise Prescient::InvalidResponseError, "Invalid JSON response: #{e.message}" rescue StandardError => e raise Prescient::Error, "Unexpected error: #{e.message}" end |
#health_check ⇒ Hash
Check the health and availability of the provider
This method must be implemented by subclasses to provide health check functionality.
86 87 88 |
# File 'lib/prescient/base.rb', line 86 def health_check raise NotImplementedError, "#{self.class} must implement #health_check" end |
#match_context_by_fields(item, context_configs) ⇒ Object (private)
Match context type based on configured field patterns
337 338 339 340 341 |
# File 'lib/prescient/base.rb', line 337 def match_context_by_fields(item, context_configs) item_fields = item.keys.map(&:to_s) best_match = find_best_field_match(item_fields, context_configs) best_match || 'default' end |
#normalize_embedding(embedding, target_dimensions) ⇒ Array<Float>? (protected)
Normalize embedding dimensions to match expected size
Ensures embedding vectors have consistent dimensions by truncating longer vectors or padding shorter ones with zeros.
146 147 148 149 150 151 |
# File 'lib/prescient/base.rb', line 146 def (, target_dimensions) return nil unless .is_a?(Array) return .first(target_dimensions) if .length >= target_dimensions + Array.new(target_dimensions - .length, 0.0) end |
#resolve_context_config(item, context_type) ⇒ Object (private)
Resolve context configuration for an item
273 274 275 276 277 278 279 |
# File 'lib/prescient/base.rb', line 273 def resolve_context_config(item, context_type) context_configs = default_context_configs.merge([:context_configs] || {}) return context_configs['default'] if context_configs.empty? detected_type = context_type || detect_context_type(item) context_configs[detected_type] || context_configs['default'] end |
#validate_configuration! ⇒ void (protected)
This method returns an undefined value.
Validate provider configuration
Override this method in subclasses to validate required configuration options and raise appropriate errors for missing or invalid settings.
108 109 110 |
# File 'lib/prescient/base.rb', line 108 def validate_configuration! # Override in subclasses to validate required configuration end |