Class: OpenRouter::Client

Inherits:
Object
  • Object
show all
Includes:
HTTP
Defined in:
lib/open_router/client.rb

Overview

rubocop:disable Metrics/ClassLength

Direct Known Subclasses

StreamingClient

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from HTTP

#delete, #get, #multipart_post, #post

Constructor Details

#initialize(access_token: nil, request_timeout: nil, uri_base: nil, extra_headers: {}, track_usage: true) {|@configuration| ... } ⇒ Client

Initializes the client with optional configurations.

Yields:



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/open_router/client.rb', line 18

def initialize(access_token: nil, request_timeout: nil, uri_base: nil, extra_headers: {}, track_usage: true)
  # Build a per-instance configuration to avoid mutating the global singleton,
  # which would cause credential leakage across Client instances in concurrent use.
  @configuration = OpenRouter.configuration.dup
  @configuration.extra_headers = OpenRouter.configuration.extra_headers.dup
  @configuration.access_token = access_token if access_token
  @configuration.request_timeout = request_timeout if request_timeout
  @configuration.uri_base = uri_base if uri_base
  @configuration.extra_headers = @configuration.extra_headers.merge(extra_headers) if extra_headers.any?
  yield(@configuration) if block_given?

  # Instance-level tracking of capability warnings to avoid memory leaks
  @capability_warnings_shown = Set.new

  # Initialize callback system
  @callbacks = {
    before_request: [],
    after_response: [],
    on_tool_call: [],
    on_error: [],
    on_stream_chunk: [],
    on_healing: []
  }

  # Initialize usage tracking
  @track_usage = track_usage
  @usage_tracker = UsageTracker.new if @track_usage
end

Instance Attribute Details

#callbacksObject (readonly)

Returns the value of attribute callbacks.



15
16
17
# File 'lib/open_router/client.rb', line 15

def callbacks
  @callbacks
end

#configurationObject (readonly)

Returns the value of attribute configuration.



15
16
17
# File 'lib/open_router/client.rb', line 15

def configuration
  @configuration
end

#usage_trackerObject (readonly)

Returns the value of attribute usage_tracker.



15
16
17
# File 'lib/open_router/client.rb', line 15

def usage_tracker
  @usage_tracker
end

Instance Method Details

#clear_callbacks(event = nil) ⇒ self

Remove all callbacks for a specific event

Parameters:

  • event (Symbol) (defaults to: nil)

    The event to clear callbacks for

Returns:

  • (self)

    Returns self for method chaining



70
71
72
73
74
75
76
77
# File 'lib/open_router/client.rb', line 70

def clear_callbacks(event = nil)
  if event
    @callbacks[event] = [] if @callbacks.key?(event)
  else
    @callbacks.each_key { |key| @callbacks[key] = [] }
  end
  self
end

#complete(messages, options = nil, stream: nil, **kwargs) ⇒ Response

Performs a chat completion request to the OpenRouter API.

Examples:

Simple usage (unchanged)

client.complete(messages, model: "gpt-4")

With CompletionOptions

opts = CompletionOptions.new(model: "gpt-4", temperature: 0.7, tools: my_tools)
client.complete(messages, opts)

Hash options

client.complete(messages, { model: "gpt-4", temperature: 0.7 })

Options with override

client.complete(messages, base_opts, temperature: 0.9)

Parameters:

  • messages (Array<Hash>)

    Array of message hashes with role and content

  • options (CompletionOptions, Hash, nil) (defaults to: nil)

    Options object or hash with configuration

  • stream (Proc, nil) (defaults to: nil)

    Optional callable object for streaming

  • kwargs (Hash)

    Additional options (merged with options parameter)

Returns:

  • (Response)

    The completion response wrapped in a Response object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/open_router/client.rb', line 113

def complete(messages, options = nil, stream: nil, **kwargs)
  opts = normalize_options(options, kwargs)
  parameters = prepare_base_parameters(messages, opts, stream)
  forced_extraction = configure_tools_and_structured_outputs!(parameters, opts)
  configure_plugins!(parameters, opts.response_format, stream)
  validate_vision_support(opts.model, messages)

  # Trigger before_request callbacks
  trigger_callbacks(:before_request, parameters)

  raw_response = execute_request(parameters)
  validate_response!(raw_response, stream)

  response = build_response(raw_response, opts.response_format, forced_extraction)

  # Track usage if enabled
  model_for_tracking = opts.model.is_a?(String) ? opts.model : opts.model.first
  @usage_tracker&.track(response, model: model_for_tracking)

  # Trigger after_response callbacks
  trigger_callbacks(:after_response, response)

  # Trigger on_tool_call callbacks if tool calls are present
  trigger_callbacks(:on_tool_call, response.tool_calls) if response.has_tool_calls?

  response
