Class: Vangrail::Provider
- Inherits:
-
Object
- Object
- Vangrail::Provider
- Defined in:
- lib/vangrail/provider.rb
Overview
Where the model-backed rails call, and what they may ask for there.
Every endpoint this gem talks to is OpenAI-compatible, so the differences that matter are not protocol at all. They are: how a credential resolves, whether the endpoint is up, and which model roles it can actually serve. A local proxy has a key sitting in a constant and may need starting; a shared gateway resolves a token from three places and is either up or not; neither necessarily hosts a safety classifier.
That last point drives a real decision rather than a label. model(:guard)
returning nil means the provider has no classifier, and the builder puts a
policy rail on the input side instead of pretending a classifier is there.
provider = Vangrail::Provider.resolve # from the environment
provider.chat(:judge) # => Chat, ready to ask
Constant Summary collapse
- ROLES =
%i[guard judge].freeze
Instance Attribute Summary collapse
-
#base_url ⇒ Object
readonly
Returns the value of attribute base_url.
-
#guard_preset ⇒ Object
readonly
Returns the value of attribute guard_preset.
-
#local ⇒ Object
readonly
Returns the value of attribute local.
-
#models ⇒ Object
readonly
Returns the value of attribute models.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
- .[](name) ⇒ Object
-
.from_env_pair(env) ⇒ Object
An endpoint given directly, which is how anything unregistered is used.
-
.gateway_in(env) ⇒ Object
A gateway described by the environment this call was handed, rather than by the one the registry happened to be installed from.
- .names ⇒ Object
- .present(value) ⇒ Object
- .register(provider) ⇒ Object
-
.registry ⇒ Object
Presets by name, in the order
resolvetries them. -
.resolve(env = ENV) ⇒ Object
Picks a provider from the environment.
Instance Method Summary collapse
- #api_key ⇒ Object
-
#available? ⇒ Boolean
Up, and holding a credential.
- #chat(role = :judge, **kwargs) ⇒ Object
- #credential_required? ⇒ Boolean
-
#guard? ⇒ Boolean
Can this provider serve a safety classifier, as opposed to an instruct model answering a written policy.
-
#initialize(name:, base_url:, models: {}, key_resolver: nil, guard_preset: nil, local: false, probe: nil) ⇒ Provider
constructor
A new instance of Provider.
- #model(role) ⇒ Object
- #to_h ⇒ Object
- #to_s ⇒ Object
-
#with_env(env) ⇒ Object
A copy that reads overrides out of an environment.
Constructor Details
#initialize(name:, base_url:, models: {}, key_resolver: nil, guard_preset: nil, local: false, probe: nil) ⇒ Provider
Returns a new instance of Provider.
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/vangrail/provider.rb', line 105 def initialize(name:, base_url:, models: {}, key_resolver: nil, guard_preset: nil, local: false, probe: nil) @name = name.to_s @base_url = base_url.to_s.sub(/\/+\z/, '') @models = models @key_resolver = key_resolver @guard_preset = guard_preset @local = local @probe = probe end |
Instance Attribute Details
#base_url ⇒ Object (readonly)
Returns the value of attribute base_url.
103 104 105 |
# File 'lib/vangrail/provider.rb', line 103 def base_url @base_url end |
#guard_preset ⇒ Object (readonly)
Returns the value of attribute guard_preset.
103 104 105 |
# File 'lib/vangrail/provider.rb', line 103 def guard_preset @guard_preset end |
#local ⇒ Object (readonly)
Returns the value of attribute local.
103 104 105 |
# File 'lib/vangrail/provider.rb', line 103 def local @local end |
#models ⇒ Object (readonly)
Returns the value of attribute models.
103 104 105 |
# File 'lib/vangrail/provider.rb', line 103 def models @models end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
103 104 105 |
# File 'lib/vangrail/provider.rb', line 103 def name @name end |
Class Method Details
.[](name) ⇒ Object
36 37 38 |
# File 'lib/vangrail/provider.rb', line 36 def [](name) registry[name.to_s] end |
.from_env_pair(env) ⇒ Object
An endpoint given directly, which is how anything unregistered is used.
85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/vangrail/provider.rb', line 85 def from_env_pair(env) base = present(env['GUARDRAILS_API_BASE']) return nil unless base new( name: 'env', base_url: base, key_resolver: -> { present(env['GUARDRAILS_API_KEY']) }, models: { judge: present(env['GUARDRAILS_JUDGE_MODEL']), guard: present(env['GUARDRAILS_MODEL']) } ) end |
.gateway_in(env) ⇒ Object
A gateway described by the environment this call was handed, rather than by the one the registry happened to be installed from. Resolution is then a function of (registry, env), which is what a caller passing an env hash is entitled to assume.
75 76 77 78 79 80 81 82 |
# File 'lib/vangrail/provider.rb', line 75 def gateway_in(env) return nil if env.equal?(ENV) spec = Providers::Gateway.from_environment(env) spec && Providers::Gateway.provider(spec, env) rescue NameError nil end |
.names ⇒ Object
40 41 42 |
# File 'lib/vangrail/provider.rb', line 40 def names registry.keys end |
.present(value) ⇒ Object
97 98 99 100 |
# File 'lib/vangrail/provider.rb', line 97 def present(value) s = value.to_s.strip s.empty? ? nil : s end |
.register(provider) ⇒ Object
31 32 33 34 |
# File 'lib/vangrail/provider.rb', line 31 def register(provider) registry[provider.name] = provider provider end |
.registry ⇒ Object
Presets by name, in the order resolve tries them.
27 28 29 |
# File 'lib/vangrail/provider.rb', line 27 def registry @registry ||= {} end |
.resolve(env = ENV) ⇒ Object
Picks a provider from the environment.
GUARDRAILS_PROVIDER=<name> take this one, and fail loudly if it is
unknown rather than falling back
GUARDRAILS_API_BASE + key an endpoint nobody registered
otherwise the first registered provider that is
actually available, in registration order
Returning nil is a legitimate answer: no endpoint is reachable, and the caller builds an engine with only the offline rails on it.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/vangrail/provider.rb', line 54 def resolve(env = ENV) candidates = registry.each_value.to_a + [gateway_in(env)].compact wanted = present(env['GUARDRAILS_PROVIDER']) if wanted found = candidates.find { |p| p.name == wanted } raise ConfigError, "unknown provider #{wanted.inspect}; known: #{names.join(', ')}" unless found return found.with_env(env) end explicit = from_env_pair(env) return explicit if explicit candidates.map { |p| p.with_env(env) }.find(&:available?) end |
Instance Method Details
#api_key ⇒ Object
134 135 136 137 138 |
# File 'lib/vangrail/provider.rb', line 134 def api_key return @api_key if defined?(@api_key) @api_key = @key_resolver&.call end |
#available? ⇒ Boolean
Up, and holding a credential. A local endpoint is probed, because a proxy that is not running is the ordinary case rather than a failure.
152 153 154 155 156 157 |
# File 'lib/vangrail/provider.rb', line 152 def available? return false unless api_key || !credential_required? return true unless @probe @probe.call end |
#chat(role = :judge, **kwargs) ⇒ Object
163 164 165 166 167 168 |
# File 'lib/vangrail/provider.rb', line 163 def chat(role = :judge, **kwargs) name = model(role) raise ConfigError, "provider #{self.name} has no #{role} model" unless name Chat.new(model: name, base_url: base_url, api_key: api_key, **kwargs) end |
#credential_required? ⇒ Boolean
159 160 161 |
# File 'lib/vangrail/provider.rb', line 159 def credential_required? !@key_resolver.nil? end |
#guard? ⇒ Boolean
Can this provider serve a safety classifier, as opposed to an instruct model answering a written policy.
146 147 148 |
# File 'lib/vangrail/provider.rb', line 146 def guard? !model(:guard).nil? && !guard_preset.nil? end |
#model(role) ⇒ Object
140 141 142 |
# File 'lib/vangrail/provider.rb', line 140 def model(role) models[role.to_sym] end |
#to_h ⇒ Object
170 171 172 173 174 175 176 177 178 179 |
# File 'lib/vangrail/provider.rb', line 170 def to_h { 'name' => name, 'base_url' => base_url, 'models' => models.transform_keys(&:to_s).compact, 'guard_preset' => guard_preset&.to_s, 'local' => local, 'available' => available? }.compact end |
#to_s ⇒ Object
181 182 183 |
# File 'lib/vangrail/provider.rb', line 181 def to_s "#{name} #{base_url}" end |
#with_env(env) ⇒ Object
A copy that reads overrides out of an environment. Providers are shared objects in a registry, so nothing mutates in place.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/vangrail/provider.rb', line 118 def with_env(env) overrides = { judge: self.class.present(env['GUARDRAILS_JUDGE_MODEL']), guard: self.class.present(env['GUARDRAILS_MODEL']) }.compact base = self.class.present(env["#{env_prefix}_API_BASE"]) || base_url key = self.class.present(env["#{env_prefix}_API_KEY"]) return self if overrides.empty? && base == base_url && key.nil? self.class.new( name: name, base_url: base, models: models.merge(overrides), key_resolver: key ? -> { key } : @key_resolver, guard_preset: guard_preset, local: local, probe: @probe ) end |