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.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -0
  3. data/RULES.md +97 -0
  4. data/lib/legion/extensions/llm/auto_registration.rb +4 -13
  5. data/lib/legion/extensions/llm/canonical/chunk.rb +106 -92
  6. data/lib/legion/extensions/llm/canonical/content_block.rb +66 -75
  7. data/lib/legion/extensions/llm/canonical/message.rb +52 -67
  8. data/lib/legion/extensions/llm/canonical/params.rb +59 -33
  9. data/lib/legion/extensions/llm/canonical/request.rb +67 -54
  10. data/lib/legion/extensions/llm/canonical/response.rb +61 -76
  11. data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
  12. data/lib/legion/extensions/llm/canonical/thinking.rb +99 -51
  13. data/lib/legion/extensions/llm/canonical/tool_call.rb +42 -51
  14. data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
  15. data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
  16. data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
  17. data/lib/legion/extensions/llm/canonical.rb +6 -5
  18. data/lib/legion/extensions/llm/configuration.rb +40 -10
  19. data/lib/legion/extensions/llm/connection.rb +8 -30
  20. data/lib/legion/extensions/llm/credential_sources.rb +32 -49
  21. data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
  22. data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
  23. data/lib/legion/extensions/llm/error.rb +0 -14
  24. data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
  25. data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
  26. data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
  27. data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
  28. data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -153
  29. data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
  30. data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -150
  31. data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
  32. data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
  33. data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
  34. data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
  35. data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
  36. data/lib/legion/extensions/llm/inventory/records.rb +82 -99
  37. data/lib/legion/extensions/llm/inventory/registry.rb +54 -46
  38. data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
  39. data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +249 -0
  40. data/lib/legion/extensions/llm/inventory/weight_schema.rb +146 -0
  41. data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +172 -182
  42. data/lib/legion/extensions/llm/provider.rb +192 -315
  43. data/lib/legion/extensions/llm/provider_contract.rb +25 -8
  44. data/lib/legion/extensions/llm/provider_settings.rb +5 -26
  45. data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
  46. data/lib/legion/extensions/llm/responses/tool_arguments.rb +34 -0
  47. data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
  48. data/lib/legion/extensions/llm/routing/records.rb +34 -17
  49. data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
  50. data/lib/legion/extensions/llm/streaming.rb +50 -35
  51. data/lib/legion/extensions/llm/taxonomies.rb +29 -17
  52. data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
  53. data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
  54. data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
  55. data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
  56. data/lib/legion/extensions/llm/utils.rb +23 -5
  57. data/lib/legion/extensions/llm/version.rb +1 -1
  58. data/lib/legion/extensions/llm.rb +10 -98
  59. data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
  60. data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
  61. data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
  62. data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
  63. data/spec/legion/extensions/llm/canonical/params_spec.rb +57 -136
  64. data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
  65. data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
  66. data/spec/legion/extensions/llm/canonical/thinking_spec.rb +68 -148
  67. data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
  68. data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
  69. data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
  70. data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
  71. data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
  72. data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
  73. data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
  74. data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
  75. data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +130 -0
  76. data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +507 -0
  77. data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
  78. data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
  79. data/spec/legion/extensions/llm/error_spec.rb +2 -12
  80. data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
  81. data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +178 -67
  82. data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
  83. data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +101 -62
  84. data/spec/legion/extensions/llm/fleet_messages_spec.rb +118 -123
  85. data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
  86. data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
  87. data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
  88. data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
  89. data/spec/legion/extensions/llm/inventory/records_spec.rb +129 -41
  90. data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +55 -4
  91. data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
  92. data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
  93. data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +312 -0
  94. data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +141 -0
  95. data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +105 -68
  96. data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
  97. data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
  98. data/spec/legion/extensions/llm/provider_spec.rb +98 -78
  99. data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
  100. data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
  101. data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
  102. data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -16
  103. data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
  104. data/spec/legion/extensions/llm/utils_spec.rb +26 -7
  105. data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
  106. data/spec/legion/extensions/llm_extension_spec.rb +5 -5
  107. data/spec/support/fake_llm_provider.rb +45 -39
  108. data/spec/support/fake_ssot_harness.rb +7 -2
  109. data/spec/support/ssot_registry_helpers.rb +3 -2
  110. metadata +15 -54
  111. data/lib/legion/extensions/llm/agent.rb +0 -366
  112. data/lib/legion/extensions/llm/aliases.json +0 -436
  113. data/lib/legion/extensions/llm/aliases.rb +0 -67
  114. data/lib/legion/extensions/llm/attachment.rb +0 -229
  115. data/lib/legion/extensions/llm/chat.rb +0 -354
  116. data/lib/legion/extensions/llm/chunk.rb +0 -10
  117. data/lib/legion/extensions/llm/content.rb +0 -81
  118. data/lib/legion/extensions/llm/context.rb +0 -33
  119. data/lib/legion/extensions/llm/embedding.rb +0 -33
  120. data/lib/legion/extensions/llm/image.rb +0 -109
  121. data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
  122. data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -311
  123. data/lib/legion/extensions/llm/message.rb +0 -118
  124. data/lib/legion/extensions/llm/mime_type.rb +0 -75
  125. data/lib/legion/extensions/llm/model/info.rb +0 -286
  126. data/lib/legion/extensions/llm/model/modalities.rb +0 -26
  127. data/lib/legion/extensions/llm/model/pricing.rb +0 -52
  128. data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
  129. data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
  130. data/lib/legion/extensions/llm/model.rb +0 -11
  131. data/lib/legion/extensions/llm/models.json +0 -57313
  132. data/lib/legion/extensions/llm/models.rb +0 -530
  133. data/lib/legion/extensions/llm/models_schema.json +0 -168
  134. data/lib/legion/extensions/llm/moderation.rb +0 -60
  135. data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
  136. data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
  137. data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
  138. data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
  139. data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
  140. data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
  141. data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
  142. data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
  143. data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
  144. data/lib/legion/extensions/llm/thinking.rb +0 -53
  145. data/lib/legion/extensions/llm/tokens.rb +0 -51
  146. data/lib/legion/extensions/llm/tool_call.rb +0 -34
  147. data/lib/legion/extensions/llm/transcription.rb +0 -39
  148. data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
  149. data/spec/legion/extensions/llm/agent_spec.rb +0 -179
  150. data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
  151. data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
  152. data/spec/legion/extensions/llm/context_spec.rb +0 -127
  153. data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
  154. data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -337
  155. data/spec/legion/extensions/llm/message_spec.rb +0 -64
  156. data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
  157. data/spec/legion/extensions/llm/models_spec.rb +0 -104
  158. data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
  159. data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
  160. data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
  161. data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
  162. data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
  163. data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
