lex-llm 0.3.0 → 0.4.2

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +18 -2
  4. data/lex-llm.gemspec +1 -0
  5. data/lib/legion/extensions/llm/auto_registration.rb +7 -35
  6. data/lib/legion/extensions/llm/embedding.rb +1 -1
  7. data/lib/legion/extensions/llm/error.rb +14 -0
  8. data/lib/legion/extensions/llm/errors/unsupported_capability.rb +21 -0
  9. data/lib/legion/extensions/llm/fleet/default_exchange_reply.rb +81 -0
  10. data/lib/legion/extensions/llm/fleet/envelope_validation.rb +39 -0
  11. data/lib/legion/extensions/llm/fleet/protocol.rb +16 -0
  12. data/lib/legion/extensions/llm/fleet/publish_safety.rb +123 -0
  13. data/lib/legion/extensions/llm/message.rb +9 -3
  14. data/lib/legion/extensions/llm/model/info.rb +1 -1
  15. data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +37 -36
  16. data/lib/legion/extensions/llm/provider.rb +198 -4
  17. data/lib/legion/extensions/llm/provider_contract.rb +21 -0
  18. data/lib/legion/extensions/llm/provider_settings.rb +18 -1
  19. data/lib/legion/extensions/llm/responses/chat_response.rb +43 -0
  20. data/lib/legion/extensions/llm/responses/embedding_response.rb +38 -0
  21. data/lib/legion/extensions/llm/responses/stream_chunk.rb +43 -0
  22. data/lib/legion/extensions/llm/responses/thinking_extractor.rb +155 -0
  23. data/lib/legion/extensions/llm/stream_accumulator.rb +12 -1
  24. data/lib/legion/extensions/llm/transport/exchanges/fleet.rb +24 -0
  25. data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +64 -0
  26. data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +155 -0
  27. data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +63 -0
  28. data/lib/legion/extensions/llm/version.rb +1 -1
  29. data/lib/legion/extensions/llm.rb +31 -11
  30. metadata +29 -1
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative '../../fleet/envelope_validation'
5
+ require_relative '../../fleet/publish_safety'
6
+ require_relative '../../fleet/protocol'
7
+ require_relative '../exchanges/fleet'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Llm
12
+ module Transport
13
+ module Messages
14
+ # Strict protocol-v2 request envelope for outbound fleet work.
15
+ class FleetRequest < ::Legion::Transport::Message
16
+ include Fleet::EnvelopeValidation
17
+ include Fleet::PublishSafety
18
+
19
+ PRIORITY_MAP = { critical: 9, high: 7, normal: 5, low: 2 }.freeze
20
+ DEFAULT_PUBLISH_OPTIONS = {
21
+ mandatory: true,
22
+ publisher_confirm: true,
23
+ spool: false,
24
+ return_result: true
25
+ }.freeze
26
+ REQUIRED_OPTIONS = %i[
27
+ request_id correlation_id operation provider provider_instance model params reply_to
28
+ message_context caller trace_context signed_token timeout_seconds expires_at protocol_version
29
+ idempotency_key
30
+ ].freeze
31
+
32
+ def exchange = Exchanges::Fleet
33
+ def type = Fleet::Protocol::REQUEST_TYPE
34
+ def app_id = @options[:app_id] || 'lex-llm'
35
+ def reply_to = @options[:reply_to]
36
+ def correlation_id = @options[:correlation_id]
37
+ def message_id = @options[:message_id] ||= "llm_fleet_req_#{SecureRandom.uuid}"
38
+
39
+ def priority
40
+ PRIORITY_MAP.fetch(@options[:priority].to_sym, 5) if @options[:priority]
41
+ end
42
+
43
+ def routing_key
44
+ @options[:routing_key] || raise(ArgumentError, 'routing_key is required')
45
+ end
46
+
47
+ def expiration
48
+ ttl = @options[:ttl] || @options[:timeout_seconds]
49
+ return super unless ttl
50
+
51
+ (Float(ttl) * 1000).ceil.to_s
52
+ rescue ArgumentError, TypeError
53
+ super
54
+ end
55
+
56
+ def publish(options = nil)
57
+ raise unless @valid
58
+
59
+ requested_options = DEFAULT_PUBLISH_OPTIONS.merge(@options).merge(options || {})
60
+ return_result = return_publish_result?(requested_options)
61
+ publish_options = request_publish_options(requested_options)
62
+ validate_payload_size
63
+ exchange_dest = fleet_exchange
64
+ return_state = {}
65
+ install_return_listener(exchange_dest, requested_options, return_state)
66
+ prepare_publisher_confirms(exchange_dest, requested_options)
67
+ exchange_dest.publish(encode_message, **publish_options)
68
+ return nil unless return_result
69
+
70
+ publish_result(exchange_dest, requested_options.merge(publish_options), return_state)
71
+ rescue Bunny::ConnectionClosedError, Bunny::ChannelAlreadyClosed, Bunny::ChannelError,
72
+ Bunny::NetworkErrorWrapper, IOError, Timeout::Error => e
73
+ handle_exception(e, level: :warn, handled: true, operation: 'llm.fleet.request.publish')
74
+ publish_failure_result(:failed, e, publish_options || requested_options || @options)
75
+ end
76
+
77
+ def validate
78
+ reject_legacy_options!
79
+ require_option!(:routing_key)
80
+ REQUIRED_OPTIONS.each { |key| require_option!(key) }
81
+ require_protocol_version!
82
+ @valid = true
83
+ end
84
+
85
+ def message
86
+ super.merge(
87
+ protocol_version: @options[:protocol_version],
88
+ request_id: @options[:request_id],
89
+ correlation_id: correlation_id,
90
+ idempotency_key: @options[:idempotency_key],
91
+ operation: @options[:operation],
92
+ provider: @options[:provider],
93
+ provider_instance: @options[:provider_instance],
94
+ model: @options[:model],
95
+ params: @options[:params] || {},
96
+ reply_to: reply_to,
97
+ message_context: @options[:message_context],
98
+ caller: @options[:caller],
99
+ trace_context: @options[:trace_context],
100
+ signed_token: @options[:signed_token],
101
+ timeout_seconds: @options[:timeout_seconds],
102
+ expires_at: @options[:expires_at]
103
+ ).compact
104
+ end
105
+
106
+ private
107
+
108
+ def fleet_exchange
109
+ exchange_class = exchange
110
+ if exchange_class.respond_to?(:cached_instance)
111
+ exchange_class.cached_instance || exchange_class.new
112
+ elsif exchange_class.respond_to?(:new)
113
+ exchange_class.new
114
+ else
115
+ exchange_class
116
+ end
117
+ end
118
+
119
+ def request_publish_options(options)
120
+ request_publish_envelope(options).tap do |envelope|
121
+ envelope[:mandatory] = true if options[:mandatory] == true
122
+ end.compact
123
+ end
124
+
125
+ def request_publish_envelope(options)
126
+ {
127
+ routing_key: routing_key || '',
128
+ content_type: options[:content_type] || content_type,
129
+ content_encoding: options[:content_encoding] || content_encoding,
130
+ type: options[:type] || type,
131
+ priority: options[:priority] || priority,
132
+ expiration: options[:expiration] || expiration,
133
+ headers: request_headers(options),
134
+ persistent: request_persistent(options),
135
+ message_id: message_id,
136
+ correlation_id: correlation_id,
137
+ reply_to: reply_to,
138
+ app_id: options[:app_id] || app_id,
139
+ timestamp: timestamp
140
+ }
141
+ end
142
+
143
+ def request_headers(options)
144
+ options[:headers] ? headers.merge(options[:headers]) : headers
145
+ end
146
+
147
+ def request_persistent(options)
148
+ options.key?(:persistent) ? options[:persistent] : persistent
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative '../../fleet/default_exchange_reply'
5
+ require_relative '../../fleet/envelope_validation'
6
+ require_relative '../../fleet/protocol'
7
+ require_relative '../exchanges/fleet'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Llm
12
+ module Transport
13
+ module Messages
14
+ # Correlated protocol-v2 response envelope for fleet reply queues.
15
+ class FleetResponse < ::Legion::Transport::Message
16
+ include Fleet::DefaultExchangeReply
17
+ include Fleet::EnvelopeValidation
18
+
19
+ def type = Fleet::Protocol::RESPONSE_TYPE
20
+ def app_id = @options[:app_id] || 'lex-llm'
21
+ def reply_to = @options[:reply_to]
22
+ def correlation_id = @options[:correlation_id]
23
+ def message_id = @options[:message_id] ||= "llm_fleet_res_#{SecureRandom.uuid}"
24
+
25
+ def routing_key
26
+ @options[:reply_to] || raise(ArgumentError, 'reply_to is required')
27
+ end
28
+
29
+ def validate
30
+ reject_legacy_options!
31
+ require_option!(:request_id)
32
+ require_option!(:correlation_id)
33
+ require_option!(:reply_to)
34
+ require_protocol_version!
35
+ @valid = true
36
+ end
37
+
38
+ def message
39
+ super.except(:thinking).merge(
40
+ protocol_version: @options[:protocol_version] || Fleet::Protocol::VERSION,
41
+ request_id: @options[:request_id],
42
+ correlation_id: correlation_id,
43
+ idempotency_key: @options[:idempotency_key],
44
+ operation: @options[:operation],
45
+ provider: @options[:provider],
46
+ provider_instance: @options[:provider_instance] || @options[:instance],
47
+ model: @options[:model],
48
+ reply_to: reply_to,
49
+ message_context: @options[:message_context],
50
+ trace_context: @options[:trace_context],
51
+ content: @options[:content],
52
+ tool_calls: @options[:tool_calls],
53
+ usage: @options[:usage] || {},
54
+ finish_reason: @options[:finish_reason],
55
+ metadata: @options[:metadata] || {}
56
+ ).compact
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Llm
6
- VERSION = '0.3.0'
6
+ VERSION = '0.4.2'
7
7
  end
