Class: LittleGhost::ModelResolver

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

Overview

Maps an application model role to an executable Model.

resolver = LittleGhost::ModelResolver.new(
profiles: {
  customer_support: {target: "openrouter:openai/gpt-5.6-luna"}
}
)
# With OPENROUTER_API_KEY set:
model = resolver.resolve(:customer_support)

A role is an application-facing name such as :customer_support. Callers may also pass a provider:model-id target or an inline configuration mapping.

Constant Summary collapse

DEFAULT_CREDENTIALS =

:nodoc:

[ # :nodoc:
  ["LITTLEGHOST_OPENROUTER_API_KEY", "openrouter"],
  ["LITTLEGHOST_OPENAI_API_KEY", "openai"],
  ["OPENROUTER_API_KEY", "openrouter"],
  ["OPENAI_API_KEY", "openai"]
].freeze
DEFAULT_MODELS =

:nodoc:

{ # :nodoc:
  "openrouter" => "openai/gpt-5.6-luna",
  "openai" => "gpt-5.6-luna"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(providers: nil, profiles: nil, default_model: nil, provider_registry: ProviderRegistry.new, catalog: nil, catalog_sources: [], provider_adapters: {}, credential_resolver: nil) ⇒ ModelResolver

Builds a resolver from explicit provider connections and profiles. With neither, conventional credentials select GPT-5.6 Luna.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/little_ghost/model_resolver.rb', line 34

def initialize(providers: nil, profiles: nil, default_model: nil, provider_registry: ProviderRegistry.new,
  catalog: nil, catalog_sources: [], provider_adapters: {}, credential_resolver: nil)
  unless providers.nil? || providers.is_a?(Providers::Configuration)
    raise ArgumentError, "providers must be a LittleGhost::Providers::Configuration"
  end

  @providers = providers
  @connections = providers&.connections || {}
  @profiles = normalize_profiles(profiles || {})
  @default_model = (default_model&.to_s || "default").dup.freeze
  @provider_registry = provider_adapters.empty? ? provider_registry : ProviderRegistry.new(adapters: provider_adapters)
  @credential_resolver = credential_resolver
  configure_default_providers unless providers
  configure_default_profiles unless profiles
  sources = catalog_sources.empty? ? built_in_catalog_sources : catalog_sources
  @catalog = catalog || Models::Catalog.new(sources:)
end

Instance Attribute Details

#catalogObject (readonly)

Provider configuration and model metadata catalog.



30
31
32
# File 'lib/little_ghost/model_resolver.rb', line 30

def catalog
  @catalog
end

#default_modelObject (readonly)

Logical role used when an agent does not declare one.



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

def default_model
  @default_model
end

#providersObject (readonly)

Provider configuration and model metadata catalog.



30
31
32
# File 'lib/little_ghost/model_resolver.rb', line 30

def providers
  @providers
end

Instance Method Details

#details(target) ⇒ Object

Returns normalized metadata for target without constructing a provider.



92
93
# File 'lib/little_ghost/model_resolver.rb', line 92

def details(target) = catalog.details(target)
# Refreshes configured metadata sources, retaining stale data on failure.

#refresh!(target: nil) ⇒ Object

Refreshes configured metadata sources, retaining stale data on failure.



94
# File 'lib/little_ghost/model_resolver.rb', line 94

def refresh!(target: nil) = catalog.refresh!(target:)

#resolve(selection, invocation: nil, context: nil, profiles: nil, **options) ⇒ Object

Resolves a logical role, canonical target, or inline model mapping into an executable Model. Inline mappings require provider and model; remaining entries are trusted model settings. The optional profiles mapping applies only to role selections and is never read from invocation.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/little_ghost/model_resolver.rb', line 59

def resolve(selection, invocation: nil, context: nil, profiles: nil, **options)
  base, role = normalize_selection(selection)
  if role
    base = resolved_role(role)
    profile_overlays = profiles || {}
    override_names(role).each do |profile|
      base = merge(base, profile_overlays[profile] || profile_overlays[profile.to_sym])
    end
  end
  target = Models::Target.parse(base.fetch(:target))
  provider_config = @connections.fetch(target.provider) do
    raise ConfigurationError, "Model target references unknown provider #{target.provider}"
  end
  provider_config = provider_config.merge(resolved_credentials(target.provider, provider_config)) if @credential_resolver
  details = details_for(target, base[:details])
  settings = clamp_output_tokens(base.fetch(:settings), details)
  adapter = provider_config.fetch("adapter")
  provider = @provider_registry.build(
    adapter:,
    model: target.model_id,
    configuration: provider_config,
    request: base.fetch(:request),
    role:,
    settings:,
    details:,
    invocation:,
    context:,
    **options
  )
  Model.new(provider:, target:, settings:, details:, role:)
end