@@ -5,106 +5,71 @@ require 'spec_helper'
5
5
  RSpec.describe Legion::Extensions::Llm do
6
6
  include_context 'with fake llm provider'
7
7
 
8
- before do
9
- stub_const(
10
- 'SpecSupport::EchoTool',
11
- Class.new(Legion::Extensions::Llm::Tool) do
12
- description 'Echo a numeric value'
13
- param :value, type: :integer, desc: 'Value to echo'
14
-
15
- def execute(value:)
16
- "echo #{value}"
17
- end
18
- end
19
- )
20
- end
21
-
22
- it 'loads and discovers provider classes from the namespace' do
23
- provider_classes = Legion::Extensions::Llm::Models.scan_provider_classes
24
- expect(provider_classes).to include(fake_llm: SpecSupport::FakeLLMProvider)
25
- expect(Legion::Extensions::Llm::Routing::ModelOffering).to be_a(Class)
26
- end
27
-
28
- it 'lets a provider gem register options and satisfy chat through the shared API' do
29
- chat = described_class.chat(model: 'fake-chat-model', provider: :fake_llm, assume_model_exists: true)
30
- response = chat.ask('hello')
31
-
32
- expect(response).to have_attributes(
33
- role: :assistant,
34
- content: 'fake response to hello',
35
- model_id: 'fake-chat-model',
36
- input_tokens: 10,
37
- output_tokens: 5
38
- )
39
- end
40
-
41
- it 'runs shared tool orchestration without provider-specific payload code' do
42
- response = described_class.chat(model: 'fake-chat-model', provider: :fake_llm, assume_model_exists: true)
43
- .with_tool(SpecSupport::EchoTool)
44
- .ask('use the tool')
45
-
46
- expect(response.content).to eq('tool result: echo 21')
8
+ it 'exposes the AutoRegistration discovery contract from the provider namespace' do
9
+ # Registration into Legion::LLM is owned by legion-llm (R12); lex-llm owns
10
+ # the namespace contract the scanner reads: PROVIDER_FAMILY (Symbol) plus
11
+ # a provider_class singleton returning the Provider subclass.
12
+ expect(Legion::Extensions::Llm::FakeLlmProvider.const_get(:PROVIDER_FAMILY)).to eq(:fake_llm)
13
+ expect(Legion::Extensions::Llm::FakeLlmProvider.provider_class).to be < Legion::Extensions::Llm::Provider
47
14
  end
48
15
 
