Class: Prescient::Base Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/prescient/base.rb

Overview

This class is abstract.

Subclass and implement #generate_embedding, #generate_response, #health_check, and any configuration validation required by #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.

Examples:

Creating a custom provider

class MyProvider < Prescient::Base
  def generate_embedding(text, **options)
    # Implementation here
  end

  def generate_response(prompt, context_items = [], **options)
    # Implementation here
  end

  def health_check
    # Implementation here
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**options) ⇒ Base

Initialize the provider with configuration options

Parameters:

  • options (Hash)

    Provider-specific configuration options

Options Hash (**options):

  • :api_key (String)

    API key for authenticated providers

  • :url (String)

    Base URL for self-hosted providers

  • :timeout (Integer)

    Request timeout in seconds

  • :prompt_templates (Hash)

    Custom prompt templates

  • :context_configs (Hash)

    Context formatting configurations

  • :embedding_dimensions (Integer)

    Expected custom embedding size

  • :context_excluded_fields (Array<Symbol, String>)

    Additional field names excluded from generic embedding text



46
47
48
49
50
# File 'lib/prescient/base.rb', line 46

def initialize(**options)
  @options = options
  @provider_name = options.fetch(:provider_name, self.class.to_s.split('::').last).to_s.sub(/\A./, &:upcase)
  validate_configuration!
end

Instance Attribute Details

#optionsHash (readonly)

Returns Configuration options for this provider instance.

Returns:

  • (Hash)

    Configuration options for this provider instance



33
34
35
# File 'lib/prescient/base.rb', line 33

def options
  @options
end

#provider_nameHash (readonly)

Returns Configuration options for this provider instance.

Returns:

  • (Hash)

    Configuration options for this provider instance



33
34
35
# File 'lib/prescient/base.rb', line 33

def provider_name
  @provider_name
end

Instance Method Details

#available?Boolean

Check if the provider is currently available

Returns true when the health check reports reachable: true. For legacy adapters that only return a status string, status == "healthy" is also treated as available.

Returns:

  • (Boolean)

    true if the provider is currently reachable



104
105
106
107
108
109
# File 'lib/prescient/base.rb', line 104

def available?
  health = health_check
  health.key?(:reachable) ? health[:reachable] == true : health[:status] == 'healthy'
rescue StandardError
  false
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.

Parameters:

  • query (String)

    The user's question or prompt

  • context_items (Array<Hash, String>) (defaults to: [])

    Optional context items

Returns:

  • (String)

    Formatted prompt ready for AI processing



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/prescient/base.rb', line 219

