open_router_enhanced 2.0.1 → 2.1.0

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: d443d948a07c5b55d6366e135354b2faa07a8edc38cb2791237a6a4a92bd229a
4
- data.tar.gz: 37a93b36720b58bf1ee1c4809aa4d54e4d29e06ba27a63c9285a78fa7074eb66
3
+ metadata.gz: 1f08650282b1232abfa779330c8f8ec9aad7bf6a984fd31e1a74ce1f3cad6962
4
+ data.tar.gz: e41ce6204abde21a34dd6c2976801666b8f4b9339cf81c845861f3837c15097c
5
5
  SHA512:
6
- metadata.gz: 78fb6b74df5a7cb901ecac23fe503c667035e4794d6e7d9b97d4ad703e1f0a40347bd3274b86ba13c35501c8afc0b3c29ec5cb7b632eb93013af5a5328403285
7
- data.tar.gz: 51d704a3035cf8211ac0d9533931d0b1a9bc9ebe49d21f192dd49b7dbb423eadcbf5adcc7b99d3212002947f2646337122e99660a589fa54be22ab3a356555eb
6
+ metadata.gz: 1d15f1e25db721734f0f9d2e43d01f6546af170096e65253c1c8438c3362484d6bad28f36669e2b8c34856ca297bb07dc1ffb18c7dc4442e2fe8c93ad79474b1
7
+ data.tar.gz: 2db9c96b66dcb0eae4b03f463db956192b38ef7444c879f7fe72a767e31c378d1ed702ee38a4c3b76e187945e6d942ac5dcbf7e7733fe8621315bee729e74940
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- open_router_enhanced (2.0.0)
4
+ open_router_enhanced (2.0.1)
5
5
  activesupport (>= 6.0, < 9.0)
6
6
  dotenv (>= 2.0, < 4.0)
7
7
  faraday (>= 1.0, < 3.0)
data/Rakefile CHANGED
@@ -30,6 +30,17 @@ task ci: %i[spec_all rubocop]
30
30
 
31
31
  # Model exploration tasks
32
32
  namespace :models do
33
+ desc "Fetch fresh model data from OpenRouter API and update local cache"
34
+ task :update do
35
+ require_relative "lib/open_router"
36
+
37
+ print "Fetching models from OpenRouter API..."
38
+ OpenRouter::ModelRegistry.refresh!
39
+ count = OpenRouter::ModelRegistry.all_models.size
40
+ puts " done. #{count} models cached."
41
+ end
42
+
43
+
33
44
  desc "Display summary of available models"
34
45
  task :summary do
35
46
  require_relative "lib/open_router"
@@ -59,18 +70,18 @@ namespace :models do
59
70
  end
60
71
 
61
72
  # Cost analysis
62
- input_costs = models.values.map { |spec| spec[:cost_per_1k_tokens][:input] }.compact.sort
63
- output_costs = models.values.map { |spec| spec[:cost_per_1k_tokens][:output] }.compact.sort
73
+ input_costs = models.values.map { |spec| spec[:cost_per_token][:input] }.compact.sort
74
+ output_costs = models.values.map { |spec| spec[:cost_per_token][:output] }.compact.sort
64
75
 
65
- puts "\nšŸ’° Cost Analysis (per 1k tokens):"
76
+ puts "\nšŸ’° Cost Analysis (per million tokens):"
66
77
  puts " Input tokens:"
67
- puts " Min: $#{format("%.6f", input_costs.min)}"
68
- puts " Max: $#{format("%.6f", input_costs.max)}"
69
- puts " Median: $#{format("%.6f", input_costs[input_costs.size / 2])}"
78
+ puts " Min: $#{format("%.4f", input_costs.min * 1_000_000)}"
79
+ puts " Max: $#{format("%.4f", input_costs.max * 1_000_000)}"
80
+ puts " Median: $#{format("%.4f", input_costs[input_costs.size / 2] * 1_000_000)}"
70
81
  puts " Output tokens:"
