lex-llm 0.7.6 → 0.8.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.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -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 +107 -94
  6. data/lib/legion/extensions/llm/canonical/content_block.rb +65 -75
  7. data/lib/legion/extensions/llm/canonical/message.rb +53 -69
  8. data/lib/legion/extensions/llm/canonical/params.rb +58 -34
  9. data/lib/legion/extensions/llm/canonical/request.rb +68 -56
  10. data/lib/legion/extensions/llm/canonical/response.rb +62 -78
  11. data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
  12. data/lib/legion/extensions/llm/canonical/thinking.rb +36 -84
  13. data/lib/legion/extensions/llm/canonical/thinking_config.rb +149 -0
  14. data/lib/legion/extensions/llm/canonical/tool_call.rb +43 -53
  15. data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
  16. data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
  17. data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
  18. data/lib/legion/extensions/llm/canonical.rb +7 -5
  19. data/lib/legion/extensions/llm/configuration.rb +40 -10
  20. data/lib/legion/extensions/llm/connection.rb +8 -30
  21. data/lib/legion/extensions/llm/credential_sources.rb +32 -49
  22. data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
  23. data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
  24. data/lib/legion/extensions/llm/error.rb +0 -14
  25. data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
  26. data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
  27. data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
  28. data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
  29. data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -156
  30. data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
  31. data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -154
  32. data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
  33. data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
  34. data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
  35. data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
  36. data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
  37. data/lib/legion/extensions/llm/inventory/records.rb +36 -100
  38. data/lib/legion/extensions/llm/inventory/registry.rb +54 -48
  39. data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
  40. data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +13 -6
  41. data/lib/legion/extensions/llm/inventory/weight_schema.rb +47 -8
  42. data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +178 -182
  43. data/lib/legion/extensions/llm/provider.rb +192 -315
  44. data/lib/legion/extensions/llm/provider_contract.rb +25 -8
  45. data/lib/legion/extensions/llm/provider_settings.rb +5 -26
  46. data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
  47. data/lib/legion/extensions/llm/responses/tool_arguments.rb +48 -0
  48. data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
  49. data/lib/legion/extensions/llm/routing/records.rb +34 -17
  50. data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
  51. data/lib/legion/extensions/llm/streaming.rb +50 -35
  52. data/lib/legion/extensions/llm/taxonomies.rb +14 -26
  53. data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
  54. data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
  55. data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
  56. data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
  57. data/lib/legion/extensions/llm/utils.rb +23 -5
  58. data/lib/legion/extensions/llm/version.rb +1 -1
  59. data/lib/legion/extensions/llm.rb +8 -98
  60. data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
  61. data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
  62. data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
  63. data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
  64. data/spec/legion/extensions/llm/canonical/params_spec.rb +55 -136
  65. data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
  66. data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
  67. data/spec/legion/extensions/llm/canonical/thinking/config_spec.rb +222 -0
  68. data/spec/legion/extensions/llm/canonical/thinking_spec.rb +23 -171
  69. data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
  70. data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
  71. data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
  72. data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
  73. data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
  74. data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
  75. data/spec/legion/extensions/llm/conformance/client_translator_examples.rb +1 -2
  76. data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
  77. data/spec/legion/extensions/llm/conformance/fixtures/canonical_fleet_round_trip.json +1 -1
  78. data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_request.json +2 -2
  79. data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
  80. data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +128 -0
  81. data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +505 -0
  82. data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
  83. data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
  84. data/spec/legion/extensions/llm/error_spec.rb +2 -12
  85. data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
  86. data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +173 -95
  87. data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
  88. data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +93 -74
  89. data/spec/legion/extensions/llm/fleet_messages_spec.rb +119 -125
  90. data/spec/legion/extensions/llm/gemspec_spec.rb +1 -2
  91. data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
  92. data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
  93. data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
  94. data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
  95. data/spec/legion/extensions/llm/inventory/records_spec.rb +82 -47
  96. data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +49 -16
  97. data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
  98. data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
  99. data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +7 -3
  100. data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +60 -10
  101. data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +128 -68
  102. data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
  103. data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
  104. data/spec/legion/extensions/llm/provider_spec.rb +97 -78
  105. data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
  106. data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
  107. data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
  108. data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -43
  109. data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
  110. data/spec/legion/extensions/llm/utils_spec.rb +26 -7
  111. data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
  112. data/spec/legion/extensions/llm_extension_spec.rb +5 -5
  113. data/spec/support/fake_llm_provider.rb +45 -39
  114. data/spec/support/fake_ssot_harness.rb +7 -2
  115. metadata +13 -54
  116. data/lib/legion/extensions/llm/agent.rb +0 -366
  117. data/lib/legion/extensions/llm/aliases.json +0 -436
  118. data/lib/legion/extensions/llm/aliases.rb +0 -67
  119. data/lib/legion/extensions/llm/attachment.rb +0 -229
  120. data/lib/legion/extensions/llm/chat.rb +0 -354
  121. data/lib/legion/extensions/llm/chunk.rb +0 -10
  122. data/lib/legion/extensions/llm/content.rb +0 -81
  123. data/lib/legion/extensions/llm/context.rb +0 -33
  124. data/lib/legion/extensions/llm/embedding.rb +0 -33
  125. data/lib/legion/extensions/llm/image.rb +0 -109
  126. data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
  127. data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -310
  128. data/lib/legion/extensions/llm/message.rb +0 -118
  129. data/lib/legion/extensions/llm/mime_type.rb +0 -75
  130. data/lib/legion/extensions/llm/model/info.rb +0 -286
  131. data/lib/legion/extensions/llm/model/modalities.rb +0 -26
  132. data/lib/legion/extensions/llm/model/pricing.rb +0 -52
  133. data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
  134. data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
  135. data/lib/legion/extensions/llm/model.rb +0 -11
  136. data/lib/legion/extensions/llm/models.json +0 -57313
  137. data/lib/legion/extensions/llm/models.rb +0 -530
  138. data/lib/legion/extensions/llm/models_schema.json +0 -168
  139. data/lib/legion/extensions/llm/moderation.rb +0 -60
  140. data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
  141. data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
  142. data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
  143. data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
  144. data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
  145. data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
  146. data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
  147. data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
  148. data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
  149. data/lib/legion/extensions/llm/thinking.rb +0 -53
  150. data/lib/legion/extensions/llm/tokens.rb +0 -51
  151. data/lib/legion/extensions/llm/tool_call.rb +0 -34
  152. data/lib/legion/extensions/llm/transcription.rb +0 -39
  153. data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
  154. data/spec/legion/extensions/llm/agent_spec.rb +0 -179
  155. data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
  156. data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
  157. data/spec/legion/extensions/llm/context_spec.rb +0 -127
  158. data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
  159. data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -340
  160. data/spec/legion/extensions/llm/message_spec.rb +0 -64
  161. data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
  162. data/spec/legion/extensions/llm/models_spec.rb +0 -104
  163. data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
  164. data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
  165. data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
  166. data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
  167. data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
  168. data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- # Identify potentially harmful content in text.