49
- it 'normalizes schema responses through the base chat layer' do
50
- schema = {
51
- name: 'answer',
52
- schema: {
53
- type: 'object',
54
- properties: { answer: { type: 'integer' } },
55
- required: ['answer']
56
- }
57
- }
58
-
59
- response = described_class.chat(model: 'fake-chat-model', provider: :fake_llm, assume_model_exists: true)
60
- .with_schema(schema)
61
- .ask('structured')
62
-
63
- expect(response.content).to eq({ 'answer' => 42 })
64
- end
16
+ describe 'provider funnel contract (0.8.0)' do
17
+ let(:provider) do
18
+ SpecSupport::FakeLLMProvider.new(
19
+ Legion::Extensions::Llm::Configuration.new.tap do |config|
20
+ config.fake_llm_api_key = 'fake-key'
21
+ end
22
+ )
23
+ end
65
24
 
66
- it 'delegates embedding, moderation, image, and transcription calls through registered providers' do
67
- expect(described_class.embed('hello', model: 'fake-embed', provider: :fake_llm, assume_model_exists: true).vectors)
68
- .to eq([0.5, 0.5, 0.5])
25
+ let(:user_message) do
26
+ Legion::Extensions::Llm::Canonical::Message.build(role: :user, content: 'hello')
27
+ end
69
28
 
70
- expect(described_class.moderate('safe', model: 'fake-moderation', provider: :fake_llm, assume_model_exists: true))
71
- .not_to be_flagged
29
+ it 'completes with canonical input and returns a Canonical::Response' do
30
+ response = provider.complete([user_message], model: 'fake-chat-model')
31
+ expect(response).to be_a(Legion::Extensions::Llm::Canonical::Response)
32
+ expect(response.text).to eq('fake response to hello')
33
+ expect(response.usage).to be_a(Legion::Extensions::Llm::Canonical::Usage)
34
+ end
72
35
 
73
- expect(described_class.paint('draw', model: 'fake-image', provider: :fake_llm, assume_model_exists: true).to_blob)
74
- .to eq('fake-image')
36
+ it 'streams canonical chunks ending in exactly one done chunk (05 O5)' do
37
+ chunks = []
38
+ provider.complete([user_message], model: 'fake-chat-model') { |chunk| chunks << chunk }
75
39
 
76
- expect(described_class.transcribe('audio.wav', model: 'fake-audio', provider: :fake_llm,
77
- assume_model_exists: true).text)
78
- .to eq('fake transcript')
79
- end
80
-
81
- it 'connects model offerings to Legion fleet queue construction end to end' do
82
- offering = Legion::Extensions::Llm::Routing::ModelOffering.new(
83
- provider_family: :fake_llm,
84
- instance_id: :worker_one,
85
- transport: :rabbitmq,
86
- model: 'fake-chat-model',
87
- limits: { context_window: 16_384 }
88
- )
89
- exchange_class = Class.new
90
- base_queue = Class.new do
91
- attr_reader :bindings
40
+ expect(chunks).to all(be_a(Legion::Extensions::Llm::Canonical::Chunk))
41
+ expect(chunks.count(&:done?)).to eq(1)
42
+ expect(chunks.last).to be_done
43
+ expect(chunks.filter_map(&:delta).join).to eq('streamed response')
44
+ end
92
45
 
93
- def initialize
94
- @bindings = []
95
- end
46
+ it 'rejects non-canonical messages at the boundary with a typed ArgumentError (08 F2)' do
47
+ expect { provider.complete([{ role: 'user', content: 'hash' }], model: 'fake-chat-model') }
48
+ .to raise_error(ArgumentError, /Canonical::Message/)
49
+ expect { provider.complete([nil], model: 'fake-chat-model') }
50
+ .to raise_error(ArgumentError, /Canonical::Message/)
51
+ expect { provider.complete(['string'], model: 'fake-chat-model') }
52
+ .to raise_error(ArgumentError, /Canonical::Message/)
53
+ end
96
54
 
97
- def bind(exchange, routing_key:)
98
- @bindings << [exchange, routing_key]
99
- end
55
+ it 'keeps the canonical conversation operations on the base' do
56
+ expect(provider).to respond_to(:chat)
57
+ expect(provider).to respond_to(:stream_chat)
58
+ expect(provider).to respond_to(:count_tokens)
59
+ expect(provider).to respond_to(:discover_offerings)
60
+ expect(provider).to respond_to(:health)
61
+ expect(provider).to respond_to(:embed)
62
+ expect(provider).to respond_to(:moderate)
63
+ expect(provider).to respond_to(:image)
64
+ expect(provider).to respond_to(:transcribe)
100
65
  end
101
66
 