end

#modelsArray<Hash>

Fetches the list of available models from the OpenRouter API.

Returns:

  • (Array<Hash>)

    The list of models.



143
144
145
# File 'lib/open_router/client.rb', line 143

def models
  get(path: "/models")["data"]
end

#on(event, &block) ⇒ self

Register a callback for a specific event

Examples:

client.on(:after_response) do |response|
  puts "Used #{response.total_tokens} tokens"
end

Parameters:

  • event (Symbol)

    The event to register for (:before_request, :after_response, :on_tool_call, :on_error, :on_stream_chunk, :on_healing)

  • block (Proc)

    The callback to execute

Returns:

  • (self)

    Returns self for method chaining



57
58
59
60
61
62
63
64
# File 'lib/open_router/client.rb', line 57

def on(event, &block)
  unless @callbacks.key?(event)
    raise ArgumentError, "Invalid event: #{event}. Valid events are: #{@callbacks.keys.join(", ")}"
  end

  @callbacks[event] << block
  self
end

#query_generation_stats(generation_id) ⇒ Hash

Queries the generation stats for a given id.

Parameters:

  • generation_id (String)

    The generation id returned from a previous request.

Returns:

  • (Hash)

    The stats including token counts and cost.



150
151
152
153
# File 'lib/open_router/client.rb', line 150

def query_generation_stats(generation_id)
  response = get(path: "/generation?id=#{generation_id}")
  response["data"]
end

#responses(input, options = nil, **kwargs) ⇒ ResponsesResponse

Performs a request to the Responses API Beta (/api/v1/responses) This is an OpenAI-compatible stateless API with support for reasoning.

Examples:

Basic usage

response = client.responses("What is 2+2?", model: "openai/o4-mini")
puts response.content

With reasoning using CompletionOptions

opts = CompletionOptions.new(
  model: "openai/o4-mini",
  reasoning: { effort: "high" }
)
response = client.responses("Solve this step by step: What is 15% of 80?", opts)
puts response.reasoning_summary
puts response.content

With kwargs (still works)

response = client.responses("Question", model: "openai/o4-mini", reasoning: { effort: "high" })

Parameters:

  • input (String, Array)

    The input text or structured message array

  • options (CompletionOptions, Hash, nil) (defaults to: nil)

    Options object or hash with configuration

  • kwargs (Hash)

    Additional options (merged with options parameter)

Returns:



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/open_router/client.rb', line 178

def responses(input, options = nil, **kwargs)
  opts = normalize_options(options, kwargs)

  # Model is required for Responses API
  if opts.model == "openrouter/auto"
    raise ArgumentError, "model is required for responses API (cannot use default 'openrouter/auto')"
  end

  parameters = { model: opts.model, input: input }
  parameters[:reasoning] = opts.reasoning if opts.reasoning
  parameters[:tools] = serialize_tools_for_responses(opts.tools) if opts.tools?
  parameters[:tool_choice] = opts.tool_choice if opts.tool_choice
  # Prefer max_completion_tokens over max_tokens (consistent with complete() method)
  parameters[:max_output_tokens] = opts.max_completion_tokens || opts.max_tokens if opts.max_completion_tokens || opts.max_tokens
  parameters[:temperature] = opts.temperature if opts.temperature
  parameters[:top_p] = opts.top_p if opts.top_p
  parameters.merge!(opts.extras || {})

  raw = post(path: "/responses", parameters: parameters)
  ResponsesResponse.new(raw)
end

#select_modelModelSelector

Create a new ModelSelector for intelligent model selection

Examples:

client = OpenRouter::Client.new
model = client.select_model.optimize_for(:cost).require(:function_calling).choose

Returns:



206
207
208
# File 'lib/open_router/client.rb', line 206

def select_model
  ModelSelector.new
end

#smart_complete(messages, requirements: {}, optimization: :cost, **extras) ⇒ Response

Smart completion that automatically selects the best model based on requirements

Examples:

response = client.smart_complete(
  messages: [{ role: "user", content: "Analyze this data" }],
  requirements: { capabilities: [:function_calling], max_input_cost: 0.01 },
  optimization: :cost
)

Parameters:

  • messages (Array<Hash>)

    Array of message hashes

  • requirements (Hash) (defaults to: {})

    Model selection requirements

  • optimization (Symbol) (defaults to: :cost)

    Optimization strategy (:cost, :performance, :latest, :context)

  • extras (Hash)

    Additional parameters for the completion request

