lex-llm-ledger 0.7.0 → 0.7.1
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 +5 -0
- data/lib/legion/extensions/llm/ledger/runners/registry_availability.rb +41 -0
- data/lib/legion/extensions/llm/ledger/version.rb +1 -1
- data/lib/legion/extensions/llm/ledger/writers/official_record_writer.rb +23 -2
- data/lib/legion/extensions/llm/ledger/writers/official_route_attempt_writer.rb +111 -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: 9dc0c64e738b5fe99d37289f48ce3264b7991f7f521d570df80a63615d419972
|
|
4
|
+
data.tar.gz: a551479f32400fb90df6e0e6bc15423c627d783149f9a17f675421cf3d3f9243
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cdf842b8a22047e92f2bd1adba9a953eefaf5bf86b255d54b6e8d22fc9f64545a45c03486348c9732539e34739e74fc43ce61c77d52772aeea93e640f0298ee
|
|
7
|
+
data.tar.gz: cd05c364405a72b9f1bfed2e539778fb301fa36f20e646f645755eb87e21a98261f5b9442a3c2b0938662a640f2708eb8886c0632dbe5118848e7616c48ad4c0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.1] - 2026-06-11
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **IDENTITY-07**: `OfficialRouteAttemptWriter` called non-existent `CallerIdentity.resolve_principal_id` and `CallerIdentity.resolve_identity_id`. Delegated to `OfficialRecordWriter.caller_identity_refs` which already implements full identity resolution with header fallbacks and DB lookups.
|
|
7
|
+
|
|
3
8
|
## [0.7.0] - 2026-06-09
|
|
4
9
|
|
|
5
10
|
### Fixed
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative '../helpers/caller_identity'
|
|
4
4
|
require_relative '../helpers/json'
|
|
5
5
|
require_relative '../helpers/persistence_logging'
|
|
6
|
+
require_relative '../writers/official_record_writer'
|
|
6
7
|
|
|
7
8
|
module Legion
|
|
8
9
|
module Extensions
|
|
@@ -66,6 +67,8 @@ module Legion
|
|
|
66
67
|
worker_id: runtime[:worker_id] || runtime[:worker],
|
|
67
68
|
node_id: runtime[:node_id] || runtime[:host_id],
|
|
68
69
|
identity_canonical_name: extract_canonical_name(body, headers),
|
|
70
|
+
identity_principal_id: extract_identity_principal_id(body, headers),
|
|
71
|
+
identity_id: extract_identity_id(body, headers),
|
|
69
72
|
offering_json: json_dump(offering),
|
|
70
73
|
runtime_json: json_dump(runtime),
|
|
71
74
|
capacity_json: json_dump(body[:capacity] || {}),
|
|
@@ -89,6 +92,44 @@ module Legion
|
|
|
89
92
|
raw.to_s
|
|
90
93
|
end
|
|
91
94
|
|
|
95
|
+
# Resolve identity_principal_id from AMQP headers or body.
|
|
96
|
+
# Uses OfficialRecordWriter.identity_tables_available? to check if FK resolution is possible.
|
|
97
|
+
def extract_identity_principal_id(body, headers)
|
|
98
|
+
raw = headers['x-legion-identity'] ||
|
|
99
|
+
body.dig(:identity, :identity) ||
|
|
100
|
+
body.dig(:identity, :canonical_name)
|
|
101
|
+
return nil unless raw && !raw.to_s.empty? # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
102
|
+
|
|
103
|
+
db = ::Legion::Data.connection
|
|
104
|
+
return nil unless Writers::OfficialRecordWriter.identity_tables_available?(db) # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
105
|
+
|
|
106
|
+
body_with_identity = body.merge(caller_identity: raw)
|
|
107
|
+
refs = Writers::OfficialRecordWriter.resolve_identity(db, body_with_identity)
|
|
108
|
+
refs[:principal_id]
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
handle_exception(e, level: :warn, handled: true, operation: 'write_registry_availability_record.identity_principal')
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Resolve identity_id from AMQP headers or body.
|
|
115
|
+
# Uses OfficialRecordWriter.identity_tables_available? to check if FK resolution is possible.
|
|
116
|
+
def extract_identity_id(body, headers)
|
|
117
|
+
raw = headers['x-legion-identity'] ||
|
|
118
|
+
body.dig(:identity, :identity) ||
|
|
119
|
+
body.dig(:identity, :canonical_name)
|
|
120
|
+
return nil unless raw && !raw.to_s.empty? # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
121
|
+
|
|
122
|
+
db = ::Legion::Data.connection
|
|
123
|
+
return nil unless Writers::OfficialRecordWriter.identity_tables_available?(db) # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
124
|
+
|
|
125
|
+
body_with_identity = body.merge(caller_identity: raw)
|
|
126
|
+
refs = Writers::OfficialRecordWriter.resolve_identity(db, body_with_identity)
|
|
127
|
+
refs[:identity_id]
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
handle_exception(e, level: :warn, handled: true, operation: 'write_registry_availability_record.identity')
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
92
133
|
def lane_key(lane)
|
|
93
134
|
if lane.is_a?(String)
|
|
94
135
|
lane
|
|
@@ -6,6 +6,7 @@ require 'legion/logging'
|
|
|
6
6
|
require 'legion/extensions/llm/responses/thinking_extractor'
|
|
7
7
|
require_relative '../helpers/json'
|
|
8
8
|
require_relative '../helpers/persistence_logging'
|
|
9
|
+
require_relative 'official_route_attempt_writer'
|
|
9
10
|
|
|
10
11
|
module Legion
|
|
11
12
|
module Extensions
|
|
@@ -30,6 +31,7 @@ module Legion
|
|
|
30
31
|
response = find_or_create_response(db, request, response_message, body)
|
|
31
32
|
link_response_message!(db, response_message, response)
|
|
32
33
|
metric = find_or_create_metric(db, request, response, body)
|
|
34
|
+
OfficialRouteAttemptWriter.write_route_attempts(db, request, response, body)
|
|
33
35
|
result = { result: :ok, request_id: request[:id], response_id: response[:id], metric_id: metric[:id] }
|
|
34
36
|
end
|
|
35
37
|
|
|
@@ -46,6 +48,7 @@ module Legion
|
|
|
46
48
|
request = find_or_create_request(db, conversation, nil, body)
|
|
47
49
|
response = find_or_create_response(db, request, nil, body)
|
|
48
50
|
metric = find_or_create_metric(db, request, response, body)
|
|
51
|
+
OfficialRouteAttemptWriter.write_route_attempts(db, request, response, body)
|
|
49
52
|
result = { result: :ok, request_id: request[:id], response_id: response[:id], metric_id: metric[:id] }
|
|
50
53
|
end
|
|
51
54
|
|
|
@@ -149,7 +152,7 @@ module Legion
|
|
|
149
152
|
budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
|
|
150
153
|
injected_tool_count: Array(body.dig(:audit, :injected_tools) || body[:injected_tools]).size,
|
|
151
154
|
context_tokens: resolve_context_tokens(body),
|
|
152
|
-
request_content_hash:
|
|
155
|
+
request_content_hash: resolve_request_content_hash(body),
|
|
153
156
|
curation_strategy: body[:curation_strategy] || body.dig(:audit, :curation_strategy),
|
|
154
157
|
tool_policy: body[:tool_policy] || body.dig(:audit, :tool_policy),
|
|
155
158
|
requested_at: recorded_at(body),
|
|
@@ -241,7 +244,7 @@ module Legion
|
|
|
241
244
|
error_category: body[:error_category] || body.dig(:error, :category),
|
|
242
245
|
error_code: body[:error_code] || body.dig(:error, :code),
|
|
243
246
|
error_message: body[:error_message] || body.dig(:error, :message),
|
|
244
|
-
response_content_hash:
|
|
247
|
+
response_content_hash: resolve_response_content_hash(body),
|
|
245
248
|
route_attempts: (body[:route_attempts] || body.dig(:audit, :route_attempts)).to_i,
|
|
246
249
|
escalation_chain_ref: body[:escalation_chain_ref],
|
|
247
250
|
identity_principal_id: caller_identity_refs(db, body)[:principal_id],
|
|
@@ -270,6 +273,7 @@ module Legion
|
|
|
270
273
|
update_if_missing(updates, existing, :finish_reason, finish_reason(body))
|
|
271
274
|
update_if_missing(updates, existing, :dispatch_path, body[:dispatch_path] || body[:tier])
|
|
272
275
|
update_if_missing(updates, existing, :identity_canonical_name, identity_canonical_name(body))
|
|
276
|
+
update_if_missing(updates, existing, :response_content_hash, resolve_response_content_hash(body))
|
|
273
277
|
|
|
274
278
|
vis = visible_response(body)
|
|
275
279
|
if vis
|
|
@@ -387,6 +391,7 @@ module Legion
|
|
|
387
391
|
update_if_missing(updates, existing, :runtime_caller_class, runtime_caller_class(body))
|
|
388
392
|
update_if_missing(updates, existing, :runtime_caller_client, runtime_caller_client(body))
|
|
389
393
|
update_if_missing(updates, existing, :identity_canonical_name, identity_canonical_name(body))
|
|
394
|
+
update_if_missing(updates, existing, :request_content_hash, resolve_request_content_hash(body))
|
|
390
395
|
|
|
391
396
|
request_json = request_payload(body) ? json_dump(request_payload(body)) : nil
|
|
392
397
|
if request_json
|
|
@@ -836,6 +841,22 @@ module Legion
|
|
|
836
841
|
Digest::SHA256.hexdigest(json_dump(content))[0..31]
|
|
837
842
|
end
|
|
838
843
|
|
|
844
|
+
# Prefer precomputed hash from emitter (A4: hash ships instead of raw content).
|
|
845
|
+
# Falls back to computing from raw content for backward compatibility.
|
|
846
|
+
def resolve_request_content_hash(body)
|
|
847
|
+
return body[:request_content_hash] if present?(body[:request_content_hash])
|
|
848
|
+
|
|
849
|
+
compute_content_hash(body.dig(:request, :content) || body.dig(:audit, :request_content))
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
# Prefer precomputed hash from emitter (A4: hash ships instead of raw content).
|
|
853
|
+
# Falls back to computing from raw content for backward compatibility.
|
|
854
|
+
def resolve_response_content_hash(body)
|
|
855
|
+
return body[:response_content_hash] if present?(body[:response_content_hash])
|
|
856
|
+
|
|
857
|
+
compute_content_hash(body[:response_content] || body.dig(:audit, :response_content))
|
|
858
|
+
end
|
|
859
|
+
|
|
839
860
|
def resolve_context_tokens(body)
|
|
840
861
|
raw = body[:tokens] || body[:audit] || body
|
|
841
862
|
val = raw[:input_tokens] || raw[:input] || raw[:context_tokens] || raw[:prompt_tokens]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require_relative '../helpers/json'
|
|
5
|
+
require_relative '../helpers/persistence_logging'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Llm
|
|
10
|
+
module Ledger
|
|
11
|
+
module Writers
|
|
12
|
+
module OfficialRouteAttemptWriter
|
|
13
|
+
extend Legion::Logging::Helper
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Persist route attempt details into llm_route_attempts table.
|
|
18
|
+
# Called from write_prompt/write_metering after the response row exists.
|
|
19
|
+
#
|
|
20
|
+
# Maps emitter keys to table columns:
|
|
21
|
+
# provider -> provider
|
|
22
|
+
# instance -> route_target
|
|
23
|
+
# model -> model_key
|
|
24
|
+
# operation -> operation
|
|
25
|
+
# dispatch_path -> dispatch_path
|
|
26
|
+
# status -> status
|
|
27
|
+
# failure_reason -> failure_reason
|
|
28
|
+
# idempotency_key -> idempotency_key
|
|
29
|
+
def write_route_attempts(db, request, response, body)
|
|
30
|
+
attempts = Array(body[:route_attempt_details])
|
|
31
|
+
return if attempts.empty?
|
|
32
|
+
|
|
33
|
+
attempts.each_with_index do |attempt, idx|
|
|
34
|
+
next unless attempt.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
attempt_no = (attempt[:attempt_no] || (idx + 1)).to_i
|
|
37
|
+
uuid = stable_uuid("#{request[:uuid]}:attempt:#{attempt_no}")
|
|
38
|
+
|
|
39
|
+
existing = db[:llm_route_attempts].where(uuid: uuid).first
|
|
40
|
+
next if existing
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
Helpers::PersistenceLogging.insert_row(
|
|
44
|
+
db,
|
|
45
|
+
:llm_route_attempts,
|
|
46
|
+
{
|
|
47
|
+
uuid: uuid,
|
|
48
|
+
message_inference_request_id: request[:id],
|
|
49
|
+
message_inference_response_id: response[:id],
|
|
50
|
+
attempt_no: attempt_no,
|
|
51
|
+
provider: attempt[:provider] || body[:provider],
|
|
52
|
+
model_key: attempt[:model] || attempt[:model_key] || body[:model_id],
|
|
53
|
+
tier: attempt[:tier] || body[:tier],
|
|
54
|
+
route_target: attempt[:route_target] || attempt[:instance],
|
|
55
|
+
status: (attempt[:status] || 'success').to_s,
|
|
56
|
+
failure_reason: attempt[:failure_reason],
|
|
57
|
+
latency_ms: (attempt[:latency_ms] || 0).to_i,
|
|
58
|
+
operation: attempt[:operation],
|
|
59
|
+
dispatch_path: attempt[:dispatch_path],
|
|
60
|
+
idempotency_key: attempt[:idempotency_key],
|
|
61
|
+
started_at: attempt[:started_at],
|
|
62
|
+
ended_at: attempt[:ended_at],
|
|
63
|
+
identity_principal_id: resolve_identity_principal_id(db, body),
|
|
64
|
+
identity_id: resolve_identity_id(db, body),
|
|
65
|
+
identity_canonical_name: identity_canonical_name(body),
|
|
66
|
+
inserted_at: Time.now.utc
|
|
67
|
+
},
|
|
68
|
+
operation: 'official_route_attempt_writer.insert'
|
|
69
|
+
)
|
|
70
|
+
rescue Sequel::UniqueConstraintViolation => e
|
|
71
|
+
log.debug("[ledger] route_attempt collision resolved uuid=#{uuid} error=#{e.class}")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def resolve_identity_principal_id(db, body)
|
|
77
|
+
OfficialRecordWriter.caller_identity_refs(db, body)[:principal_id]
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
handle_exception(e, level: :warn, handled: true, operation: 'route_attempt_writer.identity_principal')
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def resolve_identity_id(db, body)
|
|
84
|
+
OfficialRecordWriter.caller_identity_refs(db, body)[:identity_id]
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
handle_exception(e, level: :warn, handled: true, operation: 'route_attempt_writer.identity')
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def identity_canonical_name(body)
|
|
91
|
+
raw = body.dig(:identity, :canonical_name) ||
|
|
92
|
+
body.dig(:identity, :identity) ||
|
|
93
|
+
body[:caller_identity]
|
|
94
|
+
return nil unless raw && !raw.to_s.empty?
|
|
95
|
+
|
|
96
|
+
raw.to_s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def stable_uuid(value)
|
|
100
|
+
raw = value.to_s
|
|
101
|
+
return raw if raw.length <= 36
|
|
102
|
+
|
|
103
|
+
hex = Digest::SHA256.hexdigest(raw)[0, 32]
|
|
104
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
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.7.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -246,6 +246,7 @@ files:
|
|
|
246
246
|
- lib/legion/extensions/llm/ledger/writers/official_metering_writer.rb
|
|
247
247
|
- lib/legion/extensions/llm/ledger/writers/official_prompt_writer.rb
|
|
248
248
|
- lib/legion/extensions/llm/ledger/writers/official_record_writer.rb
|
|
249
|
+
- lib/legion/extensions/llm/ledger/writers/official_route_attempt_writer.rb
|
|
249
250
|
- lib/lex-llm-ledger.rb
|
|
250
251
|
homepage: https://github.com/LegionIO/lex-llm-ledger
|
|
251
252
|
licenses:
|