lex-llm-vertex 0.1.3 → 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 +12 -0
- data/README.md +50 -1
- data/lib/legion/extensions/llm/vertex/provider.rb +10 -2
- data/lib/legion/extensions/llm/vertex/registry_event_builder.rb +2 -0
- data/lib/legion/extensions/llm/vertex/registry_publisher.rb +11 -17
- data/lib/legion/extensions/llm/vertex/transport/exchanges/llm_registry.rb +2 -0
- data/lib/legion/extensions/llm/vertex/transport/messages/registry_event.rb +2 -0
- data/lib/legion/extensions/llm/vertex/version.rb +1 -1
- data/lib/legion/extensions/llm/vertex.rb +1 -0
- 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: 2a7dc0a783d3cb0961f7881a4ef0412d1842730852518f1f7ac859ad2cbb944d
|
|
4
|
+
data.tar.gz: fabd6084238f3473c1b3a75e53e1f19de820f6ce12231316c38b39a33a2a506f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 403c6201955ec9d77611f0ade5a338b58d4164f9567bab09b927c73427a06a2214e6828dff3f3fe82ac435ed33932195454a426f1c78bbac2f74c0743db26c27
|
|
7
|
+
data.tar.gz: 91592103291579ba34d8bd61dd1e066111b7a68449585bd96d8d5ca7f38b141dfc0c9d37db532c73721eb3de88d9bdca302510366088464678156c1e978132e8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.5] - 2026-04-30
|
|
4
|
+
|
|
5
|
+
- Add `Legion::Logging::Helper` to all modules and classes for structured logging
|
|
6
|
+
- Replace ad-hoc `log_publish_failure` with `handle_exception` in RegistryPublisher
|
|
7
|
+
- Add `handle_exception` to every rescue block with correct level, handled, and operation
|
|
8
|
+
- Add info-level logging for key provider actions: chat, stream, embed, count_tokens, discover_offerings, health
|
|
9
|
+
- Update README to reflect current architecture, file map, and observability conventions
|
|
10
|
+
|
|
11
|
+
## [0.1.4] - 2026-04-30
|
|
12
|
+
|
|
13
|
+
- Add headers: parameter to complete method for base provider contract compliance
|
|
14
|
+
|
|
3
15
|
## 0.1.3 - 2026-04-28
|
|
4
16
|
|
|
5
17
|
- Remove the unused runtime `legion/settings` require while preserving the gemspec dependency.
|
data/README.md
CHANGED
|
@@ -48,6 +48,18 @@ provider.count_tokens(messages, model: model)
|
|
|
48
48
|
|
|
49
49
|
`discover_offerings(live: false)` returns a conservative static catalog for routing defaults and unit tests. `discover_offerings(live: true)` calls the Vertex publisher models listing endpoint and maps returned model data into `Legion::Extensions::Llm::Routing::ModelOffering` records.
|
|
50
50
|
|
|
51
|
+
## Static Model Catalog
|
|
52
|
+
|
|
53
|
+
| Model | Alias | Publisher | Family | API Mode |
|
|
54
|
+
|-------|-------|-----------|--------|----------|
|
|
55
|
+
| gemini-2.5-flash | gemini-flash | google | gemini | generateContent |
|
|
56
|
+
| gemini-2.5-pro | gemini-pro | google | gemini | generateContent |
|
|
57
|
+
| gemini-embedding-001 | gemini-embedding | google | gemini | predict (embedding) |
|
|
58
|
+
| text-embedding-005 | text-embedding | google | gemini | predict (embedding) |
|
|
59
|
+
| claude-sonnet-4-5 | claude-sonnet | anthropic | anthropic | rawPredict |
|
|
60
|
+
| mistral-medium-3 | mistral-medium | mistralai | mistral | rawPredict |
|
|
61
|
+
| llama-4-maverick | llama-4-maverick | meta | meta | rawPredict |
|
|
62
|
+
|
|
51
63
|
## Model Offerings
|
|
52
64
|
|
|
53
65
|
Every offering uses:
|
|
@@ -60,6 +72,30 @@ Every offering uses:
|
|
|
60
72
|
|
|
61
73
|
Known aliases are intentionally small and configurable. For example, `gemini-flash` resolves to `gemini-2.5-flash`, while the offering preserves `projects/{project}/locations/{location}/publishers/google/models/gemini-2.5-flash`.
|
|
62
74
|
|
|
75
|
+
## Registry Events
|
|
76
|
+
|
|
77
|
+
When transport is available, the `RegistryPublisher` publishes best-effort readiness and offering availability events to the `llm.registry` topic exchange using `lex-llm` registry envelopes. Events are published asynchronously in background threads and never block the caller.
|
|
78
|
+
|
|
79
|
+
## File Map
|
|
80
|
+
|
|
81
|
+
| Path | Purpose |
|
|
82
|
+
|------|---------|
|
|
83
|
+
| `lib/legion/extensions/llm/vertex.rb` | Namespace module, default settings, provider registration |
|
|
84
|
+
| `lib/legion/extensions/llm/vertex/provider.rb` | Vertex AI provider: chat, stream, embed, count_tokens, health, discovery |
|
|
85
|
+
| `lib/legion/extensions/llm/vertex/registry_publisher.rb` | Async best-effort llm.registry event publisher |
|
|
86
|
+
| `lib/legion/extensions/llm/vertex/registry_event_builder.rb` | Builds sanitized registry event envelopes |
|
|
87
|
+
| `lib/legion/extensions/llm/vertex/version.rb` | `VERSION` constant |
|
|
88
|
+
| `lib/legion/extensions/llm/vertex/transport/exchanges/llm_registry.rb` | `llm.registry` topic exchange definition |
|
|
89
|
+
| `lib/legion/extensions/llm/vertex/transport/messages/registry_event.rb` | Transport message for registry events |
|
|
90
|
+
|
|
91
|
+
## Observability
|
|
92
|
+
|
|
93
|
+
All modules and classes use `Legion::Logging::Helper` for structured logging:
|
|
94
|
+
|
|
95
|
+
- **Info-level logging** on key provider actions: `chat`, `stream`, `embed`, `count_tokens`, `discover_offerings`, `health`, and registry publish operations
|
|
96
|
+
- **Every rescue block** calls `handle_exception(e, level:, handled:, operation:)` with dot-separated operation names (e.g. `vertex.provider.health`, `vertex.registry.publish_event`)
|
|
97
|
+
- **Level conventions**: `:warn` for recoverable failures, `:error` for unexpected errors, `:debug` for expected/best-effort failures (transport unavailable, etc.)
|
|
98
|
+
|
|
63
99
|
## API Contract
|
|
64
100
|
|
|
65
101
|
The implementation is intentionally limited to Vertex AI REST surfaces documented by Google Cloud:
|
|
@@ -71,7 +107,20 @@ The implementation is intentionally limited to Vertex AI REST surfaces documente
|
|
|
71
107
|
|
|
72
108
|
Provider-specific request bodies are not guessed. Partner raw-predict chat requests use the message shape documented for those partner model endpoints; embeddings are only implemented for documented Vertex text embedding models.
|
|
73
109
|
|
|
74
|
-
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bundle install
|
|
114
|
+
bundle exec rspec # 0 failures
|
|
115
|
+
bundle exec rubocop -A # auto-fix
|
|
116
|
+
bundle exec rubocop # lint check
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
Apache-2.0
|
|
122
|
+
|
|
123
|
+
## References
|
|
75
124
|
|
|
76
125
|
- [Vertex AI GenAI REST API](https://cloud.google.com/vertex-ai/generative-ai/docs/reference/rest)
|
|
77
126
|
- [Generate content with the Gemini API in Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference)
|
|
@@ -113,11 +113,13 @@ module Legion
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def discover_offerings(live: false, **filters)
|
|
116
|
+
log.info { "discovering offerings live=#{live} project=#{project} location=#{location}" }
|
|
116
117
|
return static_offerings(**filters) unless live
|
|
117
118
|
|
|
118
119
|
response = connection.get(models_url)
|
|
119
120
|
models = response.body['publisherModels'] || response.body['models'] || []
|
|
120
121
|
models.map { |model| offering_from_live_model(model) }.tap do |offerings|
|
|
122
|
+
log.info { "discovered #{offerings.size} live offering(s) from Vertex" }
|
|
121
123
|
self.class.registry_publisher.publish_offerings_async(offerings, readiness: readiness(live: false))
|
|
122
124
|
end
|
|
123
125
|
end
|
|
@@ -140,6 +142,7 @@ module Legion
|
|
|
140
142
|
end
|
|
141
143
|
|
|
142
144
|
def health(live: false)
|
|
145
|
+
log.info { "checking health live=#{live} project=#{project} location=#{location}" }
|
|
143
146
|
baseline = {
|
|
144
147
|
provider: :vertex,
|
|
145
148
|
project: project,
|
|
@@ -154,6 +157,7 @@ module Legion
|
|
|
154
157
|
connection.get(models_url)
|
|
155
158
|
baseline.merge(checked: true)
|
|
156
159
|
rescue StandardError => e
|
|
160
|
+
handle_exception(e, level: :warn, handled: true, operation: 'vertex.provider.health')
|
|
157
161
|
baseline.merge(checked: true, ready: false, error: e.class.name, message: e.message)
|
|
158
162
|
end
|
|
159
163
|
|
|
@@ -166,6 +170,7 @@ module Legion
|
|
|
166
170
|
|
|
167
171
|
def chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {})
|
|
168
172
|
model_id = model_id(model)
|
|
173
|
+
log.info { "chat model=#{model_id} messages=#{messages.size}" }
|
|
169
174
|
@model = model_id
|
|
170
175
|
payload = Utils.deep_merge(chat_payload(messages, model: model_id, temperature:, max_tokens:, tools:,
|
|
171
176
|
tool_prefs:, stream: false), params)
|
|
@@ -175,6 +180,7 @@ module Legion
|
|
|
175
180
|
|
|
176
181
|
def stream(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {})
|
|
177
182
|
model_id = model_id(model)
|
|
183
|
+
log.info { "stream model=#{model_id} messages=#{messages.size}" }
|
|
178
184
|
@model = model_id
|
|
179
185
|
payload = Utils.deep_merge(chat_payload(messages, model: model_id, temperature:, max_tokens:, tools:,
|
|
180
186
|
tool_prefs:, stream: true), params)
|
|
@@ -186,6 +192,7 @@ module Legion
|
|
|
186
192
|
|
|
187
193
|
def count_tokens(messages, model:, params: {})
|
|
188
194
|
model_id = model_id(model)
|
|
195
|
+
log.info { "count_tokens model=#{model_id}" }
|
|
189
196
|
unless generate_content_model?(model_id)
|
|
190
197
|
return {
|
|
191
198
|
supported: false,
|
|
@@ -202,6 +209,7 @@ module Legion
|
|
|
202
209
|
|
|
203
210
|
def embed(text, model:, dimensions: nil, task_type: nil, title: nil, params: {})
|
|
204
211
|
model_id = model_id(model)
|
|
212
|
+
log.info { "embed model=#{model_id} inputs=#{Array(text).size}" }
|
|
205
213
|
unless Capabilities.embeddings?(model_id)
|
|
206
214
|
raise NotImplementedError, "Vertex embedding payload for #{model_id} is not standardized"
|
|
207
215
|
end
|
|
@@ -213,8 +221,8 @@ module Legion
|
|
|
213
221
|
parse_embedding_response(response, model: model_id)
|
|
214
222
|
end
|
|
215
223
|
|
|
216
|
-
def complete(messages, tools:, temperature:, model:, params: {}, schema: nil, thinking: nil,
|
|
217
|
-
&)
|
|
224
|
+
def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, # rubocop:disable Lint/UnusedMethodArgument
|
|
225
|
+
tool_prefs: nil, &)
|
|
218
226
|
payload = params.dup
|
|
219
227
|
payload[:generationConfig] = Utils.deep_merge(payload[:generationConfig] || {},
|
|
220
228
|
generation_config(temperature, schema, thinking))
|
|
@@ -6,6 +6,8 @@ module Legion
|
|
|
6
6
|
module Vertex
|
|
7
7
|
# Builds sanitized lex-llm registry envelopes for Vertex 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 Vertex
|
|
7
7
|
# Best-effort publisher for Vertex provider availability events.
|
|
8
8
|
class RegistryPublisher
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
9
11
|
APP_ID = 'lex-llm-vertex'
|
|
10
12
|
|
|
11
13
|
def initialize(builder: RegistryEventBuilder.new)
|
|
@@ -13,10 +15,12 @@ module Legion
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def publish_readiness_async(readiness)
|
|
18
|
+
log.info { 'publishing readiness event to llm.registry' }
|
|
16
19
|
schedule { publish_event(@builder.readiness(readiness)) }
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def publish_offerings_async(offerings, readiness:)
|
|
23
|
+
log.info { "publishing #{Array(offerings).size} offering event(s) to llm.registry" }
|
|
20
24
|
schedule do
|
|
21
25
|
Array(offerings).each do |offering|
|
|
22
26
|
publish_event(@builder.offering_available(offering, readiness:))
|
|
@@ -33,10 +37,10 @@ module Legion
|
|
|
33
37
|
Thread.current.abort_on_exception = false
|
|
34
38
|
yield
|
|
35
39
|
rescue StandardError => e
|
|
36
|
-
|
|
40
|
+
handle_exception(e, level: :debug, handled: true, operation: 'vertex.registry.schedule_thread')
|
|
37
41
|
end
|
|
38
42
|
rescue StandardError => e
|
|
39
|
-
|
|
43
|
+
handle_exception(e, level: :debug, handled: true, operation: 'vertex.registry.schedule')
|
|
40
44
|
false
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -45,7 +49,7 @@ module Legion
|
|
|
45
49
|
|
|
46
50
|
message_class.new(event:, app_id: APP_ID).publish(spool: false)
|
|
47
51
|
rescue StandardError => e
|
|
48
|
-
|
|
52
|
+
handle_exception(e, level: :warn, handled: true, operation: 'vertex.registry.publish_event')
|
|
49
53
|
false
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -56,7 +60,8 @@ module Legion
|
|
|
56
60
|
return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
|
|
57
61
|
|
|
58
62
|
::Legion::Transport::Connection.session_open?
|
|
59
|
-
rescue StandardError
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
handle_exception(e, level: :debug, handled: true, operation: 'vertex.registry.publishing_available?')
|
|
60
65
|
false
|
|
61
66
|
end
|
|
62
67
|
|
|
@@ -70,7 +75,8 @@ module Legion
|
|
|
70
75
|
|
|
71
76
|
require 'legion/extensions/llm/vertex/transport/messages/registry_event'
|
|
72
77
|
message_class_defined?
|
|
73
|
-
rescue LoadError
|
|
78
|
+
rescue LoadError => e
|
|
79
|
+
handle_exception(e, level: :debug, handled: true, operation: 'vertex.registry.transport_load')
|
|
74
80
|
false
|
|
75
81
|
end
|
|
76
82
|
|
|
@@ -81,18 +87,6 @@ module Legion
|
|
|
81
87
|
def message_class
|
|
82
88
|
::Legion::Extensions::Llm::Vertex::Transport::Messages::RegistryEvent
|
|
83
89
|
end
|
|
84
|
-
|
|
85
|
-
def log_publish_failure(error, level: :warn)
|
|
86
|
-
message = "[lex-llm-vertex] 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
90
|
end
|
|
97
91
|
end
|
|
98
92
|
end
|
|
@@ -10,6 +10,8 @@ module Legion
|
|
|
10
10
|
module Messages
|
|
11
11
|
# Publishes lex-llm RegistryEvent envelopes to the llm.registry exchange.
|
|
12
12
|
class RegistryEvent < ::Legion::Transport::Message
|
|
13
|
+
include Legion::Logging::Helper
|
|
14
|
+
|
|
13
15
|
def initialize(event:, **options)
|
|
14
16
|
super(**event.to_h.merge(options))
|
|
15
17
|
end
|