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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e65f38c28a0411727577216ea5bc01057520cd14e110fdb88d02da6248125aad
4
- data.tar.gz: d93dfe83c8e3acf0944518c1e964ddcccda59301a6a83460bff4618a883f34d8
3
+ metadata.gz: 61bd89194ee181746dc54c6b898bf31b4b8d599a01121bcec9a0dbd8b80d3b5c
4
+ data.tar.gz: 97ab9f39bec79114152b42d8fdf8f996a50b4b1ac94dcb1a230049ccd8d08398
5
5
  SHA512:
6
- metadata.gz: 772d8484c70e24d732e74d751fed2264c253cdbc245d91d39c5aa0e642f0c3828fe144da025fd5237c33e694bb8ab7e9fa5dd009092ea40e75009eca34250b53
7
- data.tar.gz: a00538ee0875ea03b3c25b163eecf791107b6a8ed8ec48a705b9cb9a0f7af32a4abcc5c1c968031eb43b736249acfe55bfefe08f6b8647a5476b775c67318336
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
- AWS references:
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
- return static_offerings(**filters) unless live
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
- return baseline.merge(checked: false) unless live
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
- model_id = model_id(model)
176
- unless titan_embed?(model_id)
193
+ mid = model_id(model)
194
+ unless titan_embed?(mid)
177
195
  raise NotImplementedError,
178
- "Bedrock embedding payload for #{model_id} is not standardized"
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: 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: model_id)
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
- log_publish_failure(e, level: :debug)
42
+ handle_exception(e, level: :debug, handled: true, operation: 'bedrock.registry_publisher.schedule')
37
43
  end
38
44
  rescue StandardError => e
39
- log_publish_failure(e, level: :debug)
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
- log_publish_failure(e)
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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Bedrock
7
- VERSION = '0.1.4'
7
+ VERSION = '0.1.5'
8
8
  end
9
9
  end
10
10
  end
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.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO