lex-llm-openai 0.6.3 → 0.6.4
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 +7 -1
- data/lex-llm-openai.gemspec +1 -1
- data/lib/legion/extensions/llm/openai/actors/discovery_refresh.rb +84 -35
- data/lib/legion/extensions/llm/openai/discovery_drafts.rb +22 -41
- data/lib/legion/extensions/llm/openai/discovery_probe_reporting.rb +55 -0
- data/lib/legion/extensions/llm/openai/discovery_probing.rb +1 -24
- data/lib/legion/extensions/llm/openai/discovery_weight_publication.rb +101 -0
- data/lib/legion/extensions/llm/openai/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0ea7679c67d23d7a6b30bf8ec6057136f72c791c2846ffa805fa7fdb1b7b735
|
|
4
|
+
data.tar.gz: 3eb7f7fce099a71a77b6f5fa1acdc55610c870a0834aedcfa89c954bc7c8d687
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ff08b8ae9072732c1858bd7f88845496624c43a864d0d90d8727ca568d9edf8b59b538afdb87fc1081ddeb4354fd5c919db8bf89f6199d79f78b6c101f57792
|
|
7
|
+
data.tar.gz: c9162a28401cf7ac3544d06603f71bca9bf31a9e1136146146c23bdee261f1b90c698a2f17299f6a16e986ba0b7a0498c231f79a0bc4eb25aa456fdd4d2ac1f8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.6.4] - 2026-08-19
|
|
4
4
|
|
|
5
5
|
### Fixed
|
|
6
|
+
- **Write-time lane weights** — every discovered offering now carries the validated four-axis `weight_inputs` and `base_weight` pair from live settings. The existing periodic actor cadence atomically compares, publishes, sequences, and caches catalog or weight-only changes; unchanged passes stay no-op, initializing instances cache without replacing, and removals cannot race into late activation.
|
|
7
|
+
- **Dormant weight visibility** — ordinary discovery passes emit one info log when a configured provider, instance, model, or offering weight has no published lane; the state clears when the lane appears and logs once again if it later disappears.
|
|
8
|
+
- **D14 callable wire conformance** — an actual `OpenaiCallable#chat` request with a folded leading system message is captured at the provider connection and verified as OpenAI's native leading `role: system` message.
|
|
9
|
+
- **Malformed discovery failures fail loud** — programming/configuration errors from draft construction are no longer swallowed as an empty catalog; only Faraday transport failures and JSON parse failures take the existing empty-discovery recovery path.
|
|
10
|
+
- **lex-llm floor bumped to >= 0.7.6** — requires the shared `WeightSchema`, atomic `WeightReconciler`, weighted offering records, and operation-to-lane taxonomy used by this writer. The `legion-settings >= 1.4.2` floor and lifecycle remain unchanged.
|
|
6
11
|
- **Credential-less configured instances are discovered normally** — removed the synthetic-default suppression and its one-time warning cache. A configured instance, including one named `default`, now follows the ordinary discovery and readiness path without a skip warning.
|
|
12
|
+
- **Complete offering equivalence** — ordinary discovery now compares the complete `OfferingDraft` contract as an order-insensitive, duplicate-sensitive multiset, ignoring only evidence `observed_at` timestamps. Provider-native identity, publication source, tokenizer evidence, and all future draft fields trigger exactly one replacement when they change, while catalog reordering remains a no-op.
|
|
7
13
|
|
|
8
14
|
## [0.6.2] - 2026-08-17
|
|
9
15
|
|
data/lex-llm-openai.gemspec
CHANGED
|
@@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
28
28
|
spec.add_dependency 'legion-settings', '>= 1.4.2'
|
|
29
29
|
spec.add_dependency 'legion-transport', '>= 1.4.14'
|
|
30
|
-
spec.add_dependency 'lex-llm', '>= 0.7.
|
|
30
|
+
spec.add_dependency 'lex-llm', '>= 0.7.6'
|
|
31
31
|
end
|
|
@@ -9,8 +9,10 @@ require 'legion/extensions/llm/openai/discovery_evidence_builders'
|
|
|
9
9
|
require 'legion/extensions/llm/openai/discovery_drafts'
|
|
10
10
|
require 'legion/extensions/llm/openai/discovery_identity'
|
|
11
11
|
require 'legion/extensions/llm/openai/discovery_probing'
|
|
12
|
+
require 'legion/extensions/llm/openai/discovery_probe_reporting'
|
|
12
13
|
require 'legion/extensions/llm/openai/discovery_transport'
|
|
13
14
|
require 'legion/extensions/llm/openai/discovery_health_display'
|
|
15
|
+
require 'legion/extensions/llm/openai/discovery_weight_publication'
|
|
14
16
|
require 'legion/extensions/llm/inventory/publisher'
|
|
15
17
|
require 'legion/extensions/llm/inventory/identity'
|
|
16
18
|
require 'legion/extensions/llm/inventory/records'
|
|
@@ -54,8 +56,10 @@ module Legion
|
|
|
54
56
|
include Legion::Extensions::Llm::Openai::DiscoveryDrafts
|
|
55
57
|
include Legion::Extensions::Llm::Openai::DiscoveryIdentity
|
|
56
58
|
include Legion::Extensions::Llm::Openai::DiscoveryProbing
|
|
59
|
+
include Legion::Extensions::Llm::Openai::DiscoveryProbeReporting
|
|
57
60
|
include Legion::Extensions::Llm::Openai::DiscoveryTransport
|
|
58
61
|
include Legion::Extensions::Llm::Openai::DiscoveryHealthDisplay
|
|
62
|
+
include Legion::Extensions::Llm::Openai::DiscoveryWeightPublication
|
|
59
63
|
|
|
60
64
|
def runner_class = self.class
|
|
61
65
|
def runner_function = 'manual'
|
|
@@ -69,7 +73,7 @@ module Legion
|
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def manual
|
|
72
|
-
|
|
76
|
+
initialize_weight_publication
|
|
73
77
|
tick_refresh
|
|
74
78
|
rescue StandardError => e
|
|
75
79
|
handle_exception(e, level: :warn, operation: 'openai.actor.discovery_refresh')
|
|
@@ -78,7 +82,11 @@ module Legion
|
|
|
78
82
|
def shutdown
|
|
79
83
|
return unless @instance_states
|
|
80
84
|
|
|
81
|
-
|
|
85
|
+
instance_states_snapshot.each_key { |instance_id| remove_instance_state(instance_id) }
|
|
86
|
+
@instance_state_mutex.synchronize do
|
|
87
|
+
@instance_states.clear
|
|
88
|
+
@dormant_weight_tracker.clear!
|
|
89
|
+
end
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
private
|
|
@@ -109,12 +117,13 @@ module Legion
|
|
|
109
117
|
|
|
110
118
|
def tick_refresh
|
|
111
119
|
reconcile_configured_instances
|
|
112
|
-
|
|
120
|
+
instance_states_snapshot.each do |instance_id, state|
|
|
113
121
|
refresh_instance(instance_id: instance_id, state: state)
|
|
114
122
|
rescue StandardError => e
|
|
115
123
|
handle_exception(e, level: :warn, operation: 'openai.actor.refresh_instance',
|
|
116
124
|
instance_id: instance_id)
|
|
117
125
|
end
|
|
126
|
+
observe_dormant_weights
|
|
118
127
|
end
|
|
119
128
|
|
|
120
129
|
# Re-scans configured instances every tick so instances configured
|
|
@@ -128,10 +137,15 @@ module Legion
|
|
|
128
137
|
|
|
129
138
|
def claim_new_instances(discovered)
|
|
130
139
|
discovered.each do |name, instance_cfg|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
instance_id = name.to_s
|
|
141
|
+
next if tracked_instance_state(instance_id)
|
|
142
|
+
|
|
143
|
+
state = build_instance_context(name: name, instance_cfg: instance_cfg)
|
|
144
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.track_initializing!(
|
|
145
|
+
states: @instance_states,
|
|
146
|
+
state_key: instance_id,
|
|
147
|
+
state: state,
|
|
148
|
+
mutex: @instance_state_mutex
|
|
135
149
|
)
|
|
136
150
|
rescue StandardError => e
|
|
137
151
|
handle_exception(e, level: :warn, operation: 'openai.actor.claim_instance',
|
|
@@ -141,7 +155,9 @@ module Legion
|
|
|
141
155
|
|
|
142
156
|
def release_removed_instances(discovered)
|
|
143
157
|
discovered_names = discovered.keys.map(&:to_s)
|
|
144
|
-
(
|
|
158
|
+
(instance_states_snapshot.keys - discovered_names).each do |instance_id|
|
|
159
|
+
remove_instance_state(instance_id)
|
|
160
|
+
end
|
|
145
161
|
end
|
|
146
162
|
|
|
147
163
|
def build_instance_context(name:, instance_cfg:)
|
|
@@ -149,6 +165,9 @@ module Legion
|
|
|
149
165
|
instance_key = Legion::Extensions::Llm::Inventory::Identity::InstanceKey.new(
|
|
150
166
|
provider_family: :openai, instance_id: name.to_s, physical_id: physical_id
|
|
151
167
|
)
|
|
168
|
+
offerings = discover_offerings_for_instance(
|
|
169
|
+
instance_cfg: instance_cfg, instance_key: instance_key
|
|
170
|
+
)
|
|
152
171
|
callable = Legion::Extensions::Llm::Openai::OpenaiCallable.new(instance_cfg: instance_cfg, logger: log)
|
|
153
172
|
probe_coordinator = Legion::Extensions::Llm::Inventory::ProbeCoordinator.new(
|
|
154
173
|
instance_key: instance_key,
|
|
@@ -165,7 +184,8 @@ module Legion
|
|
|
165
184
|
instance_cfg: instance_cfg,
|
|
166
185
|
callable: callable, probe_coordinator: probe_coordinator,
|
|
167
186
|
publisher_token: publisher_token, sequence: 0, last_probe_outcome: nil,
|
|
168
|
-
offerings:
|
|
187
|
+
offerings: offerings,
|
|
188
|
+
published: false
|
|
169
189
|
}
|
|
170
190
|
end
|
|
171
191
|
|
|
@@ -187,6 +207,8 @@ module Legion
|
|
|
187
207
|
# calling replace/readiness_succeeded, which would raise
|
|
188
208
|
# InvalidTransitionError.
|
|
189
209
|
def run_initialization_probe(instance_id:, state:)
|
|
210
|
+
refresh_unpublished_offerings(instance_id: instance_id, state: state) \
|
|
211
|
+
if state[:last_probe_outcome] == :failure
|
|
190
212
|
coordinator = state[:probe_coordinator]
|
|
191
213
|
return unless coordinator.begin_probe
|
|
192
214
|
|
|
@@ -224,16 +246,24 @@ module Legion
|
|
|
224
246
|
end
|
|
225
247
|
|
|
226
248
|
def activate_after_readiness(instance_id:, state:, probe_token:)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
publisher.activate_instance_snapshot(
|
|
249
|
+
activated = Legion::Extensions::Llm::Inventory::WeightReconciler.activate_tracked!(
|
|
250
|
+
settings: Legion::Settings,
|
|
230
251
|
instance_id: instance_id,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
probe_token: probe_token
|
|
252
|
+
state_key: instance_id,
|
|
253
|
+
state: state,
|
|
254
|
+
states: @instance_states,
|
|
255
|
+
mutex: @instance_state_mutex,
|
|
256
|
+
probe_token: probe_token,
|
|
257
|
+
activate: method(:activate_weight_snapshot),
|
|
258
|
+
activation_sequence: ->(tracked) { tracked.fetch(:sequence) + 1 }
|
|
236
259
|
)
|
|
260
|
+
return unless activated
|
|
261
|
+
|
|
262
|
+
updated = update_tracked_instance(instance_id, state) do
|
|
263
|
+
state[:last_probe_outcome] = :success
|
|
264
|
+
end
|
|
265
|
+
return unless updated
|
|
266
|
+
|
|
237
267
|
write_instance_health(
|
|
238
268
|
config_name: state[:name], available: true, reason: 'startup readiness succeeded',
|
|
239
269
|
probe_outcome: :success, source: :startup_readiness,
|
|
@@ -242,11 +272,15 @@ module Legion
|
|
|
242
272
|
end
|
|
243
273
|
|
|
244
274
|
def report_initial_failure(instance_id:, state:, probe_token:, reason:)
|
|
245
|
-
state[:last_probe_outcome] = :failure
|
|
246
275
|
publisher.readiness_failed(
|
|
247
276
|
instance_id: instance_id, physical_id: state[:physical_id],
|
|
248
277
|
probe_token: probe_token, reason: reason
|
|
249
278
|
)
|
|
279
|
+
updated = update_tracked_instance(instance_id, state) do
|
|
280
|
+
state[:last_probe_outcome] = :failure
|
|
281
|
+
end
|
|
282
|
+
return unless updated
|
|
283
|
+
|
|
250
284
|
write_instance_health(
|
|
251
285
|
config_name: state[:name], available: false, reason: reason,
|
|
252
286
|
probe_outcome: :failure, source: :startup_readiness
|
|
@@ -259,16 +293,10 @@ module Legion
|
|
|
259
293
|
instance_key: state[:instance_key]
|
|
260
294
|
)
|
|
261
295
|
|
|
262
|
-
|
|
263
|
-
state
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
physical_id: state[:physical_id],
|
|
267
|
-
publisher_token: state[:publisher_token],
|
|
268
|
-
offerings: new_offerings,
|
|
269
|
-
sequence: state[:sequence]
|
|
270
|
-
)
|
|
271
|
-
state[:offerings] = new_offerings
|
|
296
|
+
changed = commit_discovered_offerings(
|
|
297
|
+
instance_id: instance_id, state: state, offerings: new_offerings
|
|
298
|
+
)
|
|
299
|
+
if changed && tracked_instance?(instance_id, state)
|
|
272
300
|
write_instance_health(
|
|
273
301
|
config_name: state[:name], available: true, reason: 'offerings refreshed',
|
|
274
302
|
probe_outcome: state[:last_probe_outcome], source: :discovery
|
|
@@ -278,17 +306,29 @@ module Legion
|
|
|
278
306
|
run_cadence_probe(instance_id: instance_id, state: state)
|
|
279
307
|
end
|
|
280
308
|
|
|
309
|
+
def refresh_unpublished_offerings(instance_id:, state:)
|
|
310
|
+
offerings = discover_offerings_for_instance(
|
|
311
|
+
instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
|
|
312
|
+
)
|
|
313
|
+
commit_discovered_offerings(instance_id: instance_id, state: state, offerings: offerings)
|
|
314
|
+
end
|
|
315
|
+
|
|
281
316
|
# -- Removal -----------------------------------------------------------
|
|
282
317
|
|
|
283
318
|
def remove_instance_state(instance_id)
|
|
284
|
-
state = @
|
|
319
|
+
state = @instance_state_mutex.synchronize do
|
|
320
|
+
tracked = @instance_states[instance_id]
|
|
321
|
+
next unless tracked
|
|
322
|
+
|
|
323
|
+
publisher.remove_instance(
|
|
324
|
+
instance_id: instance_id,
|
|
325
|
+
physical_id: tracked[:physical_id],
|
|
326
|
+
publisher_token: tracked[:publisher_token]
|
|
327
|
+
)
|
|
328
|
+
@instance_states.delete(instance_id)
|
|
329
|
+
end
|
|
285
330
|
return unless state
|
|
286
331
|
|
|
287
|
-
publisher.remove_instance(
|
|
288
|
-
instance_id: instance_id,
|
|
289
|
-
physical_id: state[:physical_id],
|
|
290
|
-
publisher_token: state[:publisher_token]
|
|
291
|
-
)
|
|
292
332
|
clear_instance_health(config_name: state[:name])
|
|
293
333
|
rescue StandardError => e
|
|
294
334
|
handle_exception(e, level: :warn, operation: 'openai.actor.remove_instance',
|
|
@@ -311,7 +351,7 @@ module Legion
|
|
|
311
351
|
instance_key: instance_key
|
|
312
352
|
)
|
|
313
353
|
end
|
|
314
|
-
rescue
|
|
354
|
+
rescue Faraday::Error, Legion::JSON::ParseError => e
|
|
315
355
|
handle_exception(e, level: :warn, operation: 'openai.actor.discover_offerings')
|
|
316
356
|
[]
|
|
317
357
|
end
|
|
@@ -325,11 +365,20 @@ module Legion
|
|
|
325
365
|
def build_offering_draft(model_id:, model_data:, instance_cfg:, instance_key:)
|
|
326
366
|
tier = instance_cfg[:tier] || :frontier
|
|
327
367
|
operations = infer_operations(model_id: model_id)
|
|
368
|
+
weight_inputs = Legion::Extensions::Llm::Inventory::WeightSchema.weight_inputs(
|
|
369
|
+
settings: Legion::Settings,
|
|
370
|
+
instance_key: instance_key,
|
|
371
|
+
provider_native_key: model_id,
|
|
372
|
+
model: model_id,
|
|
373
|
+
tier: tier
|
|
374
|
+
)
|
|
328
375
|
|
|
329
376
|
Legion::Extensions::Llm::Inventory::OfferingDraft.new(
|
|
330
377
|
provider_native_key: model_id,
|
|
331
378
|
model: model_id,
|
|
332
379
|
tier: tier,
|
|
380
|
+
weight_inputs: weight_inputs,
|
|
381
|
+
base_weight: Legion::Extensions::Llm::Inventory::WeightSchema.base_weight(weight_inputs),
|
|
333
382
|
operation_evidence: build_operation_evidence(operations: operations),
|
|
334
383
|
capability_evidence: build_capability_evidence(model_id: model_id),
|
|
335
384
|
context_evidence: build_context_evidence(model_id: model_id, model_data: model_data),
|
|
@@ -8,6 +8,12 @@ module Legion
|
|
|
8
8
|
# DiscoveryRefresh. Extracted to keep the main class and the evidence
|
|
9
9
|
# module within Metrics limits.
|
|
10
10
|
module DiscoveryDrafts
|
|
11
|
+
EVIDENCE_MAP_FIELDS = %i[operation_evidence capability_evidence].freeze
|
|
12
|
+
VALUE_EVIDENCE_FIELDS = %i[
|
|
13
|
+
context_evidence max_output_evidence embedding_dimensions_evidence
|
|
14
|
+
model_revision_evidence tokenizer_evidence
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
11
17
|
private
|
|
12
18
|
|
|
13
19
|
# -- Operation inference -----------------------------------------------
|
|
@@ -55,56 +61,31 @@ module Legion
|
|
|
55
61
|
# -- Offerings change detection (D3) -----------------------------------
|
|
56
62
|
# Time.now observed_at stamps in the evidence poison Data#==, so a
|
|
57
63
|
# rebuilt-but-unchanged catalog would otherwise replace the snapshot
|
|
58
|
-
# every tick.
|
|
64
|
+
# every tick. Every other OfferingDraft field is authoritative.
|
|
59
65
|
|
|
60
66
|
def offerings_changed?(previous:, current:)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# Changed when any draft has no stable counterpart in the
|
|
64
|
-
# previous catalog (size mismatch or an identity/status diff).
|
|
65
|
-
current.any? do |draft|
|
|
66
|
-
previous.none? { |candidate| drafts_stable?(candidate, draft) }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def drafts_stable?(candidate, draft)
|
|
71
|
-
basic_fields_stable?(candidate, draft) &&
|
|
72
|
-
evidence_maps_stable?(candidate.operation_evidence, draft.operation_evidence) &&
|
|
73
|
-
evidence_maps_stable?(candidate.capability_evidence, draft.capability_evidence) &&
|
|
74
|
-
value_fields_stable?(candidate, draft) &&
|
|
75
|
-
candidate.quota_domains == draft.quota_domains &&
|
|
76
|
-
candidate.metadata == draft.metadata
|
|
67
|
+
offering_multiset(previous) != offering_multiset(current)
|
|
77
68
|
end
|
|
78
69
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
candidate.tier == draft.tier &&
|
|
82
|
-
evidence_keys_stable?(candidate.operation_evidence, draft.operation_evidence) &&
|
|
83
|
-
evidence_keys_stable?(candidate.capability_evidence, draft.capability_evidence)
|
|
70
|
+
def offering_multiset(offerings)
|
|
71
|
+
offerings.map { |draft| stable_draft_state(draft) }.tally
|
|
84
72
|
end
|
|
85
73
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
other = current_map[key]
|
|
93
|
-
other&.status == evidence.status && other&.source == evidence.source
|
|
74
|
+
def stable_draft_state(draft)
|
|
75
|
+
state = draft.to_h
|
|
76
|
+
EVIDENCE_MAP_FIELDS.each do |field|
|
|
77
|
+
state[field] = state.fetch(field).transform_values do |evidence|
|
|
78
|
+
stable_evidence_state(evidence)
|
|
79
|
+
end
|
|
94
80
|
end
|
|
81
|
+
VALUE_EVIDENCE_FIELDS.each do |field|
|
|
82
|
+
state[field] = stable_evidence_state(state.fetch(field))
|
|
83
|
+
end
|
|
84
|
+
state
|
|
95
85
|
end
|
|
96
86
|
|
|
97
|
-
def
|
|
98
|
-
|
|
99
|
-
values_stable?(candidate.max_output_evidence, draft.max_output_evidence) &&
|
|
100
|
-
values_stable?(candidate.embedding_dimensions_evidence, draft.embedding_dimensions_evidence) &&
|
|
101
|
-
values_stable?(candidate.model_revision_evidence, draft.model_revision_evidence)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def values_stable?(previous_value, current_value)
|
|
105
|
-
previous_value.status == current_value.status &&
|
|
106
|
-
previous_value.value == current_value.value &&
|
|
107
|
-
previous_value.source == current_value.source
|
|
87
|
+
def stable_evidence_state(evidence)
|
|
88
|
+
evidence.to_h.except(:observed_at)
|
|
108
89
|
end
|
|
109
90
|
end
|
|
110
91
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Llm
|
|
6
|
+
module Openai
|
|
7
|
+
# Applies cadence/reactive readiness results after provider I/O.
|
|
8
|
+
module DiscoveryProbeReporting
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def report_probe_result(instance_id:, probe_token:, readiness:, state:)
|
|
12
|
+
if readiness.ready?
|
|
13
|
+
report_probe_success(instance_id: instance_id, probe_token: probe_token, state: state)
|
|
14
|
+
else
|
|
15
|
+
report_probe_failure(
|
|
16
|
+
instance_id: instance_id, probe_token: probe_token, readiness: readiness, state: state
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def report_probe_success(instance_id:, probe_token:, state:)
|
|
22
|
+
publisher.readiness_succeeded(
|
|
23
|
+
instance_id: instance_id, physical_id: state[:physical_id], probe_token: probe_token
|
|
24
|
+
)
|
|
25
|
+
updated = update_tracked_instance(instance_id, state) do
|
|
26
|
+
state[:last_probe_outcome] = :success
|
|
27
|
+
end
|
|
28
|
+
return unless updated
|
|
29
|
+
|
|
30
|
+
write_instance_health(
|
|
31
|
+
config_name: state[:name], available: true, reason: 'readiness probe succeeded',
|
|
32
|
+
probe_outcome: :success, source: :readiness_probe
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def report_probe_failure(instance_id:, probe_token:, readiness:, state:)
|
|
37
|
+
publisher.readiness_failed(
|
|
38
|
+
instance_id: instance_id, physical_id: state[:physical_id],
|
|
39
|
+
probe_token: probe_token, reason: readiness.reason
|
|
40
|
+
)
|
|
41
|
+
updated = update_tracked_instance(instance_id, state) do
|
|
42
|
+
state[:last_probe_outcome] = :failure
|
|
43
|
+
end
|
|
44
|
+
return unless updated
|
|
45
|
+
|
|
46
|
+
write_instance_health(
|
|
47
|
+
config_name: state[:name], available: false, reason: readiness.reason,
|
|
48
|
+
probe_outcome: :failure, source: :readiness_probe
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -68,7 +68,7 @@ module Legion
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def handle_reactive_probe(instance_id:, request:)
|
|
71
|
-
state =
|
|
71
|
+
state = tracked_instance_state(instance_id)
|
|
72
72
|
return unless state
|
|
73
73
|
|
|
74
74
|
coordinator = state[:probe_coordinator]
|
|
@@ -95,29 +95,6 @@ module Legion
|
|
|
95
95
|
instance_id: instance_id)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
def report_probe_result(instance_id:, probe_token:, readiness:, state:)
|
|
99
|
-
if readiness.ready?
|
|
100
|
-
publisher.readiness_succeeded(
|
|
101
|
-
instance_id: instance_id, physical_id: state[:physical_id], probe_token: probe_token
|
|
102
|
-
)
|
|
103
|
-
state[:last_probe_outcome] = :success
|
|
104
|
-
write_instance_health(
|
|
105
|
-
config_name: state[:name], available: true, reason: 'readiness probe succeeded',
|
|
106
|
-
probe_outcome: :success, source: :readiness_probe
|
|
107
|
-
)
|
|
108
|
-
else
|
|
109
|
-
publisher.readiness_failed(
|
|
110
|
-
instance_id: instance_id, physical_id: state[:physical_id],
|
|
111
|
-
probe_token: probe_token, reason: readiness.reason
|
|
112
|
-
)
|
|
113
|
-
state[:last_probe_outcome] = :failure
|
|
114
|
-
write_instance_health(
|
|
115
|
-
config_name: state[:name], available: false, reason: readiness.reason,
|
|
116
|
-
probe_outcome: :failure, source: :readiness_probe
|
|
117
|
-
)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
98
|
def build_probe_enqueue(instance_id:)
|
|
122
99
|
proc do |request:|
|
|
123
100
|
handle_reactive_probe(instance_id: instance_id, request: request)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/llm/inventory/weight_reconciler'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Llm
|
|
8
|
+
module Openai
|
|
9
|
+
# Binds the shared write-time weight reconciler to OpenAI's existing
|
|
10
|
+
# periodic discovery actor. Settings are read only during ordinary
|
|
11
|
+
# actor passes; this module never registers reload/reset callbacks.
|
|
12
|
+
module DiscoveryWeightPublication
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def initialize_weight_publication
|
|
16
|
+
@instance_states ||= {}
|
|
17
|
+
@instance_state_mutex ||= Mutex.new
|
|
18
|
+
dormant_weight_tracker
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def dormant_weight_tracker
|
|
22
|
+
@dormant_weight_tracker ||= Legion::Extensions::Llm::Inventory::DormantWeightTracker.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def commit_discovered_offerings(instance_id:, state:, offerings:)
|
|
26
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
|
|
27
|
+
settings: Legion::Settings,
|
|
28
|
+
instance_id: instance_id,
|
|
29
|
+
state: state,
|
|
30
|
+
discovered_offerings: offerings,
|
|
31
|
+
mutex: @instance_state_mutex,
|
|
32
|
+
equivalent: lambda do |previous, current|
|
|
33
|
+
!offerings_changed?(previous: previous, current: current)
|
|
34
|
+
end,
|
|
35
|
+
replace: method(:replace_weight_snapshot)
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def replace_weight_snapshot(instance_id:, state:, offerings:, sequence:)
|
|
40
|
+
publisher.replace_instance_snapshot(
|
|
41
|
+
instance_id: instance_id,
|
|
42
|
+
physical_id: state.fetch(:physical_id),
|
|
43
|
+
publisher_token: state.fetch(:publisher_token),
|
|
44
|
+
offerings: offerings,
|
|
45
|
+
sequence: sequence
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def activate_weight_snapshot(instance_id:, state:, offerings:, sequence:, probe_token:)
|
|
50
|
+
publisher.activate_instance_snapshot(
|
|
51
|
+
instance_id: instance_id,
|
|
52
|
+
physical_id: state.fetch(:physical_id),
|
|
53
|
+
publisher_token: state.fetch(:publisher_token),
|
|
54
|
+
offerings: offerings,
|
|
55
|
+
sequence: sequence,
|
|
56
|
+
probe_token: probe_token
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def tracked_instance_state(instance_id)
|
|
61
|
+
return unless @instance_states && @instance_state_mutex
|
|
62
|
+
|
|
63
|
+
@instance_state_mutex.synchronize { @instance_states[instance_id] }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def instance_states_snapshot
|
|
67
|
+
@instance_state_mutex.synchronize { @instance_states.each_pair.to_h }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def update_tracked_instance(instance_id, state)
|
|
71
|
+
@instance_state_mutex.synchronize do
|
|
72
|
+
return false unless @instance_states[instance_id].equal?(state)
|
|
73
|
+
|
|
74
|
+
yield
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def tracked_instance?(instance_id, state)
|
|
80
|
+
tracked_instance_state(instance_id).equal?(state)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def observe_dormant_weights
|
|
84
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.observe_dormant!(
|
|
85
|
+
settings: Legion::Settings,
|
|
86
|
+
provider_family: :openai,
|
|
87
|
+
states: @instance_states,
|
|
88
|
+
mutex: @instance_state_mutex,
|
|
89
|
+
tracker: dormant_weight_tracker,
|
|
90
|
+
dormant_logger: lambda do |key|
|
|
91
|
+
log.info do
|
|
92
|
+
"[llm][openai] action=dormant_weight weight_key=#{key.inspect} no_lane_published=true"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-openai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 0.7.
|
|
74
|
+
version: 0.7.6
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 0.7.
|
|
81
|
+
version: 0.7.6
|
|
82
82
|
description: OpenAI provider integration for the LegionIO LLM routing framework.
|
|
83
83
|
email:
|
|
84
84
|
- matthewdiverson@gmail.com
|
|
@@ -103,8 +103,10 @@ files:
|
|
|
103
103
|
- lib/legion/extensions/llm/openai/discovery_evidence_builders.rb
|
|
104
104
|
- lib/legion/extensions/llm/openai/discovery_health_display.rb
|
|
105
105
|
- lib/legion/extensions/llm/openai/discovery_identity.rb
|
|
106
|
+
- lib/legion/extensions/llm/openai/discovery_probe_reporting.rb
|
|
106
107
|
- lib/legion/extensions/llm/openai/discovery_probing.rb
|
|
107
108
|
- lib/legion/extensions/llm/openai/discovery_transport.rb
|
|
109
|
+
- lib/legion/extensions/llm/openai/discovery_weight_publication.rb
|
|
108
110
|
- lib/legion/extensions/llm/openai/instance_discovery.rb
|
|
109
111
|
- lib/legion/extensions/llm/openai/openai_callable.rb
|
|
110
112
|
- lib/legion/extensions/llm/openai/provider.rb
|