Module: Vangrail::Providers::Gateway
- Defined in:
- lib/vangrail/providers/gateway.rb
Overview
A shared OpenAI-compatible gateway, described by configuration rather than compiled in.
Institutions and vendors each run one, on their own hostname, with their own credential source and their own model names. None of that is knowledge a general-purpose gem should carry: a hostname in this source is an endpoint every installation inherits whether it can reach it or not, and a credential path is worse, because it says where somebody's secrets live.
So a gateway is registered by the application that has one:
Vangrail::Providers.register_gateway(
name: 'hub',
base_url: 'https://gateway.example/api/v0',
models: { judge: 'some/instruct-model', guard: 'some/guard-model' },
guard_preset: :apriel_guard,
key_env: 'HUB_API_KEY',
key_file: File.join(Dir.home, '.config', 'hub', 'api_key'),
pass_entry: 'hub/token'
)
or by environment, so a deployment needs no code at all:
GUARDRAILS_GATEWAY_NAME, _API_BASE, _API_KEY, _MODEL, _JUDGE_MODEL,
_GUARD_PRESET, _KEY_FILE, _PASS_ENTRY
Credentials resolve in one order, most explicit first: the environment
variable, then a key file, then pass. Nothing is cached across a
reset!, so a test can point the lookups at nothing and mean it.
Defined Under Namespace
Classes: Spec
Constant Summary collapse
- ENV_PREFIX =
'GUARDRAILS_GATEWAY'
Class Method Summary collapse
- .from_env(spec, env) ⇒ Object
-
.from_environment(env = ENV) ⇒ Object
A gateway described entirely by environment, or nil when none is.
-
.from_file(spec, env) ⇒ Object
The override replaces the configured path rather than being tried before it.
-
.from_pass(spec, env = ENV) ⇒ Object
stdin is closed for the child: gpg reads stdin, and a helper that inherits a caller's stdin can consume input the caller still needs or block forever on a pinentry prompt.
- .present(value) ⇒ Object
-
.provider(spec, env = ENV) ⇒ Object
Builds a Provider from a Spec.
- .token(spec, env = ENV) ⇒ Object
Class Method Details
.from_env(spec, env) ⇒ Object
67 68 69 70 71 |
# File 'lib/vangrail/providers/gateway.rb', line 67 def from_env(spec, env) return nil unless spec.key_env present(env[spec.key_env]) end |
.from_environment(env = ENV) ⇒ Object
A gateway described entirely by environment, or nil when none is.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/vangrail/providers/gateway.rb', line 105 def from_environment(env = ENV) base = present(env["#{ENV_PREFIX}_API_BASE"]) return nil unless base Spec.new( name: present(env["#{ENV_PREFIX}_NAME"]) || 'gateway', base_url: base, models: { judge: present(env["#{ENV_PREFIX}_JUDGE_MODEL"]) || present(env["#{ENV_PREFIX}_MODEL"]), guard: present(env["#{ENV_PREFIX}_GUARD_MODEL"]), }.compact, guard_preset: present(env["#{ENV_PREFIX}_GUARD_PRESET"])&.to_sym, key_env: "#{ENV_PREFIX}_API_KEY", file_env: "#{ENV_PREFIX}_KEY_FILE", pass_env: "#{ENV_PREFIX}_PASS_ENTRY", key_file: present(env["#{ENV_PREFIX}_KEY_FILE"]), pass_entry: present(env["#{ENV_PREFIX}_PASS_ENTRY"]), ) end |
.from_file(spec, env) ⇒ Object
The override replaces the configured path rather than being tried before it. Pointing it at a file that does not exist has to mean "no key here": otherwise there is no way to run without credentials on a machine that happens to have some, which is exactly what a test needs to do.
77 78 79 80 81 82 83 84 |
# File 'lib/vangrail/providers/gateway.rb', line 77 def from_file(spec, env) var = spec.file_env || (spec.key_env && "#{spec.key_env}_FILE") override = var && present(env[var]) path = override || spec.key_file return nil unless path && File.file?(path) present(File.read(path).lines.first) end |
.from_pass(spec, env = ENV) ⇒ Object
stdin is closed for the child: gpg reads stdin, and a helper that inherits a caller's stdin can consume input the caller still needs or block forever on a pinentry prompt.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/vangrail/providers/gateway.rb', line 89 def from_pass(spec, env = ENV) entry = (spec.pass_env && present(env[spec.pass_env])) || spec.pass_entry return nil unless entry out = IO.popen(['pass', 'show', entry], in: File::NULL, err: File::NULL, &:read) return nil unless $?&.success? present(out.to_s.lines.first) # A missing `pass`, a locked keyring, a refused pinentry: none of them are # this method's problem, and all of them mean the same thing here, which is # that no credential came from this source. rescue StandardError nil end |
.present(value) ⇒ Object
125 126 127 128 |
# File 'lib/vangrail/providers/gateway.rb', line 125 def present(value) s = value.to_s.strip s.empty? ? nil : s end |
.provider(spec, env = ENV) ⇒ Object
Builds a Provider from a Spec. The token is resolved lazily and memoized
per spec, so pass runs at most once per process.
50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/vangrail/providers/gateway.rb', line 50 def provider(spec, env = ENV) tokens = {} Provider.new( name: spec.name, base_url: spec.base_url, models: spec.models || {}, guard_preset: spec.guard_preset, key_resolver: lambda do tokens[spec.name] ||= token(spec, env) end, ) end |
.token(spec, env = ENV) ⇒ Object
63 64 65 |
# File 'lib/vangrail/providers/gateway.rb', line 63 def token(spec, env = ENV) from_env(spec, env) || from_file(spec, env) || from_pass(spec, env) end |