Class: Ask::Provider

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/provider.rb

Overview

Abstract base class for all LLM providers. Defines the interface that provider gems (ask-openai, ask-anthropic, etc.) must implement.

Provider gems subclass this and implement the abstract methods:

Providers register themselves via Provider.register so that Provider.resolve returns the correct class by name.

Examples:

Defining a custom provider

class MyProvider < Ask::Provider
  def api_base = "https://api.example.com/v1"
  def headers = { "Authorization" => "Bearer #{@config.api_key}" }
  def chat(messages, model:, **opts, &block) = # ...
  def embed(text, model:) = # ...
  def list_models = # ...
end
Ask::Provider.register(:my, MyProvider)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ Provider

Returns a new instance of Provider.

Parameters:

  • config (Object) (defaults to: {})

    provider configuration (must respond to configuration_requirements)



35
36
37
38
# File 'lib/ask/provider.rb', line 35

def initialize(config = {})
  @config = config
  ensure_configured!
end

Instance Attribute Details

#configObject (readonly)

Returns the configuration object passed to the constructor.

Returns:

  • (Object)

    the configuration object passed to the constructor



32
33
34
# File 'lib/ask/provider.rb', line 32

def config
  @config
end

Class Method Details

.assume_models_exist?Boolean

Returns whether all models can be assumed to exist.

Returns:

  • (Boolean)

    whether all models can be assumed to exist



203
204
205
# File 'lib/ask/provider.rb', line 203

def assume_models_exist?
  false
end

.capabilitiesHash?

Returns capabilities.

Returns:

  • (Hash, nil)

    capabilities



171
172
173
# File 'lib/ask/provider.rb', line 171

def capabilities
  nil
end

.clear_providers!Object

Clear all registered providers (used in testing).



152
153
154
155
156
# File 'lib/ask/provider.rb', line 152

def clear_providers!
  REGISTRY_MUTEX.synchronize do
    @registry = {}
  end
end

.configuration_optionsArray<Symbol>

Returns config keys this provider supports.

Returns:

  • (Array<Symbol>)

    config keys this provider supports



176
177
178
# File 'lib/ask/provider.rb', line 176

def configuration_options
  []
end

.configuration_requirementsArray<Symbol>

Returns config keys this provider requires.

Returns:

  • (Array<Symbol>)

    config keys this provider requires



181
182
183
# File 'lib/ask/provider.rb', line 181

def configuration_requirements
  []
end

.configured?(config) ⇒ Boolean

Check if this provider is fully configured.

Parameters:

  • config (Object)

    configuration object

Returns:

  • (Boolean)


188
189
190
# File 'lib/ask/provider.rb', line 188

def configured?(config)
  configuration_requirements.all? { |req| config.respond_to?(req) && config.public_send(req) }
end

.local?Boolean

Returns true if this provider runs locally.

Returns:

  • (Boolean)

    true if this provider runs locally



193
194
195
# File 'lib/ask/provider.rb', line 193

def local?
  false
end

.nameString

Returns class name without module prefix.

Returns:

  • (String)

    class name without module prefix



166
167
168
# File 'lib/ask/provider.rb', line 166

def name
  to_s.split("::").last
end

.providersHash{Symbol => Class<Ask::Provider>}

Return a shallow copy of all registered providers. Thread-safe via REGISTRY_MUTEX.

Returns:



145
146
147
148
149
# File 'lib/ask/provider.rb', line 145

def providers
  REGISTRY_MUTEX.synchronize do
    registry.dup
  end
end

.register(name, provider_class) ⇒ Object

Register a provider class so it can be resolved by name. Thread-safe via REGISTRY_MUTEX.

Parameters:

  • name (Symbol)

    short name for the provider

  • provider_class (Class<Ask::Provider>)

    the provider class



123
124
125
126
127
# File 'lib/ask/provider.rb', line 123

def register(name, provider_class)
  REGISTRY_MUTEX.synchronize do
    registry[name.to_sym] = provider_class
  end
end

.remote?Boolean

Returns true if this provider requires a remote API.

Returns:

  • (Boolean)

    true if this provider requires a remote API



198
199
200
# File 'lib/ask/provider.rb', line 198

def remote?
  !local?
end

.resolve(name) ⇒ Class<Ask::Provider>

Resolve a registered provider by name. Thread-safe via REGISTRY_MUTEX.

Parameters:

  • name (Symbol, String)

    provider name

Returns:

Raises:



