Class: Insika::LLMConfigurator

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/llm_configurator.rb

Overview

Applies the authored LLM providers (LLMProviderStore) to RubyLLM at RUNTIME. Goal: swap a provider's key/base WITHOUT a restart — RubyLLM exposes config.<api>_api_key= / config.<api>_api_base=, so reconfiguring is a matter of setting those accessors per provider.

Core constraint: this file does NOT require ruby_llm at load-time — the require is lazy, inside apply (and injectable via configure: for tests, which run without the gem/key). A provider that RubyLLM doesn't recognize (no matching accessor) does NOT blow up: it goes into skipped (degrades to "restart recommended", like OpenClaw), the rest applies.

⚠️ GOTCHA — the DEFAULT target is a global singleton (RubyLLM research, §10). With no configure:, apply/unapply mutate the PROCESS-WIDE config (RubyLLM.config). They are therefore admin operations (rare, operator-driven: a provider key/base edit in the Studio), NOT a per-request/per-turn path — a concurrent turn reading the config mid-mutation would observe a torn key/base. This is tolerable ONLY because reconfiguration is infrequent and single-writer (one reactor). Do NOT call this per turn, and do NOT reach for it to vary the MODEL per turn — the model is chosen at chat build time (ModelResolver -> chat(model:)), never by mutating the global.

PER-GRAPH credentials are no longer hypothetical (RFC-0017 A2): the DSL runtime passes configure: targeting its own RubyLLM.context — an isolated config dup — so an operator's key edit in an EMBEDDED graph applies to that graph and stops there. See DSL::Runtime#llm_configure and docs/EMBEDDING.md.

Instance Method Summary collapse

Constructor Details

#initialize(provider_store:, configure: nil) ⇒ LLMConfigurator

Returns a new instance of LLMConfigurator.



30
31
32
33
# File 'lib/insika/llm_configurator.rb', line 30

def initialize(provider_store:, configure: nil)
  @provider_store = provider_store
  @configure = configure # ->(&blk){ blk.call(config_target) }; default = RubyLLM
end

Instance Method Details

#apply(providers = nil) ⇒ Object

Reconfigures RubyLLM with providers (raw records, with the real api_key) or, if nil, with ALL from the store. -> { applied: [api], skipped: [reason:] }.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/insika/llm_configurator.rb', line 37

def apply(providers = nil)
  records = providers || @provider_store.all_raw
  applied = []
  skipped = []

  with_config do |config|
    records.each do |rec|
      api = fetch(rec, :api)
      key = fetch(rec, :api_key)
      if key.nil? || key.to_s.empty?
        skipped << { api: api, reason: "sem api_key" }
        next
      end

      if set_accessor(config, "#{api}_api_key", key)
        base = fetch(rec, :base_url)
        set_accessor(config, "#{api}_api_base", base) if base && !base.to_s.empty?
        applied << api
      else
        skipped << { api: api, reason: "provider '#{api}' not recognized by RubyLLM" }
      end
    end
  end

  { applied: applied, skipped: skipped }
end

#unapply(api) ⇒ Object

UNDOES a provider's config in RubyLLM at runtime (delete without a restart, §9.5): clears <api>_api_key/<api>_api_base. A provider that RubyLLM doesn't recognize (no accessor) -> unapplied: false (nothing applied, nothing to undo). -> { unapplied: bool }.



68
69
70
71
72
73
74
75
76
# File 'lib/insika/llm_configurator.rb', line 68

def unapply(api)
  api = api.to_s
  unapplied = false
  with_config do |config|
    unapplied = set_accessor(config, "#{api}_api_key", nil)
    set_accessor(config, "#{api}_api_base", nil)
  end
  { unapplied: unapplied }
end