71
- puts " Min: $#{format("%.6f", output_costs.min)}"
72
- puts " Max: $#{format("%.6f", output_costs.max)}"
73
- puts " Median: $#{format("%.6f", output_costs[output_costs.size / 2])}"
82
+ puts " Min: $#{format("%.4f", output_costs.min * 1_000_000)}"
83
+ puts " Max: $#{format("%.4f", output_costs.max * 1_000_000)}"
84
+ puts " Median: $#{format("%.4f", output_costs[output_costs.size / 2] * 1_000_000)}"
74
85
 
75
86
  # Context length analysis
76
87
  context_lengths = models.values.map { |spec| spec[:context_length] }.compact.sort
@@ -269,8 +280,8 @@ namespace :models do
269
280
  def self.display_model_info(model_id, specs, index)
270
281
  puts "#{(index + 1).to_s.rjust(3)}. #{model_id}"
271
282
  puts " Name: #{specs[:name]}" if specs[:name]
272
- puts " Cost: $#{format("%.6f", specs[:cost_per_1k_tokens][:input])}/1k input, " \
273
- "$#{format("%.6f", specs[:cost_per_1k_tokens][:output])}/1k output"
283
+ cpm = OpenRouter::ModelRegistry.cost_per_million(model_id)
284
+ puts " Cost: $#{format("%.4f", cpm[:input])}/M input, $#{format("%.4f", cpm[:output])}/M output"
274
285
  puts " Context: #{format_number_with_commas(specs[:context_length])} tokens"
275
286
  puts " Capabilities: #{specs[:capabilities].join(", ")}"
276
287
  puts " Tier: #{specs[:performance_tier]}"
@@ -318,17 +329,17 @@ namespace :models do
318
329
  def self.sort_by_strategy(candidates, strategy)
319
330
  case strategy
320
331
  when :cost
321
- candidates.sort_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
332
+ candidates.sort_by { |_, specs| specs[:cost_per_token][:input] }
322
333
  when :performance
323
334
  candidates.sort_by do |_, specs|
324
- [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_1k_tokens][:input]]
335
+ [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_token][:input]]
325
336
  end
326
337
  when :latest
327
338
  candidates.sort_by { |_, specs| -(specs[:created_at] || 0).to_i }
328
339
  when :context
329
340
  candidates.sort_by { |_, specs| -(specs[:context_length] || 0).to_i }
330
341
  else
331
- candidates.sort_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
342
+ candidates.sort_by { |_, specs| specs[:cost_per_token][:input] }
332
343
  end
333
344
  end
334
345
  end
@@ -139,7 +139,7 @@ module OpenRouter
139
139
 
