lex-llm-gateway 0.2.6 → 0.2.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6598c8eb88e900cf46910dcb33c93a51e9013e4a56795ea7a94d6aa892f8a550
4
- data.tar.gz: 7267fd499d23a63afcd45bd8780080bac45a045f917f8b60ec04181d804e5e94
3
+ metadata.gz: 893662bdc056fd78efe87c3fa3874746bc1284d436b3233a49bed368370506b2
4
+ data.tar.gz: c62e301286b6690a66c12445990424bb811d9faef6783986a4e93dca4c015672
5
5
  SHA512:
6
- metadata.gz: 0f35e042cd9c333fecae0ba9896bb79e131f9ece92af6a68e03920a6eaad652ee3a4fe1a38f1906d1378186290c66591125bf610f707896cc0c09b80ccb1dfaf
7
- data.tar.gz: bb641bd9d3efcd68dfbe0c5d818a72935c6c3c89e76a68dee56c33dc8a5a2039270c0eed875245cadf93dbe1b1f1c39b6e366637c35b19f20dfe5a411e0aa1b6
6
+ metadata.gz: 9ad0d8c1dcf0df6b447c1a12653bf7799ef95f6c8b1fba8167cd54652f7ef7aadc6414fe03397ecb3307564b48b06fd6b1ce7604d9d1b6d72886dfe4608b422a
7
+ data.tar.gz: c9d3c90429a44efa0cdb15309161d67182f8136815a119e4a4f4d76b014d0e5982bc0e161bab3db8423d69c68950a5f6cf5ef30fec176e567db2bc5d4a87fab3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.8] - 2026-03-23
4
+
5
+ ### Added
6
+ - MeteringWriter now passes trace context fields (status, event_type, extension, runner_function) to metering_records
7
+ - require_relative for CostEstimator in MeteringWriter to fix standalone require
8
+
9
+ ### Changed
10
+ - identity_fields split into identity_fields + trace_fields for rubocop compliance
11
+
12
+ ## [0.2.7] - 2026-03-23
13
+
14
+ ### Added
15
+ - CostEstimator helper: estimates cost_usd from model pricing table and token counts
16
+ - Pricing data for 14 models across Anthropic, OpenAI, and Google providers
17
+ - Fuzzy model matching for versioned/prefixed model IDs
18
+ - MeteringWriter now computes and persists cost_usd on every metering record
19
+ - 10 new CostEstimator specs covering exact pricing, zero tokens, unknown models, fuzzy matching
20
+
3
21
  ## [0.2.6] - 2026-03-23
4
22
 
5
23
  ### Added
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module LLM
6
+ module Gateway
7
+ module Helpers
8
+ module CostEstimator
9
+ # Prices per 1M tokens [input, output] in USD
10
+ # Source: published API pricing as of 2026-03
11
+ PRICING = {
12
+ 'claude-opus-4-6' => [15.0, 75.0],
13
+ 'claude-sonnet-4-6' => [3.0, 15.0],
14
+ 'claude-haiku-4-5' => [0.80, 4.0],
15
+ 'claude-3-5-sonnet' => [3.0, 15.0],
16
+ 'claude-3-haiku' => [0.25, 1.25],
17
+ 'gpt-4o' => [2.50, 10.0],
18
+ 'gpt-4o-mini' => [0.15, 0.60],
19
+ 'gpt-4-turbo' => [10.0, 30.0],
20
+ 'o3' => [10.0, 40.0],
21
+ 'o3-mini' => [1.10, 4.40],
22
+ 'o4-mini' => [1.10, 4.40],
23
+ 'gemini-2.5-pro' => [1.25, 10.0],
24
+ 'gemini-2.5-flash' => [0.15, 0.60],
25
+ 'gemini-2.0-flash' => [0.10, 0.40]
26
+ }.freeze
27
+
28
+ DEFAULT_PRICE = [1.0, 3.0].freeze
29
+
30
+ module_function
31
+
32
+ def estimate(model_id:, input_tokens: 0, output_tokens: 0, **)
33
+ price = resolve_price(model_id)
34
+ input_cost = (input_tokens.to_i / 1_000_000.0) * price[0]
35
+ output_cost = (output_tokens.to_i / 1_000_000.0) * price[1]
36
+ (input_cost + output_cost).round(6)
37
+ end
38
+
39
+ def resolve_price(model_id)
40
+ return DEFAULT_PRICE unless model_id
41
+
42
+ normalized = model_id.to_s.downcase
43
+ PRICING[normalized] || fuzzy_match(normalized) || DEFAULT_PRICE
44
+ end
45
+
46
+ def fuzzy_match(normalized)
47
+ PRICING.each do |key, price|
48
+ return price if normalized.include?(key) || key.include?(normalized)
49
+ end
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../helpers/cost_estimator'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module LLM
@@ -23,7 +25,10 @@ module Legion
23
25
  end
24
26
 
25
27
  def normalize_record(payload)
26
- identity_fields(payload).merge(metric_fields(payload))
28
+ identity_fields(payload)
29
+ .merge(trace_fields(payload))
30
+ .merge(metric_fields(payload))
31
+ .merge(cost_field(payload))
27
32
  end
28
33
 
29
34
  def identity_fields(payload)
@@ -37,6 +42,15 @@ module Legion
37
42
  }
38
43
  end
39
44
 
45
+ def trace_fields(payload)
46
+ {
47
+ status: payload[:status],
48
+ event_type: payload[:event_type],
49
+ extension: payload[:extension],
50
+ runner_function: payload[:runner_function]
51
+ }
52
+ end
53
+
40
54
  def metric_fields(payload)
41
55
  {
42
56
  input_tokens: payload[:input_tokens].to_i,
@@ -47,6 +61,15 @@ module Legion
47
61
  wall_clock_ms: payload[:wall_clock_ms].to_i
48
62
  }
49
63
  end
64
+
65
+ def cost_field(payload)
66
+ cost = payload[:cost_usd] || Helpers::CostEstimator.estimate(
67
+ model_id: payload[:model_id],
68
+ input_tokens: payload[:input_tokens].to_i,
69
+ output_tokens: payload[:output_tokens].to_i
70
+ )
71
+ { cost_usd: cost.to_f }
72
+ end
50
73
  end
51
74
  end
52
75
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module LLM
6
6
  module Gateway
7
- VERSION = '0.2.6'
7
+ VERSION = '0.2.8'
8
8
  end
9
9
  end
10
10
  end
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.6
4
+ version: 0.2.8
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/cost_estimator.rb
185
186
  - lib/legion/extensions/llm/gateway/helpers/reply_dispatcher.rb
186
187
  - lib/legion/extensions/llm/gateway/helpers/rpc.rb
187
188
  - lib/legion/extensions/llm/gateway/runners/fleet.rb