134
135
136
137
138
139
140
# File 'lib/ask/provider.rb', line 134

def resolve(name)
  REGISTRY_MUTEX.synchronize do
    registry[name.to_sym] || raise(UnknownProvider,
                                    "Unknown provider: #{name.inspect}. " \
                                    "Available: #{registry.keys.join(', ')}")
  end
end

.slugString

Returns lowercased, underscored slug from the class name.

Returns:

  • (String)

    lowercased, underscored slug from the class name



159
160
161
162
163
# File 'lib/ask/provider.rb', line 159

def slug
  name.split("::").last.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
      .gsub(/([a-z\d])([A-Z])/, '\1_\2')
      .downcase
end

Instance Method Details

#api_baseString

This method is abstract.

The base URL for this provider’s API.

Returns:

  • (String)

Raises:

  • (NotImplementedError)


71
72
73
# File 'lib/ask/provider.rb', line 71

def api_base
  raise NotImplementedError, "#{self.class} must implement #api_base"
end

#assume_models_exist?Boolean

Returns true if all models can be assumed to exist.

Returns:

  • (Boolean)

    true if all models can be assumed to exist



97
# File 'lib/ask/provider.rb', line 97

def assume_models_exist? = self.class.assume_models_exist?

#capabilitiesHash?

Returns provider capabilities metadata.

Returns:

  • (Hash, nil)

    provider capabilities metadata



112
113
114
# File 'lib/ask/provider.rb', line 112

def capabilities
  self.class.capabilities
end

#chat(messages, model:, tools: nil, temperature: nil, stream: nil, schema: nil, **params) {|Ask::Chunk| ... } ⇒ Ask::Message

Send a chat completion request.

Parameters:

  • messages (Array<Ask::Message>)

    conversation messages

  • model (String)

    model ID to use

  • tools (Array<Ask::ToolDef>, nil) (defaults to: nil)

    tool definitions

  • temperature (Float, nil) (defaults to: nil)

    sampling temperature

  • stream (Boolean, nil) (defaults to: nil)

    if true, yield Chunks to the block

  • schema (Hash, nil) (defaults to: nil)

    JSON schema for structured output

Yields:

Returns:

Raises:

  • (NotImplementedError)


51
52
53
# File 'lib/ask/provider.rb', line 51

def chat(messages, model:, tools: nil, temperature: nil, stream: nil, schema: nil, **params, &block)
  raise NotImplementedError, "#{self.class} must implement #chat"
end

#embed(text, model:) ⇒ Array<Float>

Generate embeddings for the given text.

Parameters:

  • text (String)

    input text

  • model (String)

    embedding model ID

Returns:

  • (Array<Float>)

    embedding vector

Raises:

  • (NotImplementedError)


59
60
61
# File 'lib/ask/provider.rb', line 59

def embed(text, model:)
  raise NotImplementedError, "#{self.class} must implement #embed"
end

#headersHash<String, String>

Additional HTTP headers for API requests.

Returns:

  • (Hash<String, String>)


79
80
81
# File 'lib/ask/provider.rb', line 79

def headers
  {}
end

#list_modelsArray<Ask::ModelInfo>

List available models from this provider.

Returns:

Raises:

  • (NotImplementedError)


65
66
67
# File 'lib/ask/provider.rb', line 65

def list_models
  raise NotImplementedError, "#{self.class} must implement #list_models"
end

#local?Boolean

Returns true if the provider runs locally (e.g., Ollama).

Returns:

  • (Boolean)

    true if the provider runs locally (e.g., Ollama)



91
# File 'lib/ask/provider.rb', line 91

def local? = self.class.local?

#nameString

Returns provider name (demodulized class name).

Returns:

  • (String)

    provider name (demodulized class name)



107
108
109
# File 'lib/ask/provider.rb', line 107

def name
  self.class.name
end

#parse_error(response) ⇒ String?

Parse an error response body into a human-readable message.

Parameters:

  • response (Object)

    the error response

Returns:

  • (String, nil)


86
87
88
# File 'lib/ask/provider.rb', line 86

def parse_error(response)
  nil
end

#remote?Boolean

Returns true if the provider requires a remote API.

Returns:

  • (Boolean)

    true if the provider requires a remote API



94
# File 'lib/ask/provider.rb', line 94

def remote? = !local?

#slugString

Returns lowercased provider slug.

Returns:

  • (String)

    lowercased provider slug



102
103
104
# File 'lib/ask/provider.rb', line 102

def slug
  self.class.slug
end