open_router_enhanced 2.1.0 → 2.2.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/Gemfile.lock +1 -1
- data/Rakefile +0 -1
- data/examples/dynamic_model_switching_example.rb +0 -0
- data/examples/model_selection_example.rb +0 -0
- data/examples/prompt_template_example.rb +0 -0
- data/examples/real_world_schemas_example.rb +0 -0
- data/examples/responses_api_example.rb +0 -0
- data/examples/smart_completion_example.rb +0 -0
- data/examples/structured_outputs_example.rb +0 -0
- data/examples/tool_calling_example.rb +0 -0
- data/examples/tool_loop_example.rb +0 -0
- data/lib/open_router/callbacks.rb +50 -0
- data/lib/open_router/client.rb +22 -578
- data/lib/open_router/completion_options.rb +15 -7
- data/lib/open_router/parameter_builder.rb +120 -0
- data/lib/open_router/request_handler.rb +99 -0
- data/lib/open_router/response.rb +15 -125
- data/lib/open_router/response_parsing.rb +107 -0
- data/lib/open_router/schema.rb +28 -12
- data/lib/open_router/tool_serializer.rb +152 -0
- data/lib/open_router/version.rb +1 -1
- data/lib/open_router.rb +7 -10
- metadata +7 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenRouter
|
|
4
|
+
# Mixin providing tool calling and structured output configuration for Client.
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
|
6
|
+
module ToolSerializer
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Configure tools and structured outputs, returning forced_extraction flag
|
|
10
|
+
def configure_tools_and_structured_outputs!(parameters, opts)
|
|
11
|
+
configure_tool_calling!(parameters, opts)
|
|
12
|
+
configure_structured_outputs!(parameters, opts)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def configure_tool_calling!(parameters, opts)
|
|
16
|
+
return unless opts.tools?
|
|
17
|
+
|
|
18
|
+
warn_if_unsupported(opts.model, :function_calling, "tool calling")
|
|
19
|
+
parameters[:tools] = serialize_tools(opts.tools)
|
|
20
|
+
parameters[:tool_choice] = opts.tool_choice if opts.tool_choice
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Configure the structured-output request.
|
|
24
|
+
#
|
|
25
|
+
# Default (native: false): ask the provider for a plain JSON object — the most
|
|
26
|
+
# widely supported response_format across models/providers — and describe the
|
|
27
|
+
# schema in the prompt. The model's capability registry never gates the request.
|
|
28
|
+
#
|
|
29
|
+
# Opt-in (native: true): send response_format: { type: "json_schema", ... } for
|
|
30
|
+
# grammar-constrained decoding. Only some models/providers support this; it can
|
|
31
|
+
# 400 on the rest, which is why it is explicit rather than auto-detected.
|
|
32
|
+
#
|
|
33
|
+
# Returns the lenient-extraction flag (true when the schema was injected into the
|
|
34
|
+
# prompt, so the response may need JSON extracted from surrounding text).
|
|
35
|
+
def configure_structured_outputs!(parameters, opts)
|
|
36
|
+
return false unless opts.response_format?
|
|
37
|
+
|
|
38
|
+
schema = extract_schema(opts.response_format)
|
|
39
|
+
|
|
40
|
+
if opts.native && schema
|
|
41
|
+
warn_if_unsupported(opts.model, :structured_outputs, "structured outputs")
|
|
42
|
+
parameters[:response_format] = serialize_response_format(opts.response_format)
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Default json_object path.
|
|
47
|
+
parameters[:response_format] = { type: "json_object" }
|
|
48
|
+
return false unless schema
|
|
49
|
+
|
|
50
|
+
inject_schema_instructions!(parameters[:messages], schema)
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Serialize tools to Chat Completions API format: { type: "function", function: { name:, parameters: } }
|
|
55
|
+
def serialize_tools(tools)
|
|
56
|
+
tools.map do |tool|
|
|
57
|
+
case tool
|
|
58
|
+
when Tool
|
|
59
|
+
tool.to_h
|
|
60
|
+
when Hash
|
|
61
|
+
tool
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "Tools must be Tool objects or hashes"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Serialize tools to Responses API flat format: { type: "function", name:, parameters: }
|
|
69
|
+
def serialize_tools_for_responses(tools)
|
|
70
|
+
tools.map do |tool|
|
|
71
|
+
tool_hash = case tool
|
|
72
|
+
when Tool
|
|
73
|
+
tool.to_h
|
|
74
|
+
when Hash
|
|
75
|
+
tool.transform_keys(&:to_sym)
|
|
76
|
+
else
|
|
77
|
+
raise ArgumentError, "Tools must be Tool objects or hashes"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if tool_hash[:function]
|
|
81
|
+
{
|
|
82
|
+
type: "function",
|
|
83
|
+
name: tool_hash[:function][:name],
|
|
84
|
+
description: tool_hash[:function][:description],
|
|
85
|
+
parameters: tool_hash[:function][:parameters]
|
|
86
|
+
}.compact
|
|
87
|
+
else
|
|
88
|
+
tool_hash
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def serialize_response_format(response_format)
|
|
94
|
+
case response_format
|
|
95
|
+
when Hash
|
|
96
|
+
if response_format[:json_schema].is_a?(Schema)
|
|
97
|
+
response_format.merge(json_schema: response_format[:json_schema].to_h)
|
|
98
|
+
else
|
|
99
|
+
response_format
|
|
100
|
+
end
|
|
101
|
+
when Schema
|
|
102
|
+
{ type: "json_schema", json_schema: response_format.to_h }
|
|
103
|
+
else
|
|
104
|
+
response_format
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def inject_schema_instructions!(messages, schema)
|
|
109
|
+
return unless schema
|
|
110
|
+
|
|
111
|
+
instruction_content = if schema.respond_to?(:get_format_instructions)
|
|
112
|
+
schema.get_format_instructions
|
|
113
|
+
else
|
|
114
|
+
build_schema_instruction(schema)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
messages << { role: "system", content: instruction_content }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Pull the schema out of a response_format. Returns nil for a plain
|
|
121
|
+
# { type: "json_object" } directive (no schema to describe or validate).
|
|
122
|
+
def extract_schema(response_format)
|
|
123
|
+
case response_format
|
|
124
|
+
when Schema
|
|
125
|
+
response_format
|
|
126
|
+
when Hash
|
|
127
|
+
json_schema = response_format[:json_schema] || response_format["json_schema"]
|
|
128
|
+
json_schema if json_schema.is_a?(Schema) || json_schema.is_a?(Hash)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_schema_instruction(schema)
|
|
133
|
+
schema_json = schema.respond_to?(:to_h) ? schema.to_h.to_json : schema.to_json
|
|
134
|
+
|
|
135
|
+
<<~INSTRUCTION
|
|
136
|
+
You must respond with valid JSON matching this exact schema:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
#{schema_json}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Rules:
|
|
143
|
+
- Return ONLY the JSON object, no other text
|
|
144
|
+
- Ensure all required fields are present
|
|
145
|
+
- Match the exact data types specified
|
|
146
|
+
- Follow any format constraints (email, date, etc.)
|
|
147
|
+
- Do not include trailing commas or comments
|
|
148
|
+
INSTRUCTION
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
# rubocop:enable Metrics/ModuleLength
|
|
152
|
+
end
|
data/lib/open_router/version.rb
CHANGED
data/lib/open_router.rb
CHANGED
|
@@ -54,11 +54,11 @@ module OpenRouter
|
|
|
54
54
|
# Capability validation configuration
|
|
55
55
|
attr_accessor :strict_mode
|
|
56
56
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#
|
|
61
|
-
attr_accessor :
|
|
57
|
+
# Default validation strictness for structured outputs.
|
|
58
|
+
# false (default): best-effort, return parsed JSON as-is.
|
|
59
|
+
# true: raise StructuredOutputError when the response doesn't match the schema.
|
|
60
|
+
# Overridden per-call by the `strict:` option.
|
|
61
|
+
attr_accessor :structured_output_strict
|
|
62
62
|
|
|
63
63
|
DEFAULT_API_VERSION = "v1"
|
|
64
64
|
DEFAULT_REQUEST_TIMEOUT = 120
|
|
@@ -93,11 +93,8 @@ module OpenRouter
|
|
|
93
93
|
# Capability validation defaults
|
|
94
94
|
self.strict_mode = ENV.fetch("OPENROUTER_STRICT_MODE", "false").downcase == "true"
|
|
95
95
|
|
|
96
|
-
#
|
|
97
|
-
self.
|
|
98
|
-
|
|
99
|
-
# Default structured output mode
|
|
100
|
-
self.default_structured_output_mode = ENV.fetch("OPENROUTER_DEFAULT_MODE", "strict").to_sym
|
|
96
|
+
# Default structured output validation strictness (loose/best-effort by default)
|
|
97
|
+
self.structured_output_strict = ENV.fetch("OPENROUTER_STRUCTURED_STRICT", "false").downcase == "true"
|
|
101
98
|
end
|
|
102
99
|
|
|
103
100
|
def access_token
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: open_router_enhanced
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1
|
|
4
|
+
version: 2.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Stiens
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -148,14 +148,18 @@ files:
|
|
|
148
148
|
- examples/tool_calling_example.rb
|
|
149
149
|
- examples/tool_loop_example.rb
|
|
150
150
|
- lib/open_router.rb
|
|
151
|
+
- lib/open_router/callbacks.rb
|
|
151
152
|
- lib/open_router/client.rb
|
|
152
153
|
- lib/open_router/completion_options.rb
|
|
153
154
|
- lib/open_router/http.rb
|
|
154
155
|
- lib/open_router/json_healer.rb
|
|
155
156
|
- lib/open_router/model_registry.rb
|
|
156
157
|
- lib/open_router/model_selector.rb
|
|
158
|
+
- lib/open_router/parameter_builder.rb
|
|
157
159
|
- lib/open_router/prompt_template.rb
|
|
160
|
+
- lib/open_router/request_handler.rb
|
|
158
161
|
- lib/open_router/response.rb
|
|
162
|
+
- lib/open_router/response_parsing.rb
|
|
159
163
|
- lib/open_router/responses_response.rb
|
|
160
164
|
- lib/open_router/responses_tool_call.rb
|
|
161
165
|
- lib/open_router/schema.rb
|
|
@@ -163,6 +167,7 @@ files:
|
|
|
163
167
|
- lib/open_router/tool.rb
|
|
164
168
|
- lib/open_router/tool_call.rb
|
|
165
169
|
- lib/open_router/tool_call_base.rb
|
|
170
|
+
- lib/open_router/tool_serializer.rb
|
|
166
171
|
- lib/open_router/usage_tracker.rb
|
|
167
172
|
- lib/open_router/version.rb
|
|
168
173
|
- sig/open_router.rbs
|