Module: Legion::Extensions::Llm::SettingsCascade

Defined in:
lib/legion/extensions/llm/settings_cascade.rb

Overview

The shared 3-level settings cascade: provider -> instance -> model, most-specific-first. Single home for resolving operator config (enable_* overrides, weight, preferred_min/max_context_tokens, model_whitelist/blacklist, ...) for a (provider_family, instance, model, key) request.

instance is the operator's CONFIG NAME — the same identity Identity::InstanceKey#instance_id and the router key settings lookups by — never a derived host:port/credential value.

Lookup order (first meaningful value wins):

1. model:    extensions.llm.<provider>.instances.<instance>.models.<model>.<key>
           then extensions.llm.<provider>.models.<model>.<key>
2. instance: extensions.llm.<provider>.instances.<instance>.<key>
3. provider: extensions.llm.<provider>.<key>

Empty values (nil, blank String, empty Array) never resolve; the cascade falls through to the next scope and returns nil when no scope carries a meaningful value.

The cascade READS config only. It never publishes evidence: config and

override sources remain unknown-only (Taxonomies

UNKNOWN_ONLY_EVIDENCE_SOURCES), and enable_* overrides are consumed router-side, not published as capability evidence.

Class Method Summary collapse

Class Method Details

.lookup(scope, name) ⇒ Object

Tries the name as-given, as a Symbol, then as a String, preserving meaningful falsy values (false, 0) when the key is present.



103
104
105
106
107
108
109
110
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 103

def lookup(scope, name)
  return nil unless scope.is_a?(::Hash)

  return scope[name] if scope.key?(name)
  return scope[name.to_sym] if scope.key?(name.to_sym)

  scope[name.to_s]
end

.meaningful?(value) ⇒ Boolean

Returns:

  • (Boolean)


97
98
99
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 97

def meaningful?(value)
  !value.nil? && !value.to_s.empty? && (!value.is_a?(::Array) || value.any?)
end

.merge_model_scopes(provider_conf:, instance_cfg:, model:) ⇒ Object

The merged model-scope config hash for one model: the provider's models. entry with the instance's models. entry overriding it (the merge the capability feeders use).



83
84
85
86
87
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 83

def merge_model_scopes(provider_conf:, instance_cfg:, model:)
  text_name!(model, :model)

  model_scope(provider_conf, model).merge(model_scope(instance_cfg, model))
end

.model_scope(scope_conf, model) ⇒ Object



89
90
91
92
93
94
95
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 89

def model_scope(scope_conf, model)
  models = lookup(scope_conf, :models)
  return {} unless models.is_a?(::Hash)

  entry = lookup(models, model)
  entry.is_a?(::Hash) ? entry : {}
end

.resolve(provider_family:, instance:, key:, model: nil) ⇒ Object

Resolve key for (provider_family, instance, model) from the live Legion::Settings[:llm][] path.



35
36
37
38
39
40
41
42
43
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 35

def resolve(provider_family:, instance:, key:, model: nil)
  text_name!(provider_family, :provider_family)
  text_name!(instance, :instance)
  text_name!(key, :key)
  text_name!(model, :model) if model

  llm_conf = Legion::Settings.dig(:extensions, :llm)
  resolve_from(llm_conf: llm_conf, provider_family: provider_family, instance: instance, key: key, model: model)
end

.resolve_from(llm_conf:, provider_family:, instance:, key:, model: nil) ⇒ Object

Resolve against a pre-fetched extensions.llm subtree (plain Hash), e.g. a router settings snapshot. Same cascade as .resolve.



47
48
49
50
51
52
53
54
55
56
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 47

def resolve_from(llm_conf:, provider_family:, instance:, key:, model: nil)
  text_name!(provider_family, :provider_family)
  text_name!(instance, :instance)
  text_name!(key, :key)

  provider_conf = lookup(llm_conf, provider_family)
  instances = lookup(provider_conf, :instances)
  instance_cfg = lookup(instances, instance)
  resolve_value(provider_conf: provider_conf, instance_cfg: instance_cfg, key: key, model: model)
end

.resolve_value(provider_conf:, instance_cfg:, key:, model: nil) ⇒ Object

The pure 3-level cascade over pre-fetched scope hashes: model scopes (instance-scoped first, then provider-scoped) > instance

provider. model: nil skips the model leg. Returns the first meaningful value or nil.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 62

def resolve_value(provider_conf:, instance_cfg:, key:, model: nil)
  text_name!(key, :key)
  text_name!(model, :model) if model

  scopes = []
  scopes << model_scope(instance_cfg, model) if model
  scopes << model_scope(provider_conf, model) if model
  scopes << instance_cfg
  scopes << provider_conf

  scopes.each do |scope|
    value = lookup(scope, key)
    return value if meaningful?(value)
  end

  nil
end

.text_name!(name, field) ⇒ Object

Raises:

  • (::ArgumentError)


112
113
114
115
116
# File 'lib/legion/extensions/llm/settings_cascade.rb', line 112

def text_name!(name, field)
  return if name.is_a?(::String) || name.is_a?(::Symbol)

  raise ::ArgumentError, "#{field} must be a String or Symbol"
end