lex-llm-gateway 0.2.3 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/lib/legion/extensions/llm/gateway/actors/inference_worker.rb +5 -1
- data/lib/legion/extensions/llm/gateway/helpers/reply_dispatcher.rb +107 -0
- data/lib/legion/extensions/llm/gateway/runners/fleet.rb +10 -0
- data/lib/legion/extensions/llm/gateway/runners/fleet_handler.rb +34 -2
- data/lib/legion/extensions/llm/gateway/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 708b97f62b7c6e7f238c29515948c677ed02adf7b0c0c7d3c0ba815968a07b2a
|
|
4
|
+
data.tar.gz: 02fdff0ee5e874ae0d5a4054c923e751f44491d4299e8a5a686fed18e7e4f701
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab1de24d90fda54114752b2983f7cbd2822bf83703dc3a50d7075025389b396cd8bbb1c6beca861e101f3a5ad9b3eb02bb12baa3426c618e81c3fe31c02bd1e3
|
|
7
|
+
data.tar.gz: 0cec3bd7a834241ef4426ea5bb7347362170f708d533ebe02472adbd0e23a7d5293fe21bab00ace05e03731be26660e2879827625eeb92d4b4b6cef002a577eb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.4] - 2026-03-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Implement fleet RPC `wait_for_response` with `Concurrent::Promises` future and correlation ID matching
|
|
7
|
+
- Add `Helpers::ReplyDispatcher` process-singleton for managing reply queue consumer and pending futures
|
|
8
|
+
- Add `FleetHandler.publish_reply` to send `InferenceResponse` back to requester via AMQP default exchange
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fix `Actor::InferenceWorker` runner_class mismatch: now points to `FleetHandler` instead of `Inference`
|
|
12
|
+
- Add `use_runner? false` to InferenceWorker so it dispatches directly to the runner module
|
|
13
|
+
|
|
3
14
|
## [0.2.3] - 2026-03-22
|
|
4
15
|
|
|
5
16
|
### Changed
|
|
@@ -7,12 +7,16 @@ module Legion
|
|
|
7
7
|
module Actor
|
|
8
8
|
class InferenceWorker < Legion::Extensions::Actors::Subscription
|
|
9
9
|
def runner_class
|
|
10
|
-
'Legion::Extensions::LLM::Gateway::Runners::
|
|
10
|
+
'Legion::Extensions::LLM::Gateway::Runners::FleetHandler'
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def runner_function
|
|
14
14
|
'handle_fleet_request'
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
def use_runner?
|
|
18
|
+
false
|
|
19
|
+
end
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module LLM
|
|
8
|
+
module Gateway
|
|
9
|
+
module Helpers
|
|
10
|
+
module ReplyDispatcher
|
|
11
|
+
@pending = Concurrent::Map.new
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@consumer = nil
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def register(correlation_id)
|
|
18
|
+
future = Concurrent::Promises.resolvable_future
|
|
19
|
+
@pending[correlation_id] = future
|
|
20
|
+
ensure_consumer
|
|
21
|
+
future
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deregister(correlation_id)
|
|
25
|
+
@pending.delete(correlation_id)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def handle_delivery(raw_payload, properties = {})
|
|
29
|
+
payload = parse_payload(raw_payload)
|
|
30
|
+
cid = properties[:correlation_id] || payload[:correlation_id]
|
|
31
|
+
return unless cid
|
|
32
|
+
|
|
33
|
+
future = @pending.delete(cid)
|
|
34
|
+
return unless future
|
|
35
|
+
|
|
36
|
+
future.fulfill(payload.merge(success: true))
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
log_warn("ReplyDispatcher: handle_delivery failed: #{e.message}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def pending_count
|
|
42
|
+
@pending.size
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset!
|
|
46
|
+
@mutex.synchronize do
|
|
47
|
+
cancel_consumer
|
|
48
|
+
@pending = Concurrent::Map.new
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# private
|
|
53
|
+
|
|
54
|
+
def ensure_consumer # rubocop:disable Metrics/MethodLength
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
return if @consumer
|
|
57
|
+
return unless transport_available?
|
|
58
|
+
|
|
59
|
+
queue_name = Rpc.agent_queue_name
|
|
60
|
+
return unless queue_name
|
|
61
|
+
|
|
62
|
+
channel = Legion::Transport.connection.create_channel
|
|
63
|
+
queue = channel.queue(queue_name, auto_delete: true, durable: false)
|
|
64
|
+
@consumer = queue.subscribe(manual_ack: false) do |_delivery, properties, body|
|
|
65
|
+
props = { correlation_id: properties.correlation_id }
|
|
66
|
+
handle_delivery(body, props)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
log_warn("ReplyDispatcher: consumer setup failed: #{e.message}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def cancel_consumer
|
|
74
|
+
@consumer&.cancel
|
|
75
|
+
@consumer = nil
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
log_warn("ReplyDispatcher: cancel failed: #{e.message}")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def transport_available?
|
|
81
|
+
defined?(Legion::Transport) &&
|
|
82
|
+
Legion::Transport.respond_to?(:connection) &&
|
|
83
|
+
Legion::Transport.connection
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_payload(raw)
|
|
87
|
+
return raw if raw.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
if defined?(Legion::JSON)
|
|
90
|
+
Legion::JSON.load(raw)
|
|
91
|
+
else
|
|
92
|
+
require 'json'
|
|
93
|
+
JSON.parse(raw, symbolize_names: true)
|
|
94
|
+
end
|
|
95
|
+
rescue StandardError
|
|
96
|
+
{}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def log_warn(msg)
|
|
100
|
+
Legion::Logging.warn(msg) if defined?(Legion::Logging)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -78,6 +78,16 @@ module Legion
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def wait_for_response(correlation_id, timeout:)
|
|
81
|
+
future = Helpers::ReplyDispatcher.register(correlation_id)
|
|
82
|
+
result = future.value!(timeout)
|
|
83
|
+
result || timeout_result(correlation_id, timeout)
|
|
84
|
+
rescue Concurrent::CancelledOperationError
|
|
85
|
+
timeout_result(correlation_id, timeout)
|
|
86
|
+
ensure
|
|
87
|
+
Helpers::ReplyDispatcher.deregister(correlation_id)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def timeout_result(correlation_id, timeout)
|
|
81
91
|
{ success: false, error: 'fleet_timeout', correlation_id: correlation_id, timeout: timeout }
|
|
82
92
|
end
|
|
83
93
|
|
|
@@ -10,10 +10,16 @@ module Legion
|
|
|
10
10
|
|
|
11
11
|
def handle_fleet_request(payload)
|
|
12
12
|
token = payload[:signed_token]
|
|
13
|
-
|
|
13
|
+
if require_auth? && !valid_token?(token)
|
|
14
|
+
error_response = { success: false, error: 'invalid_token' }
|
|
15
|
+
publish_reply(payload[:reply_to], payload[:correlation_id], error_response) if payload[:reply_to]
|
|
16
|
+
return error_response
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
response = call_local_llm(payload)
|
|
16
|
-
build_response(payload[:correlation_id], response)
|
|
20
|
+
response_hash = build_response(payload[:correlation_id], response)
|
|
21
|
+
publish_reply(payload[:reply_to], payload[:correlation_id], response_hash) if payload[:reply_to]
|
|
22
|
+
response_hash
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def require_auth?
|
|
@@ -47,6 +53,32 @@ module Legion
|
|
|
47
53
|
}
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
def publish_reply(reply_to, correlation_id, response_hash) # rubocop:disable Metrics/MethodLength
|
|
57
|
+
return unless defined?(Legion::Transport)
|
|
58
|
+
|
|
59
|
+
payload = if defined?(Legion::JSON)
|
|
60
|
+
Legion::JSON.dump(response_hash)
|
|
61
|
+
else
|
|
62
|
+
require 'json'
|
|
63
|
+
JSON.generate(response_hash)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
channel = Legion::Transport.connection.create_channel
|
|
67
|
+
channel.default_exchange.publish(
|
|
68
|
+
payload,
|
|
69
|
+
routing_key: reply_to,
|
|
70
|
+
correlation_id: correlation_id,
|
|
71
|
+
content_type: 'application/json'
|
|
72
|
+
)
|
|
73
|
+
channel.close
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
log_warn("FleetHandler: publish_reply failed: #{e.message}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def log_warn(msg)
|
|
79
|
+
Legion::Logging.warn(msg) if defined?(Legion::Logging)
|
|
80
|
+
end
|
|
81
|
+
|
|
50
82
|
def extract_token(response, field)
|
|
51
83
|
return 0 unless response.respond_to?(field)
|
|
52
84
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-gateway
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -182,6 +182,7 @@ files:
|
|
|
182
182
|
- lib/legion/extensions/llm/gateway/actors/spool_flush.rb
|
|
183
183
|
- lib/legion/extensions/llm/gateway/client.rb
|
|
184
184
|
- lib/legion/extensions/llm/gateway/helpers/auth.rb
|
|
185
|
+
- lib/legion/extensions/llm/gateway/helpers/reply_dispatcher.rb
|
|
185
186
|
- lib/legion/extensions/llm/gateway/helpers/rpc.rb
|
|
186
187
|
- lib/legion/extensions/llm/gateway/runners/fleet.rb
|
|
187
188
|
- lib/legion/extensions/llm/gateway/runners/fleet_handler.rb
|