lex-llm-bedrock 0.4.4 → 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 +34 -0
- data/lex-llm-bedrock.gemspec +1 -1
- data/lib/legion/extensions/llm/bedrock/actors/discovery_refresh.rb +118 -13
- data/lib/legion/extensions/llm/bedrock/provider.rb +128 -93
- data/lib/legion/extensions/llm/bedrock/thinking_modes.rb +65 -0
- data/lib/legion/extensions/llm/bedrock/translator.rb +19 -10
- data/lib/legion/extensions/llm/bedrock/version.rb +1 -1
- data/lib/legion/extensions/llm/bedrock.rb +14 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee19df358e0fe9b9c6020be38aca9500e1053aee94afd929e83b6a041dca82d5
|
|
4
|
+
data.tar.gz: 6d1ccabe1fa7709a941b1f679dad26ec49fa62950ca764d2fb07ffed1fa63c7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9849e1efcf3a3d35ce2c9ea5c4b2fa61c5232a361b0a33c29f3deb6d98c1aad66672ffccafbebfdd94b4c353218befc12f39f0f13eba2b7429a0ab000d6ea4ab
|
|
7
|
+
data.tar.gz: db563c25558e547ac90bacc8d8dd4d0eb8628d8e3135063b32eaff6c48eddbcb128967cda735b4204eee6946b91d912852268f16167e261d8dfcbbfbcedd0f6d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.10] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Claude models now advertise the `:thinking` capability.** Discovery passed `provider_catalog: {}` to `CapabilityPolicy.resolve`, so per-model capabilities from the shared lex-llm catalog (which correctly tags Claude 3.7 / 4+ models `reasoning` → `:thinking`) were ignored — every Bedrock Claude model reported no thinking capability, so the router's thinking filter could not route thinking requests correctly. `offering_from_model` now consults the shared catalog via `catalog_capabilities`.
|
|
7
|
+
- **Thinking payload no longer sends unsupported `adaptive` mode.** `invoke_model_thinking` / `build_invoke_thinking` (and the converse-path `bedrock_additional_fields` / `build_additional_fields`) previously emitted `{type: 'adaptive'}` for every non-`claude-sonnet-4` model, which Bedrock rejects with `ValidationException: adaptive thinking is not supported on this model` (HTTP 500, e.g. claude-opus-4-5). A new `ThinkingModes` module is the single source of truth shared by provider and translator: budgeted-thinking models emit `{type: 'enabled', budget_tokens: N}`; known non-thinking models omit thinking entirely. `adaptive` is never emitted.
|
|
8
|
+
|
|
9
|
+
## [0.4.9] - 2026-06-20
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Stub shared registry publishing through `RegistryPublisher#schedule` in specs so async availability-event coverage stays stable after the shared publisher moved off raw `Thread.new`.
|
|
13
|
+
|
|
14
|
+
## [0.4.8] - 2026-06-20
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Stop bulk-publishing Bedrock model availability from `list_models`; discovery now emits one registry event per seen model from the shared `lex-llm` policy-filter path so blocked models stay observable without duplicate publishes.
|
|
18
|
+
|
|
19
|
+
## [0.4.7] - 2026-06-20
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Stop deriving Bedrock `us.`/`eu.`/`ap.` inference-profile prefixes from AWS regions. Model invocation now strips any existing geo prefix and prepends only an explicit Bedrock geo prefix setting, defaulting to `us`.
|
|
23
|
+
|
|
24
|
+
## [0.4.6] - 2026-06-20
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Canonicalize Bedrock embedding discovery to the shared singular `:embedding` capability and route provider/instance/model override extraction through the `lex-llm` base provider contract.
|
|
28
|
+
|
|
29
|
+
## [0.4.5] - 2026-06-19
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Adopt `Legion::Extensions::Llm::Inventory::ScopedRefresher` mixin (lex-llm 0.6.0). Discovery
|
|
33
|
+
refresh actors now write directly to the live `Inventory` catalog via `Inventory.write_lane`.
|
|
34
|
+
- Pin `lex-llm >= 0.6.0` and `legion-llm >= 0.14.0` in gemspec.
|
|
35
|
+
- Standard `weight: 100` default added to provider instance settings schema.
|
|
36
|
+
|
|
3
37
|
## 0.4.4 - 2026-06-17
|
|
4
38
|
|
|
5
39
|
### Fixed
|
data/lex-llm-bedrock.gemspec
CHANGED
|
@@ -29,5 +29,5 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
30
30
|
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
31
31
|
spec.add_dependency 'legion-transport', '>= 1.4.14'
|
|
32
|
-
spec.add_dependency 'lex-llm', '>= 0.
|
|
32
|
+
spec.add_dependency 'lex-llm', '>= 0.6.0'
|
|
33
33
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
3
5
|
begin
|
|
4
6
|
require 'legion/extensions/actors/every'
|
|
5
7
|
rescue LoadError => e
|
|
@@ -8,15 +10,27 @@ end
|
|
|
8
10
|
|
|
9
11
|
return unless defined?(Legion::Extensions::Actors::Every)
|
|
10
12
|
|
|
13
|
+
begin
|
|
14
|
+
require 'legion/extensions/llm/inventory/scoped_refresher'
|
|
15
|
+
rescue LoadError => e
|
|
16
|
+
warn(e.message) if $VERBOSE
|
|
17
|
+
end
|
|
18
|
+
|
|
11
19
|
module Legion
|
|
12
20
|
module Extensions
|
|
13
21
|
module Llm
|
|
14
22
|
module Bedrock
|
|
15
23
|
module Actor
|
|
16
|
-
class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation
|
|
24
|
+
class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation,Metrics/ClassLength
|
|
17
25
|
include Legion::Logging::Helper
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
if defined?(Legion::Extensions::Llm::Inventory::ScopedRefresher)
|
|
28
|
+
include Legion::Extensions::Llm::Inventory::ScopedRefresher
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
EMBED_TYPES = %i[embed embedding].freeze
|
|
32
|
+
|
|
33
|
+
def self.every_seconds = 3600
|
|
20
34
|
|
|
21
35
|
def runner_class = self.class
|
|
22
36
|
def runner_function = 'manual'
|
|
@@ -26,26 +40,117 @@ module Legion
|
|
|
26
40
|
def generate_task? = false
|
|
27
41
|
|
|
28
42
|
def time
|
|
29
|
-
return
|
|
43
|
+
return self.class.every_seconds unless defined?(Legion::Settings)
|
|
44
|
+
|
|
45
|
+
Legion::Settings.dig(:extensions, :llm, :bedrock, :discovery_interval) || self.class.every_seconds
|
|
46
|
+
end
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
def scope_key(**)
|
|
49
|
+
{ provider: :bedrock }
|
|
32
50
|
end
|
|
33
51
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
return unless defined?(Legion::LLM::Discovery)
|
|
52
|
+
def compute_lanes_for_scope(**)
|
|
53
|
+
return [] unless defined?(Legion::LLM::Call::Registry)
|
|
37
54
|
|
|
38
|
-
Legion::
|
|
55
|
+
settings = Legion::Settings.dig(:extensions, :llm, :bedrock) || {}
|
|
56
|
+
fleet_enabled = settings.dig(:fleet, :dispatch, :enabled)
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
if defined?(Legion::LLM::Inventory) && Legion::LLM::Inventory.respond_to?(:invalidate_offerings_cache!)
|
|
44
|
-
Legion::LLM::Inventory.invalidate_offerings_cache!
|
|
58
|
+
instances = Legion::LLM::Call::Registry.all_instances.select do |e|
|
|
59
|
+
(e[:provider] || '').to_sym == :bedrock
|
|
45
60
|
end
|
|
61
|
+
|
|
62
|
+
instances.flat_map { |inst| lanes_for_instance(inst, fleet_enabled: fleet_enabled) }
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
handle_exception(e, level: :warn, handled: true,
|
|
65
|
+
operation: 'bedrock.actor.discovery_refresh.compute_lanes')
|
|
66
|
+
[]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def credential_hash(**)
|
|
70
|
+
raw = Legion::Settings.dig(:extensions, :llm, :bedrock) || {}
|
|
71
|
+
Digest::SHA256.hexdigest(raw[:api_key].to_s + raw[:instances].to_s)[0, 16]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def manual(**)
|
|
75
|
+
tick if defined?(Legion::Extensions::Llm::Inventory::ScopedRefresher) &&
|
|
76
|
+
self.class.ancestors.include?(Legion::Extensions::Llm::Inventory::ScopedRefresher)
|
|
46
77
|
rescue StandardError => e
|
|
47
78
|
handle_exception(e, level: :warn, handled: true, operation: 'bedrock.actor.discovery_refresh')
|
|
48
79
|
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def lanes_for_instance(instance, fleet_enabled: false)
|
|
84
|
+
adapter = instance[:adapter]
|
|
85
|
+
return [] unless adapter.respond_to?(:discover_offerings)
|
|
86
|
+
|
|
87
|
+
Array(adapter.discover_offerings(live: true)).flat_map do |raw_offering|
|
|
88
|
+
offering = offering_to_hash(raw_offering)
|
|
89
|
+
next [] unless offering
|
|
90
|
+
|
|
91
|
+
build_offering_lanes(offering, instance, fleet_enabled: fleet_enabled)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def offering_to_hash(offering)
|
|
96
|
+
return nil if offering.nil?
|
|
97
|
+
return offering if offering.is_a?(Hash)
|
|
98
|
+
|
|
99
|
+
hash = offering.to_h
|
|
100
|
+
hash[:type] ||= hash[:usage_type]
|
|
101
|
+
hash[:enabled] = offering.respond_to?(:enabled?) ? offering.enabled? : true
|
|
102
|
+
hash
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def build_offering_lanes(offering, instance, fleet_enabled: false)
|
|
106
|
+
raw_tier = offering[:tier] || :cloud
|
|
107
|
+
type = EMBED_TYPES.include?(offering[:type]&.to_sym) ? :embedding : :inference
|
|
108
|
+
|
|
109
|
+
lane_fields = {
|
|
110
|
+
tier: raw_tier,
|
|
111
|
+
provider_family: :bedrock,
|
|
112
|
+
instance_id: instance[:instance] || instance[:instance_id] || instance[:id] || 'default',
|
|
113
|
+
type: type,
|
|
114
|
+
model: offering[:model]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
lane = build_lane(offering, lane_fields)
|
|
118
|
+
result = [lane]
|
|
119
|
+
|
|
120
|
+
if fleet_enabled && type == :inference
|
|
121
|
+
fleet_fields = lane_fields.merge(tier: :fleet)
|
|
122
|
+
result << lane.merge(
|
|
123
|
+
id: Legion::Extensions::Llm::Inventory::ScopedRefresher.compose_id(fleet_fields),
|
|
124
|
+
tier: :fleet
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
result
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_lane(offering, lane_fields)
|
|
132
|
+
capabilities = normalize_capabilities(offering[:capabilities])
|
|
133
|
+
{
|
|
134
|
+
id: Legion::Extensions::Llm::Inventory::ScopedRefresher.compose_id(lane_fields),
|
|
135
|
+
tier: lane_fields[:tier],
|
|
136
|
+
provider_family: :bedrock,
|
|
137
|
+
instance_id: lane_fields[:instance_id],
|
|
138
|
+
model: offering[:model],
|
|
139
|
+
canonical_model_alias: offering[:canonical_model_alias],
|
|
140
|
+
type: lane_fields[:type],
|
|
141
|
+
capabilities: capabilities,
|
|
142
|
+
limits: offering[:limits] || {},
|
|
143
|
+
enabled: offering.fetch(:enabled, true),
|
|
144
|
+
cost: offering[:cost] || {}
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def normalize_capabilities(caps)
|
|
149
|
+
return [] unless defined?(Legion::Extensions::Llm::Inventory::Capabilities)
|
|
150
|
+
return [] unless Legion::Extensions::Llm::Inventory::Capabilities.respond_to?(:normalize)
|
|
151
|
+
|
|
152
|
+
Legion::Extensions::Llm::Inventory::Capabilities.normalize(caps)
|
|
153
|
+
end
|
|
49
154
|
end
|
|
50
155
|
end
|
|
51
156
|
end
|
|
@@ -6,6 +6,7 @@ require 'aws-sdk-bedrockruntime'
|
|
|
6
6
|
require 'legion/json'
|
|
7
7
|
require 'legion/logging/helper'
|
|
8
8
|
require 'legion/extensions/llm'
|
|
9
|
+
require_relative 'thinking_modes'
|
|
9
10
|
|
|
10
11
|
module Legion
|
|
11
12
|
module Extensions
|
|
@@ -66,6 +67,7 @@ module Legion
|
|
|
66
67
|
bedrock_access_key_id
|
|
67
68
|
bedrock_secret_access_key
|
|
68
69
|
bedrock_session_token
|
|
70
|
+
bedrock_geo_prefix
|
|
69
71
|
bedrock_profile
|
|
70
72
|
bedrock_stub_responses
|
|
71
73
|
bearer_token
|
|
@@ -85,24 +87,19 @@ module Legion
|
|
|
85
87
|
|
|
86
88
|
INFERENCE_PROFILE_PREFIXES = %w[anthropic. meta. mistral. cohere. ai21.].freeze
|
|
87
89
|
|
|
88
|
-
def inference_profile_id(model, region: nil)
|
|
89
|
-
return model if model.start_with?('
|
|
90
|
-
return model unless INFERENCE_PROFILE_PREFIXES.any? { |p| model.start_with?(p) }
|
|
90
|
+
def inference_profile_id(model, geo_prefix: 'us', region: nil)
|
|
91
|
+
return model if model.start_with?('arn:')
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end
|
|
93
|
+
canonical = model.sub(/\A(?:us|eu|ap)\./, '')
|
|
94
|
+
return canonical unless INFERENCE_PROFILE_PREFIXES.any? { |p| canonical.start_with?(p) }
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
'us-east-1' => 'us', 'us-east-2' => 'us', 'us-west-1' => 'us', 'us-west-2' => 'us',
|
|
100
|
-
'eu-central-1' => 'eu', 'eu-west-1' => 'eu', 'eu-west-2' => 'eu', 'eu-west-3' => 'eu',
|
|
101
|
-
'ap-south-1' => 'ap', 'ap-southeast-1' => 'ap', 'ap-southeast-2' => 'ap', 'ap-northeast-1' => 'ap'
|
|
102
|
-
}.freeze
|
|
96
|
+
prefix = normalize_geo_prefix(geo_prefix || region)
|
|
97
|
+
"#{prefix}.#{canonical}"
|
|
98
|
+
end
|
|
103
99
|
|
|
104
|
-
def
|
|
105
|
-
|
|
100
|
+
def normalize_geo_prefix(value)
|
|
101
|
+
candidate = value.to_s.downcase
|
|
102
|
+
%w[us eu ap].include?(candidate) ? candidate : 'us'
|
|
106
103
|
end
|
|
107
104
|
end
|
|
108
105
|
|
|
@@ -127,6 +124,10 @@ module Legion
|
|
|
127
124
|
@translator ||= Translator.new(region: region)
|
|
128
125
|
end
|
|
129
126
|
|
|
127
|
+
def settings
|
|
128
|
+
Bedrock.default_settings
|
|
129
|
+
end
|
|
130
|
+
|
|
130
131
|
def api_base
|
|
131
132
|
config.bedrock_endpoint || "https://bedrock-runtime.#{region}.amazonaws.com"
|
|
132
133
|
end
|
|
@@ -141,25 +142,9 @@ module Legion
|
|
|
141
142
|
config.bedrock_region || settings[:region] || 'us-east-1'
|
|
142
143
|
end
|
|
143
144
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
log.debug { 'bedrock.provider.discover_offerings: returning static catalog' }
|
|
149
|
-
return static_offerings(**filters)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
log.info { "bedrock.provider.discover_offerings: listing foundation models (region=#{region})" }
|
|
153
|
-
response = bedrock_client.list_foundation_models(**filters)
|
|
154
|
-
@cached_offerings = Array(value(response, :model_summaries)).filter_map do |summary|
|
|
155
|
-
offering = offering_from_summary(summary)
|
|
156
|
-
model_id = offering.respond_to?(:model) ? offering.model : (offering[:model] || offering[:id])
|
|
157
|
-
next unless model_allowed?(model_id.to_s)
|
|
158
|
-
|
|
159
|
-
offering
|
|
160
|
-
end
|
|
161
|
-
log.info { "bedrock.provider.discover_offerings: found #{@cached_offerings.size} models" }
|
|
162
|
-
@cached_offerings
|
|
145
|
+
def geo_prefix
|
|
146
|
+
configured = config.bedrock_geo_prefix if config.respond_to?(:bedrock_geo_prefix)
|
|
147
|
+
self.class.normalize_geo_prefix(configured || settings[:geo_prefix])
|
|
163
148
|
end
|
|
164
149
|
|
|
165
150
|
def offering_for(model:, model_family: nil, instance_id: :default, **metadata)
|
|
@@ -205,15 +190,26 @@ module Legion
|
|
|
205
190
|
end
|
|
206
191
|
end
|
|
207
192
|
|
|
208
|
-
def list_models(**)
|
|
193
|
+
def list_models(**filters)
|
|
194
|
+
request_filters = {}
|
|
195
|
+
request_filters[:by_provider] = filters[:by_provider] if filters[:by_provider]
|
|
196
|
+
|
|
209
197
|
log.info { 'bedrock.provider.list_models: fetching live model list' }
|
|
210
|
-
response = bedrock_client.list_foundation_models
|
|
198
|
+
response = bedrock_client.list_foundation_models(**request_filters)
|
|
211
199
|
models = Array(value(response, :model_summaries)).filter_map { |summary| model_info_from_summary(summary) }
|
|
212
200
|
log.info { "bedrock.provider.list_models: found #{models.size} models" }
|
|
213
|
-
self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
|
|
214
201
|
models
|
|
215
202
|
end
|
|
216
203
|
|
|
204
|
+
def discover_offerings(live: false, **filters)
|
|
205
|
+
return static_offerings(**filters) unless live
|
|
206
|
+
|
|
207
|
+
provider_health = health(live:)
|
|
208
|
+
@cached_offerings = discover_live_offerings(filters, provider_health, live:)
|
|
209
|
+
log_discover_complete(@cached_offerings)
|
|
210
|
+
@cached_offerings
|
|
211
|
+
end
|
|
212
|
+
|
|
217
213
|
def chat(
|
|
218
214
|
messages:,
|
|
219
215
|
model:,
|
|
@@ -310,6 +306,42 @@ module Legion
|
|
|
310
306
|
parse_converse_response(response, model_id(model))
|
|
311
307
|
end
|
|
312
308
|
|
|
309
|
+
def discovery_registry_readiness(provider_health, live:)
|
|
310
|
+
{
|
|
311
|
+
provider: slug.to_sym,
|
|
312
|
+
configured: configured?,
|
|
313
|
+
ready: provider_health[:ready] == true,
|
|
314
|
+
live: live,
|
|
315
|
+
health: provider_health
|
|
316
|
+
}
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def discover_live_offerings(filters, provider_health, live:)
|
|
320
|
+
readiness = discovery_registry_readiness(provider_health, live:)
|
|
321
|
+
Array(list_models(live:, **filters)).filter_map do |model|
|
|
322
|
+
self.class.registry_publisher.publish_models_async([model], readiness:)
|
|
323
|
+
next unless model_matches_filters?(model, filters)
|
|
324
|
+
next unless model_allowed?(model.id)
|
|
325
|
+
|
|
326
|
+
log_model_discovered(model)
|
|
327
|
+
offering_from_model(model, health: provider_health)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def log_model_discovered(model)
|
|
332
|
+
log.debug(
|
|
333
|
+
"[#{slug}] instance=#{provider_instance_id} action=model_discovered " \
|
|
334
|
+
"model=#{model.id} family=#{model.family}"
|
|
335
|
+
)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def log_discover_complete(offerings)
|
|
339
|
+
log.info(
|
|
340
|
+
"[#{slug}] instance=#{provider_instance_id} action=discover_complete " \
|
|
341
|
+
"model_count=#{Array(offerings).size}"
|
|
342
|
+
)
|
|
343
|
+
end
|
|
344
|
+
|
|
313
345
|
def stream(messages:, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {},
|
|
314
346
|
thinking: nil, **_provider_options, &)
|
|
315
347
|
enforce_model_allowed!(model_id(model))
|
|
@@ -353,7 +385,7 @@ module Legion
|
|
|
353
385
|
log.debug { "bedrock.provider.count_tokens: model=#{model_id(model)}" }
|
|
354
386
|
request = Utils.deep_merge(
|
|
355
387
|
{
|
|
356
|
-
model_id: self.class.inference_profile_id(model_id(model),
|
|
388
|
+
model_id: self.class.inference_profile_id(model_id(model), geo_prefix: geo_prefix),
|
|
357
389
|
input: { converse: { messages: format_messages(messages), system: system_blocks(system) }.compact }
|
|
358
390
|
},
|
|
359
391
|
params
|
|
@@ -425,7 +457,7 @@ module Legion
|
|
|
425
457
|
log.debug { "bedrock.provider.invoke_model_chat: model=#{mid} thinking=#{thinking.inspect}" }
|
|
426
458
|
|
|
427
459
|
response = runtime_client.invoke_model(
|
|
428
|
-
model_id: self.class.inference_profile_id(mid,
|
|
460
|
+
model_id: self.class.inference_profile_id(mid, geo_prefix: geo_prefix),
|
|
429
461
|
content_type: 'application/json',
|
|
430
462
|
accept: 'application/json',
|
|
431
463
|
body: Legion::JSON.generate(body)
|
|
@@ -479,7 +511,7 @@ module Legion
|
|
|
479
511
|
|
|
480
512
|
# rubocop:disable Metrics/BlockLength
|
|
481
513
|
runtime_client.invoke_model_with_response_stream(
|
|
482
|
-
model_id: self.class.inference_profile_id(mid,
|
|
514
|
+
model_id: self.class.inference_profile_id(mid, geo_prefix: geo_prefix),
|
|
483
515
|
content_type: 'application/json',
|
|
484
516
|
accept: 'application/json',
|
|
485
517
|
body: Legion::JSON.generate(body)
|
|
@@ -589,7 +621,10 @@ module Legion
|
|
|
589
621
|
body[:tools] = tool_format[:tools]
|
|
590
622
|
body[:tool_choice] = tool_format[:tool_choice] if tool_format[:tool_choice]
|
|
591
623
|
end
|
|
592
|
-
|
|
624
|
+
if thinking
|
|
625
|
+
thinking_cfg = invoke_model_thinking(model: rest[:model] || model_id(rest[:model]), thinking: thinking)
|
|
626
|
+
body[:thinking] = thinking_cfg if thinking_cfg
|
|
627
|
+
end
|
|
593
628
|
body
|
|
594
629
|
end
|
|
595
630
|
|
|
@@ -609,11 +644,19 @@ module Legion
|
|
|
609
644
|
parts.map { |t| { type: 'text', text: t } }
|
|
610
645
|
end
|
|
611
646
|
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
|
|
647
|
+
# Emit the thinking wire shape the model actually supports.
|
|
648
|
+
# Budgeted-thinking Claude models get { type: 'enabled', budget_tokens: N }.
|
|
649
|
+
# Every other model returns nil so the caller OMITS the thinking field —
|
|
650
|
+
# Bedrock rejects { type: 'adaptive' } with a ValidationException (HTTP 500).
|
|
651
|
+
def invoke_model_thinking(model:, thinking:)
|
|
652
|
+
mid = model_id(model)
|
|
653
|
+
return nil if ThinkingModes.known_non_thinking?(mid)
|
|
615
654
|
|
|
616
|
-
thinking.
|
|
655
|
+
budget = if thinking.is_a?(Hash)
|
|
656
|
+
thinking[:budget_tokens] || thinking['budget_tokens'] ||
|
|
657
|
+
thinking[:budget] || thinking['budget']
|
|
658
|
+
end
|
|
659
|
+
{ type: 'enabled', budget_tokens: budget }.compact
|
|
617
660
|
end
|
|
618
661
|
|
|
619
662
|
def format_invoke_model_messages(messages)
|
|
@@ -867,12 +910,19 @@ module Legion
|
|
|
867
910
|
end
|
|
868
911
|
end
|
|
869
912
|
|
|
870
|
-
def
|
|
871
|
-
model =
|
|
872
|
-
real =
|
|
913
|
+
def offering_from_model(model_info, health: {})
|
|
914
|
+
model = model_info.respond_to?(:id) ? model_info.id : model_info
|
|
915
|
+
real = if model_info.respond_to?(:capabilities)
|
|
916
|
+
Array(model_info.capabilities).to_h do |capability|
|
|
917
|
+
[capability.to_s.downcase.tr('-', '_').to_sym, true]
|
|
918
|
+
end
|
|
919
|
+
else
|
|
920
|
+
{}
|
|
921
|
+
end
|
|
922
|
+
metadata = model_info.respond_to?(:metadata) && model_info.metadata.is_a?(Hash) ? model_info.metadata : {}
|
|
873
923
|
policy = Legion::Extensions::Llm::CapabilityPolicy.resolve(
|
|
874
924
|
real: real,
|
|
875
|
-
provider_catalog:
|
|
925
|
+
provider_catalog: catalog_capabilities(model),
|
|
876
926
|
probe: {},
|
|
877
927
|
provider_envelope: provider_envelope_capabilities,
|
|
878
928
|
provider_config: provider_capability_config,
|
|
@@ -883,11 +933,12 @@ module Legion
|
|
|
883
933
|
build_offering(
|
|
884
934
|
model: model,
|
|
885
935
|
alias_name: alias_for(model),
|
|
886
|
-
model_family:
|
|
887
|
-
usage_type:
|
|
936
|
+
model_family: model_info.respond_to?(:family) ? model_info.family : model_family_for(model),
|
|
937
|
+
usage_type: model_info.respond_to?(:embedding?) && model_info.embedding? ? :embedding : :inference,
|
|
888
938
|
capabilities: policy[:capabilities],
|
|
889
939
|
capability_sources: policy[:sources],
|
|
890
|
-
metadata:
|
|
940
|
+
metadata: metadata,
|
|
941
|
+
health: health
|
|
891
942
|
)
|
|
892
943
|
end
|
|
893
944
|
|
|
@@ -908,9 +959,14 @@ module Legion
|
|
|
908
959
|
)
|
|
909
960
|
end
|
|
910
961
|
|
|
962
|
+
def offering_from_summary(summary, health: {})
|
|
963
|
+
offering_from_model(model_info_from_summary(summary), health:)
|
|
964
|
+
end
|
|
965
|
+
|
|
911
966
|
def build_offering(model:, model_family:, usage_type:, instance_id: :default, alias_name: nil,
|
|
912
|
-
capabilities: nil, capability_sources: nil, metadata: {})
|
|
967
|
+
capabilities: nil, capability_sources: nil, metadata: {}, health: {})
|
|
913
968
|
limits = infer_limits(model)
|
|
969
|
+
normalized_family = model_family&.to_sym
|
|
914
970
|
Legion::Extensions::Llm::Routing::ModelOffering.new(
|
|
915
971
|
provider_family: :bedrock,
|
|
916
972
|
instance_id: instance_id,
|
|
@@ -921,7 +977,8 @@ module Legion
|
|
|
921
977
|
capabilities: capabilities || default_capabilities(model),
|
|
922
978
|
capability_sources: capability_sources,
|
|
923
979
|
limits: limits,
|
|
924
|
-
|
|
980
|
+
health: health,
|
|
981
|
+
metadata: metadata.merge(model_family: normalized_family, alias: alias_name).compact
|
|
925
982
|
)
|
|
926
983
|
end
|
|
927
984
|
|
|
@@ -941,19 +998,19 @@ module Legion
|
|
|
941
998
|
def converse_request(messages, model:, temperature:, max_tokens:, tools:, tool_prefs:, guardrail_config: nil,
|
|
942
999
|
thinking: nil)
|
|
943
1000
|
{
|
|
944
|
-
model_id: self.class.inference_profile_id(model_id(model),
|
|
1001
|
+
model_id: self.class.inference_profile_id(model_id(model), geo_prefix: geo_prefix),
|
|
945
1002
|
messages: format_messages(messages.reject { |message| message.role == :system }),
|
|
946
1003
|
system: format_system(messages),
|
|
947
1004
|
inference_config: { temperature: temperature, max_tokens: max_tokens || model_max_tokens(model) }.compact,
|
|
948
1005
|
tool_config: format_tool_config(tools, tool_prefs),
|
|
949
1006
|
guardrail_config: guardrail_config,
|
|
950
|
-
additional_model_request_fields: bedrock_additional_fields(thinking)
|
|
1007
|
+
additional_model_request_fields: bedrock_additional_fields(thinking, model: model_id(model))
|
|
951
1008
|
}.compact
|
|
952
1009
|
end
|
|
953
1010
|
|
|
954
|
-
def bedrock_additional_fields(thinking)
|
|
1011
|
+
def bedrock_additional_fields(thinking, model:)
|
|
955
1012
|
fields = {}
|
|
956
|
-
if thinking
|
|
1013
|
+
if thinking && !ThinkingModes.known_non_thinking?(model)
|
|
957
1014
|
fields[:thinking] = {
|
|
958
1015
|
type: 'enabled',
|
|
959
1016
|
budget_tokens: if thinking.is_a?(Hash)
|
|
@@ -1535,7 +1592,6 @@ module Legion
|
|
|
1535
1592
|
caps << :streaming if value(summary, :response_streaming_supported)
|
|
1536
1593
|
end
|
|
1537
1594
|
caps << :vision if input_mods.include?('image')
|
|
1538
|
-
caps << :tools if caps.include?(:completion)
|
|
1539
1595
|
caps
|
|
1540
1596
|
end
|
|
1541
1597
|
|
|
@@ -1545,7 +1601,7 @@ module Legion
|
|
|
1545
1601
|
input_mods = Array(value(summary, :input_modalities)).map { |m| m.to_s.upcase }
|
|
1546
1602
|
caps[:vision] = true if input_mods.include?('IMAGE')
|
|
1547
1603
|
output_mods = Array(value(summary, :output_modalities)).map { |m| m.to_s.upcase }
|
|
1548
|
-
caps[:
|
|
1604
|
+
caps[:embedding] = true if output_mods.include?('EMBEDDING')
|
|
1549
1605
|
caps
|
|
1550
1606
|
end
|
|
1551
1607
|
|
|
@@ -1554,41 +1610,20 @@ module Legion
|
|
|
1554
1610
|
{ tools: true }
|
|
1555
1611
|
end
|
|
1556
1612
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1613
|
+
# Capability truth from the shared lex-llm catalog (models.json), keyed by
|
|
1614
|
+
# bedrock model id. This is the source of truth for per-model capabilities
|
|
1615
|
+
# (e.g. :thinking for Claude 4+) that the live AWS ListFoundationModels
|
|
1616
|
+
# summary does not expose. Surfaced to CapabilityPolicy as :provider_catalog
|
|
1617
|
+
# so operator/instance/model overrides still win, and the boolean map is fed
|
|
1618
|
+
# through the shared Capabilities alias table (reasoning -> :thinking).
|
|
1619
|
+
def catalog_capabilities(model)
|
|
1620
|
+
info = Legion::Extensions::Llm::Models.find(model.to_s, :bedrock)
|
|
1621
|
+
caps = Legion::Extensions::Llm::Capabilities.normalize(info.capabilities)
|
|
1622
|
+
caps.to_h { |cap| [cap, true] }
|
|
1623
|
+
rescue Legion::Extensions::Llm::ModelNotFoundError
|
|
1564
1624
|
{}
|
|
1565
|
-
end
|
|
1566
|
-
|
|
1567
|
-
def instance_capability_config
|
|
1568
|
-
cfg = config
|
|
1569
|
-
result = {}
|
|
1570
|
-
%i[capabilities enable_thinking enable_tools enable_streaming enable_vision enable_embeddings
|
|
1571
|
-
thinking_flag tools_flag streaming_flag vision_flag embedding_flag embeddings_flag
|
|
1572
|
-
tool_flag images_flag image_flag].each do |key|
|
|
1573
|
-
next unless cfg.respond_to?(key)
|
|
1574
|
-
|
|
1575
|
-
val = cfg.send(key)
|
|
1576
|
-
result[key] = val unless val.nil?
|
|
1577
|
-
rescue StandardError
|
|
1578
|
-
next
|
|
1579
|
-
end
|
|
1580
|
-
result
|
|
1581
|
-
end
|
|
1582
|
-
|
|
1583
|
-
def model_capability_config(model_id)
|
|
1584
|
-
models_conf = nil
|
|
1585
|
-
models_conf = config.models if config.respond_to?(:models)
|
|
1586
|
-
models_conf ||= config[:models] if config.respond_to?(:[])
|
|
1587
|
-
return {} unless models_conf.respond_to?(:to_h)
|
|
1588
|
-
|
|
1589
|
-
models_conf.to_h[model_id.to_s] || models_conf.to_h[model_id.to_sym] || {}
|
|
1590
1625
|
rescue StandardError => e
|
|
1591
|
-
handle_exception(e, level: :
|
|
1626
|
+
handle_exception(e, level: :warn, handled: true, operation: 'bedrock.provider.catalog_capabilities')
|
|
1592
1627
|
{}
|
|
1593
1628
|
end
|
|
1594
1629
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Llm
|
|
6
|
+
module Bedrock
|
|
7
|
+
# Single source of truth for how each Bedrock model expresses extended
|
|
8
|
+
# thinking on the wire. Shared by both the Provider (invoke_model /
|
|
9
|
+
# converse paths) and the Translator (canonical render path) so the two
|
|
10
|
+
# never diverge.
|
|
11
|
+
#
|
|
12
|
+
# Bedrock supports exactly one thinking wire shape for Anthropic Claude:
|
|
13
|
+
# { type: 'enabled', budget_tokens: N } (native Anthropic Messages API)
|
|
14
|
+
#
|
|
15
|
+
# There is NO Bedrock Claude model that accepts { type: 'adaptive' } —
|
|
16
|
+
# sending adaptive raises `ValidationException: adaptive thinking is not
|
|
17
|
+
# supported on this model` (observed live on opus-4-5) and surfaces as an
|
|
18
|
+
# HTTP 500. So a model either supports budgeted thinking (emit `enabled`)
|
|
19
|
+
# or it does not (OMIT the thinking field entirely — never `adaptive`).
|
|
20
|
+
#
|
|
21
|
+
# Budgeted extended thinking arrived with Claude 3.7 Sonnet and is
|
|
22
|
+
# supported across the entire Claude 4 family (sonnet-4, opus-4.x,
|
|
23
|
+
# haiku-4.5). The match is a substring so it tolerates the many Bedrock
|
|
24
|
+
# model-id decorations (geo prefixes `us.`/`eu.`/`ap.`, `anthropic.`
|
|
25
|
+
# provider prefix, `-vN:0` version suffixes, `:200k` context suffixes).
|
|
26
|
+
module ThinkingModes
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Model-id fragments for Claude families that support explicit budgeted
|
|
30
|
+
# extended thinking via { type: 'enabled', budget_tokens: N }.
|
|
31
|
+
BUDGETED_THINKING_FRAGMENTS = %w[
|
|
32
|
+
claude-3-7-sonnet
|
|
33
|
+
claude-sonnet-4
|
|
34
|
+
claude-opus-4
|
|
35
|
+
claude-haiku-4
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] true when the model supports { type: 'enabled', budget_tokens: N }
|
|
39
|
+
def budgeted_thinking?(model_id)
|
|
40
|
+
return false if model_id.nil? || model_id.to_s.strip.empty?
|
|
41
|
+
|
|
42
|
+
mid = model_id.to_s
|
|
43
|
+
# Substring scan (String#include?), NOT array intersection: intersect?
|
|
44
|
+
# raises TypeError on a String argument.
|
|
45
|
+
BUDGETED_THINKING_FRAGMENTS.any? { |fragment| mid.include?(fragment) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] true when the model is KNOWN not to support thinking.
|
|
49
|
+
# Distinct from `!budgeted_thinking?`: a nil/blank/unknown model id is
|
|
50
|
+
# NOT known-unsupported — we then honor an explicit thinking request and
|
|
51
|
+
# emit the (safe) `enabled` shape rather than dropping it. The router's
|
|
52
|
+
# capability filter (fed by the shared catalog) is the real guard that
|
|
53
|
+
# keeps thinking requests off non-thinking models; this method only
|
|
54
|
+
# strips thinking for a positively-identified non-thinking Claude model
|
|
55
|
+
# so we never emit an unsupported shape and 500.
|
|
56
|
+
def known_non_thinking?(model_id)
|
|
57
|
+
return false if model_id.nil? || model_id.to_s.strip.empty?
|
|
58
|
+
|
|
59
|
+
!budgeted_thinking?(model_id)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'legion/json'
|
|
4
4
|
require 'legion/logging/helper'
|
|
5
5
|
require 'legion/extensions/llm/canonical'
|
|
6
|
+
require_relative 'thinking_modes'
|
|
6
7
|
|
|
7
8
|
module Legion
|
|
8
9
|
module Extensions
|
|
@@ -20,8 +21,9 @@ module Legion
|
|
|
20
21
|
|
|
21
22
|
DEFAULT_MAX_TOKENS = 4096
|
|
22
23
|
|
|
23
|
-
def initialize(region: nil)
|
|
24
|
+
def initialize(region: nil, geo_prefix: nil)
|
|
24
25
|
@region = region
|
|
26
|
+
@geo_prefix = geo_prefix
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def capabilities
|
|
@@ -150,17 +152,13 @@ module Legion
|
|
|
150
152
|
end
|
|
151
153
|
|
|
152
154
|
def inference_profile_id(model_id)
|
|
153
|
-
return model_id if model_id.nil? || model_id.start_with?('
|
|
155
|
+
return model_id if model_id.nil? || model_id.start_with?('arn:')
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
canonical = model_id.sub(/\A(?:us|eu|ap)\./, '')
|
|
158
|
+
return canonical unless MODEL_PREFIXED_FAMILIES.any? { |p| canonical.start_with?(p) }
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
prefix
|
|
159
|
-
'eu'
|
|
160
|
-
else
|
|
161
|
-
region.include?('ap') ? 'ap' : 'us'
|
|
162
|
-
end
|
|
163
|
-
"#{prefix}.#{model_id}"
|
|
160
|
+
prefix = normalize_geo_prefix(@geo_prefix)
|
|
161
|
+
"#{prefix}.#{canonical}"
|
|
164
162
|
end
|
|
165
163
|
|
|
166
164
|
def build_inference_config(canonical)
|
|
@@ -181,12 +179,18 @@ module Legion
|
|
|
181
179
|
|
|
182
180
|
def build_additional_fields(canonical)
|
|
183
181
|
return nil unless canonical.thinking
|
|
182
|
+
return nil if ThinkingModes.known_non_thinking?(model_from_request(canonical))
|
|
184
183
|
|
|
185
184
|
budget = canonical_thinking_budget(canonical)
|
|
186
185
|
budget ||= DEFAULT_MAX_TOKENS / 4
|
|
187
186
|
{ thinking: { type: 'enabled', budget_tokens: budget } }
|
|
188
187
|
end
|
|
189
188
|
|
|
189
|
+
def normalize_geo_prefix(value)
|
|
190
|
+
candidate = value.to_s.downcase
|
|
191
|
+
%w[us eu ap].include?(candidate) ? candidate : 'us'
|
|
192
|
+
end
|
|
193
|
+
|
|
190
194
|
def canonical_thinking_budget(canonical)
|
|
191
195
|
return nil unless canonical.thinking
|
|
192
196
|
|
|
@@ -251,8 +255,13 @@ module Legion
|
|
|
251
255
|
body.compact
|
|
252
256
|
end
|
|
253
257
|
|
|
258
|
+
# Emit the thinking wire shape the model supports. Budgeted-thinking
|
|
259
|
+
# Claude models get { type: 'enabled', budget_tokens: N }; every other
|
|
260
|
+
# model returns nil so render_invoke_model OMITS the thinking field.
|
|
261
|
+
# Bedrock rejects { type: 'adaptive' } (ValidationException -> HTTP 500).
|
|
254
262
|
def build_invoke_thinking(canonical)
|
|
255
263
|
return nil unless canonical.thinking
|
|
264
|
+
return nil if ThinkingModes.known_non_thinking?(model_from_request(canonical))
|
|
256
265
|
|
|
257
266
|
budget = canonical_thinking_budget(canonical)
|
|
258
267
|
budget ||= DEFAULT_MAX_TOKENS / 4
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/llm'
|
|
4
|
+
require 'legion/extensions/llm/bedrock/thinking_modes'
|
|
4
5
|
require 'legion/extensions/llm/bedrock/provider'
|
|
5
6
|
require 'legion/extensions/llm/bedrock/translator'
|
|
6
7
|
require 'legion/extensions/llm/bedrock/version'
|
|
@@ -30,6 +31,7 @@ module Legion
|
|
|
30
31
|
instance: {
|
|
31
32
|
default_model: DEFAULT_MODEL,
|
|
32
33
|
region: 'us-east-1',
|
|
34
|
+
geo_prefix: 'us',
|
|
33
35
|
tier: :cloud,
|
|
34
36
|
transport: :aws_sdk,
|
|
35
37
|
credentials: {
|
|
@@ -41,6 +43,7 @@ module Legion
|
|
|
41
43
|
},
|
|
42
44
|
provider: {
|
|
43
45
|
region: DEFAULT_REGION,
|
|
46
|
+
geo_prefix: 'us',
|
|
44
47
|
endpoint: nil,
|
|
45
48
|
stub_responses: false
|
|
46
49
|
},
|
|
@@ -85,9 +88,17 @@ module Legion
|
|
|
85
88
|
# Resolve a default_model that never violates the configured model policy
|
|
86
89
|
# (whitelist/blacklist stays authoritative over the DEFAULT_MODEL fallback).
|
|
87
90
|
def self.resolve_default_model(config)
|
|
91
|
+
cfg = config.is_a?(Hash) ? config : {}
|
|
92
|
+
provider_conf = CredentialSources.setting(:extensions, :llm, PROVIDER_FAMILY)
|
|
93
|
+
provider_conf = {} unless provider_conf.is_a?(Hash)
|
|
94
|
+
global_conf = (::Legion::Settings.dig(:extensions, :llm) if defined?(::Legion::Settings))
|
|
95
|
+
global_conf = {} unless global_conf.is_a?(Hash)
|
|
96
|
+
|
|
88
97
|
provider_class.policy_safe_default_model(
|
|
89
|
-
configured:
|
|
90
|
-
|
|
98
|
+
configured: cfg[:default_model],
|
|
99
|
+
fallback: DEFAULT_MODEL,
|
|
100
|
+
whitelist: provider_class.resolve_policy_value(cfg, provider_conf, global_conf, :model_whitelist),
|
|
101
|
+
blacklist: provider_class.resolve_policy_value(cfg, provider_conf, global_conf, :model_blacklist)
|
|
91
102
|
)
|
|
92
103
|
end
|
|
93
104
|
|
|
@@ -214,6 +225,7 @@ module Legion
|
|
|
214
225
|
|
|
215
226
|
normalized = config.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
216
227
|
normalized[:bedrock_region] ||= normalized.delete(:region)
|
|
228
|
+
normalized[:bedrock_geo_prefix] ||= normalized.delete(:geo_prefix)
|
|
217
229
|
normalized[:bedrock_endpoint] ||= normalized.delete(:endpoint)
|
|
218
230
|
normalized[:bedrock_endpoint] ||= normalized.delete(:base_url)
|
|
219
231
|
normalized[:bedrock_endpoint] ||= normalized.delete(:api_base)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-bedrock
|
|
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
|
|
@@ -99,14 +99,14 @@ dependencies:
|
|
|
99
99
|
requirements:
|
|
100
100
|
- - ">="
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: 0.
|
|
102
|
+
version: 0.6.0
|
|
103
103
|
type: :runtime
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - ">="
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 0.
|
|
109
|
+
version: 0.6.0
|
|
110
110
|
description: Amazon Bedrock provider integration for the LegionIO LLM routing framework.
|
|
111
111
|
email:
|
|
112
112
|
- matthewdiverson@gmail.com
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/legion/extensions/llm/bedrock/actors/fleet_worker.rb
|
|
130
130
|
- lib/legion/extensions/llm/bedrock/provider.rb
|
|
131
131
|
- lib/legion/extensions/llm/bedrock/runners/fleet_worker.rb
|
|
132
|
+
- lib/legion/extensions/llm/bedrock/thinking_modes.rb
|
|
132
133
|
- lib/legion/extensions/llm/bedrock/translator.rb
|
|
133
134
|
- lib/legion/extensions/llm/bedrock/version.rb
|
|
134
135
|
homepage: https://github.com/LegionIO/lex-llm-bedrock
|