140
140
  models[model_id] = {
141
141
  name: model_data["name"],
142
- cost_per_1k_tokens: {
142
+ cost_per_token: {
143
143
  input: model_data.dig("pricing", "prompt").to_f,
144
144
  output: model_data.dig("pricing", "completion").to_f
145
145
  },
@@ -262,12 +262,30 @@ module OpenRouter
262
262
  model_info = get_model_info(model)
263
263
  return 0 unless model_info
264
264
 
265
- input_cost = (input_tokens / 1000.0) * model_info[:cost_per_1k_tokens][:input]
266
- output_cost = (output_tokens / 1000.0) * model_info[:cost_per_1k_tokens][:output]
265
+ input_cost = input_tokens * model_info[:cost_per_token][:input]
266
+ output_cost = output_tokens * model_info[:cost_per_token][:output]
267
267
 
268
268
  input_cost + output_cost
269
269
  end
270
270
 
271
+ # Cost per 1,000 tokens — { input: Float, output: Float } or nil
272
+ def cost_per_thousand(model)
273
+ info = get_model_info(model)
274
+ return nil unless info
275
+
276
+ { input: info[:cost_per_token][:input] * 1_000,
277
+ output: info[:cost_per_token][:output] * 1_000 }
278
+ end
279
+
280
+ # Cost per 1,000,000 tokens — { input: Float, output: Float } or nil
281
+ def cost_per_million(model)
282
+ info = get_model_info(model)
283
+ return nil unless info
284
+
285
+ { input: info[:cost_per_token][:input] * 1_000_000,
286
+ output: info[:cost_per_token][:output] * 1_000_000 }
287
+ end
288
+
271
289
  private
272
290
 
273
291
  # Check if model specs meet the given requirements
@@ -279,11 +297,11 @@ module OpenRouter
279
297
  end
280
298
 
281
299
  # Check cost requirements
282
- if requirements[:max_input_cost] && (specs[:cost_per_1k_tokens][:input] > requirements[:max_input_cost])
300
+ if requirements[:max_input_cost] && (specs[:cost_per_token][:input] > requirements[:max_input_cost])
283
301
  return false
284
302
  end
285
303
 
286
- if requirements[:max_output_cost] && (specs[:cost_per_1k_tokens][:output] > requirements[:max_output_cost])
304
+ if requirements[:max_output_cost] && (specs[:cost_per_token][:output] > requirements[:max_output_cost])
287
305
  return false
288
306
  end
289
307
 
@@ -334,7 +352,7 @@ module OpenRouter
334
352
  def calculate_model_cost(specs, _requirements)
335
353
  # Simple cost calculation for sorting - could be made more sophisticated
336
354
  # For now, just use input token cost as the primary metric
337
- specs[:cost_per_1k_tokens][:input]
355
+ specs[:cost_per_token][:input]
338
356
  end
339
357
 
340
358
  # Set up cleanup hook to manage cache size
@@ -343,7 +343,7 @@ module OpenRouter
343
343
  all_candidates = filter_by_providers(ModelRegistry.all_models)
344
344
  return nil if all_candidates.empty?
345
345
 
346
- all_candidates.min_by { |_, specs| specs[:cost_per_1k_tokens][:input] }&.first
346
+ all_candidates.min_by { |_, specs| specs[:cost_per_token][:input] }&.first
347
347
  end
348
348
 
349
349
  # Get detailed information about the current selection criteria
@@ -426,18 +426,18 @@ module OpenRouter
426
426
  def apply_strategy_sorting(candidates)
427
427
  case @strategy
428
428
  when :cost
429
- candidates.min_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
429
+ candidates.min_by { |_, specs| specs[:cost_per_token][:input] }
430
430
  when :performance
431
431
  # Prefer premium tier, then by cost within tier
432
432
  candidates.min_by do |_, specs|
433
- [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_1k_tokens][:input]]
433
+ [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_token][:input]]
434
434
  end
435
435
  when :latest
436
436
  candidates.max_by { |_, specs| (specs[:created_at] || 0).to_i }
437
437
  when :context
438
438
  candidates.max_by { |_, specs| (specs[:context_length] || 0).to_i }
439
439
  else
440
- candidates.min_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
440
+ candidates.min_by { |_, specs| specs[:cost_per_token][:input] }
441
441
  end
442
442
  end
443
443
 
@@ -445,17 +445,17 @@ module OpenRouter
445
445
  def apply_strategy_sorting_all(candidates)
446
446
  case @strategy
447
447
  when :cost
448
- candidates.sort_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
448
+ candidates.sort_by { |_, specs| specs[:cost_per_token][:input] }
449
449
  when :performance
450
450
  candidates.sort_by do |_, specs|
451
- [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_1k_tokens][:input]]
451
+ [specs[:performance_tier] == :premium ? 0 : 1, specs[:cost_per_token][:input]]
452
452
  end
453
453
  when :latest
454
454
  candidates.sort_by { |_, specs| -(specs[:created_at] || 0).to_i }
455
455
  when :context
456
456
  candidates.sort_by { |_, specs| -(specs[:context_length] || 0).to_i }
457
457
  else
458
- candidates.sort_by { |_, specs| specs[:cost_per_1k_tokens][:input] }
458
+ candidates.sort_by { |_, specs| specs[:cost_per_token][:input] }
459
459
  end
460
460
  end
461
461
  end
@@ -110,7 +110,12 @@ module OpenRouter
110
110
  end
111
111
  end
112
112
 
113
- @structured_output ||= result
113
+ # Use a flag rather than ||= so nil results don't trigger re-parsing on every call
114
+ unless @structured_output_computed
115
+ @structured_output = result
116
+ @structured_output_computed = true
117
+ end
118
+ @structured_output
114
119
  when :gentle
115
120
  # New gentle mode: best-effort parsing, no healing, no validation
116
121
  content_to_parse = @forced_extraction ? extract_json_from_text(content) : content
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenRouter
4
- VERSION = "2.0.1"
4
+ VERSION = "2.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open_router_enhanced
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Stiens