open_router_enhanced 1.2.5 → 2.0.1
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/CHANGELOG.md +249 -0
- data/Gemfile.lock +28 -23
- data/README.md +83 -0
- data/docs/model_selection.md +30 -0
- data/docs/plugins.md +34 -0
- data/docs/responses_api.md +34 -0
- data/docs/streaming.md +32 -0
- data/docs/structured_outputs.md +31 -0
- data/docs/tools.md +32 -1
- data/lib/open_router/client.rb +183 -80
- data/lib/open_router/completion_options.rb +265 -0
- data/lib/open_router/http.rb +7 -7
- data/lib/open_router/json_healer.rb +7 -0
- data/lib/open_router/model_registry.rb +15 -7
- data/lib/open_router/streaming_client.rb +27 -13
- data/lib/open_router/version.rb +1 -1
- data/lib/open_router.rb +1 -0
- metadata +3 -2
data/docs/tools.md
CHANGED
|
@@ -9,7 +9,7 @@ The OpenRouter gem provides comprehensive support for OpenRouter's function call
|
|
|
9
9
|
weather_tool = OpenRouter::Tool.define do
|
|
10
10
|
name "get_weather"
|
|
11
11
|
description "Get current weather for a location"
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
parameters do
|
|
14
14
|
string :location, required: true, description: "City name or coordinates"
|
|
15
15
|
string :units, enum: ["celsius", "fahrenheit"], description: "Temperature units"
|
|
@@ -23,6 +23,37 @@ response = client.complete(
|
|
|
23
23
|
tools: [weather_tool],
|
|
24
24
|
tool_choice: "auto"
|
|
25
25
|
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Using CompletionOptions (v2.0+)
|
|
29
|
+
|
|
30
|
+
For cleaner, reusable configurations, use the `CompletionOptions` class:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Create reusable tool configuration
|
|
34
|
+
tool_opts = OpenRouter::CompletionOptions.new(
|
|
35
|
+
model: "anthropic/claude-3.5-sonnet",
|
|
36
|
+
tools: [weather_tool, calculator_tool],
|
|
37
|
+
tool_choice: "auto",
|
|
38
|
+
max_tokens: 1000
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Use with complete
|
|
42
|
+
response = client.complete(messages, tool_opts)
|
|
43
|
+
|
|
44
|
+
# Override tool_choice per request
|
|
45
|
+
response = client.complete(messages, tool_opts, tool_choice: "required")
|
|
46
|
+
|
|
47
|
+
# Configure parallel tool calling
|
|
48
|
+
parallel_opts = OpenRouter::CompletionOptions.new(
|
|
49
|
+
model: "openai/gpt-4o",
|
|
50
|
+
tools: tools,
|
|
51
|
+
tool_choice: "auto",
|
|
52
|
+
parallel_tool_calls: true # Allow multiple tool calls simultaneously
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
All keyword argument patterns continue to work for backward compatibility.
|
|
26
57
|
|
|
27
58
|
# Handle tool calls
|
|
28
59
|
if response.has_tool_calls?
|
data/lib/open_router/client.rb
CHANGED
|
@@ -4,7 +4,6 @@ require "active_support/core_ext/object/blank"
|
|
|
4
4
|
require "active_support/core_ext/hash/indifferent_access"
|
|
5
5
|
|
|
6
6
|
require_relative "http"
|
|
7
|
-
require "pry"
|
|
8
7
|
|
|
9
8
|
module OpenRouter
|
|
10
9
|
class ServerError < StandardError; end
|
|
@@ -13,15 +12,19 @@ module OpenRouter
|
|
|
13
12
|
class Client
|
|
14
13
|
include OpenRouter::HTTP
|
|
15
14
|
|
|
16
|
-
attr_reader :callbacks, :usage_tracker
|
|
15
|
+
attr_reader :callbacks, :usage_tracker, :configuration
|
|
17
16
|
|
|
18
17
|
# Initializes the client with optional configurations.
|
|
19
18
|
def initialize(access_token: nil, request_timeout: nil, uri_base: nil, extra_headers: {}, track_usage: true)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
OpenRouter.configuration.
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
# Build a per-instance configuration to avoid mutating the global singleton,
|
|
20
|
+
# which would cause credential leakage across Client instances in concurrent use.
|
|
21
|
+
@configuration = OpenRouter.configuration.dup
|
|
22
|
+
@configuration.extra_headers = OpenRouter.configuration.extra_headers.dup
|
|
23
|
+
@configuration.access_token = access_token if access_token
|
|
24
|
+
@configuration.request_timeout = request_timeout if request_timeout
|
|
25
|
+
@configuration.uri_base = uri_base if uri_base
|
|
26
|
+
@configuration.extra_headers = @configuration.extra_headers.merge(extra_headers) if extra_headers.any?
|
|
27
|
+
yield(@configuration) if block_given?
|
|
25
28
|
|
|
26
29
|
# Instance-level tracking of capability warnings to avoid memory leaks
|
|
27
30
|
@capability_warnings_shown = Set.new
|
|
@@ -41,10 +44,6 @@ module OpenRouter
|
|
|
41
44
|
@usage_tracker = UsageTracker.new if @track_usage
|
|
42
45
|
end
|
|
43
46
|
|
|
44
|
-
def configuration
|
|
45
|
-
OpenRouter.configuration
|
|
46
|
-
end
|
|
47
|
-
|
|
48
47
|
# Register a callback for a specific event
|
|
49
48
|
#
|
|
50
49
|
# @param event [Symbol] The event to register for (:before_request, :after_response, :on_tool_call, :on_error, :on_stream_chunk, :on_healing)
|
|
@@ -92,27 +91,31 @@ module OpenRouter
|
|
|
92
91
|
end
|
|
93
92
|
|
|
94
93
|
# Performs a chat completion request to the OpenRouter API.
|
|
95
|
-
#
|
|
96
|
-
# @param
|
|
97
|
-
# @param
|
|
98
|
-
# @param transforms [Array<String>] Optional array of strings that tell OpenRouter to apply a series of transformations to the prompt before sending it to the model. Transformations are applied in-order
|
|
99
|
-
# @param plugins [Array<Hash>] Optional array of plugin hashes like [{id: "response-healing"}]. Available plugins: response-healing, web-search, pdf-inputs
|
|
100
|
-
# @param tools [Array<Tool>] Optional array of Tool objects or tool definition hashes for function calling
|
|
101
|
-
# @param tool_choice [String|Hash] Optional tool choice: "auto", "none", "required", or specific tool selection
|
|
102
|
-
# @param response_format [Hash] Optional response format for structured outputs
|
|
103
|
-
# @param prediction [Hash] Optional predicted output for latency reduction, e.g. {type: "content", content: "predicted text"}
|
|
104
|
-
# @param extras [Hash] Optional hash of model-specific parameters to send to the OpenRouter API
|
|
94
|
+
#
|
|
95
|
+
# @param messages [Array<Hash>] Array of message hashes with role and content
|
|
96
|
+
# @param options [CompletionOptions, Hash, nil] Options object or hash with configuration
|
|
105
97
|
# @param stream [Proc, nil] Optional callable object for streaming
|
|
106
|
-
# @
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
98
|
+
# @param kwargs [Hash] Additional options (merged with options parameter)
|
|
99
|
+
# @return [Response] The completion response wrapped in a Response object
|
|
100
|
+
#
|
|
101
|
+
# @example Simple usage (unchanged)
|
|
102
|
+
# client.complete(messages, model: "gpt-4")
|
|
103
|
+
#
|
|
104
|
+
# @example With CompletionOptions
|
|
105
|
+
# opts = CompletionOptions.new(model: "gpt-4", temperature: 0.7, tools: my_tools)
|
|
106
|
+
# client.complete(messages, opts)
|
|
107
|
+
#
|
|
108
|
+
# @example Hash options
|
|
109
|
+
# client.complete(messages, { model: "gpt-4", temperature: 0.7 })
|
|
110
|
+
#
|
|
111
|
+
# @example Options with override
|
|
112
|
+
# client.complete(messages, base_opts, temperature: 0.9)
|
|
113
|
+
def complete(messages, options = nil, stream: nil, **kwargs)
|
|
114
|
+
opts = normalize_options(options, kwargs)
|
|
115
|
+
parameters = prepare_base_parameters(messages, opts, stream)
|
|
116
|
+
forced_extraction = configure_tools_and_structured_outputs!(parameters, opts)
|
|
117
|
+
configure_plugins!(parameters, opts.response_format, stream)
|
|
118
|
+
validate_vision_support(opts.model, messages)
|
|
116
119
|
|
|
117
120
|
# Trigger before_request callbacks
|
|
118
121
|
trigger_callbacks(:before_request, parameters)
|
|
@@ -120,10 +123,11 @@ module OpenRouter
|
|
|
120
123
|
raw_response = execute_request(parameters)
|
|
121
124
|
validate_response!(raw_response, stream)
|
|
122
125
|
|
|
123
|
-
response = build_response(raw_response, response_format, forced_extraction)
|
|
126
|
+
response = build_response(raw_response, opts.response_format, forced_extraction)
|
|
124
127
|
|
|
125
128
|
# Track usage if enabled
|
|
126
|
-
|
|
129
|
+
model_for_tracking = opts.model.is_a?(String) ? opts.model : opts.model.first
|
|
130
|
+
@usage_tracker&.track(response, model: model_for_tracking)
|
|
127
131
|
|
|
128
132
|
# Trigger after_response callbacks
|
|
129
133
|
trigger_callbacks(:after_response, response)
|
|
@@ -152,39 +156,42 @@ module OpenRouter
|
|
|
152
156
|
# This is an OpenAI-compatible stateless API with support for reasoning.
|
|
153
157
|
#
|
|
154
158
|
# @param input [String, Array] The input text or structured message array
|
|
155
|
-
# @param
|
|
156
|
-
# @param
|
|
157
|
-
# Effort levels: "minimal", "low", "medium", "high"
|
|
158
|
-
# @param tools [Array<Tool, Hash>] Optional array of tool definitions
|
|
159
|
-
# @param tool_choice [String, Hash, nil] Optional: "auto", "none", "required", or specific tool
|
|
160
|
-
# @param max_output_tokens [Integer, nil] Maximum tokens to generate
|
|
161
|
-
# @param temperature [Float, nil] Sampling temperature (0-2)
|
|
162
|
-
# @param top_p [Float, nil] Nucleus sampling parameter (0-1)
|
|
163
|
-
# @param extras [Hash] Additional parameters to pass to the API
|
|
159
|
+
# @param options [CompletionOptions, Hash, nil] Options object or hash with configuration
|
|
160
|
+
# @param kwargs [Hash] Additional options (merged with options parameter)
|
|
164
161
|
# @return [ResponsesResponse] The response wrapped in a ResponsesResponse object
|
|
165
162
|
#
|
|
166
163
|
# @example Basic usage
|
|
167
164
|
# response = client.responses("What is 2+2?", model: "openai/o4-mini")
|
|
168
165
|
# puts response.content
|
|
169
166
|
#
|
|
170
|
-
# @example With reasoning
|
|
171
|
-
#
|
|
172
|
-
# "Solve this step by step: What is 15% of 80?",
|
|
167
|
+
# @example With reasoning using CompletionOptions
|
|
168
|
+
# opts = CompletionOptions.new(
|
|
173
169
|
# model: "openai/o4-mini",
|
|
174
170
|
# reasoning: { effort: "high" }
|
|
175
171
|
# )
|
|
172
|
+
# response = client.responses("Solve this step by step: What is 15% of 80?", opts)
|
|
176
173
|
# puts response.reasoning_summary
|
|
177
174
|
# puts response.content
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
175
|
+
#
|
|
176
|
+
# @example With kwargs (still works)
|
|
177
|
+
# response = client.responses("Question", model: "openai/o4-mini", reasoning: { effort: "high" })
|
|
178
|
+
def responses(input, options = nil, **kwargs)
|
|
179
|
+
opts = normalize_options(options, kwargs)
|
|
180
|
+
|
|
181
|
+
# Model is required for Responses API
|
|
182
|
+
if opts.model == "openrouter/auto"
|
|
183
|
+
raise ArgumentError, "model is required for responses API (cannot use default 'openrouter/auto')"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
parameters = { model: opts.model, input: input }
|
|
187
|
+
parameters[:reasoning] = opts.reasoning if opts.reasoning
|
|
188
|
+
parameters[:tools] = serialize_tools_for_responses(opts.tools) if opts.tools?
|
|
189
|
+
parameters[:tool_choice] = opts.tool_choice if opts.tool_choice
|
|
190
|
+
# Prefer max_completion_tokens over max_tokens (consistent with complete() method)
|
|
191
|
+
parameters[:max_output_tokens] = opts.max_completion_tokens || opts.max_tokens if opts.max_completion_tokens || opts.max_tokens
|
|
192
|
+
parameters[:temperature] = opts.temperature if opts.temperature
|
|
193
|
+
parameters[:top_p] = opts.top_p if opts.top_p
|
|
194
|
+
parameters.merge!(opts.extras || {})
|
|
188
195
|
|
|
189
196
|
raw = post(path: "/responses", parameters: parameters)
|
|
190
197
|
ResponsesResponse.new(raw)
|
|
@@ -314,18 +321,47 @@ module OpenRouter
|
|
|
314
321
|
|
|
315
322
|
private
|
|
316
323
|
|
|
324
|
+
# Normalize options from various input formats into CompletionOptions
|
|
325
|
+
#
|
|
326
|
+
# @param options [CompletionOptions, Hash, nil] Options object or hash
|
|
327
|
+
# @param kwargs [Hash] Additional keyword arguments
|
|
328
|
+
# @return [CompletionOptions] Normalized options object
|
|
329
|
+
def normalize_options(options, kwargs)
|
|
330
|
+
case options
|
|
331
|
+
when CompletionOptions
|
|
332
|
+
kwargs.empty? ? options : options.merge(**kwargs)
|
|
333
|
+
when Hash
|
|
334
|
+
# Symbolize keys to handle both string and symbol key hashes
|
|
335
|
+
symbolized = options.transform_keys(&:to_sym)
|
|
336
|
+
CompletionOptions.new(**symbolized.merge(kwargs))
|
|
337
|
+
when nil
|
|
338
|
+
CompletionOptions.new(**kwargs)
|
|
339
|
+
else
|
|
340
|
+
raise ArgumentError, "options must be CompletionOptions, Hash, or nil"
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
317
344
|
# Prepare the base parameters for the API request
|
|
318
|
-
|
|
345
|
+
#
|
|
346
|
+
# @param messages [Array<Hash>] Array of message hashes
|
|
347
|
+
# @param opts [CompletionOptions] Normalized options object
|
|
348
|
+
# @param stream [Proc, nil] Optional streaming handler
|
|
349
|
+
# @return [Hash] Parameters hash for the API request
|
|
350
|
+
def prepare_base_parameters(messages, opts, stream)
|
|
319
351
|
parameters = { messages: messages.dup }
|
|
320
352
|
|
|
321
|
-
configure_model_parameter!(parameters, model)
|
|
322
|
-
configure_provider_parameter!(parameters,
|
|
323
|
-
configure_transforms_parameter!(parameters, transforms)
|
|
324
|
-
configure_plugins_parameter!(parameters, plugins)
|
|
325
|
-
configure_prediction_parameter!(parameters, prediction)
|
|
353
|
+
configure_model_parameter!(parameters, opts.model)
|
|
354
|
+
configure_provider_parameter!(parameters, opts)
|
|
355
|
+
configure_transforms_parameter!(parameters, opts.transforms)
|
|
356
|
+
configure_plugins_parameter!(parameters, opts.plugins)
|
|
357
|
+
configure_prediction_parameter!(parameters, opts.prediction)
|
|
326
358
|
configure_stream_parameter!(parameters, stream)
|
|
359
|
+
configure_sampling_parameters!(parameters, opts)
|
|
360
|
+
configure_output_parameters!(parameters, opts)
|
|
361
|
+
configure_routing_parameters!(parameters, opts)
|
|
327
362
|
|
|
328
|
-
|
|
363
|
+
# Merge any extras last (allows overriding anything)
|
|
364
|
+
parameters.merge!(opts.extras || {})
|
|
329
365
|
parameters
|
|
330
366
|
end
|
|
331
367
|
|
|
@@ -339,9 +375,66 @@ module OpenRouter
|
|
|
339
375
|
end
|
|
340
376
|
end
|
|
341
377
|
|
|
342
|
-
# Configure the provider parameter
|
|
343
|
-
|
|
344
|
-
|
|
378
|
+
# Configure the provider parameter from options
|
|
379
|
+
#
|
|
380
|
+
# @param parameters [Hash] Request parameters hash
|
|
381
|
+
# @param opts [CompletionOptions] Options object
|
|
382
|
+
def configure_provider_parameter!(parameters, opts)
|
|
383
|
+
# Full provider config takes precedence over simple providers array
|
|
384
|
+
if opts.provider && !opts.provider.empty?
|
|
385
|
+
parameters[:provider] = opts.provider
|
|
386
|
+
elsif opts.providers.any?
|
|
387
|
+
parameters[:provider] = { order: opts.providers }
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Route parameter for fallback models
|
|
391
|
+
parameters[:route] = opts.route if opts.route
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Configure sampling parameters (temperature, top_p, etc.)
|
|
395
|
+
#
|
|
396
|
+
# @param parameters [Hash] Request parameters hash
|
|
397
|
+
# @param opts [CompletionOptions] Options object
|
|
398
|
+
def configure_sampling_parameters!(parameters, opts)
|
|
399
|
+
parameters[:temperature] = opts.temperature if opts.temperature
|
|
400
|
+
parameters[:top_p] = opts.top_p if opts.top_p
|
|
401
|
+
parameters[:top_k] = opts.top_k if opts.top_k
|
|
402
|
+
parameters[:frequency_penalty] = opts.frequency_penalty if opts.frequency_penalty
|
|
403
|
+
parameters[:presence_penalty] = opts.presence_penalty if opts.presence_penalty
|
|
404
|
+
parameters[:repetition_penalty] = opts.repetition_penalty if opts.repetition_penalty
|
|
405
|
+
parameters[:min_p] = opts.min_p if opts.min_p
|
|
406
|
+
parameters[:top_a] = opts.top_a if opts.top_a
|
|
407
|
+
parameters[:seed] = opts.seed if opts.seed
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Configure output control parameters
|
|
411
|
+
#
|
|
412
|
+
# @param parameters [Hash] Request parameters hash
|
|
413
|
+
# @param opts [CompletionOptions] Options object
|
|
414
|
+
def configure_output_parameters!(parameters, opts)
|
|
415
|
+
# Prefer max_completion_tokens over max_tokens if both are set
|
|
416
|
+
if opts.max_completion_tokens
|
|
417
|
+
parameters[:max_completion_tokens] = opts.max_completion_tokens
|
|
418
|
+
elsif opts.max_tokens
|
|
419
|
+
parameters[:max_tokens] = opts.max_tokens
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
parameters[:stop] = opts.stop if opts.stop
|
|
423
|
+
parameters[:logprobs] = opts.logprobs unless opts.logprobs.nil?
|
|
424
|
+
parameters[:top_logprobs] = opts.top_logprobs if opts.top_logprobs
|
|
425
|
+
parameters[:logit_bias] = opts.logit_bias if opts.logit_bias && !opts.logit_bias.empty?
|
|
426
|
+
parameters[:parallel_tool_calls] = opts.parallel_tool_calls unless opts.parallel_tool_calls.nil?
|
|
427
|
+
parameters[:verbosity] = opts.verbosity if opts.verbosity
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Configure OpenRouter-specific routing parameters
|
|
431
|
+
#
|
|
432
|
+
# @param parameters [Hash] Request parameters hash
|
|
433
|
+
# @param opts [CompletionOptions] Options object
|
|
434
|
+
def configure_routing_parameters!(parameters, opts)
|
|
435
|
+
parameters[:metadata] = opts.metadata if opts.metadata && !opts.metadata.empty?
|
|
436
|
+
parameters[:user] = opts.user if opts.user
|
|
437
|
+
parameters[:session_id] = opts.session_id if opts.session_id
|
|
345
438
|
end
|
|
346
439
|
|
|
347
440
|
# Configure the transforms parameter if transforms are specified
|
|
@@ -396,32 +489,42 @@ module OpenRouter
|
|
|
396
489
|
end
|
|
397
490
|
|
|
398
491
|
# Configure tools and structured outputs, returning forced_extraction flag
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
492
|
+
#
|
|
493
|
+
# @param parameters [Hash] Request parameters hash
|
|
494
|
+
# @param opts [CompletionOptions] Options object
|
|
495
|
+
# @return [Boolean] Whether forced extraction mode is being used
|
|
496
|
+
def configure_tools_and_structured_outputs!(parameters, opts)
|
|
497
|
+
configure_tool_calling!(parameters, opts)
|
|
498
|
+
configure_structured_outputs!(parameters, opts)
|
|
403
499
|
end
|
|
404
500
|
|
|
405
501
|
# Configure tool calling support
|
|
406
|
-
|
|
407
|
-
|
|
502
|
+
#
|
|
503
|
+
# @param parameters [Hash] Request parameters hash
|
|
504
|
+
# @param opts [CompletionOptions] Options object
|
|
505
|
+
def configure_tool_calling!(parameters, opts)
|
|
506
|
+
return unless opts.tools?
|
|
408
507
|
|
|
409
|
-
warn_if_unsupported(model, :function_calling, "tool calling")
|
|
410
|
-
parameters[:tools] = serialize_tools(tools)
|
|
411
|
-
parameters[:tool_choice] = tool_choice if tool_choice
|
|
508
|
+
warn_if_unsupported(opts.model, :function_calling, "tool calling")
|
|
509
|
+
parameters[:tools] = serialize_tools(opts.tools)
|
|
510
|
+
parameters[:tool_choice] = opts.tool_choice if opts.tool_choice
|
|
412
511
|
end
|
|
413
512
|
|
|
414
513
|
# Configure structured output support and return forced_extraction flag
|
|
415
|
-
|
|
416
|
-
|
|
514
|
+
#
|
|
515
|
+
# @param parameters [Hash] Request parameters hash
|
|
516
|
+
# @param opts [CompletionOptions] Options object
|
|
517
|
+
# @return [Boolean] Whether forced extraction mode is being used
|
|
518
|
+
def configure_structured_outputs!(parameters, opts)
|
|
519
|
+
return false unless opts.response_format?
|
|
417
520
|
|
|
418
|
-
|
|
521
|
+
force_extraction = determine_forced_extraction_mode(opts.model, opts.force_structured_output)
|
|
419
522
|
|
|
420
|
-
if
|
|
421
|
-
handle_forced_structured_output!(parameters, model, response_format)
|
|
523
|
+
if force_extraction
|
|
524
|
+
handle_forced_structured_output!(parameters, opts.model, opts.response_format)
|
|
422
525
|
true
|
|
423
526
|
else
|
|
424
|
-
handle_native_structured_output!(parameters, model, response_format)
|
|
527
|
+
handle_native_structured_output!(parameters, opts.model, opts.response_format)
|
|
425
528
|
false
|
|
426
529
|
end
|
|
427
530
|
end
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenRouter
|
|
4
|
+
# CompletionOptions provides a structured way to configure API requests.
|
|
5
|
+
#
|
|
6
|
+
# Supports all OpenRouter API parameters plus client-side options.
|
|
7
|
+
# Can be used with complete(), stream_complete(), and responses() methods.
|
|
8
|
+
#
|
|
9
|
+
# @example Simple usage with kwargs (unchanged)
|
|
10
|
+
# client.complete(messages, model: "gpt-4")
|
|
11
|
+
#
|
|
12
|
+
# @example Using CompletionOptions for complex requests
|
|
13
|
+
# options = OpenRouter::CompletionOptions.new(
|
|
14
|
+
# model: "anthropic/claude-3.5-sonnet",
|
|
15
|
+
# temperature: 0.7,
|
|
16
|
+
# tools: [weather_tool],
|
|
17
|
+
# providers: ["anthropic"]
|
|
18
|
+
# )
|
|
19
|
+
# client.complete(messages, options)
|
|
20
|
+
#
|
|
21
|
+
# @example Merging options with overrides
|
|
22
|
+
# base_opts = CompletionOptions.new(model: "gpt-4", temperature: 0.5)
|
|
23
|
+
# client.complete(messages, base_opts, temperature: 0.9) # overrides temperature
|
|
24
|
+
#
|
|
25
|
+
class CompletionOptions
|
|
26
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
# Common params (used by both Complete and Responses APIs)
|
|
28
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
# @return [String, Array<String>] Model ID or array for fallback routing
|
|
31
|
+
attr_accessor :model
|
|
32
|
+
|
|
33
|
+
# @return [Array<Tool, Hash>] Tool/function definitions for function calling
|
|
34
|
+
attr_accessor :tools
|
|
35
|
+
|
|
36
|
+
# @return [String, Hash, nil] Tool selection: "auto", "none", "required", or specific
|
|
37
|
+
attr_accessor :tool_choice
|
|
38
|
+
|
|
39
|
+
# @return [Hash] Pass-through for any additional/future API params
|
|
40
|
+
attr_accessor :extras
|
|
41
|
+
|
|
42
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
# Sampling parameters (OpenRouter passes these to underlying models)
|
|
44
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
# @return [Float, nil] Sampling temperature (0.0-2.0, default 1.0)
|
|
47
|
+
attr_accessor :temperature
|
|
48
|
+
|
|
49
|
+
# @return [Float, nil] Nucleus sampling (0.0-1.0)
|
|
50
|
+
attr_accessor :top_p
|
|
51
|
+
|
|
52
|
+
# @return [Integer, nil] Limits token selection to top K options
|
|
53
|
+
attr_accessor :top_k
|
|
54
|
+
|
|
55
|
+
# @return [Float, nil] Frequency penalty (-2.0 to 2.0)
|
|
56
|
+
attr_accessor :frequency_penalty
|
|
57
|
+
|
|
58
|
+
# @return [Float, nil] Presence penalty (-2.0 to 2.0)
|
|
59
|
+
attr_accessor :presence_penalty
|
|
60
|
+
|
|
61
|
+
# @return [Float, nil] Repetition penalty (0.0-2.0)
|
|
62
|
+
attr_accessor :repetition_penalty
|
|
63
|
+
|
|
64
|
+
# @return [Float, nil] Minimum probability threshold (0.0-1.0)
|
|
65
|
+
attr_accessor :min_p
|
|
66
|
+
|
|
67
|
+
# @return [Float, nil] Dynamic filtering based on confidence (0.0-1.0)
|
|
68
|
+
attr_accessor :top_a
|
|
69
|
+
|
|
70
|
+
# @return [Integer, nil] Random seed for reproducibility
|
|
71
|
+
attr_accessor :seed
|
|
72
|
+
|
|
73
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
+
# Output control
|
|
75
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
+
|
|
77
|
+
# @return [Integer, nil] Legacy max tokens limit
|
|
78
|
+
attr_accessor :max_tokens
|
|
79
|
+
|
|
80
|
+
# @return [Integer, nil] Preferred max completion tokens limit
|
|
81
|
+
attr_accessor :max_completion_tokens
|
|
82
|
+
|
|
83
|
+
# @return [String, Array<String>, nil] Stop sequences
|
|
84
|
+
attr_accessor :stop
|
|
85
|
+
|
|
86
|
+
# @return [Boolean, nil] Return log probabilities of output tokens
|
|
87
|
+
attr_accessor :logprobs
|
|
88
|
+
|
|
89
|
+
# @return [Integer, nil] Number of top logprobs to return (0-20)
|
|
90
|
+
attr_accessor :top_logprobs
|
|
91
|
+
|
|
92
|
+
# @return [Hash, nil] Token ID to bias mapping (-100 to 100)
|
|
93
|
+
attr_accessor :logit_bias
|
|
94
|
+
|
|
95
|
+
# @return [Hash, Schema, nil] Structured output schema/format
|
|
96
|
+
attr_accessor :response_format
|
|
97
|
+
|
|
98
|
+
# @return [Boolean, nil] Allow parallel tool calls
|
|
99
|
+
attr_accessor :parallel_tool_calls
|
|
100
|
+
|
|
101
|
+
# @return [Symbol, String, nil] Output verbosity (:low, :medium, :high)
|
|
102
|
+
attr_accessor :verbosity
|
|
103
|
+
|
|
104
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
105
|
+
# OpenRouter-specific routing & features
|
|
106
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
# @return [Array<String>] Simple provider ordering (becomes provider.order)
|
|
109
|
+
attr_accessor :providers
|
|
110
|
+
|
|
111
|
+
# @return [Hash, nil] Full provider config (overrides :providers if set)
|
|
112
|
+
# Supports: order, only, ignore, allow_fallbacks, require_parameters,
|
|
113
|
+
# data_collection, zdr, quantizations, sort, max_price, etc.
|
|
114
|
+
attr_accessor :provider
|
|
115
|
+
|
|
116
|
+
# @return [Array<String>] Transform identifiers (e.g., ["middle-out"])
|
|
117
|
+
attr_accessor :transforms
|
|
118
|
+
|
|
119
|
+
# @return [Array<Hash>] Plugin configurations
|
|
120
|
+
attr_accessor :plugins
|
|
121
|
+
|
|
122
|
+
# @return [Hash, nil] Predicted output for latency reduction
|
|
123
|
+
# Format: { type: "content", content: "predicted text" }
|
|
124
|
+
attr_accessor :prediction
|
|
125
|
+
|
|
126
|
+
# @return [String, nil] Routing strategy: "fallback" or "sort"
|
|
127
|
+
attr_accessor :route
|
|
128
|
+
|
|
129
|
+
# @return [Hash, nil] Custom key-value metadata
|
|
130
|
+
attr_accessor :metadata
|
|
131
|
+
|
|
132
|
+
# @return [String, nil] End-user identifier for tracking
|
|
133
|
+
attr_accessor :user
|
|
134
|
+
|
|
135
|
+
# @return [String, nil] Session grouping identifier (max 128 chars)
|
|
136
|
+
attr_accessor :session_id
|
|
137
|
+
|
|
138
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
139
|
+
# Responses API specific
|
|
140
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
141
|
+
|
|
142
|
+
# @return [Hash, nil] Reasoning configuration for Responses API
|
|
143
|
+
# Format: { effort: "minimal"|"low"|"medium"|"high" }
|
|
144
|
+
attr_accessor :reasoning
|
|
145
|
+
|
|
146
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
147
|
+
# Client-side options (not sent to API)
|
|
148
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
|
|
150
|
+
# @return [Boolean, nil] Override forced extraction mode for structured outputs
|
|
151
|
+
# true: Force extraction via system message injection
|
|
152
|
+
# false: Use native structured output
|
|
153
|
+
# nil: Auto-determine based on model capability
|
|
154
|
+
attr_accessor :force_structured_output
|
|
155
|
+
|
|
156
|
+
# All supported parameters with their defaults
|
|
157
|
+
DEFAULTS = {
|
|
158
|
+
# Common
|
|
159
|
+
model: "openrouter/auto",
|
|
160
|
+
tools: [],
|
|
161
|
+
tool_choice: nil,
|
|
162
|
+
extras: {},
|
|
163
|
+
# Sampling
|
|
164
|
+
temperature: nil,
|
|
165
|
+
top_p: nil,
|
|
166
|
+
top_k: nil,
|
|
167
|
+
frequency_penalty: nil,
|
|
168
|
+
presence_penalty: nil,
|
|
169
|
+
repetition_penalty: nil,
|
|
170
|
+
min_p: nil,
|
|
171
|
+
top_a: nil,
|
|
172
|
+
seed: nil,
|
|
173
|
+
# Output
|
|
174
|
+
max_tokens: nil,
|
|
175
|
+
max_completion_tokens: nil,
|
|
176
|
+
stop: nil,
|
|
177
|
+
logprobs: nil,
|
|
178
|
+
top_logprobs: nil,
|
|
179
|
+
logit_bias: nil,
|
|
180
|
+
response_format: nil,
|
|
181
|
+
parallel_tool_calls: nil,
|
|
182
|
+
verbosity: nil,
|
|
183
|
+
# OpenRouter routing
|
|
184
|
+
providers: [],
|
|
185
|
+
provider: nil,
|
|
186
|
+
transforms: [],
|
|
187
|
+
plugins: [],
|
|
188
|
+
prediction: nil,
|
|
189
|
+
route: nil,
|
|
190
|
+
metadata: nil,
|
|
191
|
+
user: nil,
|
|
192
|
+
session_id: nil,
|
|
193
|
+
# Responses API
|
|
194
|
+
reasoning: nil,
|
|
195
|
+
# Client-side
|
|
196
|
+
force_structured_output: nil
|
|
197
|
+
}.freeze
|
|
198
|
+
|
|
199
|
+
# Parameters that are client-side only (not sent to API)
|
|
200
|
+
CLIENT_SIDE_PARAMS = %i[force_structured_output extras].freeze
|
|
201
|
+
|
|
202
|
+
# Initialize with keyword arguments
|
|
203
|
+
#
|
|
204
|
+
# @param attrs [Hash] Parameter values (see DEFAULTS for available keys)
|
|
205
|
+
def initialize(**attrs)
|
|
206
|
+
DEFAULTS.each do |key, default|
|
|
207
|
+
value = attrs.key?(key) ? attrs[key] : default
|
|
208
|
+
# Deep dup arrays/hashes to prevent mutation of shared defaults
|
|
209
|
+
value = value.dup if value.is_a?(Array) || value.is_a?(Hash)
|
|
210
|
+
instance_variable_set(:"@#{key}", value)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Convert to hash, excluding nil values and empty collections
|
|
215
|
+
#
|
|
216
|
+
# @return [Hash] Non-empty parameter values
|
|
217
|
+
def to_h
|
|
218
|
+
DEFAULTS.keys.each_with_object({}) do |key, hash|
|
|
219
|
+
value = instance_variable_get(:"@#{key}")
|
|
220
|
+
next if value.nil?
|
|
221
|
+
next if value.respond_to?(:empty?) && value.empty?
|
|
222
|
+
|
|
223
|
+
hash[key] = value
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Create a new CompletionOptions with merged overrides
|
|
228
|
+
#
|
|
229
|
+
# @param overrides [Hash] Values to override
|
|
230
|
+
# @return [CompletionOptions] New instance with merged values
|
|
231
|
+
def merge(**overrides)
|
|
232
|
+
self.class.new(**to_h.merge(overrides))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Build API request parameters hash
|
|
236
|
+
# Excludes client-side-only options and merges extras
|
|
237
|
+
#
|
|
238
|
+
# @return [Hash] Parameters ready for API request
|
|
239
|
+
def to_api_params
|
|
240
|
+
api_params = to_h.reject { |key, _| CLIENT_SIDE_PARAMS.include?(key) }
|
|
241
|
+
api_params.merge(extras || {})
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Check if this options object has any tools defined
|
|
245
|
+
#
|
|
246
|
+
# @return [Boolean]
|
|
247
|
+
def tools?
|
|
248
|
+
tools.is_a?(Array) && !tools.empty?
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Check if response format is configured
|
|
252
|
+
#
|
|
253
|
+
# @return [Boolean]
|
|
254
|
+
def response_format?
|
|
255
|
+
!response_format.nil?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Check if using model fallback (array of models)
|
|
259
|
+
#
|
|
260
|
+
# @return [Boolean]
|
|
261
|
+
def fallback_models?
|
|
262
|
+
model.is_a?(Array)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|