7
- # https://platform.openai.com/docs/guides/moderation
8
- class Moderation
9
- attr_reader :id, :model, :results
10
-
11
- def initialize(id:, model:, results:)
12
- @id = id
13
- @model = model
14
- @results = results
15
- end
16
-
17
- def self.moderate(input,
18
- model: nil,
19
- provider: nil,
20
- assume_model_exists: false,
21
- context: nil)
22
- config = context&.config || Legion::Extensions::Llm.config
23
- model ||= config.default_moderation_model
24
- model, provider_instance = Models.resolve(model, provider: provider, assume_exists: assume_model_exists,
25
- config: config)
26
- model_id = model.id
27
-
28
- provider_instance.moderate(input, model: model_id)
29
- end
30
-
31
- # Convenience method to get content from moderation result
32
- def content
33
- results
34
- end
35
-
36
- # Check if any content was flagged
37
- def flagged?
38
- results.any? { |result| result['flagged'] }
39
- end
40
-
41
- # Get all flagged categories across all results
42
- def flagged_categories
43
- results.flat_map do |result|
44
- result['categories']&.select { |_category, flagged| flagged }&.keys || []
45
- end.uniq
46
- end
47
-
48
- # Get category scores for the first result (most common case)
49
- def category_scores
50
- results.first&.dig('category_scores') || {}
51
- end
52
-
53
- # Get categories for the first result (most common case)
54
- def categories
55
- results.first&.dig('categories') || {}
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,141 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- # Builds sanitized lex-llm registry envelopes for provider state.
7
- # Parameterized by `provider_family` so each lex-llm-* gem can reuse this
8
- # class without defining its own copy.
9
- class RegistryEventBuilder
10
- include Legion::Logging::Helper
11
-
12
- attr_reader :provider_family
13
-
14
- def initialize(provider_family:)
15
- @provider_family = provider_family.to_s.downcase.to_sym
16
- end
17
-
18
- def readiness(readiness)
19
- registry_event_class.public_send(
20
- readiness[:ready] ? :available : :unavailable,
21
- provider_offering(readiness),
22
- runtime: runtime_metadata,
23
- health: readiness_health(readiness),
24
- metadata: readiness_metadata(readiness)
25
- )
26
- end
27
-
28
- def model_available(model, readiness:)
29
- registry_event_class.available(
30
- model_offering(model),
31
- runtime: runtime_metadata,
32
- health: model_health(readiness),
33
- metadata: model_metadata(model)
34
- )
35
- end
36
-
37
- private
38
-
39
- def provider_offering(readiness)
40
- {
41
- provider_family: provider_family,
42
- provider_instance: provider_instance,
43
- transport: :http,
44
- model: 'provider-readiness',
45
- usage_type: :inference,
46
- capabilities: [],
47
- health: readiness_health(readiness),
48
- metadata: { lex: extension_sym, provider_readiness: true }
49
- }
50
- end
51
-
52
- def model_offering(model)
53
- {
54
- provider_family: provider_family,
55
- provider_instance: provider_instance,
56
- transport: :http,
57
- model: model.id,
58
- usage_type: usage_type_for(model),
59
- capabilities: Array(model.capabilities).map(&:to_sym),
60
- limits: model_limits(model),
61
- metadata: { lex: extension_sym, model_name: model.name }.compact
62
- }
63
- end
64
-
65
- def readiness_health(readiness)
66
- source = readiness[:health]
67
- health = {
68
- ready: readiness[:ready] == true,
69
- status: readiness[:ready] ? :available : :unavailable,
70
- checked: !source.is_a?(Hash) || source[:checked] != false
71
- }
72
- add_readiness_error(health, source)
73
- end
74
-
75
- def add_readiness_error(health, source)
76
- error = source.is_a?(Hash) ? source : {}
77
- error_class = error[:error] || error['error']
78
- error_message = error[:message] || error['message']
79
- health[:error_class] = error_class if error_class
80
- health[:error] = error_message if error_message
81
- health
82
- end
83
-
84
- def model_health(readiness)
85
- ready = readiness.fetch(:ready, true) == true
86
- { ready:, status: ready ? :available : :degraded }
87
- end
88
-
89
- def readiness_metadata(readiness)
90
- {
91
- extension: extension_sym,
92
- provider: provider_family,
93
- configured: readiness[:configured] == true,
94
- live: readiness[:live] == true
95
- }
96
- end
97
-
98
- def model_metadata(model)
99
- { extension: extension_sym, provider: provider_family, model_type: model_type_for(model) }
100
- end
101
-
102
- def runtime_metadata
103
- { node: provider_instance }
104
- end
105
-
106
- def model_limits(model)
107
- limits = {}
108
- limits[:context_window] = model.context_window if model.respond_to?(:context_window)
109
- limits[:max_output_tokens] = model.max_output_tokens if model.respond_to?(:max_output_tokens)
110
- limits.compact
111
- end
112
-
113
- def usage_type_for(model)
114
- model_type_for(model) == 'embedding' ? :embedding : :inference
115
- end
116
-
117
- def model_type_for(model)
118
- model.respond_to?(:type) ? model.type : 'chat'
119
- end
120
-
121
- def extension_sym
122
- :"llm_#{provider_family}"
123
- end
124
-
125
- def provider_instance
126
- configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
127
- value = configured_node.to_s.strip
128
- value.empty? ? provider_family : value.to_sym
129
- rescue StandardError => e
130
- handle_exception(e, level: :warn, handled: true,
131
- operation: "#{provider_family}.registry.provider_instance")
132
- provider_family
133
- end
134
-
135
- def registry_event_class
136
- ::Legion::Extensions::Llm::Routing::RegistryEvent
137
- end
138
- end
139
- end
140
- end
141
- end
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'concurrent'
4
-
5
- module Legion
6
- module Extensions
7
- module Llm
8
- # Best-effort publisher for LLM provider availability events.
9
- # Parameterized by `provider_family` so each lex-llm-* gem can reuse this
10
- # class without defining its own copy.
11
- class RegistryPublisher
12
- include Legion::Logging::Helper
13
-
14
- ASYNC_THREAD_POOL = Concurrent::FixedThreadPool.new(1, fallback_policy: :caller_runs)
15
-
16
- attr_reader :provider_family
17
-
18
- def initialize(provider_family:, builder: nil)
19
- @provider_family = provider_family.to_s.downcase.to_sym
20
- @builder = builder || RegistryEventBuilder.new(provider_family: @provider_family)
21
- end
22
-
23
- def app_id
24
- "lex-llm-#{provider_family}"
25
- end
26
-
27
- def publish_readiness_async(readiness)
28
- log.debug { "publishing readiness event to llm.registry for #{provider_family}" }
29
- schedule { publish_event(@builder.readiness(readiness)) }
30
- end
31
-
32
- def publish_models_async(models, readiness:)
33
- log.debug { "publishing #{Array(models).size} model event(s) to llm.registry for #{provider_family}" }
34
- schedule do
35
- Array(models).each do |model|
36
- publish_event(@builder.model_available(model, readiness:))
37
- end
38
- end
39
- end
40
-
41
- private
42
-
43
- def schedule(&)
44
- return false unless publishing_available?
45
-
46
- ASYNC_THREAD_POOL.post do
47
- yield
48
- rescue StandardError => e
49
- handle_exception(e, level: :warn, handled: true,
50
- operation: "#{provider_family}.registry.schedule_thread")
51
- end
52
- rescue StandardError => e
53
- handle_exception(e, level: :warn, handled: true,
54
- operation: "#{provider_family}.registry.schedule")
55
- false
56
- end
57
-
58
- def publish_event(event)
59
- return false unless publishing_available?
60
-
61
- message_class.new(event:, provider_family: provider_family, app_id: app_id).publish(spool: false)
62
- rescue StandardError => e
63
- handle_exception(e, level: :warn, handled: true,
64
- operation: "#{provider_family}.registry.publish_event")
65
- false
66
- end
67
-
68
- def publishing_available?
69
- return false unless registry_event_available?
70
- return false unless transport_message_available?
71
- return true unless defined?(::Legion::Transport::Connection)
72
- return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
73
-
74
- ::Legion::Transport::Connection.session_open?
75
- rescue StandardError => e
76
- handle_exception(e, level: :warn, handled: true,
77
- operation: "#{provider_family}.registry.publishing_available?")
78
- false
79
- end
80
-
81
- def registry_event_available?
82
- defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
83
- end
84
-
85
- def transport_message_available?
86
- return true if message_class_defined?
87
- return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
88
-
89
- require 'legion/extensions/llm/transport/messages/registry_event'
90
- message_class_defined?
91
- rescue LoadError => e
92
- handle_exception(e, level: :warn, handled: true,
93
- operation: "#{provider_family}.registry.transport_load")
94
- false
95
- end
96
-
97
- def message_class_defined?
98
- defined?(::Legion::Extensions::Llm::Transport::Messages::RegistryEvent)
99
- end
100
-
101
- def message_class
102
- ::Legion::Extensions::Llm::Transport::Messages::RegistryEvent
103
- end
104
- end
105
- end
106
- end
107
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Responses
7
- # Normalized non-streaming chat provider response.
8
- class ChatResponse
9
- attr_reader :content, :thinking, :metadata, :model, :tool_calls, :tokens, :raw, :internal_metadata
10
-
11
- def initialize(content:, thinking: nil, metadata: {}, model: nil, tool_calls: nil, tokens: nil, raw: nil) # rubocop:disable Metrics/ParameterLists
12
- extraction = ThinkingExtractor.extract(content, metadata: metadata)
13
-
14
- @content = extraction.content
15
- @thinking = thinking || extraction.thinking
16
- @metadata = extraction.metadata
17
- @internal_metadata = metadata.to_h
18
- @model = model
19
- @tool_calls = tool_calls
20
- @tokens = tokens
21
- @raw = raw
22
-
23
- freeze
24
- end
25
-
26
- def to_h
27
- {
28
- content: content,
29
- metadata: metadata,
30
- model: model,
31
- tool_calls: tool_calls,
32
- tokens: tokens
33
- }.compact
34
- end
35
-
36
- def to_internal_h
37
- to_h.merge(thinking: thinking, metadata: internal_metadata, raw: raw).compact
38
- end
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Responses
7
- # Normalized embedding provider response.
8
- class EmbeddingResponse
9
- attr_reader :vectors, :model, :tokens, :metadata, :raw
10
-
11
- def initialize(vectors:, model:, tokens: nil, metadata: {}, raw: nil)
12
- @vectors = vectors
13
- @model = model
14
- @tokens = tokens
15
- @metadata = ThinkingExtractor.extract(nil, metadata: metadata).metadata
16
- @internal_metadata = metadata.to_h
17
- @raw = raw
18
-
19
- freeze
20
- end
21
-
22
- def to_h
23
- {
24
- vectors: vectors,
25
- model: model,
26
- tokens: tokens,
27
- metadata: metadata
28
- }.compact
29
- end
30
-
31
- def to_internal_h
32
- to_h.merge(metadata: @internal_metadata, raw: raw).compact
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Responses
7
- # Normalized streaming provider response chunk.
8
- class StreamChunk
9
- attr_reader :content, :thinking, :metadata, :model, :tool_calls, :tokens, :raw, :internal_metadata
10
-
11
- def initialize(content: nil, thinking: nil, metadata: {}, model: nil, tool_calls: nil, tokens: nil, raw: nil) # rubocop:disable Metrics/ParameterLists
12
- extraction = ThinkingExtractor.extract(content, metadata: metadata)
13
-
14
- @content = extraction.content
15
- @thinking = thinking || extraction.thinking
16
- @metadata = extraction.metadata
17
- @internal_metadata = metadata.to_h
18
- @model = model
19
- @tool_calls = tool_calls
20
- @tokens = tokens
21
- @raw = raw
22
-
23
- freeze
24
- end
25
-
26
- def to_h
27
- {
28
- content: content,
29
- metadata: metadata,
30
- model: model,
31
- tool_calls: tool_calls,
32
- tokens: tokens
33
- }.compact
34
- end
35
-
36
- def to_internal_h
37
- to_h.merge(thinking: thinking, metadata: internal_metadata, raw: raw).compact
38
- end
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Routing
7
- # DEPRECATED (Phase 4 removal): retained only for protocol-v2 fleet
8
- # compatibility. The new canonical execution identity comes solely from
9
- # Inventory::Identity (off:v1:/lane:v1:); do not reinterpret an old
10
- # five-part lane as a new lane:v1: identity.
11
- # Builds stable fleet lane keys from provider-neutral model offerings.
12
- module LaneKey
13
- module_function
14
-
15
- def for(offering, prefix: 'llm.fleet', include_context: true, include_fingerprint: false)
16
- parts = [prefix, lane_kind(offering), model_slug(lane_model(offering))]
17
- parts << "ctx#{offering.context_window}" if include_context && offering.inference? && offering.context_window
18
- parts.push('elig', eligibility_fingerprint(offering)) if include_fingerprint
19
- parts.join('.')
20
- end
21
-
22
- def lane_model(offering)
23
- return offering.canonical_model_alias if offering.respond_to?(:canonical_model_alias) &&
24
- offering.canonical_model_alias.to_s != ''
25
-
26
- offering.model
27
- end
28
-
29
- def lane_kind(offering)
30
- offering.embedding? ? 'embed' : 'inference'
31
- end
32
-
33
- def model_slug(model)
34
- model.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/\A-+|-+\z/, '')
35
- end
36
-
37
- def eligibility_fingerprint(offering)
38
- canonical = {
39
- usage_type: offering.usage_type,
40
- capabilities: offering.capabilities.sort,
41
- context_window: offering.context_window,
42
- max_output_tokens: offering.max_output_tokens,
43
- policy_tags: offering.policy_tags.sort,
44
- metadata: fingerprint_metadata(offering.metadata)
45
- }
46
- Digest::SHA1.hexdigest(Legion::JSON.generate(canonical))[0, 10]
47
- end
48
-
49
- def fingerprint_metadata(metadata)
50
- metadata.fetch(:eligibility, {})
51
- .to_h
52
- .transform_keys(&:to_sym)
53
- .reject { |key, _| sensitive_fingerprint_key?(key) }
54
- .sort
55
- .to_h
56
- end
57
-
58
- def sensitive_fingerprint_key?(key)
59
- %i[credential credentials endpoint endpoint_url identity path prompt reply_to secret secrets token
60
- url].include?(key)
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end