Class: OpenRouter::ModelSelector

Inherits:
Object
  • Object
show all
Defined in:
lib/open_router/model_selector.rb

Overview

ModelSelector provides a fluent DSL interface for selecting the best AI model based on specific requirements. It wraps the ModelRegistry functionality with an intuitive, chainable API.

Examples:

Basic usage

selector = OpenRouter::ModelSelector.new
model = selector.optimize_for(:cost)
                .require(:function_calling, :vision)
                .within_budget(max_cost: 0.01)
                .choose

With provider preferences

model = OpenRouter::ModelSelector.new
                                .prefer_providers("anthropic", "openai")
                                .require(:function_calling)
                                .choose

With fallback selection

models = OpenRouter::ModelSelector.new
                                  .optimize_for(:performance)
                                  .choose_with_fallbacks(limit: 3)

Constant Summary collapse

STRATEGIES =

Available optimization strategies

{
  cost: { sort_by: :cost, pick_newer: false },
  performance: { sort_by: :performance, pick_newer: false },
  latest: { sort_by: :date, pick_newer: true },
  context: { sort_by: :context_length, pick_newer: false }
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(requirements: {}, strategy: :cost, provider_preferences: {}, fallback_options: {}) ⇒ ModelSelector

Returns a new instance of ModelSelector.



38
39
40
41
42
43
# File 'lib/open_router/model_selector.rb', line 38

def initialize(requirements: {}, strategy: :cost, provider_preferences: {}, fallback_options: {})
  @requirements = requirements.dup
  @strategy = strategy
  @provider_preferences = provider_preferences.dup
  @fallback_options = fallback_options.dup
end

Instance Method Details

#avoid_patterns(*patterns) ⇒ ModelSelector

Avoid models matching specific patterns

Examples:

selector.avoid_patterns("*-free", "*-preview")

Parameters:

  • patterns (Array<String>)

    Glob patterns to avoid

Returns:



226
227
228
229
230
231
232
233
234
235
236
# File 'lib/open_router/model_selector.rb', line 226

def avoid_patterns(*patterns)
  new_provider_preferences = @provider_preferences.dup
  new_provider_preferences[:avoided_patterns] = patterns.flatten

  self.class.new(
    requirements: @requirements,
    strategy: @strategy,
    provider_preferences: new_provider_preferences,
    fallback_options: @fallback_options
  )
end

#avoid_providers(*providers) ⇒ ModelSelector

Avoid specific providers (blacklist)

Examples:

selector.avoid_providers("google")

Parameters:

  • providers (Array<String>)

    Provider names to avoid

Returns:



207
208
209
210
211
212
213
214
215
216
217
# File 'lib/open_router/model_selector.rb', line 207

def avoid_providers(*providers)
  new_provider_preferences = @provider_preferences.dup
  new_provider_preferences[:avoided] = providers.flatten

  self.class.new(
    requirements: @requirements,
    strategy: @strategy,
    provider_preferences: new_provider_preferences,
    fallback_options: @fallback_options
  )
end

#choose(return_specs: false) ⇒ String, ...

Select the best model based on configured requirements

Examples:

model = selector.choose
model, specs = selector.choose(return_specs: true)

Parameters:

  • return_specs (Boolean) (defaults to: false)

    Whether to return model specs along with model ID

Returns:

  • (String, Array)

    Model ID or [model_id, specs] tuple if return_specs is true

  • (nil)

    If no models match requirements



266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/open_router/model_selector.rb', line 266

def choose(return_specs: false)
  # Get all models that meet basic requirements
  candidates = filter_by_providers(ModelRegistry.models_meeting_requirements(@requirements))

  return nil if candidates.empty?

  # Apply strategy-specific sorting
  best_match = apply_strategy_sorting(candidates)

  return nil unless best_match

  return_specs ? best_match : best_match.first
end

#choose_with_fallbackString?

Choose with graceful degradation if no models meet all requirements

Examples:

model = selector.choose_with_fallback

Returns:

  • (String, nil)

    Model ID or nil if no models available at all



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/open_router/model_selector.rb', line 307

def choose_with_fallback
  # Try with all requirements first
  result = choose
  return result if result

  # Try dropping least important requirements progressively
  fallback_requirements = @requirements.dup

  # Drop requirements in order of importance (least to most important)
  i[
    released_after_date
    performance_tier
    max_output_cost
    min_context_length
    max_input_cost
  ].each do |requirement|
    next unless fallback_requirements.key?(requirement)

    fallback_requirements.delete(requirement)
    candidates = filter_by_providers(ModelRegistry.models_meeting_requirements(fallback_requirements))

    unless candidates.empty?
      result = apply_strategy_sorting(candidates)
      return result&.first
    end
  end

  # Last resort: just pick any model that meets capability requirements
  if fallback_requirements[:capabilities]
    basic_requirements = { capabilities: fallback_requirements[:capabilities] }
    candidates = filter_by_providers(ModelRegistry.models_meeting_requirements(basic_requirements))
    result = apply_strategy_sorting(candidates) unless candidates.empty?
    return result&.first if result
  end

  # Final fallback: cheapest available model
  all_candidates = filter_by_providers(ModelRegistry.all_models)
  return nil if all_candidates.empty?

  all_candidates.min_by { |_, specs| specs[:cost_per_1k_tokens][:input] }&.first
end

#choose_with_fallbacks(limit: 3) ⇒ Array<String>, Array

Select the best model with fallback options

Examples:

models = selector.choose_with_fallbacks(limit: 3)
# => ["gpt-4", "claude-3-opus", "gpt-3.5-turbo"]

Parameters:

  • limit (Integer) (defaults to: 3)

    Maximum number of models to return (including primary choice)

Returns:

  • (Array<String>)

    Array of model IDs in order of preference

  • (Array)

    Empty array if no models match requirements



289
290
291
292
293
294
295
296
297
298
299
# File 'lib/open_router/model_selector.rb', line 289

def choose_with_fallbacks(limit: 3)
  candidates = filter_by_providers(ModelRegistry.models_meeting_requirements(@requirements))

  return [] if candidates.empty?

  # Apply strategy-specific sorting to get ordered list
  sorted_candidates = apply_strategy_sorting_all(candidates)

  # Return up to `limit` models
  sorted_candidates.first(limit).map(&:first)
end

#estimate_cost(model, input_tokens: 1000, output_tokens: 1000) ⇒ Float

Estimate cost for a given model with expected token usage

Parameters:

  • model (String)

    Model ID

  • input_tokens (Integer) (defaults to: 1000)

    Expected input tokens

  • output_tokens (Integer) (defaults to: 1000)

    Expected output tokens

Returns:

  • (Float)

    Estimated cost in dollars



367
368
369
# File 'lib/open_router/model_selector.rb', line 367

def estimate_cost(model, input_tokens: 1000, output_tokens: 1000)
  ModelRegistry.calculate_estimated_cost(model, input_tokens:, output_tokens:)
end

#min_context(tokens) ⇒ ModelSelector

Set minimum context length requirement

Examples:

selector.min_context(100_000)  # Require at least 100k context

Parameters:

  • tokens (Integer)

    Minimum context length in tokens

Returns:



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/open_router/model_selector.rb', line 130

def min_context(tokens)
  new_requirements = @requirements.dup
  new_requirements[:min_context_length] = tokens

  self.class.new(
    requirements: new_requirements,
    strategy: @strategy,
    provider_preferences: @provider_preferences,
    fallback_options: @fallback_options
  )
end

#newer_than(date) ⇒ ModelSelector

Require models released after a specific date

Examples:

selector.newer_than(Date.new(2024, 1, 1))
selector.newer_than(Time.now - 30.days)

Parameters:

  • date (Date, Time, Integer)

    Cutoff date (Date/Time object or Unix timestamp)

Returns:



150
151
152
153
154
155
156
157
158
159
160
# File 'lib/open_router/model_selector.rb', line 150

def newer_than(date)
  new_requirements = @requirements.dup
  new_requirements[:released_after_date] = date

  self.class.new(
    requirements: new_requirements,
    strategy: @strategy,
    provider_preferences: @provider_preferences,
    fallback_options: @fallback_options
  )
end

#optimize_for(strategy) ⇒ ModelSelector

Set the optimization strategy for model selection

Examples:

selector.optimize_for(:cost)      # Choose cheapest model
selector.optimize_for(:performance) # Choose highest performance tier
selector.optimize_for(:latest)    # Choose newest model
selector.optimize_for(:context)   # Choose model with largest context window

Parameters:

  • strategy (Symbol)

    The optimization strategy (:cost, :performance, :latest, :context)

Returns:

Raises:

  • (ArgumentError)

    If strategy is not supported



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/open_router/model_selector.rb', line 56

def optimize_for(strategy)
  unless STRATEGIES.key?(strategy)
    raise ArgumentError,
          "Unknown strategy: #{strategy}. Available: #{STRATEGIES.keys.join(", ")}"
  end

  new_requirements = @requirements.dup

  # Apply strategy-specific requirements
  case strategy
  when :performance
    new_requirements[:performance_tier] = :premium
  when :latest
    new_requirements[:pick_newer] = true
  end

  self.class.new(
    requirements: new_requirements,
    strategy:,
    provider_preferences: @provider_preferences,
    fallback_options: @fallback_options
  )
end

#prefer_providers(*providers) ⇒ ModelSelector

Set provider preferences (soft preference - won’t exclude other providers)

Examples:

selector.prefer_providers("anthropic", "openai")

Parameters:

  • providers (Array<String>)

    Preferred provider names in order of preference

Returns:



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/open_router/model_selector.rb', line 169

def prefer_providers(*providers)
  new_provider_preferences = @provider_preferences.dup
  new_provider_preferences[:preferred] = providers.flatten

  self.class.new(
    requirements: @requirements,
    strategy: @strategy,
    provider_preferences: new_provider_preferences,
    fallback_options: @fallback_options
  )
end

#require(*capabilities) ⇒ ModelSelector

Require specific capabilities from the selected model

Examples:

selector.require(:function_calling)
selector.require(:function_calling, :vision, :structured_outputs)

Parameters:

  • capabilities (Array<Symbol>)

    Required capabilities

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/open_router/model_selector.rb', line 88

def require(*capabilities)
  new_requirements = @requirements.dup
  new_requirements[:capabilities] = Array(new_requirements[:capabilities]) + capabilities
  new_requirements[:capabilities].uniq!

  self.class.new(
    requirements: new_requirements,
    strategy: @strategy,
    provider_preferences: @provider_preferences,
    fallback_options: @fallback_options
  )
end

#require_providers(*providers) ⇒ ModelSelector

Require specific providers (hard filter - only these providers)

Examples:

selector.require_providers("anthropic")

Parameters:

  • providers (Array<String>)

    Required provider names

Returns:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/open_router/model_selector.rb', line 188

def require_providers(*providers)
  new_provider_preferences = @provider_preferences.dup
  new_provider_preferences[:required] = providers.flatten

  self.class.new(
    requirements: @requirements,
    strategy: @strategy,
    provider_preferences: new_provider_preferences,
    fallback_options: @fallback_options
  )
end

#selection_criteriaHash

Get detailed information about the current selection criteria

Returns:

  • (Hash)

    Hash containing requirements, strategy, and provider preferences



352
353
354
355
356
357
358
359
# File 'lib/open_router/model_selector.rb', line 352

def selection_criteria
  {
    requirements: deep_dup(@requirements),
    strategy: @strategy,
    provider_preferences: deep_dup(@provider_preferences),
    fallback_options: deep_dup(@fallback_options)
  }
end

#with_fallbacks(max: 3, strategy: :similar) ⇒ ModelSelector

Configure fallback behavior

Examples:

selector.with_fallbacks(max: 3, strategy: :similar)

Parameters:

  • max_fallbacks (Integer)

    Maximum number of fallback models to include

  • strategy (Symbol) (defaults to: :similar)

    Fallback strategy (:similar, :cheaper, :different_provider)

Returns:



246
247
248
249
250
251
252
253
254
255
# File 'lib/open_router/model_selector.rb', line 246

def with_fallbacks(max: 3, strategy: :similar)
  new_fallback_options = { max_fallbacks: max, strategy: }

  self.class.new(
    requirements: @requirements,
    strategy: @strategy,
    provider_preferences: @provider_preferences,
    fallback_options: new_fallback_options
  )
end

#within_budget(max_cost: nil, max_output_cost: nil) ⇒ ModelSelector

Set budget constraints for model selection

Examples:

selector.within_budget(max_cost: 0.01)
selector.within_budget(max_cost: 0.01, max_output_cost: 0.02)

Parameters:

  • max_cost (Float) (defaults to: nil)

    Maximum cost per 1k input tokens

  • max_output_cost (Float) (defaults to: nil)

    Maximum cost per 1k output tokens (optional)

Returns:



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/open_router/model_selector.rb', line 110

def within_budget(max_cost: nil, max_output_cost: nil)
  new_requirements = @requirements.dup
  new_requirements[:max_input_cost] = max_cost if max_cost
  new_requirements[:max_output_cost] = max_output_cost if max_output_cost

  self.class.new(
    requirements: new_requirements,
    strategy: @strategy,
    provider_preferences: @provider_preferences,
    fallback_options: @fallback_options
  )
end