8
8
  end
9
9
  end
@@ -110,20 +110,26 @@ module Legion
110
110
  def self.default_settings
111
111
  {
112
112
  fleet: {
113
- enabled: false,
114
- scheduler: :basic_get,
115
- consumer_priority: 0,
116
- queue_expires_ms: 60_000,
117
- message_ttl_ms: 120_000,
118
- queue_max_length: 100,
119
- delivery_limit: 3,
120
- consumer_ack_timeout_ms: 300_000,
121
- endpoint: {
113
+ consumer: {
122
114
  enabled: false,
115
+ scheduler: :basic_get,
116
+ consumer_priority: 0,
117
+ queue_expires_ms: 60_000,
118
+ message_ttl_ms: 120_000,
119
+ queue_max_length: 100,
120
+ delivery_limit: 3,
121
+ consumer_ack_timeout_ms: 90_000,
123
122
  empty_lane_backoff_ms: 250,
124
123
  idle_backoff_ms: 1_000,
125
- max_consecutive_pulls_per_lane: 0,
126
- accept_when: []
124
+ max_consecutive_pulls_per_lane: 0
125
+ },
126
+ auth: {
127
+ require_signed_token: true,
128
+ issuer: 'legion-llm',
129
+ audience: 'lex-llm-fleet-worker',
130
+ algorithm: 'HS256',
131
+ accepted_issuers: ['legion-llm'],
132
+ max_clock_skew_seconds: 30
127
133
  }
128
134
  }
129
135
  }
@@ -136,6 +142,20 @@ module Legion
136
142
  require_relative 'llm/auto_registration'
137
143
  require_relative 'llm/credential_sources'
138
144
  loader.eager_load
145
+
146
+ module Transport
147
+ # Local autoloads for fleet exchange classes that depend on legion-transport.
148
+ module Exchanges
149
+ autoload :Fleet, File.expand_path('llm/transport/exchanges/fleet', __dir__)
150
+ end
151
+
152
+ # Local autoloads for fleet message classes that depend on legion-transport.
153
+ module Messages
154
+ autoload :FleetRequest, File.expand_path('llm/transport/messages/fleet_request', __dir__)
155
+ autoload :FleetResponse, File.expand_path('llm/transport/messages/fleet_response', __dir__)
156
+ autoload :FleetError, File.expand_path('llm/transport/messages/fleet_error', __dir__)
157
+ end
158
+ end
139
159
  end
140
160
  end
141
161
  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.3.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: 1.3.14
139
+ - !ruby/object:Gem::Dependency
140
+ name: legion-transport
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 1.4.14
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.4.14
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: marcel
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -211,6 +225,11 @@ files:
211
225
  - lib/legion/extensions/llm/credential_sources.rb
212
226
  - lib/legion/extensions/llm/embedding.rb
213
227
  - lib/legion/extensions/llm/error.rb
228
+ - lib/legion/extensions/llm/errors/unsupported_capability.rb
229
+ - lib/legion/extensions/llm/fleet/default_exchange_reply.rb
230
+ - lib/legion/extensions/llm/fleet/envelope_validation.rb
231
+ - lib/legion/extensions/llm/fleet/protocol.rb
232
+ - lib/legion/extensions/llm/fleet/publish_safety.rb
214
233
  - lib/legion/extensions/llm/image.rb
215
234
  - lib/legion/extensions/llm/message.rb
216
235
  - lib/legion/extensions/llm/mime_type.rb
@@ -226,9 +245,14 @@ files:
226
245
  - lib/legion/extensions/llm/moderation.rb
227
246
  - lib/legion/extensions/llm/provider.rb
228
247
  - lib/legion/extensions/llm/provider/open_ai_compatible.rb
248
+ - lib/legion/extensions/llm/provider_contract.rb
229
249
  - lib/legion/extensions/llm/provider_settings.rb
230
250
  - lib/legion/extensions/llm/registry_event_builder.rb
231
251
  - lib/legion/extensions/llm/registry_publisher.rb
252
+ - lib/legion/extensions/llm/responses/chat_response.rb
253
+ - lib/legion/extensions/llm/responses/embedding_response.rb
254
+ - lib/legion/extensions/llm/responses/stream_chunk.rb
255
+ - lib/legion/extensions/llm/responses/thinking_extractor.rb
232
256
  - lib/legion/extensions/llm/routing.rb
233
257
  - lib/legion/extensions/llm/routing/lane_key.rb
234
258
  - lib/legion/extensions/llm/routing/model_offering.rb
@@ -241,8 +265,12 @@ files:
241
265
  - lib/legion/extensions/llm/tool.rb
242
266
  - lib/legion/extensions/llm/tool_call.rb
243
267
  - lib/legion/extensions/llm/transcription.rb
268
+ - lib/legion/extensions/llm/transport/exchanges/fleet.rb
244
269
  - lib/legion/extensions/llm/transport/exchanges/llm_registry.rb
245
270
  - lib/legion/extensions/llm/transport/fleet_lane.rb
271
+ - lib/legion/extensions/llm/transport/messages/fleet_error.rb
272
+ - lib/legion/extensions/llm/transport/messages/fleet_request.rb
273
+ - lib/legion/extensions/llm/transport/messages/fleet_response.rb
246
274
  - lib/legion/extensions/llm/transport/messages/registry_event.rb
247
275
  - lib/legion/extensions/llm/utils.rb
248
276
  - lib/legion/extensions/llm/version.rb