Class: LittleGhost::ModelRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/model_registry.rb

Overview

Give application roles stable model choices without coupling agents to vendors. A registry joins provider factories with named, inheritable profiles.

A support application can keep its general and research agents on related profiles while selecting one provider in one place:

class CustomerSupportModels < LittleGhost::ModelRegistry
def initialize
  super
  provider(:openai) do |model:, **|
    LittleGhost::Providers::OpenAI.new(
      api_key: ENV.fetch("OPENAI_API_KEY"), model: model
    )
  end
  profile "customer_support", provider: :openai, model: "gpt-5"
  profile "customer_support.research", inherit: "customer_support",
    settings: {temperature: 0.1}
end
end

model = CustomerSupportModels.new.resolve("customer_support.research")
model.id       # => "gpt-5"
model.settings # => {temperature: 0.1}

Dotted roles resolve from the exact role toward shorter registered parents. Explicit profile inheritance supplies provider, model, settings, and metadata; per-invocation overrides then layer from inherited parents through the exact requested role. A final override passed to resolve wins over both. Settings and overrides are control-plane input: construct or allowlist them in application code rather than copying unchecked request fields. An override can select a different registered provider or model and change the destination, capability, and cost of a request.

Provider factories receive the resolved identity, settings, metadata, invocation, run context, and forwarding options. Resolution raises ConfigurationError for missing roles, providers, models, factories, or circular inheritance; exceptions raised inside a factory are not masked.

Direct Known Subclasses

DefaultModelRegistry

Instance Method Summary collapse

Constructor Details

#initializeModelRegistry

Starts an empty registry ready for provider factories and model profiles.



43
44
45
46
# File 'lib/little_ghost/model_registry.rb', line 43

def initialize
  @providers = {}
  @profiles = {}
end

Instance Method Details

#profile(name, provider: nil, model: nil, settings: {}, metadata: {}, inherit: nil) ⇒ Object

Adds an inheritable model profile named name and returns self.

Parent settings and metadata merge into the child. A child may inherit its provider and model or replace either one.



61
62
63
64
65
66
67
68
69
70
# File 'lib/little_ghost/model_registry.rb', line 61

def profile(name, provider: nil, model: nil, settings: {}, metadata: {}, inherit: nil)
  @profiles[name.to_s] = {
    provider: provider&.to_sym,
    model: model&.to_s,
    settings: settings.to_h.transform_keys(&:to_sym),
    metadata: .to_h,
    inherit: inherit&.to_s
  }
  self
end

#provider(name, callable = nil, &factory) ⇒ Object

Associates name with a provider factory and returns self.

The factory receives the resolved model, logical role, profile settings and metadata, invocation, run context, and resolution options.



52
53
54
55
# File 'lib/little_ghost/model_registry.rb', line 52

def provider(name, callable = nil, &factory)
  @providers[name.to_sym] = factory || callable || raise(ArgumentError, "provider factory is required")
  self
end

#resolve(name, invocation: nil, override: nil, run: nil, context: nil, **options) ⇒ Object

Materializes name as a configured Model for the current invocation.

override applies after registered profiles and invocation profile overrides. context takes precedence over the legacy run argument when passed to the provider factory.

Raises:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/little_ghost/model_registry.rb', line 77

def resolve(name, invocation: nil, override: nil, run: nil, context: nil, **options)
  role = name.to_s
  profile_name = profile_for(role)
  configuration = resolved_profile(profile_name)
  override_profiles(profile_name, role).each do |profile|
    configuration = merge(configuration, profile_override(invocation, profile))
  end
  configuration = merge(configuration, override)
  provider_name = configuration[:provider]
  model_id = configuration[:model]
  raise ConfigurationError, "Model profile #{profile_name} does not define a provider" unless provider_name
  raise ConfigurationError, "Model profile #{profile_name} does not define a model" unless model_id

  factory = @providers.fetch(provider_name) do
    raise ConfigurationError, "No provider is registered for #{provider_name}"
  end
  provider = factory.call(
    model: model_id,
    role:,
    settings: configuration.fetch(:settings),
    metadata: configuration.fetch(:metadata),
    invocation:,
    context: context || run,
    **options
  )
  Model.new(
    provider:,
    provider_name:,
    model: model_id,
    settings: configuration.fetch(:settings),
    metadata: configuration.fetch(:metadata),
    role:
  )
end