102
- queue_class = Legion::Extensions::Llm::Transport::FleetLane.build_queue_class(
103
- queue_name: offering.lane_key,
104
- exchange_class: exchange_class,
105
- base_queue_class: base_queue
106
- )
67
+ it 'serves discover_offerings from the registry snapshot (07 C5)' do
68
+ expect(provider.discover_offerings).to eq([])
69
+ end
107
70
 
108
- expect(queue_class.new.queue_name).to eq('llm.fleet.inference.fake-chat-model.ctx16384')
71
+ it 'counts tokens for canonical messages' do
72
+ expect(provider.count_tokens(messages: [user_message], model: 'fake-chat-model')).to be_a(Integer)
73
+ end
109
74
  end
110
75
  end
@@ -3,11 +3,11 @@
3
3
  require 'legion/extensions/llm'
4
4
 
5
5
  RSpec.describe Legion::Extensions::Llm do
6
- it 'exposes the Legion-native extension namespace for autoloading' do
7
- expect(described_class::Types::ModelOffering).to equal(Legion::Extensions::Llm::Routing::ModelOffering)
8
- expect(described_class::Types::OfferingRegistry).to equal(Legion::Extensions::Llm::Routing::OfferingRegistry)
9
- expect(described_class::Routing::LaneKey).to equal(Legion::Extensions::Llm::Routing::LaneKey)
10
- expect(described_class::Routing::OfferingRegistry).to equal(Legion::Extensions::Llm::Routing::OfferingRegistry)
6
+ it 'exposes no legacy dual-constant paths after the 0.8.0 rip' do
7
+ expect(described_class.const_defined?(:Types, false)).to be(false)
8
+ expect(Legion::Extensions::Llm::Routing.const_defined?(:ModelOffering, false)).to be(false)
9
+ expect(Legion::Extensions::Llm::Routing.const_defined?(:OfferingRegistry, false)).to be(false)
10
+ expect(Legion::Extensions::Llm::Routing.const_defined?(:LaneKey, false)).to be(false)
11
11
  end
12
12
 
13
13
  it 'provides complete default fleet settings' do
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecSupport
4
+ # Contract-conformant fake provider (0.8.0): canonical in, canonical out.
5
+ # No HTTP — complete is overridden to return canned Canonical::Response
6
+ # objects (and yield Canonical::Chunk for streams).
4
7
  class FakeLLMProvider < Legion::Extensions::Llm::Provider
5
8
  class << self
6
9
  def configuration_options
@@ -29,26 +32,28 @@ module SpecSupport
29
32
  config.fake_llm_api_base || 'https://fake-llm.invalid/v1'
30
33
  end
31
34
 
32
- # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
33
- def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil,
34
- tool_prefs: nil)
35
+ def complete(messages, tools: [], model:, params: nil, headers: {}, schema: nil, thinking: nil, # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument, Style/KeywordParametersOrder
36
+ tool_prefs: nil, &) # rubocop:disable Lint/UnusedMethodArgument
37
+ enforce_canonical_messages!(messages)
38
+
35
39
  if block_given?
36
- yield Legion::Extensions::Llm::Chunk.new(content: 'streamed ', role: :assistant)
37
- yield Legion::Extensions::Llm::Chunk.new(content: 'response', role: :assistant)
40
+ yield Legion::Extensions::Llm::Canonical::Chunk.text_delta(delta: 'streamed ', request_id: nil)
41
+ yield Legion::Extensions::Llm::Canonical::Chunk.text_delta(delta: 'response', request_id: nil)
42
+ yield Legion::Extensions::Llm::Canonical::Chunk.done(request_id: nil, stop_reason: :end_turn)
38
43
  end
39
44
 
40
- if tools.any? && messages.none?(&:tool_result?)
45
+ has_tool_result = messages.any?(&:tool_call_id)
46
+ if tools.any? && !has_tool_result
41
47
  tool = tools.values.first
