lex-llm 0.7.3 → 0.8.0
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 +106 -0
- data/RULES.md +97 -0
- data/lib/legion/extensions/llm/auto_registration.rb +4 -13
- data/lib/legion/extensions/llm/canonical/chunk.rb +106 -92
- data/lib/legion/extensions/llm/canonical/content_block.rb +66 -75
- data/lib/legion/extensions/llm/canonical/message.rb +52 -67
- data/lib/legion/extensions/llm/canonical/params.rb +59 -33
- data/lib/legion/extensions/llm/canonical/request.rb +67 -54
- data/lib/legion/extensions/llm/canonical/response.rb +61 -76
- data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +99 -51
- data/lib/legion/extensions/llm/canonical/tool_call.rb +42 -51
- data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
- data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
- data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
- data/lib/legion/extensions/llm/canonical.rb +6 -5
- data/lib/legion/extensions/llm/configuration.rb +40 -10
- data/lib/legion/extensions/llm/connection.rb +8 -30
- data/lib/legion/extensions/llm/credential_sources.rb +32 -49
- data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
- data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
- data/lib/legion/extensions/llm/error.rb +0 -14
- data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
- data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
- data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -153
- data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -150
- data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
- data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
- data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
- data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
- data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
- data/lib/legion/extensions/llm/inventory/records.rb +82 -99
- data/lib/legion/extensions/llm/inventory/registry.rb +54 -46
- data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
- data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +249 -0
- data/lib/legion/extensions/llm/inventory/weight_schema.rb +146 -0
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +172 -182
- data/lib/legion/extensions/llm/provider.rb +192 -315
- data/lib/legion/extensions/llm/provider_contract.rb +25 -8
- data/lib/legion/extensions/llm/provider_settings.rb +5 -26
- data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
- data/lib/legion/extensions/llm/responses/tool_arguments.rb +34 -0
- data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
- data/lib/legion/extensions/llm/routing/records.rb +34 -17
- data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
- data/lib/legion/extensions/llm/streaming.rb +50 -35
- data/lib/legion/extensions/llm/taxonomies.rb +29 -17
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
- data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
- data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
- data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
- data/lib/legion/extensions/llm/utils.rb +23 -5
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +10 -98
- data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
- data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
- data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
- data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
- data/spec/legion/extensions/llm/canonical/params_spec.rb +57 -136
- data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
- data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +68 -148
- data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
- data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
- data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
- data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
- data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
- data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
- data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
- data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +130 -0
- data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +507 -0
- data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
- data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
- data/spec/legion/extensions/llm/error_spec.rb +2 -12
- data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
- data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +178 -67
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +101 -62
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +118 -123
- data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
- data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
- data/spec/legion/extensions/llm/inventory/records_spec.rb +129 -41
- data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +55 -4
- data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
- data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
- data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +312 -0
- data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +141 -0
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +105 -68
- data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
- data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
- data/spec/legion/extensions/llm/provider_spec.rb +98 -78
- data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
- data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
- data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
- data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -16
- data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
- data/spec/legion/extensions/llm/utils_spec.rb +26 -7
- data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
- data/spec/legion/extensions/llm_extension_spec.rb +5 -5
- data/spec/support/fake_llm_provider.rb +45 -39
- data/spec/support/fake_ssot_harness.rb +7 -2
- data/spec/support/ssot_registry_helpers.rb +3 -2
- metadata +15 -54
- data/lib/legion/extensions/llm/agent.rb +0 -366
- data/lib/legion/extensions/llm/aliases.json +0 -436
- data/lib/legion/extensions/llm/aliases.rb +0 -67
- data/lib/legion/extensions/llm/attachment.rb +0 -229
- data/lib/legion/extensions/llm/chat.rb +0 -354
- data/lib/legion/extensions/llm/chunk.rb +0 -10
- data/lib/legion/extensions/llm/content.rb +0 -81
- data/lib/legion/extensions/llm/context.rb +0 -33
- data/lib/legion/extensions/llm/embedding.rb +0 -33
- data/lib/legion/extensions/llm/image.rb +0 -109
- data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
- data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -311
- data/lib/legion/extensions/llm/message.rb +0 -118
- data/lib/legion/extensions/llm/mime_type.rb +0 -75
- data/lib/legion/extensions/llm/model/info.rb +0 -286
- data/lib/legion/extensions/llm/model/modalities.rb +0 -26
- data/lib/legion/extensions/llm/model/pricing.rb +0 -52
- data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
- data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
- data/lib/legion/extensions/llm/model.rb +0 -11
- data/lib/legion/extensions/llm/models.json +0 -57313
- data/lib/legion/extensions/llm/models.rb +0 -530
- data/lib/legion/extensions/llm/models_schema.json +0 -168
- data/lib/legion/extensions/llm/moderation.rb +0 -60
- data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
- data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
- data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
- data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
- data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
- data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
- data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
- data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
- data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
- data/lib/legion/extensions/llm/thinking.rb +0 -53
- data/lib/legion/extensions/llm/tokens.rb +0 -51
- data/lib/legion/extensions/llm/tool_call.rb +0 -34
- data/lib/legion/extensions/llm/transcription.rb +0 -39
- data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
- data/spec/legion/extensions/llm/agent_spec.rb +0 -179
- data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
- data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
- data/spec/legion/extensions/llm/context_spec.rb +0 -127
- data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
- data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -337
- data/spec/legion/extensions/llm/message_spec.rb +0 -64
- data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
- data/spec/legion/extensions/llm/models_spec.rb +0 -104
- data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
- data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
- data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
- data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
- data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
- data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
|
@@ -1,80 +1,88 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'spec_helper'
|
|
3
4
|
require 'legion/extensions/llm/fleet/worker_execution'
|
|
5
|
+
require 'support/ssot_registry_helpers'
|
|
4
6
|
|
|
5
7
|
RSpec.describe Legion::Extensions::Llm::Fleet::WorkerExecution do
|
|
8
|
+
include SsotRegistryHelpers
|
|
9
|
+
|
|
10
|
+
let(:key) { instance_key(family: 'vllm', instance: 'h200') }
|
|
11
|
+
let(:callable) do
|
|
12
|
+
Class.new do
|
|
13
|
+
attr_reader :dispatched
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@dispatched = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def chat(messages, model:, **rest)
|
|
20
|
+
@dispatched << { operation: :chat, model: model, first: messages.first, rest: rest }
|
|
21
|
+
Legion::Extensions::Llm::Canonical::Response.build(text: "#{model}:#{messages.first.text}", model: model)
|
|
22
|
+
end
|
|
23
|
+
end.new
|
|
24
|
+
end
|
|
6
25
|
let(:envelope) do
|
|
7
26
|
{
|
|
8
|
-
|
|
9
|
-
|
|
27
|
+
request_id: 'req-1',
|
|
28
|
+
correlation_id: 'corr-1',
|
|
29
|
+
idempotency_key: 'idem-1',
|
|
30
|
+
operation: 'chat',
|
|
31
|
+
provider: 'vllm',
|
|
32
|
+
provider_instance: 'h200',
|
|
33
|
+
model: 'gemma4',
|
|
10
34
|
params: { messages: [{ role: 'user', content: 'hello' }] },
|
|
35
|
+
reply_to: 'reply.1',
|
|
36
|
+
message_context: {},
|
|
37
|
+
caller: 'e2e',
|
|
38
|
+
trace_context: {},
|
|
11
39
|
signed_token: 'unsigned',
|
|
12
|
-
|
|
40
|
+
timeout_seconds: 30,
|
|
41
|
+
expires_at: (Time.now + 120).utc.iso8601,
|
|
42
|
+
protocol_version: Legion::Extensions::Llm::Fleet::Protocol::VERSION,
|
|
43
|
+
execution_contract: Legion::Extensions::Llm::Fleet::Protocol::EXACT_EXECUTION_CONTRACT,
|
|
44
|
+
offering_id: Legion::Extensions::Llm::Inventory::Registry.snapshot
|
|
45
|
+
.lanes_for(instance_key: key).first.lane_id
|
|
13
46
|
}
|
|
14
47
|
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ content: "#{model}:#{messages.first[:content]}" }
|
|
19
|
-
end
|
|
20
|
-
end.new
|
|
48
|
+
|
|
49
|
+
def activate!
|
|
50
|
+
claim_and_activate(key: key, callable: callable, coordinator: probe_coordinator(key), model: 'gemma4')
|
|
21
51
|
end
|
|
22
52
|
|
|
23
53
|
before do
|
|
54
|
+
Legion::Extensions::Llm::Inventory::Registry.reset!
|
|
55
|
+
activate!
|
|
24
56
|
described_class.reset_idempotency_cache!
|
|
25
57
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value).and_call_original
|
|
26
58
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
27
59
|
.with(:fleet, :auth, :require_signed_token, default: true).and_return(false)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'dispatches canonical chat operations directly to the local provider' do
|
|
31
60
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
32
61
|
.with(:fleet, :responder, :require_idempotency, default: nil).and_return(false)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'executes the exact request against the captured registry callable (W1/W2)' do
|
|
65
|
+
response = described_class.call(envelope: envelope, registry: registry)
|
|
33
66
|
|
|
34
|
-
expect(
|
|
67
|
+
expect(response).to be_a(Legion::Extensions::Llm::Canonical::Response)
|
|
68
|
+
expect(response.text).to eq('gemma4:hello')
|
|
69
|
+
expect(callable.dispatched.first[:first]).to be_a(Legion::Extensions::Llm::Canonical::Message)
|
|
35
70
|
end
|
|
36
71
|
|
|
37
|
-
it '
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
capture_provider = Class.new do
|
|
42
|
-
define_method(:chat) do |messages:, model:, **options|
|
|
43
|
-
captured_options = options
|
|
44
|
-
{ content: "#{model}:#{messages.first[:content]}" }
|
|
45
|
-
end
|
|
46
|
-
end.new
|
|
47
|
-
legacy_envelope = envelope.merge(
|
|
48
|
-
params: {
|
|
49
|
-
messages: [{ role: 'user', content: 'hello' }],
|
|
50
|
-
stream: false,
|
|
51
|
-
tools: { current: { name: 'current' } },
|
|
52
|
-
options: {
|
|
53
|
-
'stream' => true,
|
|
54
|
-
'system' => 'Use available tools.',
|
|
55
|
-
'tools' => { legacy: { name: 'legacy' } }
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
described_class.call(envelope: legacy_envelope, provider: capture_provider)
|
|
61
|
-
|
|
62
|
-
expect(captured_options).to include(
|
|
63
|
-
stream: false,
|
|
64
|
-
system: 'Use available tools.',
|
|
65
|
-
tools: { current: { name: 'current' } }
|
|
66
|
-
)
|
|
67
|
-
expect(captured_options).not_to have_key(:options)
|
|
72
|
+
it 'requires registry — the provider-object topology is deleted (W1)' do
|
|
73
|
+
expect { described_class.call(envelope: envelope, registry: nil) }
|
|
74
|
+
.to raise_error(described_class::PolicyError, /requires registry/)
|
|
75
|
+
expect(described_class.method(:call).parameters).to eq([%i[keyreq envelope], %i[keyreq registry]])
|
|
68
76
|
end
|
|
69
77
|
|
|
70
|
-
it 'rejects duplicate idempotency keys before executing the
|
|
78
|
+
it 'rejects duplicate idempotency keys before executing the callable again (W7)' do
|
|
71
79
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
72
80
|
.with(:fleet, :responder, :require_idempotency, default: nil).and_return(true)
|
|
73
81
|
|
|
74
|
-
described_class.call(envelope: envelope,
|
|
82
|
+
described_class.call(envelope: envelope, registry: registry)
|
|
75
83
|
|
|
76
84
|
expect do
|
|
77
|
-
described_class.call(envelope: envelope,
|
|
85
|
+
described_class.call(envelope: envelope, registry: registry)
|
|
78
86
|
end.to raise_error(described_class::PolicyError, /duplicate fleet idempotency key/)
|
|
79
87
|
end
|
|
80
88
|
|
|
@@ -91,38 +99,69 @@ RSpec.describe Legion::Extensions::Llm::Fleet::WorkerExecution do
|
|
|
91
99
|
end.to raise_error(described_class::PolicyError, /duplicate fleet idempotency key/)
|
|
92
100
|
end
|
|
93
101
|
|
|
94
|
-
it 'reserves token replay
|
|
102
|
+
it 'reserves token replay before dispatch and marks success after (S4)' do
|
|
95
103
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
96
104
|
.with(:fleet, :auth, :require_signed_token, default: true).and_return(true)
|
|
97
|
-
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
98
|
-
.with(:fleet, :responder, :require_idempotency, default: nil).and_return(false)
|
|
99
105
|
allow(Legion::Extensions::Llm::Fleet::TokenValidator).to receive(:validate!).and_return({ jti: 'jti-1' })
|
|
100
106
|
allow(Legion::Extensions::Llm::Fleet::TokenValidator).to receive(:mark_replay!)
|
|
101
107
|
|
|
102
|
-
described_class.call(envelope: envelope,
|
|
108
|
+
described_class.call(envelope: envelope, registry: registry)
|
|
103
109
|
|
|
104
|
-
expect(Legion::Extensions::Llm::Fleet::TokenValidator).to have_received(:validate!)
|
|
105
|
-
.with(token: 'unsigned', envelope: envelope)
|
|
106
110
|
expect(Legion::Extensions::Llm::Fleet::TokenValidator).to have_received(:mark_replay!).with('jti-1')
|
|
107
111
|
end
|
|
108
112
|
|
|
109
|
-
it 'releases reserved
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
it 'releases reserved replay state when dispatch fails (S4)' do
|
|
114
|
+
failing_callable = Class.new { def chat(*, **) = raise('provider unavailable') }.new
|
|
115
|
+
key2 = instance_key(family: 'vllm', instance: 'h201')
|
|
116
|
+
claim_and_activate(key: key2, callable: failing_callable, coordinator: probe_coordinator(key2), model: 'gemma4')
|
|
117
|
+
failing_envelope = envelope.merge(provider_instance: 'h201',
|
|
118
|
+
offering_id: Legion::Extensions::Llm::Inventory::Registry.snapshot
|
|
119
|
+
.lanes_for(instance_key: key2).first.lane_id)
|
|
115
120
|
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
116
121
|
.with(:fleet, :auth, :require_signed_token, default: true).and_return(true)
|
|
117
|
-
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
118
|
-
.with(:fleet, :responder, :require_idempotency, default: nil).and_return(false)
|
|
119
122
|
allow(Legion::Extensions::Llm::Fleet::TokenValidator).to receive(:validate!).and_return({ jti: 'jti-2' })
|
|
120
123
|
allow(Legion::Extensions::Llm::Fleet::TokenValidator).to receive(:release_replay!)
|
|
121
124
|
|
|
122
125
|
expect do
|
|
123
|
-
described_class.call(envelope:
|
|
126
|
+
described_class.call(envelope: failing_envelope, registry: registry)
|
|
124
127
|
end.to raise_error(RuntimeError, /provider unavailable/)
|
|
125
128
|
|
|
126
129
|
expect(Legion::Extensions::Llm::Fleet::TokenValidator).to have_received(:release_replay!).with('jti-2')
|
|
127
130
|
end
|
|
131
|
+
|
|
132
|
+
describe 'W5/W4 — params and wire-message contract errors' do
|
|
133
|
+
it 'raises ContractError when params contain :model' do
|
|
134
|
+
bad = envelope.merge(params: { messages: [{ role: 'user', content: 'x' }], model: 'other' })
|
|
135
|
+
expect { described_class.call(envelope: bad, registry: registry) }
|
|
136
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /must not contain model/)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'raises ContractError on a non-Hash wire message' do
|
|
140
|
+
bad = envelope.merge(params: { messages: ['not a message'] })
|
|
141
|
+
expect { described_class.call(envelope: bad, registry: registry) }
|
|
142
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /serialized Canonical::Message/)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'raises ContractError on a missing per-operation param' do
|
|
146
|
+
bad = envelope.merge(params: {})
|
|
147
|
+
expect { described_class.call(envelope: bad, registry: registry) }
|
|
148
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /requires the messages param/)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'raises ContractError on an unknown operation' do
|
|
152
|
+
bad = envelope.merge(operation: 'teleport')
|
|
153
|
+
expect { described_class.call(envelope: bad, registry: registry) }
|
|
154
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /unsupported exact operation/)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
describe 'W6 — fail-closed policy' do
|
|
159
|
+
it 'raises when require_policy is enabled and no policy engine exists' do
|
|
160
|
+
allow(Legion::Extensions::Llm::Fleet::Settings).to receive(:value)
|
|
161
|
+
.with(:fleet, :responder, :require_policy, default: nil).and_return(true)
|
|
162
|
+
|
|
163
|
+
expect { described_class.call(envelope: envelope, registry: registry) }
|
|
164
|
+
.to raise_error(described_class::PolicyError, /no policy engine is configured/)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
128
167
|
end
|
|
@@ -20,6 +20,7 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
20
20
|
routing_key: 'llm.fleet.inference.qwen.ctx8192',
|
|
21
21
|
request_id: 'req-1',
|
|
22
22
|
correlation_id: 'corr-1',
|
|
23
|
+
idempotency_key: 'idem-1',
|
|
23
24
|
operation: :chat,
|
|
24
25
|
provider: :vllm,
|
|
25
26
|
provider_instance: :apollo,
|
|
@@ -33,21 +34,64 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
33
34
|
timeout_seconds: 30,
|
|
34
35
|
expires_at: '2026-05-06T12:00:30Z',
|
|
35
36
|
protocol_version: protocol::VERSION,
|
|
36
|
-
|
|
37
|
+
execution_contract: protocol::EXACT_EXECUTION_CONTRACT,
|
|
38
|
+
offering_id: 'local:vllm:h200:inference:gemma4'
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid_response_options
|
|
43
|
+
{
|
|
44
|
+
protocol_version: protocol::VERSION,
|
|
45
|
+
request_id: 'req-1',
|
|
46
|
+
correlation_id: 'corr-1',
|
|
47
|
+
idempotency_key: 'idem-1',
|
|
48
|
+
operation: :chat,
|
|
49
|
+
provider: :vllm,
|
|
50
|
+
provider_instance: :apollo,
|
|
51
|
+
model: 'qwen',
|
|
52
|
+
reply_to: 'llm.fleet.reply.node',
|
|
53
|
+
message_context: { conversation_id: 'conv-1' },
|
|
54
|
+
trace_context: { trace_id: 'trace-1' },
|
|
55
|
+
response: { text: 'hello', stop_reason: :end_turn, usage: { input_tokens: 1, output_tokens: 2 } },
|
|
56
|
+
execution_contract: protocol::EXACT_EXECUTION_CONTRACT,
|
|
57
|
+
offering_id: 'local:vllm:h200:inference:gemma4'
|
|
37
58
|
}
|
|
38
59
|
end
|
|
39
60
|
|
|
40
61
|
describe Legion::Extensions::Llm::Fleet::Protocol do
|
|
41
|
-
it 'defines fleet protocol
|
|
42
|
-
expect(described_class::VERSION).to eq(
|
|
62
|
+
it 'defines fleet protocol v3 with the one required-field list (P1/E2)' do
|
|
63
|
+
expect(described_class::VERSION).to eq(3)
|
|
43
64
|
expect(described_class::REQUEST_TYPE).to eq('llm.fleet.request')
|
|
44
65
|
expect(described_class::RESPONSE_TYPE).to eq('llm.fleet.response')
|
|
45
66
|
expect(described_class::ERROR_TYPE).to eq('llm.fleet.error')
|
|
67
|
+
expect(described_class::REQUIRED_FIELDS).to include(
|
|
68
|
+
:request_id, :correlation_id, :idempotency_key, :operation, :provider, :provider_instance,
|
|
69
|
+
:model, :params, :reply_to, :message_context, :caller, :trace_context, :signed_token,
|
|
70
|
+
:timeout_seconds, :expires_at, :protocol_version, :execution_contract, :offering_id
|
|
71
|
+
)
|
|
72
|
+
expect(described_class::LEGACY_FIELDS).to eq(%i[schema_version request_type fleet_correlation_id])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe Legion::Extensions::Llm::Fleet::FleetEnvelope do
|
|
77
|
+
let(:envelope_class) { described_class }
|
|
78
|
+
|
|
79
|
+
it 'wraps a Hash payload and passes an existing envelope through' do
|
|
80
|
+
expect(envelope_class.wrap(a: 1).to_h).to eq(a: 1)
|
|
81
|
+
envelope = envelope_class.new(data: { b: 2 })
|
|
82
|
+
expect(envelope_class.wrap(envelope)).to be(envelope)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'L12: a non-Hash payload is a typed ContractError at the wrap boundary' do
|
|
86
|
+
expect { envelope_class.wrap('not a hash') }
|
|
87
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /expected Hash, got String/)
|
|
88
|
+
expect { envelope_class.wrap(42) }
|
|
89
|
+
.to raise_error(Legion::Extensions::Llm::Fleet::ContractError, /expected Hash, got Integer/)
|
|
46
90
|
end
|
|
47
91
|
end
|
|
48
92
|
|
|
49
93
|
describe Legion::Extensions::Llm::Transport::Messages::FleetRequest do
|
|
50
|
-
it 'builds a strict protocol
|
|
94
|
+
it 'builds a strict protocol v3 request envelope' do
|
|
51
95
|
message = described_class.new(**valid_request_options)
|
|
52
96
|
|
|
53
97
|
expect(message.type).to eq(protocol::REQUEST_TYPE)
|
|
@@ -55,7 +99,7 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
55
99
|
expect(message.reply_to).to eq('llm.fleet.reply.node')
|
|
56
100
|
expect(message.correlation_id).to eq('corr-1')
|
|
57
101
|
expect(message.message).to include(
|
|
58
|
-
protocol_version:
|
|
102
|
+
protocol_version: 3,
|
|
59
103
|
request_id: 'req-1',
|
|
60
104
|
correlation_id: 'corr-1',
|
|
61
105
|
operation: :chat,
|
|
@@ -70,16 +114,15 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
70
114
|
signed_token: 'signed.jwt',
|
|
71
115
|
timeout_seconds: 30,
|
|
72
116
|
expires_at: '2026-05-06T12:00:30Z',
|
|
73
|
-
idempotency_key: 'idem-1'
|
|
117
|
+
idempotency_key: 'idem-1',
|
|
118
|
+
execution_contract: protocol::EXACT_EXECUTION_CONTRACT,
|
|
119
|
+
offering_id: 'local:vllm:h200:inference:gemma4'
|
|
74
120
|
)
|
|
75
121
|
expect(message.message).not_to include(:schema_version, :request_type, :fleet_correlation_id)
|
|
76
122
|
end
|
|
77
123
|
|
|
78
|
-
it 'requires every protocol
|
|
79
|
-
required = %i[
|
|
80
|
-
request_id correlation_id operation provider provider_instance model params reply_to
|
|
81
|
-
message_context caller trace_context signed_token timeout_seconds expires_at protocol_version idempotency_key
|
|
82
|
-
]
|
|
124
|
+
it 'requires every protocol v3 request field (one Protocol list, E2)' do
|
|
125
|
+
required = protocol::REQUIRED_FIELDS + %i[routing_key]
|
|
83
126
|
|
|
84
127
|
required.each do |field|
|
|
85
128
|
expect do
|
|
@@ -88,10 +131,16 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
88
131
|
end
|
|
89
132
|
end
|
|
90
133
|
|
|
91
|
-
it 'rejects protocol versions other than
|
|
134
|
+
it 'rejects protocol versions other than v3' do
|
|
135
|
+
expect do
|
|
136
|
+
described_class.new(**valid_request_options, protocol_version: 2)
|
|
137
|
+
end.to raise_error(ArgumentError, /protocol_version must be 3/)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'rejects a missing protocol_version (P3 — no default fill)' do
|
|
92
141
|
expect do
|
|
93
|
-
described_class.new(**valid_request_options
|
|
94
|
-
end.to raise_error(ArgumentError, /protocol_version/)
|
|
142
|
+
described_class.new(**valid_request_options.except(:protocol_version))
|
|
143
|
+
end.to raise_error(ArgumentError, /protocol_version is required/)
|
|
95
144
|
end
|
|
96
145
|
|
|
97
146
|
it 'rejects legacy fleet envelope fields' do
|
|
@@ -141,9 +190,9 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
141
190
|
name: 'llm.fleet',
|
|
142
191
|
channel: channel
|
|
143
192
|
)
|
|
144
|
-
allow(channel).to receive(:confirm_select)
|
|
145
193
|
message = described_class.new(**valid_request_options)
|
|
146
194
|
|
|
195
|
+
allow(channel).to receive(:confirm_select)
|
|
147
196
|
allow(Legion::Extensions::Llm::Transport::Exchanges::Fleet).to receive(:cached_instance).and_return(exchange)
|
|
148
197
|
result = message.publish(publish_confirm_timeout_ms: 5000)
|
|
149
198
|
|
|
@@ -154,26 +203,14 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
154
203
|
end
|
|
155
204
|
|
|
156
205
|
describe Legion::Extensions::Llm::Transport::Messages::FleetResponse do
|
|
157
|
-
it 'builds a protocol
|
|
158
|
-
message = described_class.new(
|
|
159
|
-
request_id: 'req-1',
|
|
160
|
-
correlation_id: 'corr-1',
|
|
161
|
-
idempotency_key: 'idem-1',
|
|
162
|
-
operation: :chat,
|
|
163
|
-
reply_to: 'llm.fleet.reply.node',
|
|
164
|
-
provider: :vllm,
|
|
165
|
-
provider_instance: :apollo,
|
|
166
|
-
model: 'qwen',
|
|
167
|
-
content: 'hello',
|
|
168
|
-
message_context: { conversation_id: 'conv-1' },
|
|
169
|
-
trace_context: { trace_id: 'trace-1' }
|
|
170
|
-
)
|
|
206
|
+
it 'builds a protocol v3 correlated response envelope carrying the serialized Canonical::Response (E3)' do
|
|
207
|
+
message = described_class.new(**valid_response_options)
|
|
171
208
|
|
|
172
209
|
expect(message.type).to eq(protocol::RESPONSE_TYPE)
|
|
173
210
|
expect(message.routing_key).to eq('llm.fleet.reply.node')
|
|
174
211
|
expect(message.correlation_id).to eq('corr-1')
|
|
175
212
|
expect(message.message).to include(
|
|
176
|
-
protocol_version:
|
|
213
|
+
protocol_version: 3,
|
|
177
214
|
request_id: 'req-1',
|
|
178
215
|
correlation_id: 'corr-1',
|
|
179
216
|
idempotency_key: 'idem-1',
|
|
@@ -182,62 +219,48 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
182
219
|
provider: :vllm,
|
|
183
220
|
provider_instance: :apollo,
|
|
184
221
|
model: 'qwen',
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
response: { text: 'hello', stop_reason: :end_turn, usage: { input_tokens: 1, output_tokens: 2 } },
|
|
223
|
+
execution_contract: protocol::EXACT_EXECUTION_CONTRACT,
|
|
224
|
+
offering_id: 'local:vllm:h200:inference:gemma4'
|
|
188
225
|
)
|
|
189
|
-
expect(message.message).not_to include(:schema_version)
|
|
226
|
+
expect(message.message).not_to include(:schema_version, :content, :usage, :finish_reason, :tool_calls, :instance)
|
|
190
227
|
end
|
|
191
228
|
|
|
192
|
-
it '
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
legacy = described_class.new(
|
|
200
|
-
request_id: 'req-1', correlation_id: 'corr-1', reply_to: 'llm.fleet.reply.node', content: 'hi'
|
|
201
|
-
)
|
|
202
|
-
expect(legacy.message).not_to have_key(:execution_contract)
|
|
203
|
-
expect(legacy.message).not_to have_key(:offering_id)
|
|
229
|
+
it 'requires the response payload and a protocol version' do
|
|
230
|
+
expect do
|
|
231
|
+
described_class.new(**valid_response_options.except(:response))
|
|
232
|
+
end.to raise_error(ArgumentError, /response is required/)
|
|
233
|
+
expect do
|
|
234
|
+
described_class.new(**valid_response_options.except(:protocol_version))
|
|
235
|
+
end.to raise_error(ArgumentError, /protocol_version is required/)
|
|
204
236
|
end
|
|
205
237
|
|
|
206
|
-
it 'rejects response protocol versions other than
|
|
238
|
+
it 'rejects response protocol versions other than v3' do
|
|
207
239
|
expect do
|
|
208
|
-
described_class.new(
|
|
209
|
-
|
|
210
|
-
correlation_id: 'corr-1',
|
|
211
|
-
reply_to: 'llm.fleet.reply.node',
|
|
212
|
-
content: 'hello',
|
|
213
|
-
protocol_version: 1
|
|
214
|
-
)
|
|
215
|
-
end.to raise_error(ArgumentError, /protocol_version/)
|
|
240
|
+
described_class.new(**valid_response_options, protocol_version: 2)
|
|
241
|
+
end.to raise_error(ArgumentError, /protocol_version must be 3/)
|
|
216
242
|
end
|
|
217
243
|
|
|
218
|
-
it '
|
|
219
|
-
message = described_class.new(
|
|
220
|
-
request_id: 'req-1',
|
|
221
|
-
correlation_id: 'corr-1',
|
|
222
|
-
reply_to: 'llm.fleet.reply.node',
|
|
223
|
-
content: 'visible',
|
|
224
|
-
thinking: 'hidden'
|
|
225
|
-
)
|
|
244
|
+
it 'is a pass-through for the response payload — the G5 thinking exclusion happens exactly once at the responder builder' do
|
|
245
|
+
message = described_class.new(**valid_response_options, response: { text: 'visible', stop_reason: :end_turn })
|
|
226
246
|
|
|
227
|
-
expect(message.message).to
|
|
247
|
+
expect(message.message[:response]).to eq(text: 'visible', stop_reason: :end_turn)
|
|
228
248
|
expect(message.message).not_to have_key(:thinking)
|
|
229
|
-
expect(message.encode_message).not_to include('
|
|
249
|
+
expect(message.encode_message).not_to include('thinking')
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it 'rejects legacy fleet envelope fields' do
|
|
253
|
+
%i[schema_version request_type fleet_correlation_id].each do |field|
|
|
254
|
+
expect do
|
|
255
|
+
described_class.new(**valid_response_options, field => 'legacy')
|
|
256
|
+
end.to raise_error(ArgumentError, /#{field}/)
|
|
257
|
+
end
|
|
230
258
|
end
|
|
231
259
|
|
|
232
260
|
it 'publishes replies through the AMQP default exchange without confirms or mandatory routing' do
|
|
233
261
|
default_exchange = instance_double(Bunny::Exchange, publish: true)
|
|
234
262
|
channel = instance_double(Bunny::Channel, default_exchange: default_exchange)
|
|
235
|
-
message = described_class.new(
|
|
236
|
-
request_id: 'req-1',
|
|
237
|
-
correlation_id: 'corr-1',
|
|
238
|
-
reply_to: 'llm.fleet.reply.node',
|
|
239
|
-
content: 'hello'
|
|
240
|
-
)
|
|
263
|
+
message = described_class.new(**valid_response_options)
|
|
241
264
|
|
|
242
265
|
allow(message).to receive(:channel).and_return(channel)
|
|
243
266
|
message.publish
|
|
@@ -257,12 +280,7 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
257
280
|
channel = instance_double(channel_class, default_exchange: nil, on_return: nil)
|
|
258
281
|
default_exchange = instance_double(Bunny::Exchange, publish: true, name: 'default', channel: channel)
|
|
259
282
|
allow(channel).to receive(:default_exchange).and_return(default_exchange)
|
|
260
|
-
message = described_class.new(
|
|
261
|
-
request_id: 'req-1',
|
|
262
|
-
correlation_id: 'corr-1',
|
|
263
|
-
reply_to: 'llm.fleet.reply.node',
|
|
264
|
-
content: 'hello'
|
|
265
|
-
)
|
|
283
|
+
message = described_class.new(**valid_response_options)
|
|
266
284
|
|
|
267
285
|
allow(message).to receive(:channel).and_return(channel)
|
|
268
286
|
result = message.publish(return_result: true, headers: { 'x-custom' => 'yes' }, mandatory: true)
|
|
@@ -287,12 +305,7 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
287
305
|
it 'returns a transport-style failure result for transient reply publish errors' do
|
|
288
306
|
default_exchange = instance_double(Bunny::Exchange, name: 'default')
|
|
289
307
|
channel = instance_double(Bunny::Channel, default_exchange: default_exchange)
|
|
290
|
-
message = described_class.new(
|
|
291
|
-
request_id: 'req-1',
|
|
292
|
-
correlation_id: 'corr-1',
|
|
293
|
-
reply_to: 'llm.fleet.reply.node',
|
|
294
|
-
content: 'hello'
|
|
295
|
-
)
|
|
308
|
+
message = described_class.new(**valid_response_options)
|
|
296
309
|
|
|
297
310
|
allow(default_exchange).to receive(:publish).and_raise(IOError, 'socket closed')
|
|
298
311
|
allow(message).to receive(:channel).and_return(channel)
|
|
@@ -310,27 +323,20 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
310
323
|
end
|
|
311
324
|
|
|
312
325
|
describe Legion::Extensions::Llm::Transport::Messages::FleetError do
|
|
313
|
-
it 'builds a protocol
|
|
326
|
+
it 'builds a protocol v3 correlated error envelope (E6)' do
|
|
314
327
|
message = described_class.new(
|
|
315
|
-
request_id:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
operation: :chat,
|
|
319
|
-
reply_to: 'llm.fleet.reply.node',
|
|
320
|
-
provider: :vllm,
|
|
321
|
-
provider_instance: :apollo,
|
|
322
|
-
model: 'qwen',
|
|
328
|
+
**valid_response_options.slice(:protocol_version, :request_id, :correlation_id, :idempotency_key, :operation,
|
|
329
|
+
:provider, :provider_instance, :model, :reply_to, :message_context,
|
|
330
|
+
:trace_context, :execution_contract, :offering_id),
|
|
323
331
|
code: 'provider_failed',
|
|
324
332
|
message: 'provider failed',
|
|
325
|
-
retryable: true
|
|
326
|
-
message_context: { conversation_id: 'conv-1' },
|
|
327
|
-
trace_context: { trace_id: 'trace-1' }
|
|
333
|
+
retryable: true
|
|
328
334
|
)
|
|
329
335
|
|
|
330
336
|
expect(message.type).to eq(protocol::ERROR_TYPE)
|
|
331
337
|
expect(message.routing_key).to eq('llm.fleet.reply.node')
|
|
332
338
|
expect(message.message).to include(
|
|
333
|
-
protocol_version:
|
|
339
|
+
protocol_version: 3,
|
|
334
340
|
request_id: 'req-1',
|
|
335
341
|
correlation_id: 'corr-1',
|
|
336
342
|
idempotency_key: 'idem-1',
|
|
@@ -342,35 +348,28 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
342
348
|
code: 'provider_failed',
|
|
343
349
|
message: 'provider failed',
|
|
344
350
|
retryable: true,
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
execution_contract: protocol::EXACT_EXECUTION_CONTRACT,
|
|
352
|
+
offering_id: 'local:vllm:h200:inference:gemma4'
|
|
347
353
|
)
|
|
348
|
-
expect(message.message).not_to include(:schema_version)
|
|
354
|
+
expect(message.message).not_to include(:schema_version, :instance)
|
|
349
355
|
end
|
|
350
356
|
|
|
351
|
-
it 'rejects error protocol versions other than
|
|
357
|
+
it 'rejects error protocol versions other than v3 and missing versions' do
|
|
358
|
+
base = valid_response_options.slice(:request_id, :correlation_id, :reply_to)
|
|
352
359
|
expect do
|
|
353
|
-
described_class.new(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
message: 'provider failed',
|
|
359
|
-
protocol_version: 1
|
|
360
|
-
)
|
|
361
|
-
end.to raise_error(ArgumentError, /protocol_version/)
|
|
360
|
+
described_class.new(**base, protocol_version: 2, code: 'provider_failed', message: 'provider failed')
|
|
361
|
+
end.to raise_error(ArgumentError, /protocol_version must be 3/)
|
|
362
|
+
expect do
|
|
363
|
+
described_class.new(**base, code: 'provider_failed', message: 'provider failed')
|
|
364
|
+
end.to raise_error(ArgumentError, /protocol_version is required/)
|
|
362
365
|
end
|
|
363
366
|
|
|
364
367
|
it 'publishes errors through the AMQP default exchange without confirms or mandatory routing' do
|
|
365
368
|
default_exchange = instance_double(Bunny::Exchange, publish: true)
|
|
366
369
|
channel = instance_double(Bunny::Channel, default_exchange: default_exchange)
|
|
367
|
-
message = described_class.new(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
reply_to: 'llm.fleet.reply.node',
|
|
371
|
-
code: 'provider_failed',
|
|
372
|
-
message: 'provider failed'
|
|
373
|
-
)
|
|
370
|
+
message = described_class.new(**valid_response_options.slice(:protocol_version, :request_id, :correlation_id,
|
|
371
|
+
:reply_to),
|
|
372
|
+
code: 'provider_failed', message: 'provider failed')
|
|
374
373
|
|
|
375
374
|
allow(message).to receive(:channel).and_return(channel)
|
|
376
375
|
message.publish
|
|
@@ -390,13 +389,9 @@ RSpec.describe 'LLM fleet message envelopes' do
|
|
|
390
389
|
channel = instance_double(channel_class, default_exchange: nil, on_return: nil)
|
|
391
390
|
default_exchange = instance_double(Bunny::Exchange, publish: true, name: 'default', channel: channel)
|
|
392
391
|
allow(channel).to receive(:default_exchange).and_return(default_exchange)
|
|
393
|
-
message = described_class.new(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
reply_to: 'llm.fleet.reply.node',
|
|
397
|
-
code: 'provider_failed',
|
|
398
|
-
message: 'provider failed'
|
|
399
|
-
)
|
|
392
|
+
message = described_class.new(**valid_response_options.slice(:protocol_version, :request_id, :correlation_id,
|
|
393
|
+
:reply_to),
|
|
394
|
+
code: 'provider_failed', message: 'provider failed')
|
|
400
395
|
|
|
401
396
|
allow(message).to receive(:channel).and_return(channel)
|
|
402
397
|
result = message.publish(return_result: true, headers: { 'x-custom' => 'yes' }, mandatory: true)
|
|
@@ -47,7 +47,7 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Boot do
|
|
|
47
47
|
it 'exposes every require-order constant' do
|
|
48
48
|
%i[
|
|
49
49
|
Errors ImmutableValue Identity Evidence CallableHandle DispatchLease ProbeToken ProbeRequest
|
|
50
|
-
ProbeCoordinator PublisherToken OfferingDraft
|
|
50
|
+
ProbeCoordinator PublisherToken OfferingDraft LaneRecord AvailabilityFact
|
|
51
51
|
ReadinessResult InstanceRecord PublicationStatus MutationResult Snapshot Registry Publisher
|
|
52
52
|
].each { |const| expect(inventory.const_defined?(const)).to be(true), "missing Inventory::#{const}" }
|
|
53
53
|
%i[AttemptTargetKey QuotaDomainKey Exclusion Selection Rejection BodyModelHintDecision ProviderOutcome].each do |const|
|
|
@@ -69,9 +69,9 @@ RSpec.describe Legion::Extensions::Llm::Inventory::Boot do
|
|
|
69
69
|
expect(Thread.list.size).to eq(baseline)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
it '
|
|
73
|
-
expect(Legion::Extensions::Llm
|
|
74
|
-
expect(Legion::Extensions::Llm::
|
|
72
|
+
it 'removes the legacy Types aliases (0.8.0 rip)' do
|
|
73
|
+
expect(Legion::Extensions::Llm.const_defined?(:Types, false)).to be(false)
|
|
74
|
+
expect(Legion::Extensions::Llm::Inventory.const_defined?(:InstanceKey, false)).to be(false)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
it 'preserves the optional transport message registration (autoload or loaded)' do
|