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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenRouter
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.1"
5
5
  end
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
- # Automatic forcing configuration
58
- attr_accessor :auto_force_on_unsupported_models
59
-
60
- # Default structured output mode configuration
61
- attr_accessor :default_structured_output_mode
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
- # Auto forcing defaults
97
- self.auto_force_on_unsupported_models = ENV.fetch("OPENROUTER_AUTO_FORCE", "true").downcase == "true"
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.0
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-04-16 00:00:00.000000000 Z
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