def build_prompt(query, context_items = [])
  templates = default_prompt_templates.merge(@options[: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

#clean_text(text) ⇒ String (protected)

Clean and preprocess text for AI processing

Removes excess whitespace, normalizes spacing, and truncates to the library's current 8,000-character input ceiling.

Parameters:

  • text (String, nil)

    The text to clean

Returns:

  • (String)

    Cleaned text, empty string if input was nil/empty



176
177
178
179
# File 'lib/prescient/base.rb', line 176

def clean_text(text)
  # Limit length for most models
  text.to_s.gsub(/\s+/, ' ').strip.slice(0, 8000)
end

#default_context_configsObject (protected)

Minimal default context configuration - users should define their own contexts



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/prescient/base.rb', line 242

def default_context_configs
  embedding_fields = [] # : Array[untyped]
  fields = [] # : Array[untyped]

  {
    # Generic fallback configuration - works with any hash structure
    'default' => {
      fields:           fields, # Will be dynamically determined from item keys
      format:           nil, # Will use fallback formatting
      embedding_fields: embedding_fields, # Will use all string/text fields
    },
  }
end

#default_prompt_templatesHash (protected)

Get default prompt templates

Provides standard templates for system prompts and context handling that can be overridden via provider options.

Returns:

  • (Hash)

    Hash containing template strings with placeholders



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/prescient/base.rb', line 188

def default_prompt_templates
  {
    system_prompt:         'You are a helpful AI assistant. Answer questions clearly and accurately.',
    no_context_template:   <<~TEMPLATE.strip,
    with_context_template: <<~TEMPLATE.strip,
      %<system_prompt>s Use the following context to answer the question. If the context doesn't contain relevant information, say so clearly.

      Context:
      %<context>s

      Question: %<query>s

      Please provide a helpful response based on the context above.
    TEMPLATE
  }
end

#extract_embedding_text(item, context_type = nil) ⇒ Object (protected)

Extract text for embedding generation based on context configuration



257
258
259
260
261
262
263
# File 'lib/prescient/base.rb', line 257

def extract_embedding_text(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



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/prescient/base.rb', line 266

def extract_text_values(item)
  # Common fields to exclude from embedding text. Provider-specific fields can
  # be added with the :context_excluded_fields option.
  default_excluded_fields = ['id', '_id', 'uuid', 'created_at', 'updated_at', 'timestamp', 'version', 'status',
                             'active']
  configured_fields = Array(@options[:context_excluded_fields]) # : Array[untyped]
  configured_excluded_fields = configured_fields.map { |field| field.to_s.downcase }
  exclude_fields = default_excluded_fields | configured_excluded_fields

  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

#format_context_item(item) ⇒ Object (protected)

Generic context item formatting using configurable contexts



285
286
287
288
289
290
291
# File 'lib/prescient/base.rb', line 285

def format_context_item(item)
  case item
  when Hash then format_hash_item(item)
  when String then item
  else item.to_s
  end
end

#generate_embedding(text, **options) ⇒ Array<Float>

This method is abstract.

Generate embeddings for the given text

This method must be implemented by subclasses to provide embedding generation functionality.

Parameters:

  • text (String)

    The text to generate embeddings for

  • options (Hash)

    Provider-specific options

Returns:

  • (Array<Float>)

    Array of embedding values

Raises:

  • (NotImplementedError)

    If not implemented by subclass



62
63
64
# File 'lib/prescient/base.rb', line 62

def generate_embedding(text, **options)
  raise NotImplementedError, "#{self.class} must implement #generate_embedding"
end

#generate_response(prompt, context_items = [], **options) ⇒ Hash

This method is abstract.

Generate text response for the given prompt

This method must be implemented by subclasses to provide text generation functionality with optional context items.

Parameters:

  • prompt (String)

    The prompt to generate a response for

  • context_items (Array<Hash, String>) (defaults to: [])

    Optional context items to include

  • options (Hash)

    Provider-specific generation options

Options Hash (**options):

  • :temperature (Float)

    Sampling temperature (0.0-2.0)

  • :max_tokens (Integer)

    Maximum tokens to generate

  • :top_p (Float)

    Nucleus sampling parameter

Returns:

  • (Hash)

    Response hash with :response, :model, :provider keys

Raises:

  • (NotImplementedError)

    If not implemented by subclass



80
81
82
# File 'lib/prescient/base.rb', line 80

def generate_response(prompt, context_items = [], **options)
  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.

Yields:

  • The operation block to execute with error handling

Returns:

  • (Object)

    The result of the yielded block

Raises:



135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/prescient/base.rb', line 135

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_checkHash

This method is abstract.

Check the health and availability of the provider

This method must be implemented by subclasses to provide health check functionality.

Returns:

  • (Hash)

    Health status with at least :status and :provider keys, and typically :reachable and :ready for modern adapters

Raises:

  • (NotImplementedError)

    If not implemented by subclass



93
94
95
# File 'lib/prescient/base.rb', line 93

def health_check
  raise NotImplementedError, "#{self.class} must implement #health_check"
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.

Raises:



120
121
122
# File 'lib/prescient/base.rb', line 120

def validate_configuration!
  # Override in subclasses to validate required configuration
end

#validate_embedding_dimensions(embedding, target_dimensions) ⇒ Array<Float> (protected)

Validate embedding dimensions against the configured model dimension.

Embedding dimensions are part of the vector-storage contract. Vectors are never padded or truncated because either operation changes their meaning.

Parameters:

  • embedding (Array<Float>)

    The embedding vector to validate

  • target_dimensions (Integer)

    The required number of dimensions

Returns:

  • (Array<Float>)

    The original embedding when dimensions are valid

Raises:



160
161
162
163
164
165
166
167
# File 'lib/prescient/base.rb', line 160

def validate_embedding_dimensions(embedding, target_dimensions)
  raise Prescient::InvalidResponseError, 'Embedding response is not an array' unless embedding.is_a?(Array)

  return embedding if embedding.length == target_dimensions

  raise Prescient::InvalidResponseError,
        "Invalid embedding dimensions: expected #{target_dimensions}, got #{embedding.length}"
end