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 +4 -4
- data/Gemfile.lock +1 -1
- data/Rakefile +25 -14
- data/lib/open_router/model_registry.rb +24 -6
- data/lib/open_router/model_selector.rb +7 -7
- data/lib/open_router/response.rb +6 -1
- data/lib/open_router/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f08650282b1232abfa779330c8f8ec9aad7bf6a984fd31e1a74ce1f3cad6962
|
|
4
|
+
data.tar.gz: e41ce6204abde21a34dd6c2976801666b8f4b9339cf81c845861f3837c15097c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d15f1e25db721734f0f9d2e43d01f6546af170096e65253c1c8438c3362484d6bad28f36669e2b8c34856ca297bb07dc1ffb18c7dc4442e2fe8c93ad79474b1
|
|
7
|
+
data.tar.gz: 2db9c96b66dcb0eae4b03f463db956192b38ef7444c879f7fe72a767e31c378d1ed702ee38a4c3b76e187945e6d942ac5dcbf7e7733fe8621315bee729e74940
|
data/Gemfile.lock
CHANGED
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[:
|
|
63
|
-
output_costs = models.values.map { |spec| spec[:
|
|
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
|
|
76
|
+
puts "\nš° Cost Analysis (per million tokens):"
|
|
66
77
|
puts " Input tokens:"
|
|
67
|
-
puts " Min: $#{format("%.
|
|
68
|
-
puts " Max: $#{format("%.
|
|
69
|
-
puts " Median: $#{format("%.
|
|
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("%.
|
|
72
|
-
puts " Max: $#{format("%.
|
|
73
|
-
puts " Median: $#{format("%.
|
|
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
|
-
|
|
273
|
-
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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
|
-
|
|
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 =
|
|
266
|
-
output_cost =
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
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[:
|
|
458
|
+
candidates.sort_by { |_, specs| specs[:cost_per_token][:input] }
|
|
459
459
|
end
|
|
460
460
|
end
|
|
461
461
|
end
|
data/lib/open_router/response.rb
CHANGED
|
@@ -110,7 +110,12 @@ module OpenRouter
|
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
|
|
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
|
data/lib/open_router/version.rb
CHANGED