Module: Esp::Providers

Defined in:
lib/esp/providers.rb,
lib/esp/providers/ollama.rb,
lib/esp/providers/openai.rb,
lib/esp/providers/anthropic.rb

Overview

The LLM-provider seam behind Esp::Agent. Each provider translates the agent’s neutral transcript to its API’s native wire shape and normalizes the response back to a Completion. Providers self-register here, so adding one is a new file under providers/ plus a require in the load manifest.

Provider contract:

#complete(system:, tools:, messages:) -> Completion(text:, tool_calls:, raw:)
  system   — String system prompt
  tools    — Array<{name:, description:, input_schema:}> (JSON-Schema)
  messages — the neutral transcript (see Esp::Agent)
  raw      — the provider-native assistant *message* for this turn, stored
             on the assistant entry and replayed verbatim next call so
             provider-side state (Anthropic thinking signatures, OpenAI
             tool_calls) survives multi-turn tool use.

Defined Under Namespace

Classes: Anthropic, Completion, Entry, Ollama, OpenAI, UnknownProvider

Class Method Summary collapse

Class Method Details

.availableObject

The providers a client can choose from: id, default model, and whether each provider considers itself configured (per its own ‘configured?` — env-key presence for cloud providers, reachability probe for local).



55
56
57
58
59
60
# File 'lib/esp/providers.rb', line 55

def available
  registry.values.map do |entry|
    { id: entry.id, default_model: entry.default_model.to_s,
      configured: entry.klass.configured? }
  end
end

.build(id, model: nil) ⇒ Object

Instantiate a provider by id, with an optional model override (blank →the provider’s default). Extra opts (e.g. an injected client) pass through for tests.



46
47
48
49
50
# File 'lib/esp/providers.rb', line 46

def build(id, model: nil, **)
  entry = registry.fetch(id.to_s) { raise UnknownProvider, Esp.t('errors.providers.unknown', id: id) }
  chosen = model.nil? || model.to_s.empty? ? entry.default_model : model
  entry.klass.new(model: chosen, **)
end

.default_idObject

The provider to use when the caller doesn’t pick one: an explicit ESP_PROVIDER override (legacy MW_PROVIDER still honoured until step 24 ships), else the first auto-default configured provider, else the first registered. Manual-only providers (Ollama) are excluded from automatic selection — see Entry#auto_default.



67
68
69
70
71
72
73
# File 'lib/esp/providers.rb', line 67

def default_id
  override = ENV['ESP_PROVIDER'] || ENV.fetch('MW_PROVIDER', nil)
  return override if registry.key?(override.to_s)

  preferred = registry.values.find { |entry| entry.auto_default && entry.klass.configured? }
  preferred ? preferred.id : registry.keys.first
end

.register(id, klass, default_model:, env_key:, auto_default: true) ⇒ Object

Providers call this at load time. ‘env_key` names the environment variable that carries the API key (the shell injects it per provider). Provider classes must define `self.configured?` — usually “env key set” for cloud providers, “reachable” for local runtimes. `auto_default: false` keeps a provider out of automatic default selection (used by Ollama so the user picks it explicitly).



38
39
40
41
# File 'lib/esp/providers.rb', line 38

def register(id, klass, default_model:, env_key:, auto_default: true)
  registry[id] = Entry.new(id: id, klass: klass, default_model: default_model,
                           env_key: env_key, auto_default: auto_default)
end

.registryObject

The id → Entry map, lazily initialized and populated by providers at load time (a plain mutable hash, hence not a frozen constant).



28
29
30
# File 'lib/esp/providers.rb', line 28

def registry
  @registry ||= {}
end