42
- return Legion::Extensions::Llm::Message.new(
43
- role: :assistant,
44
- content: nil,
45
- model_id: model.id,
46
- tool_calls: {
47
- tool.name.to_sym => Legion::Extensions::Llm::ToolCall.new(id: 'tool-call-1', name: tool.name,
48
- arguments: { value: 21 })
49
- },
50
- input_tokens: 12,
51
- output_tokens: 3
48
+ return Legion::Extensions::Llm::Canonical::Response.build(
49
+ text: nil,
50
+ tool_calls: [
51
+ Legion::Extensions::Llm::Canonical::ToolCall.build(name: tool.name, id: 'tool-call-1',
52
+ arguments: { value: 21 })
53
+ ],
54
+ stop_reason: :tool_use,
55
+ model: model,
56
+ usage: Legion::Extensions::Llm::Canonical::Usage.build(input_tokens: 12, output_tokens: 3)
52
57
  )
53
58
  end
54
59
 
@@ -56,45 +61,46 @@ module SpecSupport
56
61
  Legion::JSON.generate({ answer: 42 })
57
62
  elsif thinking
58
63
  'fake response with thinking enabled'
59
- elsif messages.any?(&:tool_result?)
60
- "tool result: #{messages.last.content}"
64
+ elsif has_tool_result
65
+ "tool result: #{messages.last.text}"
61
66
  else
62
- "fake response to #{messages.last.content}"
67
+ "fake response to #{messages.last.text}"
63
68
  end
64
69
 
65
- Legion::Extensions::Llm::Message.new(
66
- role: :assistant,
67
- content: content,
68
- model_id: model.id,
69
- input_tokens: 10,
70
- output_tokens: 5
70
+ Legion::Extensions::Llm::Canonical::Response.build(
71
+ text: content,
72
+ model: model,
73
+ stop_reason: :end_turn,
74
+ usage: Legion::Extensions::Llm::Canonical::Usage.build(input_tokens: 10, output_tokens: 5)
71
75
  )
72
76
  end
73
- # rubocop:enable Metrics/ParameterLists, Lint/UnusedMethodArgument
74
77
 
75
- def embed(text:, model:, dimensions: nil)
78
+ def embed(text:, model:, dimensions: nil, params: nil, headers: {})
79
+ _ = [params, headers]
76
80
  size = dimensions || 3
77
81
  vectors = Array(text).map { Array.new(size, 0.5) }
78
82
  vectors = vectors.first unless text.is_a?(Array)
79
83
 
80
- Legion::Extensions::Llm::Embedding.new(vectors: vectors, model: model, input_tokens: Array(text).size)
84
+ {
85
+ text: text,
86
+ model: model,
87
+ embedding: vectors,
88
+ usage: Legion::Extensions::Llm::Canonical::Usage.build(input_tokens: Array(text).size)
89
+ }
81
90
  end
82
91
 
83
- def moderate(_input, model:)
84
- Legion::Extensions::Llm::Moderation.new(
85
- id: 'moderation-1',
86
- model: model,
87
- results: [{ 'flagged' => false, 'categories' => {}, 'category_scores' => {} }]
88
- )
92
+ def moderate(input:, model:)
93
+ _ = input
94
+ { model: model, result: { flagged: false, categories: {} } }
89
95
  end
90
96
 
91
- def paint(_prompt, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
92
- Legion::Extensions::Llm::Image.new(data: Base64.strict_encode64('fake-image'), mime_type: 'image/png',
93
- model_id: model)
97
+ def image(prompt:, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Metrics/ParameterLists
98
+ _ = [prompt, with, mask, params]
99
+ { model: model, image: Base64.strict_encode64('fake-image'), size: }
94
100
  end
95
101
 
96
102
  def transcribe(_audio_file, model:, language:, **)
97
- Legion::Extensions::Llm::Transcription.new(text: 'fake transcript', model: model, language: language)
103
+ { model: model, text: 'fake transcript', language: }
98
104
  end
99
105
 
100
106
  # Records exactly-once disconnect for the callable-lifecycle contract; does
@@ -123,7 +129,7 @@ module SpecSupport
123
129
  end
124
130
 
125
131
  # Namespace module that mimics a real lex-llm-* provider extension.
126
- # Models.scan_provider_classes discovers providers via this pattern.
132
+ # legion-llm's Call::Providers scanner discovers providers via this pattern.
127
133
  module Legion
128
134
  module Extensions
129
135
  module Llm
@@ -28,9 +28,14 @@ module SpecSupport
28
28
  true
29
29
  end
30
30
 
31
- def chat(messages:, model:, **rest)
31
+ # L11: the fake mimics the 0.8.0 callable contract — chat returns a
32
+ # Canonical::Response (the raw-Hash return violated the contract the
33
+ # harness impersonates; conformance B2 asserts exactly this shape).
34
+ def chat(messages, model:, **rest)
32
35
  @inference_calls += 1
33
- { content: "fake chat #{model} #{messages.size} #{rest.size}" }
36
+ Legion::Extensions::Llm::Canonical::Response.build(
37
+ text: "fake chat #{model} #{messages.size} #{rest.size}", model: model, stop_reason: :end_turn
38
+ )
34
39
  end
35
40
 
36
41
  # Runtime error normalization on the provider callable itself, mirroring the
@@ -50,14 +50,15 @@ module SsotRegistryHelpers
50
50
  end
51
51
  end
52
52
 
53
- def drafts(model: 'gemma4', native: 'gemma4', supported: %i[chat], tier: :local)
53
+ def drafts(model: 'gemma4', native: 'gemma4', supported: %i[chat], tier: :local, **weight_pair)
54
54
  [
55
55
  Legion::Extensions::Llm::Inventory::OfferingDraft.new(
56
56
  provider_native_key: native, model: model, tier: tier,
57
57
  operation_evidence: operation_evidence(supported: supported),
58
58
  context_evidence: unknown_value, max_output_evidence: unknown_value,
59
59
  embedding_dimensions_evidence: unknown_value, model_revision_evidence: unknown_value,
60
- tokenizer_evidence: unknown_value, publication_source: :provider_catalog
60
+ tokenizer_evidence: unknown_value, publication_source: :provider_catalog,
61
+ **weight_pair
61
62
  )
62
63
  ]
63
64
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -253,13 +253,10 @@ files:
253
253
  - Gemfile
254
254
  - LICENSE
255
255
  - README.md
256
+ - RULES.md
256
257
  - lex-llm.gemspec
257
258
  - lib/legion/extensions/llm.rb
258
259
  - lib/legion/extensions/llm/actors/metering_flush.rb
259
- - lib/legion/extensions/llm/agent.rb
260
- - lib/legion/extensions/llm/aliases.json
261
- - lib/legion/extensions/llm/aliases.rb
262
- - lib/legion/extensions/llm/attachment.rb
263
260
  - lib/legion/extensions/llm/auto_registration.rb
264
261
  - lib/legion/extensions/llm/canonical.rb
265
262
  - lib/legion/extensions/llm/canonical/chunk.rb
@@ -268,6 +265,7 @@ files:
268
265
  - lib/legion/extensions/llm/canonical/params.rb
269
266
  - lib/legion/extensions/llm/canonical/request.rb
270
267
  - lib/legion/extensions/llm/canonical/response.rb
268
+ - lib/legion/extensions/llm/canonical/strict.rb
271
269
  - lib/legion/extensions/llm/canonical/thinking.rb
272
270
  - lib/legion/extensions/llm/canonical/tool_call.rb
273
271
  - lib/legion/extensions/llm/canonical/tool_definition.rb
@@ -275,18 +273,17 @@ files:
275
273
  - lib/legion/extensions/llm/canonical/usage.rb
276
274
  - lib/legion/extensions/llm/capabilities.rb
277
275
  - lib/legion/extensions/llm/capability_policy.rb
278
- - lib/legion/extensions/llm/chat.rb
279
- - lib/legion/extensions/llm/chunk.rb
280
276
  - lib/legion/extensions/llm/configuration.rb
281
277
  - lib/legion/extensions/llm/connection.rb
282
- - lib/legion/extensions/llm/content.rb
283
- - lib/legion/extensions/llm/context.rb
284
278
  - lib/legion/extensions/llm/credential_sources.rb
285
- - lib/legion/extensions/llm/embedding.rb
279
+ - lib/legion/extensions/llm/discovery/actor.rb
280
+ - lib/legion/extensions/llm/discovery/pipeline.rb
286
281
  - lib/legion/extensions/llm/error.rb
287
282
  - lib/legion/extensions/llm/errors/unsupported_capability.rb
283
+ - lib/legion/extensions/llm/fleet/contract_error.rb
288
284
  - lib/legion/extensions/llm/fleet/default_exchange_reply.rb
289
285
  - lib/legion/extensions/llm/fleet/envelope_validation.rb
286
+ - lib/legion/extensions/llm/fleet/fleet_envelope.rb
290
287
  - lib/legion/extensions/llm/fleet/protocol.rb
291
288
  - lib/legion/extensions/llm/fleet/provider_responder.rb
292
289
  - lib/legion/extensions/llm/fleet/publish_safety.rb
@@ -294,9 +291,7 @@ files:
294
291
  - lib/legion/extensions/llm/fleet/token_error.rb
295
292
  - lib/legion/extensions/llm/fleet/token_validator.rb
296
293
  - lib/legion/extensions/llm/fleet/worker_execution.rb
297
- - lib/legion/extensions/llm/image.rb
298
294
  - lib/legion/extensions/llm/inventory/callable_handle.rb
299
- - lib/legion/extensions/llm/inventory/capabilities.rb
300
295
  - lib/legion/extensions/llm/inventory/errors.rb
301
296
  - lib/legion/extensions/llm/inventory/evidence.rb
302
297
  - lib/legion/extensions/llm/inventory/identity.rb
@@ -306,54 +301,30 @@ files:
306
301
  - lib/legion/extensions/llm/inventory/publisher.rb
307
302
  - lib/legion/extensions/llm/inventory/records.rb
308
303
  - lib/legion/extensions/llm/inventory/registry.rb
309
- - lib/legion/extensions/llm/inventory/scoped_refresher.rb
310
304
  - lib/legion/extensions/llm/inventory/snapshot.rb
311
- - lib/legion/extensions/llm/message.rb
312
- - lib/legion/extensions/llm/mime_type.rb
313
- - lib/legion/extensions/llm/model.rb
314
- - lib/legion/extensions/llm/model/info.rb
315
- - lib/legion/extensions/llm/model/modalities.rb
316
- - lib/legion/extensions/llm/model/pricing.rb
317
- - lib/legion/extensions/llm/model/pricing_category.rb
318
- - lib/legion/extensions/llm/model/pricing_tier.rb
319
- - lib/legion/extensions/llm/models.json
320
- - lib/legion/extensions/llm/models.rb
321
- - lib/legion/extensions/llm/models_schema.json
322
- - lib/legion/extensions/llm/moderation.rb
305
+ - lib/legion/extensions/llm/inventory/weight_reconciler.rb
306
+ - lib/legion/extensions/llm/inventory/weight_schema.rb
323
307
  - lib/legion/extensions/llm/provider.rb
324
308
  - lib/legion/extensions/llm/provider/open_ai_compatible.rb
325
309
  - lib/legion/extensions/llm/provider_contract.rb
326
310
  - lib/legion/extensions/llm/provider_settings.rb
327
- - lib/legion/extensions/llm/registry_event_builder.rb
328
- - lib/legion/extensions/llm/registry_publisher.rb
329
- - lib/legion/extensions/llm/responses/chat_response.rb
330
- - lib/legion/extensions/llm/responses/embedding_response.rb
331
- - lib/legion/extensions/llm/responses/stream_chunk.rb
332
311
  - lib/legion/extensions/llm/responses/thinking_extractor.rb
312
+ - lib/legion/extensions/llm/responses/tool_arguments.rb
333
313
  - lib/legion/extensions/llm/routing.rb
334
- - lib/legion/extensions/llm/routing/lane_key.rb
335
- - lib/legion/extensions/llm/routing/model_offering.rb
336
- - lib/legion/extensions/llm/routing/offering_registry.rb
337
314
  - lib/legion/extensions/llm/routing/provider_outcome.rb
338
315
  - lib/legion/extensions/llm/routing/records.rb
339
- - lib/legion/extensions/llm/routing/registry_event.rb
340
316
  - lib/legion/extensions/llm/settings_cascade.rb
341
317
  - lib/legion/extensions/llm/stop_reason_mapping.rb
342
318
  - lib/legion/extensions/llm/stream_accumulator.rb
343
319
  - lib/legion/extensions/llm/streaming.rb
344
320
  - lib/legion/extensions/llm/taxonomies.rb
345
- - lib/legion/extensions/llm/thinking.rb
346
- - lib/legion/extensions/llm/tokens.rb
347
321
  - lib/legion/extensions/llm/tool.rb
348
- - lib/legion/extensions/llm/tool_call.rb
349
- - lib/legion/extensions/llm/transcription.rb
350
322
  - lib/legion/extensions/llm/transport/exchanges/fleet.rb
351
323
  - lib/legion/extensions/llm/transport/exchanges/llm_registry.rb
352
324
  - lib/legion/extensions/llm/transport/fleet_lane.rb
353
325
  - lib/legion/extensions/llm/transport/messages/fleet_error.rb
354
326
  - lib/legion/extensions/llm/transport/messages/fleet_request.rb
355
327
  - lib/legion/extensions/llm/transport/messages/fleet_response.rb
356
- - lib/legion/extensions/llm/transport/messages/registry_event.rb
357
328
  - lib/legion/extensions/llm/utils.rb
358
329
  - lib/legion/extensions/llm/version.rb
359
330
  - spec/fixtures/ruby.mp3
@@ -364,8 +335,6 @@ files:
364
335
  - spec/fixtures/ruby.xml
365
336
  - spec/fixtures/sample.pdf
366
337
  - spec/legion/extensions/llm/actor/metering_flush_spec.rb
367
- - spec/legion/extensions/llm/agent_spec.rb
368
- - spec/legion/extensions/llm/attachment_spec.rb
369
338
  - spec/legion/extensions/llm/auto_registration_spec.rb
370
339
  - spec/legion/extensions/llm/canonical/chunk_spec.rb
371
340
  - spec/legion/extensions/llm/canonical/content_block_spec.rb
@@ -381,6 +350,7 @@ files:
381
350
  - spec/legion/extensions/llm/capabilities_spec.rb
382
351
  - spec/legion/extensions/llm/capability_policy_spec.rb
383
352
  - spec/legion/extensions/llm/configuration_spec.rb
353
+ - spec/legion/extensions/llm/conformance/canonical_type_examples.rb
384
354
  - spec/legion/extensions/llm/conformance/client_translator_examples.rb
385
355
  - spec/legion/extensions/llm/conformance/conformance.rb
386
356
  - spec/legion/extensions/llm/conformance/echo_translator.rb
@@ -407,15 +377,15 @@ files:
407
377
  - spec/legion/extensions/llm/conformance/fixtures/canonical_tool_results_continuation_request.json
408
378
  - spec/legion/extensions/llm/conformance/fixtures/canonical_tool_use_response.json
409
379
  - spec/legion/extensions/llm/conformance/fixtures/canonical_tools_request.json
410
- - spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json
411
380
  - spec/legion/extensions/llm/conformance/provider_tool_rendering_examples.rb
412
381
  - spec/legion/extensions/llm/conformance/provider_translator_examples.rb
382
+ - spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb
383
+ - spec/legion/extensions/llm/conformance/ssot_contract_examples.rb
413
384
  - spec/legion/extensions/llm/conformance/ssot_provider_conformance_spec.rb
414
385
  - spec/legion/extensions/llm/conformance/ssot_provider_examples.rb
415
386
  - spec/legion/extensions/llm/connection_close_spec.rb
416
387
  - spec/legion/extensions/llm/connection_logging_spec.rb
417
388
  - spec/legion/extensions/llm/connection_retry_spec.rb
418
- - spec/legion/extensions/llm/context_spec.rb
419
389
  - spec/legion/extensions/llm/credential_sources_spec.rb
420
390
  - spec/legion/extensions/llm/error_middleware_spec.rb
421
391
  - spec/legion/extensions/llm/error_spec.rb
@@ -427,7 +397,6 @@ files:
427
397
  - spec/legion/extensions/llm/gemspec_spec.rb
428
398
  - spec/legion/extensions/llm/inventory/boot_spec.rb
429
399
  - spec/legion/extensions/llm/inventory/callable_handle_spec.rb
430
- - spec/legion/extensions/llm/inventory/capabilities_spec.rb
431
400
  - spec/legion/extensions/llm/inventory/evidence_spec.rb
432
401
  - spec/legion/extensions/llm/inventory/identity_spec.rb
433
402
  - spec/legion/extensions/llm/inventory/immutable_value_spec.rb
@@ -439,25 +408,17 @@ files:
439
408
  - spec/legion/extensions/llm/inventory/registry_availability_spec.rb
440
409
  - spec/legion/extensions/llm/inventory/registry_callable_spec.rb
441
410
  - spec/legion/extensions/llm/inventory/registry_replacement_spec.rb
442
- - spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb
443
411
  - spec/legion/extensions/llm/inventory/snapshot_spec.rb
444
- - spec/legion/extensions/llm/message_spec.rb
445
- - spec/legion/extensions/llm/model/info_spec.rb
446
- - spec/legion/extensions/llm/models_spec.rb
412
+ - spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb
413
+ - spec/legion/extensions/llm/inventory/weight_schema_spec.rb
447
414
  - spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb
448
415
  - spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb
449
416
  - spec/legion/extensions/llm/provider_contract_spec.rb
450
417
  - spec/legion/extensions/llm/provider_settings_spec.rb
451
418
  - spec/legion/extensions/llm/provider_spec.rb
452
- - spec/legion/extensions/llm/registry_event_builder_spec.rb
453
- - spec/legion/extensions/llm/registry_publisher_spec.rb
454
- - spec/legion/extensions/llm/responses/response_objects_spec.rb
455
419
  - spec/legion/extensions/llm/responses/thinking_extractor_spec.rb
456
- - spec/legion/extensions/llm/routing/model_offering_spec.rb
457
- - spec/legion/extensions/llm/routing/offering_registry_spec.rb
458
420
  - spec/legion/extensions/llm/routing/provider_outcome_spec.rb
459
421
  - spec/legion/extensions/llm/routing/records_spec.rb
460
- - spec/legion/extensions/llm/routing/registry_event_spec.rb
461
422
  - spec/legion/extensions/llm/settings_cascade_spec.rb
462
423
  - spec/legion/extensions/llm/stop_reason_mapping_spec.rb
463
424
  - spec/legion/extensions/llm/stream_accumulator_spec.rb