Returns:

  • (Response)

    The completion response

Raises:



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/open_router/client.rb', line 225

def smart_complete(messages, requirements: {}, optimization: :cost, **extras)
  selector = ModelSelector.new.optimize_for(optimization)

  # Apply requirements using fluent interface
  selector = selector.require(*requirements[:capabilities]) if requirements[:capabilities]

  if requirements[:max_cost] || requirements[:max_input_cost]
    cost_opts = {}
    cost_opts[:max_cost] = requirements[:max_cost] || requirements[:max_input_cost]
    cost_opts[:max_output_cost] = requirements[:max_output_cost] if requirements[:max_output_cost]
    selector = selector.within_budget(**cost_opts)
  end

  selector = selector.min_context(requirements[:min_context_length]) if requirements[:min_context_length]

  if requirements[:providers]
    case requirements[:providers]
    when Hash
      selector = selector.prefer_providers(*requirements[:providers][:prefer]) if requirements[:providers][:prefer]
      if requirements[:providers][:require]
        selector = selector.require_providers(*requirements[:providers][:require])
      end
      selector = selector.avoid_providers(*requirements[:providers][:avoid]) if requirements[:providers][:avoid]
    when Array
      selector = selector.prefer_providers(*requirements[:providers])
    end
  end

  # Select the best model
  model = selector.choose
  raise ModelSelectionError, "No model found matching requirements: #{requirements}" unless model

  # Perform the completion with the selected model
  complete(messages, model:, **extras)
end

#smart_complete_with_fallback(messages, requirements: {}, optimization: :cost, max_retries: 3, **extras) ⇒ Response

Smart completion with automatic fallback to alternative models

Examples:

response = client.smart_complete_with_fallback(
  messages: [{ role: "user", content: "Hello" }],
  requirements: { capabilities: [:function_calling] },
  max_retries: 3
)

Parameters:

  • messages (Array<Hash>)

    Array of message hashes

  • requirements (Hash) (defaults to: {})

    Model selection requirements

  • optimization (Symbol) (defaults to: :cost)

    Optimization strategy

  • max_retries (Integer) (defaults to: 3)

    Maximum number of fallback attempts

  • extras (Hash)

    Additional parameters for the completion request

Returns:

  • (Response)

    The completion response

Raises:



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/open_router/client.rb', line 277

def smart_complete_with_fallback(messages, requirements: {}, optimization: :cost, max_retries: 3, **extras)
  selector = ModelSelector.new.optimize_for(optimization)

  # Apply requirements (same logic as smart_complete)
  selector = selector.require(*requirements[:capabilities]) if requirements[:capabilities]

  if requirements[:max_cost] || requirements[:max_input_cost]
    cost_opts = {}
    cost_opts[:max_cost] = requirements[:max_cost] || requirements[:max_input_cost]
    cost_opts[:max_output_cost] = requirements[:max_output_cost] if requirements[:max_output_cost]
    selector = selector.within_budget(**cost_opts)
  end

  selector = selector.min_context(requirements[:min_context_length]) if requirements[:min_context_length]

  if requirements[:providers]
    case requirements[:providers]
    when Hash
      selector = selector.prefer_providers(*requirements[:providers][:prefer]) if requirements[:providers][:prefer]
      if requirements[:providers][:require]
        selector = selector.require_providers(*requirements[:providers][:require])
      end
      selector = selector.avoid_providers(*requirements[:providers][:avoid]) if requirements[:providers][:avoid]
    when Array
      selector = selector.prefer_providers(*requirements[:providers])
    end
  end

  # Get fallback models
  fallback_models = selector.choose_with_fallbacks(limit: max_retries + 1)
  raise ModelSelectionError, "No models found matching requirements: #{requirements}" if fallback_models.empty?

  last_error = nil

  fallback_models.each do |model|
    return complete(messages, model:, **extras)
  rescue StandardError => e
    last_error = e
    # Continue to next model in fallback list
  end

  # If we get here, all models failed
  raise ModelSelectionError, "All fallback models failed. Last error: #{last_error&.message}"
end

#trigger_callbacks(event, data = nil) ⇒ Object

Trigger callbacks for a specific event

Parameters:

  • event (Symbol)

    The event to trigger

  • data (Object) (defaults to: nil)

    Data to pass to the callbacks



83
84
85
86
87
88
89
90
91
# File 'lib/open_router/client.rb', line 83

def trigger_callbacks(event, data = nil)
  return unless @callbacks[event]

  @callbacks[event].each do |callback|
    callback.call(data)
  rescue StandardError => e
    warn "[OpenRouter] Callback error for #{event}: #{e.message}"
  end
end