lex-llm 0.4.9 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lex-llm.gemspec +1 -0
- data/lib/legion/extensions/llm/connection.rb +1 -1
- data/lib/legion/extensions/llm/credential_sources.rb +27 -1
- data/lib/legion/extensions/llm/provider.rb +43 -13
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +9 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a203372d751b290a71cc289e382d80a49fafcc0687925c02594b8c5cfe6ef7aa
|
|
4
|
+
data.tar.gz: 95cfd5a03c002a16da80bac58914f1fb808a940db378d99035f97b6256240863
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 645bde1f8e4b6701fa5092f2b92e2867f63d243c239341e691921efa9cc74a861b3382f00665efd8f4d1420976c462f9c47eaa89bdae93ae60655983681bcddc
|
|
7
|
+
data.tar.gz: f180a90275c427970e6129ae3f0ef285fabb68fa92a97059687fb37fcf9282f5e083b159f3757293a79ae4bf71f263a54fb9387469f55da25a23959e295d2371
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.10 - 2026-05-13
|
|
4
|
+
|
|
5
|
+
- Add cache-backed `model_detail` lookup with 24-hour TTL; nil results are not cached; `fetch_model_detail` hook for subclasses to override with live API calls.
|
|
6
|
+
- Build `model_detail_cache_key` from tier, slug, instance, and credential fingerprint so remote providers never share model detail entries across credentials.
|
|
7
|
+
- Add `credential_cache_fragment` — includes an 8-char SHA-256 credential fingerprint in cache keys for non-local providers.
|
|
8
|
+
- Add `source_tag`, `credential_fingerprint`, and `config_fingerprint` to `CredentialSources` for provenance tracking across discovered instances.
|
|
9
|
+
- Suppress Faraday raw stacktrace dumps on connection failures by setting `errors: false` on the response logger middleware.
|
|
10
|
+
- Rescue `Faraday::ConnectionFailed` in `discover_offerings` and return an empty list with a concise warning instead of propagating the exception.
|
|
11
|
+
- Wire `model_allowed?` filtering into `discover_offerings` so whitelist/blacklist settings are enforced during live discovery (was dead code before).
|
|
12
|
+
- Check instance config first for `model_whitelist`/`model_blacklist` before falling back to provider settings, enabling per-instance override.
|
|
13
|
+
- Add `legion-cache >= 1.3.0` as a runtime dependency and include `Legion::Cache::Helper` in the base `Provider` class.
|
|
14
|
+
|
|
3
15
|
## 0.4.9 - 2026-05-13
|
|
4
16
|
|
|
5
17
|
- Route provider, tool, streaming, model, attachment, connection, credential, and fleet diagnostics through `Legion::Logging::Helper`.
|
data/lex-llm.gemspec
CHANGED
|
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
|
|
|
35
35
|
spec.add_dependency 'faraday-multipart', '>= 1'
|
|
36
36
|
spec.add_dependency 'faraday-net_http', '>= 1'
|
|
37
37
|
spec.add_dependency 'faraday-retry', '>= 1'
|
|
38
|
+
spec.add_dependency 'legion-cache', '>= 1.3.0'
|
|
38
39
|
spec.add_dependency 'legion-crypt', '>= 1.5.1'
|
|
39
40
|
spec.add_dependency 'legion-json', '>= 1.2.1'
|
|
40
41
|
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
@@ -167,6 +167,30 @@ module Legion
|
|
|
167
167
|
Digest::SHA256.hexdigest(val.to_s)
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
+
# Build a human-readable source tag describing where a credential was found.
|
|
171
|
+
# Format: "type:location:key" e.g. "env:ANTHROPIC_API_KEY", "file:~/.claude/settings.json:anthropicApiKey"
|
|
172
|
+
def source_tag(type, location, key = nil)
|
|
173
|
+
parts = [type.to_s, location.to_s]
|
|
174
|
+
parts << key.to_s if key && !key.to_s.empty?
|
|
175
|
+
parts.join(':')
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Generate a short fingerprint (first 8 chars of SHA-256) for a credential value.
|
|
179
|
+
# Stable for the lifetime of the credential; safe to log and include in audit events.
|
|
180
|
+
def credential_fingerprint(value)
|
|
181
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
182
|
+
|
|
183
|
+
Digest::SHA256.hexdigest(value.to_s)[0, 8]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Extract fingerprint from a config hash by finding the first credential field.
|
|
187
|
+
def config_fingerprint(config)
|
|
188
|
+
val = config[:api_key] || config['api_key'] ||
|
|
189
|
+
config[:bearer_token] || config['bearer_token'] ||
|
|
190
|
+
config[:access_token] || config['access_token']
|
|
191
|
+
credential_fingerprint(val)
|
|
192
|
+
end
|
|
193
|
+
|
|
170
194
|
# Returns true when the URL points to localhost / 127.0.0.1 / ::1.
|
|
171
195
|
def localhost?(url)
|
|
172
196
|
return false if url.nil?
|
|
@@ -185,7 +209,9 @@ module Legion
|
|
|
185
209
|
module_function :env, :claude_config, :claude_config_value,
|
|
186
210
|
:claude_env_value, :codex_token, :codex_openai_key,
|
|
187
211
|
:setting, :socket_open?, :http_ok?,
|
|
188
|
-
:dedup_credentials, :credential_hash,
|
|
212
|
+
:dedup_credentials, :credential_hash,
|
|
213
|
+
:source_tag, :credential_fingerprint, :config_fingerprint,
|
|
214
|
+
:localhost?
|
|
189
215
|
|
|
190
216
|
# --- private helpers -----------------------------------------------
|
|
191
217
|
|
|
@@ -28,6 +28,7 @@ module Legion
|
|
|
28
28
|
class Provider
|
|
29
29
|
include Streaming
|
|
30
30
|
include Legion::Logging::Helper
|
|
31
|
+
include Legion::Cache::Helper
|
|
31
32
|
|
|
32
33
|
attr_reader :config, :connection
|
|
33
34
|
|
|
@@ -123,10 +124,14 @@ module Legion
|
|
|
123
124
|
provider_health = health(live:)
|
|
124
125
|
@cached_offerings = Array(list_models(live:, **filters)).filter_map do |model|
|
|
125
126
|
next unless model_matches_filters?(model, filters)
|
|
127
|
+
next unless model_allowed?(model.id)
|
|
126
128
|
|
|
127
129
|
offering_from_model(model, health: provider_health)
|
|
128
130
|
end
|
|
129
131
|
@cached_offerings
|
|
132
|
+
rescue Faraday::ConnectionFailed => e
|
|
133
|
+
log.warn("[#{slug}] instance=#{provider_instance_id} unreachable: #{e.message}")
|
|
134
|
+
[]
|
|
130
135
|
end
|
|
131
136
|
|
|
132
137
|
def health(live: false)
|
|
@@ -284,12 +289,14 @@ module Legion
|
|
|
284
289
|
# ── Model allow-list / deny-list filtering ────────────────────────
|
|
285
290
|
|
|
286
291
|
def model_whitelist
|
|
287
|
-
wl =
|
|
292
|
+
wl = config.model_whitelist if config.respond_to?(:model_whitelist)
|
|
293
|
+
wl ||= settings[:model_whitelist] if respond_to?(:settings)
|
|
288
294
|
Array(wl).map { |p| p.to_s.downcase }
|
|
289
295
|
end
|
|
290
296
|
|
|
291
297
|
def model_blacklist
|
|
292
|
-
bl =
|
|
298
|
+
bl = config.model_blacklist if config.respond_to?(:model_blacklist)
|
|
299
|
+
bl ||= settings[:model_blacklist] if respond_to?(:settings)
|
|
293
300
|
Array(bl).map { |p| p.to_s.downcase }
|
|
294
301
|
end
|
|
295
302
|
|
|
@@ -371,21 +378,24 @@ module Legion
|
|
|
371
378
|
nil
|
|
372
379
|
end
|
|
373
380
|
|
|
374
|
-
def
|
|
375
|
-
|
|
381
|
+
def model_detail(model_name)
|
|
382
|
+
key = model_detail_cache_key(model_name)
|
|
383
|
+
cached = cache_get(key)
|
|
384
|
+
return cached if cached
|
|
376
385
|
|
|
377
|
-
|
|
386
|
+
result = fetch_model_detail(model_name)
|
|
387
|
+
cache_set(key, result, ttl: 86_400) if result
|
|
388
|
+
result
|
|
378
389
|
rescue StandardError => e
|
|
379
|
-
handle_exception(e, level: :
|
|
390
|
+
handle_exception(e, level: :warn, handled: true, operation: 'llm.provider.model_detail',
|
|
391
|
+
model: model_name)
|
|
392
|
+
nil
|
|
380
393
|
end
|
|
381
394
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
rescue StandardError => e
|
|
387
|
-
handle_exception(e, level: :debug, handled: true, operation: 'llm.provider.model_cache_fetch', key:)
|
|
388
|
-
yield
|
|
395
|
+
# Override in subclasses to make a live API call for model detail.
|
|
396
|
+
# Must return a Hash with symbol keys (e.g. { context_window: 128000 }).
|
|
397
|
+
def fetch_model_detail(_model_name)
|
|
398
|
+
nil
|
|
389
399
|
end
|
|
390
400
|
|
|
391
401
|
def cache_instance_key
|
|
@@ -448,6 +458,26 @@ module Legion
|
|
|
448
458
|
|
|
449
459
|
private
|
|
450
460
|
|
|
461
|
+
def model_detail_cache_key(model_name)
|
|
462
|
+
tier = offering_tier
|
|
463
|
+
instance_key = cache_instance_key
|
|
464
|
+
cred_fp = credential_cache_fragment
|
|
465
|
+
key_parts = ['model_info', tier, slug, instance_key, cred_fp, model_name].compact
|
|
466
|
+
key_parts.join('.')
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def credential_cache_fragment
|
|
470
|
+
return nil if cache_local_instance?
|
|
471
|
+
|
|
472
|
+
cred = config.respond_to?(:bearer_token) && config.bearer_token
|
|
473
|
+
cred ||= config.respond_to?(:api_key) && config.api_key
|
|
474
|
+
cred ||= config.respond_to?(:bedrock_access_key_id) && config.bedrock_access_key_id
|
|
475
|
+
return nil unless cred
|
|
476
|
+
|
|
477
|
+
require 'digest'
|
|
478
|
+
Digest::SHA256.hexdigest(cred.to_s)[0, 8]
|
|
479
|
+
end
|
|
480
|
+
|
|
451
481
|
def validate_paint_inputs!(with:, mask:)
|
|
452
482
|
return if with.nil? && mask.nil?
|
|
453
483
|
|
|
@@ -9,6 +9,15 @@ require 'faraday/multipart'
|
|
|
9
9
|
require 'faraday/retry'
|
|
10
10
|
require 'legion/json'
|
|
11
11
|
require 'legion/logging'
|
|
12
|
+
# legion/cache writes DEBUG lines to $stdout on first load; suppress them here
|
|
13
|
+
# so callers that capture our stdout (e.g. Open3-based integration tests) are unaffected.
|
|
14
|
+
begin
|
|
15
|
+
old_stdout = $stdout
|
|
16
|
+
$stdout = File.open(File::NULL, 'w')
|
|
17
|
+
require 'legion/cache'
|
|
18
|
+
ensure
|
|
19
|
+
$stdout = old_stdout
|
|
20
|
+
end
|
|
12
21
|
require 'logger'
|
|
13
22
|
require 'marcel'
|
|
14
23
|
require 'ruby_llm/schema'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -108,6 +108,20 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '1'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: legion-cache
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 1.3.0
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 1.3.0
|
|
111
125
|
- !ruby/object:Gem::Dependency
|
|
112
126
|
name: legion-crypt
|
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|