Module: Aireview::ConfigFallbacks
- Included in:
- Config
- Defined in:
- lib/aireview/config_fallbacks.rb
Overview
Reserves for when the primary model is down or a key is out of quota: the routing plan (see StageChains and ModelPool) and the list of keys per provider. Models are set in .aireview.yml or the environment, keys only in the environment.
Constant Summary collapse
- KEYLESS_PROVIDERS =
%w[ollama].freeze
- DEFAULT_TIME_BUDGET =
1_800- DEFAULT_OVERLOADED_QUARANTINE =
120- SELF_HOSTED_PLACEHOLDER_KEY =
The keys a model is called with. A server of your own (a model with api_base) never gets the provider's key — OPENAI_API_KEY must not leave for a self-hosted server — but LLM_API_KEY, or a placeholder for a server without auth, since the client wants some key.
'no-key'
Instance Method Summary collapse
-
#api_key_counts(stages) ⇒ Object
Only the number of keys per provider, for --dry-run; the values never leave.
- #candidate_api_keys(candidate) ⇒ Object
- #fallback_names(stage) ⇒ Object
- #fallbacks_disabled? ⇒ Boolean
-
#llm_time_budget ⇒ Object
The ceiling for all LLM requests of the run, pauses between attempts included: the chain of reserves must not eat the whole CI job.
-
#overloaded_quarantine ⇒ Object
For how many seconds an overloaded or hung model is skipped before the router tries it again.
-
#provider_api_keys(provider) ⇒ Object
Keys in order of preference; a single nil for a keyless provider, so that walking the chain does not depend on the provider.
-
#require_llm_configuration!(critique: true) ⇒ Object
The plan is built whole (pool and critique policy validated) before the first request, not in Critique after a paid-for Generate.
-
#require_models!(critique: true) ⇒ Object
critique — the run has a critique (false with --no-critique); only the stages that need an LLM in such a run are required (see llm_stages).
-
#routing ⇒ Object
The routing plan is built once: a shared pool when llm.models is set, independent stage chains otherwise.
- #stage_chain(stage) ⇒ Object
Instance Method Details
#api_key_counts(stages) ⇒ Object
Only the number of keys per provider, for --dry-run; the values never leave. Servers of your own are not counted: they take LLM_API_KEY.
64 65 66 67 68 |
# File 'lib/aireview/config_fallbacks.rb', line 64 def api_key_counts(stages) providers = stages.flat_map { |stage| stage_chain(stage).reject(&:api_base).map(&:provider) }.uniq providers.reject { |provider| KEYLESS_PROVIDERS.include?(provider.to_s) } .to_h { |provider| [provider, provider_api_keys(provider).size] } end |
#candidate_api_keys(candidate) ⇒ Object
36 37 38 39 40 41 |
# File 'lib/aireview/config_fallbacks.rb', line 36 def candidate_api_keys(candidate) return provider_api_keys(candidate.provider) unless candidate.api_base return [nil] if KEYLESS_PROVIDERS.include?(candidate.provider.to_s) [Aireview::Utils.presence(llm_api_key) || SELF_HOSTED_PLACEHOLDER_KEY] end |
#fallback_names(stage) ⇒ Object
58 59 60 |
# File 'lib/aireview/config_fallbacks.rb', line 58 def fallback_names(stage) stage_chain(stage).drop(1).map(&:to_s) end |
#fallbacks_disabled? ⇒ Boolean
54 55 56 |
# File 'lib/aireview/config_fallbacks.rb', line 54 def fallbacks_disabled? @data['fallbacks_disabled'] == true end |
#llm_time_budget ⇒ Object
The ceiling for all LLM requests of the run, pauses between attempts included: the chain of reserves must not eat the whole CI job.
72 73 74 |
# File 'lib/aireview/config_fallbacks.rb', line 72 def llm_time_budget positive_integer!(dig('llm', 'time_budget') || DEFAULT_TIME_BUDGET, 'llm.time_budget') end |
#overloaded_quarantine ⇒ Object
For how many seconds an overloaded or hung model is skipped before the router tries it again.
78 79 80 81 |
# File 'lib/aireview/config_fallbacks.rb', line 78 def overloaded_quarantine positive_integer!(dig('llm', 'overloaded_quarantine') || DEFAULT_OVERLOADED_QUARANTINE, 'llm.overloaded_quarantine') end |
#provider_api_keys(provider) ⇒ Object
Keys in order of preference; a single nil for a keyless provider, so that walking the chain does not depend on the provider.
45 46 47 48 49 50 51 52 |
# File 'lib/aireview/config_fallbacks.rb', line 45 def provider_api_keys(provider) provider = provider.to_s return [nil] if KEYLESS_PROVIDERS.include?(provider) keys = Array(@data["#{provider}_api_keys"]).compact keys = [provider_api_key(provider)].compact if keys.empty? fallbacks_disabled? ? keys.first(1) : keys end |
#require_llm_configuration!(critique: true) ⇒ Object
The plan is built whole (pool and critique policy validated) before the first request, not in Critique after a paid-for Generate. Keys are required for the stages that need an LLM: generate on Ollama with Jev as the critic and no fallback needs no Gemini key.
99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/aireview/config_fallbacks.rb', line 99 def require_llm_configuration!(critique: true) require_models!(critique: critique) require_jev!(critique: critique) routing missing_keys = llm_stages(critique: critique).flat_map do |stage| providers = stage_chain(stage).reject(&:api_base).map(&:provider).uniq providers.reject { |provider| provider_keys_present?(provider) } .map { |provider| "#{stage}: API key is required for provider #{provider.inspect}" } end raise ConfigError, missing_keys.join(', ') unless missing_keys.empty? end |
#require_models!(critique: true) ⇒ Object
critique — the run has a critique (false with --no-critique); only the stages that need an LLM in such a run are required (see llm_stages).
85 86 87 88 89 90 91 92 93 |
# File 'lib/aireview/config_fallbacks.rb', line 85 def require_models!(critique: true) stages = llm_stages(critique: critique) missing = [] missing << 'llm.generate.model (or LLM_GENERATE_MODEL)' if Aireview::Utils.blank?(generate_model) if stages.include?('critique') && Aireview::Utils.blank?(critique_model) missing << 'llm.critique.model (or LLM_CRITIQUE_MODEL)' end raise ConfigError, "LLM models are required: #{missing.join(', ')}" unless missing.empty? end |
#routing ⇒ Object
The routing plan is built once: a shared pool when llm.models is set, independent stage chains otherwise. A stage with a model of its own inside the pool is an independent chain.
22 23 24 |
# File 'lib/aireview/config_fallbacks.rb', line 22 def routing @routing ||= build_routing end |
#stage_chain(stage) ⇒ Object
26 27 28 |
# File 'lib/aireview/config_fallbacks.rb', line 26 def stage_chain(stage) routing.chain(stage) end |