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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +18 -2
- data/lex-llm.gemspec +1 -0
- data/lib/legion/extensions/llm/auto_registration.rb +7 -35
- data/lib/legion/extensions/llm/embedding.rb +1 -1
- data/lib/legion/extensions/llm/error.rb +14 -0
- data/lib/legion/extensions/llm/errors/unsupported_capability.rb +21 -0
- data/lib/legion/extensions/llm/fleet/default_exchange_reply.rb +81 -0
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +39 -0
- data/lib/legion/extensions/llm/fleet/protocol.rb +16 -0
- data/lib/legion/extensions/llm/fleet/publish_safety.rb +123 -0
- data/lib/legion/extensions/llm/message.rb +9 -3
- data/lib/legion/extensions/llm/model/info.rb +1 -1
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +37 -36
- data/lib/legion/extensions/llm/provider.rb +198 -4
- data/lib/legion/extensions/llm/provider_contract.rb +21 -0
- data/lib/legion/extensions/llm/provider_settings.rb +18 -1
- data/lib/legion/extensions/llm/responses/chat_response.rb +43 -0
- data/lib/legion/extensions/llm/responses/embedding_response.rb +38 -0
- data/lib/legion/extensions/llm/responses/stream_chunk.rb +43 -0
- data/lib/legion/extensions/llm/responses/thinking_extractor.rb +155 -0
- data/lib/legion/extensions/llm/stream_accumulator.rb +12 -1
- data/lib/legion/extensions/llm/transport/exchanges/fleet.rb +24 -0
- data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +64 -0
- data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +155 -0
- data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +63 -0
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +31 -11
- 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
|
|
@@ -110,20 +110,26 @@ module Legion
|
|
|
110
110
|
def self.default_settings
|
|
111
111
|
{
|
|
112
112
|
fleet: {
|
|
113
|
-
|
|
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
|
-
|
|
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.
|
|
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
|