lex-llm-bedrock 0.1.4 → 0.1.5
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 +9 -0
- data/README.md +68 -1
- data/lib/legion/extensions/llm/bedrock/provider.rb +26 -7
- data/lib/legion/extensions/llm/bedrock/registry_event_builder.rb +2 -0
- data/lib/legion/extensions/llm/bedrock/registry_publisher.rb +15 -17
- data/lib/legion/extensions/llm/bedrock/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61bd89194ee181746dc54c6b898bf31b4b8d599a01121bcec9a0dbd8b80d3b5c
|
|
4
|
+
data.tar.gz: 97ab9f39bec79114152b42d8fdf8f996a50b4b1ac94dcb1a230049ccd8d08398
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 441194151664025152b62567db2f84d9b406779502f560c9f8b0a41c891ec8f487624a8fd5bf3d6a7c86172b15a5f218bfa38c9f847d7a7bdec5db49e678ccca
|
|
7
|
+
data.tar.gz: 7676dff0e80cc6a3a0589a01acc723b7920c3d59193936c2f5a8bc89444957f1f43e9cf8b003ab19badd21720a74a26d8b3046d7e673c1333a1fca458b12af43
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5 - 2026-04-30
|
|
4
|
+
|
|
5
|
+
- Audit logging, rescue blocks, and README for full observability.
|
|
6
|
+
- Add `include Legion::Logging::Helper` to Provider, RegistryPublisher, and RegistryEventBuilder.
|
|
7
|
+
- Replace all bare rescue blocks with `handle_exception(e, level:, handled:, operation:)` calls.
|
|
8
|
+
- Add `log.info` for key actions: chat, stream, embed, health, discovery, list_models.
|
|
9
|
+
- Remove custom `log_publish_failure` method in favor of standard `handle_exception`.
|
|
10
|
+
- Update README with architecture, file map, dependency table, and development guide.
|
|
11
|
+
|
|
3
12
|
## 0.1.4 - 2026-04-30
|
|
4
13
|
|
|
5
14
|
- Add headers: parameter to complete method signature matching base provider contract
|
data/README.md
CHANGED
|
@@ -4,6 +4,48 @@ Amazon Bedrock provider extension for `Legion::Extensions::Llm`.
|
|
|
4
4
|
|
|
5
5
|
This gem adds a hosted Bedrock provider surface for Legion LLM routing without depending on the old `legion-llm` gem. It uses the official AWS SDK for Ruby and keeps discovery offline by default, so loading the extension or running tests does not require live AWS credentials. It requires `lex-llm >= 0.1.5` for the shared model offering, alias, readiness, and fleet lane contract.
|
|
6
6
|
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Legion::Extensions::Llm::Bedrock
|
|
11
|
+
├── Provider # Bedrock implementation of the lex-llm Provider contract
|
|
12
|
+
│ ├── Capabilities # Capability predicates inferred from model IDs
|
|
13
|
+
│ ├── chat / stream # Converse / ConverseStream API calls
|
|
14
|
+
│ ├── embed # Titan InvokeModel embedding
|
|
15
|
+
│ ├── count_tokens # CountTokens API call
|
|
16
|
+
│ ├── discover_offerings # Static catalog + live ListFoundationModels
|
|
17
|
+
│ ├── health / readiness # Provider health checks with live AWS verification
|
|
18
|
+
│ └── list_models # Live model enumeration
|
|
19
|
+
├── RegistryEventBuilder # Builds sanitized lex-llm registry envelopes
|
|
20
|
+
├── RegistryPublisher # Best-effort async publisher for registry events
|
|
21
|
+
└── Transport
|
|
22
|
+
├── Exchanges::LlmRegistry # Topic exchange for llm.registry events
|
|
23
|
+
└── Messages::RegistryEvent # AMQP message for registry event publishing
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Dependencies
|
|
27
|
+
|
|
28
|
+
| Gem | Required | Purpose |
|
|
29
|
+
|-----|----------|---------|
|
|
30
|
+
| `aws-sdk-bedrock` | Yes | Bedrock management client (ListFoundationModels) |
|
|
31
|
+
| `aws-sdk-bedrockruntime` | Yes | Bedrock runtime client (Converse, InvokeModel) |
|
|
32
|
+
| `legion-json` (>= 1.2.1) | Yes | JSON serialization |
|
|
33
|
+
| `legion-logging` (>= 1.3.2) | Yes | Structured logging via Helper |
|
|
34
|
+
| `legion-settings` (>= 1.3.14) | Yes | Configuration |
|
|
35
|
+
| `lex-llm` (>= 0.1.5) | Yes | Shared provider contract, model offerings, routing |
|
|
36
|
+
|
|
37
|
+
## File Map
|
|
38
|
+
|
|
39
|
+
| Path | Purpose |
|
|
40
|
+
|------|---------|
|
|
41
|
+
| `lib/legion/extensions/llm/bedrock.rb` | Entry point: namespace, default settings, provider registration |
|
|
42
|
+
| `lib/legion/extensions/llm/bedrock/provider.rb` | Full Bedrock provider implementation |
|
|
43
|
+
| `lib/legion/extensions/llm/bedrock/registry_event_builder.rb` | Builds lex-llm registry event envelopes |
|
|
44
|
+
| `lib/legion/extensions/llm/bedrock/registry_publisher.rb` | Best-effort async registry event publishing |
|
|
45
|
+
| `lib/legion/extensions/llm/bedrock/transport/exchanges/llm_registry.rb` | AMQP topic exchange definition |
|
|
46
|
+
| `lib/legion/extensions/llm/bedrock/transport/messages/registry_event.rb` | AMQP message class for registry events |
|
|
47
|
+
| `lib/legion/extensions/llm/bedrock/version.rb` | `VERSION` constant |
|
|
48
|
+
|
|
7
49
|
## Install
|
|
8
50
|
|
|
9
51
|
```ruby
|
|
@@ -31,6 +73,8 @@ If explicit keys are not configured, the AWS SDK default credential provider cha
|
|
|
31
73
|
Legion::Extensions::Llm::Bedrock.default_settings
|
|
32
74
|
```
|
|
33
75
|
|
|
76
|
+
Configuration options: `bedrock_region`, `bedrock_endpoint`, `bedrock_access_key_id`, `bedrock_secret_access_key`, `bedrock_session_token`, `bedrock_profile`, `bedrock_stub_responses`.
|
|
77
|
+
|
|
34
78
|
## Provider Surface
|
|
35
79
|
|
|
36
80
|
```ruby
|
|
@@ -58,6 +102,8 @@ Every offering uses:
|
|
|
58
102
|
|
|
59
103
|
Known aliases are intentionally small and conservative. For example, `claude-3-haiku` resolves to `anthropic.claude-3-haiku-20240307-v1:0`, while the preserved Bedrock model ID remains the routing model.
|
|
60
104
|
|
|
105
|
+
Static models: `claude-3-haiku`, `titan-text-express`, `titan-embed-text-v2`, `llama-3.2-11b-instruct`, `mistral-large-3`.
|
|
106
|
+
|
|
61
107
|
## API Contract
|
|
62
108
|
|
|
63
109
|
The implementation is intentionally limited to Bedrock operations documented by AWS:
|
|
@@ -70,10 +116,31 @@ The implementation is intentionally limited to Bedrock operations documented by
|
|
|
70
116
|
|
|
71
117
|
Provider-specific request bodies are not guessed. Non-Titan embedding models raise until their documented body shape is added explicitly.
|
|
72
118
|
|
|
73
|
-
|
|
119
|
+
## Observability
|
|
120
|
+
|
|
121
|
+
All classes include `Legion::Logging::Helper` for structured logging:
|
|
122
|
+
|
|
123
|
+
- **Info-level**: provider connections, API calls (chat, stream, embed), model listing, health checks
|
|
124
|
+
- **Debug-level**: offline health checks, readiness probes, token counting, registry event scheduling
|
|
125
|
+
- **Rescue blocks**: every rescue calls `handle_exception(e, level:, handled:, operation:)` with dot-separated operation names (e.g., `bedrock.provider.health`, `bedrock.registry_publisher.publish_event`)
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bundle install
|
|
131
|
+
bundle exec rspec --format progress # all pass
|
|
132
|
+
bundle exec rubocop -A # auto-fix
|
|
133
|
+
bundle exec rubocop # lint check (0 offenses expected)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## AWS References
|
|
74
137
|
|
|
75
138
|
- [Converse](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
|
|
76
139
|
- [ConverseStream](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html)
|
|
77
140
|
- [CountTokens](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_CountTokens.html)
|
|
78
141
|
- [ListFoundationModels](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_ListFoundationModels.html)
|
|
79
142
|
- [Foundation model information](https://docs.aws.amazon.com/bedrock/latest/userguide/foundation-models-reference.html)
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -12,6 +12,8 @@ module Legion
|
|
|
12
12
|
module Bedrock
|
|
13
13
|
# Amazon Bedrock provider implementation for the Legion::Extensions::Llm contract.
|
|
14
14
|
class Provider < Legion::Extensions::Llm::Provider # rubocop:disable Metrics/ClassLength
|
|
15
|
+
include Legion::Logging::Helper
|
|
16
|
+
|
|
15
17
|
DEFAULT_REGION = 'us-east-1'
|
|
16
18
|
|
|
17
19
|
STATIC_MODELS = [
|
|
@@ -85,10 +87,15 @@ module Legion
|
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
def discover_offerings(live: false, **filters)
|
|
88
|
-
|
|
90
|
+
unless live
|
|
91
|
+
log.debug { 'bedrock.provider.discover_offerings: returning static catalog' }
|
|
92
|
+
return static_offerings(**filters)
|
|
93
|
+
end
|
|
89
94
|
|
|
95
|
+
log.info { "bedrock.provider.discover_offerings: listing foundation models (region=#{region})" }
|
|
90
96
|
response = bedrock_client.list_foundation_models(**filters)
|
|
91
97
|
Array(value(response, :model_summaries)).map { |summary| offering_from_summary(summary) }.tap do |offerings|
|
|
98
|
+
log.info { "bedrock.provider.discover_offerings: found #{offerings.size} models" }
|
|
92
99
|
self.class.registry_publisher.publish_offerings_async(offerings, readiness: readiness(live: false))
|
|
93
100
|
end
|
|
94
101
|
end
|
|
@@ -114,15 +121,22 @@ module Legion
|
|
|
114
121
|
live: live,
|
|
115
122
|
credentials: credential_source
|
|
116
123
|
}
|
|
117
|
-
|
|
124
|
+
unless live
|
|
125
|
+
log.debug { "bedrock.provider.health: offline check (region=#{region})" }
|
|
126
|
+
return baseline.merge(checked: false)
|
|
127
|
+
end
|
|
118
128
|
|
|
129
|
+
log.info { "bedrock.provider.health: live check (region=#{region})" }
|
|
119
130
|
bedrock_client.list_foundation_models
|
|
131
|
+
log.info { 'bedrock.provider.health: live check passed' }
|
|
120
132
|
baseline.merge(checked: true)
|
|
121
133
|
rescue StandardError => e
|
|
134
|
+
handle_exception(e, level: :warn, handled: true, operation: 'bedrock.provider.health')
|
|
122
135
|
baseline.merge(checked: true, ready: false, error: e.class.name, message: e.message)
|
|
123
136
|
end
|
|
124
137
|
|
|
125
138
|
def readiness(live: false)
|
|
139
|
+
log.debug { "bedrock.provider.readiness: checking (live=#{live})" }
|
|
126
140
|
health(live: live).merge(local: false, remote: true, api_base: api_base,
|
|
127
141
|
endpoints: endpoint_manifest).tap do |metadata|
|
|
128
142
|
self.class.registry_publisher.publish_readiness_async(metadata) if live
|
|
@@ -130,6 +144,7 @@ module Legion
|
|
|
130
144
|
end
|
|
131
145
|
|
|
132
146
|
def list_models
|
|
147
|
+
log.info { 'bedrock.provider.list_models: fetching live model list' }
|
|
133
148
|
discover_offerings(live: true).map do |offering|
|
|
134
149
|
Legion::Extensions::Llm::Model::Info.new(
|
|
135
150
|
id: offering.model,
|
|
@@ -143,6 +158,7 @@ module Legion
|
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
def chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {})
|
|
161
|
+
log.info { "bedrock.provider.chat: model=#{model_id(model)} messages=#{messages.size}" }
|
|
146
162
|
request = Utils.deep_merge(
|
|
147
163
|
converse_request(messages, model:, temperature:, max_tokens:, tools:, tool_prefs:),
|
|
148
164
|
params
|
|
@@ -152,6 +168,7 @@ module Legion
|
|
|
152
168
|
|
|
153
169
|
def stream(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {},
|
|
154
170
|
&)
|
|
171
|
+
log.info { "bedrock.provider.stream: model=#{model_id(model)} messages=#{messages.size}" }
|
|
155
172
|
request = Utils.deep_merge(
|
|
156
173
|
converse_request(messages, model:, temperature:, max_tokens:, tools:, tool_prefs:),
|
|
157
174
|
params
|
|
@@ -160,6 +177,7 @@ module Legion
|
|
|
160
177
|
end
|
|
161
178
|
|
|
162
179
|
def count_tokens(messages, model:, system: nil, params: {})
|
|
180
|
+
log.debug { "bedrock.provider.count_tokens: model=#{model_id(model)}" }
|
|
163
181
|
request = Utils.deep_merge(
|
|
164
182
|
{
|
|
165
183
|
model_id: model_id(model),
|
|
@@ -172,20 +190,21 @@ module Legion
|
|
|
172
190
|
end
|
|
173
191
|
|
|
174
192
|
def embed(text, model:, dimensions: nil)
|
|
175
|
-
|
|
176
|
-
unless titan_embed?(
|
|
193
|
+
mid = model_id(model)
|
|
194
|
+
unless titan_embed?(mid)
|
|
177
195
|
raise NotImplementedError,
|
|
178
|
-
"Bedrock embedding payload for #{
|
|
196
|
+
"Bedrock embedding payload for #{mid} is not standardized"
|
|
179
197
|
end
|
|
180
198
|
|
|
199
|
+
log.info { "bedrock.provider.embed: model=#{mid}" }
|
|
181
200
|
body = { inputText: text, dimensions: dimensions }.compact
|
|
182
201
|
response = runtime_client.invoke_model(
|
|
183
|
-
model_id:
|
|
202
|
+
model_id: mid,
|
|
184
203
|
content_type: 'application/json',
|
|
185
204
|
accept: 'application/json',
|
|
186
205
|
body: Legion::JSON.generate(body)
|
|
187
206
|
)
|
|
188
|
-
parse_embedding_response(response, model:
|
|
207
|
+
parse_embedding_response(response, model: mid)
|
|
189
208
|
end
|
|
190
209
|
|
|
191
210
|
def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, # rubocop:disable Lint/UnusedMethodArgument
|
|
@@ -6,6 +6,8 @@ module Legion
|
|
|
6
6
|
module Bedrock
|
|
7
7
|
# Builds sanitized lex-llm registry envelopes for Bedrock provider state.
|
|
8
8
|
class RegistryEventBuilder
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
9
11
|
def readiness(readiness)
|
|
10
12
|
registry_event_class.public_send(
|
|
11
13
|
readiness[:ready] ? :available : :unavailable,
|
|
@@ -6,6 +6,8 @@ module Legion
|
|
|
6
6
|
module Bedrock
|
|
7
7
|
# Best-effort publisher for Bedrock provider availability events.
|
|
8
8
|
class RegistryPublisher
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
9
11
|
APP_ID = 'lex-llm-bedrock'
|
|
10
12
|
|
|
11
13
|
def initialize(builder: RegistryEventBuilder.new)
|
|
@@ -13,10 +15,14 @@ module Legion
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def publish_readiness_async(readiness)
|
|
18
|
+
log.debug { 'bedrock.registry_publisher.publish_readiness_async: scheduling readiness event' }
|
|
16
19
|
schedule { publish_event(@builder.readiness(readiness)) }
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def publish_offerings_async(offerings, readiness:)
|
|
23
|
+
log.debug do
|
|
24
|
+
"bedrock.registry_publisher.publish_offerings_async: scheduling #{Array(offerings).size} offerings"
|
|
25
|
+
end
|
|
20
26
|
schedule do
|
|
21
27
|
Array(offerings).each do |offering|
|
|
22
28
|
publish_event(@builder.offering_available(offering, readiness:))
|
|
@@ -33,10 +39,10 @@ module Legion
|
|
|
33
39
|
Thread.current.abort_on_exception = false
|
|
34
40
|
yield
|
|
35
41
|
rescue StandardError => e
|
|
36
|
-
|
|
42
|
+
handle_exception(e, level: :debug, handled: true, operation: 'bedrock.registry_publisher.schedule')
|
|
37
43
|
end
|
|
38
44
|
rescue StandardError => e
|
|
39
|
-
|
|
45
|
+
handle_exception(e, level: :debug, handled: true, operation: 'bedrock.registry_publisher.schedule')
|
|
40
46
|
false
|
|
41
47
|
end
|
|
42
48
|
|
|
@@ -45,7 +51,7 @@ module Legion
|
|
|
45
51
|
|
|
46
52
|
message_class.new(event:, app_id: APP_ID).publish(spool: false)
|
|
47
53
|
rescue StandardError => e
|
|
48
|
-
|
|
54
|
+
handle_exception(e, level: :warn, handled: true, operation: 'bedrock.registry_publisher.publish_event')
|
|
49
55
|
false
|
|
50
56
|
end
|
|
51
57
|
|
|
@@ -56,7 +62,9 @@ module Legion
|
|
|
56
62
|
return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
|
|
57
63
|
|
|
58
64
|
::Legion::Transport::Connection.session_open?
|
|
59
|
-
rescue StandardError
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
handle_exception(e, level: :debug, handled: true,
|
|
67
|
+
operation: 'bedrock.registry_publisher.publishing_available?')
|
|
60
68
|
false
|
|
61
69
|
end
|
|
62
70
|
|
|
@@ -70,7 +78,9 @@ module Legion
|
|
|
70
78
|
|
|
71
79
|
require 'legion/extensions/llm/bedrock/transport/messages/registry_event'
|
|
72
80
|
message_class_defined?
|
|
73
|
-
rescue LoadError
|
|
81
|
+
rescue LoadError => e
|
|
82
|
+
handle_exception(e, level: :debug, handled: true,
|
|
83
|
+
operation: 'bedrock.registry_publisher.transport_message_available?')
|
|
74
84
|
false
|
|
75
85
|
end
|
|
76
86
|
|
|
@@ -81,18 +91,6 @@ module Legion
|
|
|
81
91
|
def message_class
|
|
82
92
|
::Legion::Extensions::Llm::Bedrock::Transport::Messages::RegistryEvent
|
|
83
93
|
end
|
|
84
|
-
|
|
85
|
-
def log_publish_failure(error, level: :warn)
|
|
86
|
-
message = "[lex-llm-bedrock] llm.registry publish failed: #{error.class}: #{error.message}"
|
|
87
|
-
logger = ::Legion::Extensions::Llm.logger if defined?(::Legion::Extensions::Llm)
|
|
88
|
-
if logger.respond_to?(level)
|
|
89
|
-
logger.public_send(level, message)
|
|
90
|
-
elsif logger.respond_to?(:debug)
|
|
91
|
-
logger.debug(message)
|
|
92
|
-
end
|
|
93
|
-
rescue StandardError
|
|
94
|
-
nil
|
|
95
|
-
end
|
|
96
94
|
end
|
|
97
95
|
end
|
|
98
96
|
end
|