lex-llm-ledger 0.1.8 → 0.1.10
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 +13 -0
- data/lib/legion/extensions/llm/ledger/actors/prompt_writer.rb +5 -0
- data/lib/legion/extensions/llm/ledger/actors/spool_flush.rb +1 -1
- data/lib/legion/extensions/llm/ledger/actors/tool_writer.rb +5 -0
- data/lib/legion/extensions/llm/ledger/helpers/subscription_message.rb +114 -0
- data/lib/legion/extensions/llm/ledger/runners/metering.rb +11 -0
- data/lib/legion/extensions/llm/ledger/runners/prompts.rb +6 -1
- data/lib/legion/extensions/llm/ledger/runners/tools.rb +6 -1
- data/lib/legion/extensions/llm/ledger/version.rb +1 -1
- data/lib/legion/extensions/llm/ledger.rb +1 -0
- 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: a595a76ee91def4ec66dc3ec9b1daa0c40f872704a73659981b8eecebf5bd48e
|
|
4
|
+
data.tar.gz: 2f1c5ff38e83093df3c88e39a9a6c376f13f9af97fa000126a2d24c51324e3f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd1e3ae7439152285ee97d681d72bf98131504ab81e0107bd1a4c3f254c6dc2b4738a8562725bfc8e16b2a3c5d2fe44eb94e8df418b422f9fb48d1f746a8d026
|
|
7
|
+
data.tar.gz: aebc36c406ece5b8c4c51e315eccd771dcca1d70589e776c2a64d5e24d7dd4c59ada66139760b5d8874fa6cbbb6ace87cb846cc8a22092daee77afab0272d6d9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.10] - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Decode encrypted prompt and tool audit subscription payloads with either string or symbol `iv` headers before dispatch
|
|
7
|
+
- Preserve cleartext prompt and tool audit subscription payload handling when audit encryption is disabled
|
|
8
|
+
- Preserve AMQP headers and properties when prompt and tool subscription actors call their ledger runners
|
|
9
|
+
|
|
10
|
+
## [0.1.9] - 2026-04-09
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- SpoolFlush runner_function points to `spool_flush` (zero-arg) instead of `write_metering_record` (requires payload)
|
|
14
|
+
- Add `Runners::Metering.spool_flush` method that the framework can call on Every actor ticks
|
|
15
|
+
|
|
3
16
|
## [0.1.8] - 2026-04-09
|
|
4
17
|
|
|
5
18
|
### Fixed
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/actors/subscription'
|
|
4
|
+
require_relative '../helpers/subscription_message'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module Extensions
|
|
@@ -17,6 +18,10 @@ module Legion
|
|
|
17
18
|
def use_runner?
|
|
18
19
|
false
|
|
19
20
|
end
|
|
21
|
+
|
|
22
|
+
def process_message(message, metadata, delivery_info)
|
|
23
|
+
Helpers::SubscriptionMessage.decode_payload(message, metadata, delivery_info)
|
|
24
|
+
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/actors/subscription'
|
|
4
|
+
require_relative '../helpers/subscription_message'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module Extensions
|
|
@@ -17,6 +18,10 @@ module Legion
|
|
|
17
18
|
def use_runner?
|
|
18
19
|
false
|
|
19
20
|
end
|
|
21
|
+
|
|
22
|
+
def process_message(message, metadata, delivery_info)
|
|
23
|
+
Helpers::SubscriptionMessage.decode_payload(message, metadata, delivery_info)
|
|
24
|
+
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'decryption'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Llm
|
|
8
|
+
module Ledger
|
|
9
|
+
module Helpers
|
|
10
|
+
module SubscriptionMessage
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def decode_payload(message, metadata, delivery_info)
|
|
14
|
+
headers = metadata_headers(metadata)
|
|
15
|
+
properties = metadata_properties(metadata)
|
|
16
|
+
payload = decrypt_payload(message, headers, properties)
|
|
17
|
+
body = parse_payload(payload, properties)
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
payload: body,
|
|
21
|
+
metadata: {
|
|
22
|
+
headers: headers,
|
|
23
|
+
properties: properties.merge(routing_key: routing_key(delivery_info))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def metadata_headers(metadata)
|
|
29
|
+
headers = metadata.respond_to?(:headers) ? metadata.headers : nil
|
|
30
|
+
headers ||= {}
|
|
31
|
+
headers.each_with_object({}) do |(key, value), normalized|
|
|
32
|
+
normalized[key] = value
|
|
33
|
+
normalized[key.to_s] = value unless key.is_a?(String)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def metadata_properties(metadata)
|
|
38
|
+
{
|
|
39
|
+
content_encoding: metadata_value(metadata, :content_encoding),
|
|
40
|
+
content_type: metadata_value(metadata, :content_type),
|
|
41
|
+
message_id: metadata_value(metadata, :message_id),
|
|
42
|
+
correlation_id: metadata_value(metadata, :correlation_id),
|
|
43
|
+
app_id: metadata_value(metadata, :app_id),
|
|
44
|
+
timestamp: metadata_value(metadata, :timestamp)
|
|
45
|
+
}.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def decrypt_payload(message, headers, properties)
|
|
49
|
+
return message unless properties[:content_encoding] == 'encrypted/cs'
|
|
50
|
+
|
|
51
|
+
iv = headers['iv'] || headers[:iv]
|
|
52
|
+
raise DecryptionFailed, 'Encrypted audit record is missing iv header' if iv.nil?
|
|
53
|
+
|
|
54
|
+
Legion::Crypt.decrypt(message, iv)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_payload(payload, properties)
|
|
58
|
+
return payload unless properties[:content_type] == 'application/json'
|
|
59
|
+
|
|
60
|
+
if json_load_keyword?(:symbolize_keys)
|
|
61
|
+
Legion::JSON.load(payload, symbolize_keys: true) # rubocop:disable Legion/HelperMigration/DirectJson
|
|
62
|
+
else
|
|
63
|
+
Legion::JSON.load(payload, symbolize_names: true) # rubocop:disable Legion/HelperMigration/DirectJson
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def runner_args(payload, metadata, message)
|
|
68
|
+
message.key?(:payload) ? [message[:payload], message[:metadata] || {}] : [payload, metadata]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def routing_key(delivery_info)
|
|
72
|
+
return delivery_info[:routing_key] if delivery_info.respond_to?(:[])
|
|
73
|
+
return delivery_info.routing_key if delivery_info.respond_to?(:routing_key)
|
|
74
|
+
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def metadata_value(metadata, key)
|
|
79
|
+
return metadata.public_send(key) if metadata.respond_to?(key)
|
|
80
|
+
return metadata[key] if metadata.respond_to?(:[]) && metadata_key?(metadata, key)
|
|
81
|
+
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def metadata_key?(metadata, key)
|
|
86
|
+
return metadata.key?(key) if metadata.respond_to?(:key?)
|
|
87
|
+
return metadata.members.include?(key) if metadata.respond_to?(:members)
|
|
88
|
+
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def symbolize(value)
|
|
93
|
+
case value
|
|
94
|
+
when Hash
|
|
95
|
+
value.to_h { |key, nested| [key.to_sym, symbolize(nested)] }
|
|
96
|
+
when Array
|
|
97
|
+
value.map { |nested| symbolize(nested) }
|
|
98
|
+
else
|
|
99
|
+
value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def json_load_keyword?(keyword)
|
|
104
|
+
Legion::JSON.method(:load).parameters.any? { |type, name| type == :key && name == keyword }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private_class_method :metadata_headers, :metadata_properties, :decrypt_payload, :parse_payload,
|
|
108
|
+
:routing_key, :metadata_value, :metadata_key?, :symbolize, :json_load_keyword?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -8,6 +8,17 @@ module Legion
|
|
|
8
8
|
module Metering
|
|
9
9
|
extend self
|
|
10
10
|
|
|
11
|
+
def spool_flush
|
|
12
|
+
return unless defined?(Legion::LLM::Metering) &&
|
|
13
|
+
Legion::LLM::Metering.respond_to?(:flush_spool)
|
|
14
|
+
|
|
15
|
+
Legion::LLM::Metering.flush_spool
|
|
16
|
+
{ result: :ok }
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
Legion::Logging.warn("[lex-llm-ledger] spool_flush failed: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
|
|
19
|
+
{ result: :error, error: e.message }
|
|
20
|
+
end
|
|
21
|
+
|
|
11
22
|
def write_metering_record(payload, metadata = {})
|
|
12
23
|
ctx = payload[:message_context] || {}
|
|
13
24
|
props = metadata[:properties] || {}
|
|
@@ -8,7 +8,8 @@ module Legion
|
|
|
8
8
|
module Prompts
|
|
9
9
|
extend self
|
|
10
10
|
|
|
11
|
-
def write_prompt_record(payload, metadata = {})
|
|
11
|
+
def write_prompt_record(payload = nil, metadata = {}, **message)
|
|
12
|
+
payload, metadata = normalize_runner_args(payload, metadata, message)
|
|
12
13
|
headers = metadata[:headers] || {}
|
|
13
14
|
props = metadata[:properties] || {}
|
|
14
15
|
|
|
@@ -34,6 +35,10 @@ module Legion
|
|
|
34
35
|
|
|
35
36
|
private
|
|
36
37
|
|
|
38
|
+
def normalize_runner_args(payload, metadata, message)
|
|
39
|
+
Helpers::SubscriptionMessage.runner_args(payload, metadata, message)
|
|
40
|
+
end
|
|
41
|
+
|
|
37
42
|
def build_prompt_record(body, ctx, props, headers, expires_at)
|
|
38
43
|
routing = body[:routing] || {}
|
|
39
44
|
tokens = body[:tokens] || {}
|
|
@@ -8,7 +8,8 @@ module Legion
|
|
|
8
8
|
module Tools
|
|
9
9
|
extend self
|
|
10
10
|
|
|
11
|
-
def write_tool_record(payload, metadata = {})
|
|
11
|
+
def write_tool_record(payload = nil, metadata = {}, **message)
|
|
12
|
+
payload, metadata = normalize_runner_args(payload, metadata, message)
|
|
12
13
|
headers = metadata[:headers] || {}
|
|
13
14
|
props = metadata[:properties] || {}
|
|
14
15
|
|
|
@@ -35,6 +36,10 @@ module Legion
|
|
|
35
36
|
|
|
36
37
|
private
|
|
37
38
|
|
|
39
|
+
def normalize_runner_args(payload, metadata, message)
|
|
40
|
+
Helpers::SubscriptionMessage.runner_args(payload, metadata, message)
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
def build_tool_record(body, ctx, tool, props, headers, expires_at)
|
|
39
44
|
src = tool[:source] || {}
|
|
40
45
|
cls = body[:classification] || {}
|
|
@@ -4,6 +4,7 @@ require_relative 'ledger/version'
|
|
|
4
4
|
require_relative 'ledger/helpers/decryption'
|
|
5
5
|
require_relative 'ledger/helpers/retention'
|
|
6
6
|
require_relative 'ledger/helpers/queries'
|
|
7
|
+
require_relative 'ledger/helpers/subscription_message'
|
|
7
8
|
require_relative 'ledger/runners/metering'
|
|
8
9
|
require_relative 'ledger/runners/prompts'
|
|
9
10
|
require_relative 'ledger/runners/tools'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-ledger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -198,6 +198,7 @@ files:
|
|
|
198
198
|
- lib/legion/extensions/llm/ledger/helpers/decryption.rb
|
|
199
199
|
- lib/legion/extensions/llm/ledger/helpers/queries.rb
|
|
200
200
|
- lib/legion/extensions/llm/ledger/helpers/retention.rb
|
|
201
|
+
- lib/legion/extensions/llm/ledger/helpers/subscription_message.rb
|
|
201
202
|
- lib/legion/extensions/llm/ledger/migrations/001_create_metering_records.rb
|
|
202
203
|
- lib/legion/extensions/llm/ledger/migrations/002_create_prompt_records.rb
|
|
203
204
|
- lib/legion/extensions/llm/ledger/migrations/003_create_tool_records.rb
|