omniai-openai 3.1.2 → 3.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c84d3becf5f93c58078e182e156861262bafc112e59ccb2c67d6ff08d0bb399
4
- data.tar.gz: 31cb3de11d80088728a2f80193903f2740db4fd13c058b72db780561b25fc317
3
+ metadata.gz: 3d8e230669e337e7f48a0494bf3f7535f7d6d4a990ca2ed63c7929d76c0266a1
4
+ data.tar.gz: 5a5e9fe6083dab2b2f028d1ed56569bdc2a8cf4bc9b3d800da0809d6340aaf25
5
5
  SHA512:
6
- metadata.gz: 8b789a962479764a1de021ee658cf27d82550e8d2699777bf7cc8294ffe099c9748ec4411d2296ece309e3dbd039685d0d2da0ed1f4380c573180fe042a7aa94
7
- data.tar.gz: 180e1daaebcddb697f9e63d2b41e2adcd25a3e22e2845a184d43669497d415e350afeced8aa8cc99ec55c19dd67233685d034e138e80dcc0a26b7a089d216ca0
6
+ metadata.gz: 67d27b500204debfaa90b5da1eae2b4b6b398f4567c76ad361799930f958cedc3b720c2b60a03026651cfd7e036d8e5320623e6cdf0d91bfb12ca97a87f33668
7
+ data.tar.gz: 25990d1321f578a00ed518417d0f2d9c8725c4ba798b34f37bde7fea4ac96240eae146f0c3ba31a2d7fa5c37aef087ec3f5b5e3e5173e988014de0caa2fcb962
data/README.md CHANGED
@@ -76,6 +76,18 @@ client = OmniAI::OpenAI::Client.new(
76
76
  api_prefix: '/api')
77
77
  ```
78
78
 
79
+ #### Usage with OpenAI-compatible gateways
80
+
81
+ Private gateways and governed AI control planes can also be configured with a
82
+ custom `host` while keeping application code on OmniAI's OpenAI provider:
83
+
84
+ ```ruby
85
+ client = OmniAI::OpenAI::Client.new(
86
+ host: ENV.fetch('OPENAI_HOST', 'https://api.openai.com'),
87
+ api_key: ENV.fetch('OPENAI_API_KEY')
88
+ )
89
+ ```
90
+
79
91
  ### Chat
80
92
 
81
93
  A chat completion is generated by passing in a simple text prompt:
@@ -97,13 +109,16 @@ completion.content # 'The capital of Canada is Ottawa.'
97
109
 
98
110
  #### Model
99
111
 
100
- `model` takes an optional string (default is `gpt-4o`):
112
+ `model` takes an optional string (default is `gpt-5.2`):
101
113
 
102
114
  ```ruby
103
- completion = client.chat('How fast is a cheetah?', model: OmniAI::OpenAI::Chat::Model::GPT_3_5_TURBO)
115
+ completion = client.chat('How fast is a cheetah?', model: OmniAI::OpenAI::Chat::Model::GPT_5_5)
104
116
  completion.content # 'A cheetah can reach speeds over 100 km/h.'
105
117
  ```
106
118
 
119
+ Note that `temperature` is not supported by every model (e.g. `gpt-5`, `gpt-5.5` and the `o`-series). It is
120
+ omitted from the request for those models rather than sent and rejected — see `TEMPERATURE_UNSUPPORTED_MODELS`.
121
+
107
122
  [OpenAI API Reference `model`](https://platform.openai.com/docs/api-reference/chat/create#chat-create-model)
108
123
 
109
124
  #### Temperature
@@ -148,20 +163,17 @@ JSON.parse(completion.content) # { "name": "Ringo" }
148
163
 
149
164
  #### Reasoning
150
165
 
151
- OpenAI o1 and o3 models support reasoning, which provides a summary of the model's thought process.
166
+ OpenAI reasoning models (GPT-5 and later) can return a summary of the model's thought process.
152
167
 
153
168
  ```ruby
154
169
  # Enable reasoning with unified thinking API
155
- response = client.chat("What is 25 * 25?", model: "o3-mini", thinking: true)
170
+ response = client.chat("What is 25 * 25?", model: "gpt-5.6-sol", thinking: true)
156
171
 
157
172
  # Or use OpenAI-specific reasoning options
158
- response = client.chat("What is 25 * 25?", model: "o3-mini", reasoning: { effort: "high", summary: "auto" })
173
+ response = client.chat("What is 25 * 25?", model: "gpt-5.6-sol", reasoning: { effort: "high", summary: "auto" })
159
174
  ```
160
175
 
161
- **Reasoning Effort Levels:**
162
- - `low` - Minimal reasoning
163
- - `medium` - Balanced reasoning
164
- - `high` - Maximum reasoning effort
176
+ **Reasoning Effort Levels** (`OmniAI::OpenAI::Chat::ReasoningEffort`): `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Support varies by model: GPT-6 models don't accept `minimal`, and `gpt-6-astra` and `gpt-6.1-sol` also reject `none`. Check the [model pages](https://developers.openai.com/api/docs/models/all).
165
177
 
166
178
  #### Accessing Reasoning Content
167
179
 
@@ -179,11 +191,28 @@ end
179
191
  #### Streaming with Reasoning
180
192
 
181
193
  ```ruby
182
- client.chat("What are the prime factors of 1234567?", model: "o3-mini", thinking: true, stream: $stdout)
194
+ client.chat("What are the prime factors of 1234567?", model: "gpt-5.6-sol", thinking: true, stream: $stdout)
183
195
  ```
184
196
 
185
197
  [OpenAI API Reference `reasoning`](https://platform.openai.com/docs/guides/reasoning)
186
198
 
199
+ #### Other Responses API Options
200
+
201
+ Any option that is not modelled explicitly is forwarded to the Responses API verbatim, so parameters the gem
202
+ does not wrap are still reachable:
203
+
204
+ ```ruby
205
+ completion = client.chat('Summarize this.', model: 'gpt-5.5', max_output_tokens: 512, store: false)
206
+
207
+ # continue a stored conversation
208
+ completion = client.chat('And the next one?', previous_response_id: 'resp_123')
209
+ ```
210
+
211
+ This covers `max_output_tokens`, `previous_response_id`, `store`, `parallel_tool_calls`, `metadata`,
212
+ `truncation`, `top_p`, `service_tier`, and `prompt_cache_key`, among others.
213
+
214
+ [OpenAI API Reference `responses`](https://platform.openai.com/docs/api-reference/responses/create)
215
+
187
216
  ### Transcribe
188
217
 
189
218
  A transcription is generated by passing in a path to a file:
@@ -269,10 +298,10 @@ client.speak('She sells seashells by the seashore.', voice: OmniAI::OpenAI::Spea
269
298
 
270
299
  #### Model
271
300
 
272
- `model` is optional and must be either `tts-1` or `tts-1-hd` (default):
301
+ `model` is optional and is one of `gpt-4o-mini-tts` (default), `tts-1` or `tts-1-hd` (both shut down 2027-01-06):
273
302
 
274
303
  ```ruby
275
- client.speak('I saw a kitten eating chicken in the kitchen.', format: OmniAI::OpenAI::Speak::Model::TTS_1)
304
+ client.speak('I saw a kitten eating chicken in the kitchen.', model: OmniAI::OpenAI::Speak::Model::GPT_4O_MINI_TTS)
276
305
  ```
277
306
 
278
307
  [OpenAI API Refernce `model`](https://platform.openai.com/docs/api-reference/audio/createSpeech#audio-createspeech-model)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ module OpenAI
5
+ class Chat
6
+ # Overrides usage deserialize to read OpenAI's reasoning breakdown.
7
+ module UsageSerializer
8
+ # OpenAI already counts reasoning inside `output_tokens` and reports the breakdown alongside it, so the
9
+ # breakdown is read into `thinking_tokens` and the output count is left exactly as reported. Adding the two
10
+ # together would double count.
11
+ #
12
+ # This gem targets the Responses API (`/responses`), whose usage object reports the breakdown at
13
+ # `output_tokens_details.reasoning_tokens`. The Chat Completions vocabulary from the previous API
14
+ # generation (`completion_tokens_details`) is deliberately not read — this gem never receives it.
15
+ #
16
+ # @param data [Hash]
17
+ # @return [OmniAI::Chat::Usage]
18
+ def self.deserialize(data, *)
19
+ # Deserialize without a context so the generic flat parse runs rather than recursing into this method.
20
+ usage = OmniAI::Chat::Usage.deserialize(data)
21
+
22
+ # Only overwrite when the vendor container is actually present. A payload produced by `Usage#serialize`
23
+ # carries base's own `thinking_tokens` key and no `output_tokens_details`, so assigning unconditionally
24
+ # would clobber a correctly-parsed value with nil and break the round-trip. `unless nil?` rather than
25
+ # `||=`, so a reported zero from the wire still wins over base's nil.
26
+ reasoning_tokens = data.dig("output_tokens_details", "reasoning_tokens")
27
+ usage.thinking_tokens = reasoning_tokens unless reasoning_tokens.nil?
28
+
29
+ usage
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -20,9 +20,12 @@ module OmniAI
20
20
 
21
21
  module ReasoningEffort
22
22
  NONE = "none"
23
+ MINIMAL = "minimal"
23
24
  LOW = "low"
24
25
  MEDIUM = "medium"
25
26
  HIGH = "high"
27
+ XHIGH = "xhigh"
28
+ MAX = "max"
26
29
  end
27
30
 
28
31
  module VerbosityText
@@ -32,28 +35,92 @@ module OmniAI
32
35
  end
33
36
 
34
37
  module Model
38
+ GPT_6_1_SOL = "gpt-6.1-sol"
39
+ GPT_6_ASTRA = "gpt-6-astra"
40
+ GPT_6_SOL = "gpt-6-sol"
41
+ GPT_6_LUNA = "gpt-6-luna"
42
+ GPT_5_6_LUNA = "gpt-5.6-luna"
43
+ GPT_5_6_SOL = "gpt-5.6-sol"
44
+ GPT_5_6_TERRA = "gpt-5.6-terra"
45
+ GPT_5_5 = "gpt-5.5"
46
+ GPT_5_5_PRO = "gpt-5.5-pro"
47
+ GPT_5_4 = "gpt-5.4"
48
+ GPT_5_4_MINI = "gpt-5.4-mini"
49
+ # @deprecated OpenAI shuts this down 2027-04-01; use GPT_6_LUNA.
50
+ GPT_5_4_NANO = "gpt-5.4-nano"
51
+ GPT_5_4_PRO = "gpt-5.4-pro"
52
+ # @deprecated OpenAI shuts this down 2027-04-01; use GPT_6_SOL.
53
+ GPT_5_3_CODEX = "gpt-5.3-codex"
35
54
  GPT_5_2 = "gpt-5.2"
55
+ GPT_5_2_PRO = "gpt-5.2-pro"
56
+ # @deprecated OpenAI shuts this down 2027-04-01; use GPT_6_SOL.
36
57
  GPT_5_1 = "gpt-5.1"
58
+ # @deprecated OpenAI shuts down its gpt-5-2025-08-07 snapshot 2026-12-11; use GPT_5_6_SOL.
37
59
  GPT_5 = "gpt-5"
60
+ # @deprecated OpenAI shuts down its gpt-5-mini-2025-08-07 snapshot 2026-12-11; use GPT_5_6_TERRA.
38
61
  GPT_5_MINI = "gpt-5-mini"
62
+ # @deprecated OpenAI shuts down its gpt-5-nano-2025-08-07 snapshot 2026-12-11; use GPT_5_6_LUNA.
39
63
  GPT_5_NANO = "gpt-5-nano"
64
+ # @deprecated OpenAI shuts down its gpt-5-pro-2025-10-06 snapshot 2026-12-11; use GPT_5_6_SOL.
65
+ GPT_5_PRO = "gpt-5-pro"
40
66
  GPT_4_1 = "gpt-4.1"
67
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_LUNA.
41
68
  GPT_4_1_NANO = "gpt-4.1-nano"
42
69
  GPT_4_1_MINI = "gpt-4.1-mini"
43
70
  GPT_4O = "gpt-4o"
44
71
  GPT_4O_MINI = "gpt-4o-mini"
72
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_SOL.
45
73
  GPT_4 = "gpt-4"
74
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_SOL.
46
75
  GPT_4_TURBO = "gpt-4-turbo"
76
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_TERRA.
47
77
  GPT_3_5_TURBO = "gpt-3.5-turbo"
78
+ # @deprecated OpenAI shut this down 2025-10-27; use GPT_5_6_SOL.
48
79
  O1_MINI = "o1-mini"
80
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_SOL.
49
81
  O3_MINI = "o3-mini"
82
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_TERRA.
50
83
  O4_MINI = "o4-mini"
84
+ # @deprecated OpenAI shuts this down 2026-10-23; use GPT_5_6_SOL.
51
85
  O1 = "o1"
86
+ # @deprecated OpenAI shuts down its o3-2025-04-16 snapshot 2026-12-11; use GPT_5_6_SOL.
52
87
  O3 = "o3"
53
88
  end
54
89
 
55
90
  DEFAULT_MODEL = Model::GPT_5_2
56
91
 
92
+ # Models that reject `temperature` on the Responses API.
93
+ #
94
+ # OpenAI's support here is not patterned by version, so this cannot be a prefix rule: `gpt-5` and its
95
+ # mini/nano/pro variants reject `temperature`, `gpt-5.1` through `gpt-5.4` accept it, and `gpt-5.5`
96
+ # onwards rejects it again. Every entry below was verified live against the API on 2026-08-16 by issuing
97
+ # a request with `temperature` and checking for `Unsupported parameter`.
98
+ #
99
+ # Models absent from this list pass `temperature` through unchanged. That is deliberate: an unlisted
100
+ # model that rejects it surfaces a loud API error the caller can act on, whereas over-applying the guard
101
+ # would silently discard a caller's temperature.
102
+ TEMPERATURE_UNSUPPORTED_MODELS = [
103
+ Model::GPT_5_6_LUNA,
104
+ Model::GPT_5_6_SOL,
105
+ Model::GPT_5_6_TERRA,
106
+ Model::GPT_5_5,
107
+ Model::GPT_5_5_PRO,
108
+ Model::GPT_5_4_PRO,
109
+ Model::GPT_5_2_PRO,
110
+ Model::GPT_5,
111
+ Model::GPT_5_MINI,
112
+ Model::GPT_5_NANO,
113
+ Model::GPT_5_PRO,
114
+ Model::O1_MINI,
115
+ Model::O1,
116
+ Model::O3,
117
+ Model::O3_MINI,
118
+ Model::O4_MINI,
119
+ ].freeze
120
+
121
+ # Keys in `@options` consumed by dedicated builders below rather than sent verbatim.
122
+ RESERVED_OPTIONS = %i[text reasoning thinking].freeze
123
+
57
124
  # @return [Context]
58
125
  CONTEXT = Context.build do |context|
59
126
  context.serializers[:choice] = ChoiceSerializer.method(:serialize)
@@ -65,6 +132,7 @@ module OmniAI
65
132
  context.serializers[:response] = ResponseSerializer.method(:serialize)
66
133
  context.deserializers[:response] = ResponseSerializer.method(:deserialize)
67
134
  context.deserializers[:content] = ContentSerializer.method(:deserialize)
135
+ context.deserializers[:usage] = UsageSerializer.method(:deserialize)
68
136
  context.serializers[:file] = FileSerializer.method(:serialize)
69
137
  context.serializers[:url] = URLSerializer.method(:serialize)
70
138
  context.serializers[:tool] = ToolSerializer.method(:serialize)
@@ -106,36 +174,38 @@ module OmniAI
106
174
  parts.join("\n\n")
107
175
  end
108
176
 
177
+ # @return [String]
178
+ def model
179
+ @model || DEFAULT_MODEL
180
+ end
181
+
109
182
  # @return [Float, nil]
110
183
  def temperature
111
184
  return if @temperature.nil?
112
-
113
- return if [
114
- Model::GPT_5,
115
- Model::GPT_5_MINI,
116
- Model::GPT_5_NANO,
117
- Model::GPT_5_1,
118
- Model::GPT_5_2,
119
- Model::O1_MINI,
120
- Model::O1,
121
- Model::O3_MINI,
122
- ].include?(@model)
185
+ return if TEMPERATURE_UNSUPPORTED_MODELS.include?(model)
123
186
 
124
187
  @temperature
125
188
  end
126
189
 
190
+ # Unrecognized options are passed through verbatim so callers can reach Responses API parameters the gem
191
+ # does not model explicitly (e.g. `max_output_tokens`, `previous_response_id`, `store`,
192
+ # `parallel_tool_calls`, `metadata`, `truncation`, `top_p`). Keys built by dedicated methods are excluded
193
+ # so they are not sent twice, and the explicit keys below win over any same-named passthrough.
194
+ #
127
195
  # @return [Hash]
128
196
  def payload
129
- OmniAI::OpenAI.config.chat_options.merge({
130
- instructions:,
131
- input:,
132
- model: @model || DEFAULT_MODEL,
133
- stream: stream? || nil,
134
- temperature:,
135
- tools:,
136
- text:,
137
- reasoning:,
138
- }).compact
197
+ OmniAI::OpenAI.config.chat_options
198
+ .merge(@options.except(*RESERVED_OPTIONS))
199
+ .merge({
200
+ instructions:,
201
+ input:,
202
+ model:,
203
+ stream: stream? || nil,
204
+ temperature:,
205
+ tools:,
206
+ text:,
207
+ reasoning:,
208
+ }).compact
139
209
  end
140
210
 
141
211
  # @return [Array<Hash>]
@@ -5,22 +5,28 @@ module OmniAI
5
5
  # An OpenAI transcribe implementation.
6
6
  class Speak < OmniAI::Speak
7
7
  module Model
8
+ # @deprecated OpenAI shuts this down 2027-01-06.
8
9
  TTS_1 = "tts-1"
10
+ # @deprecated OpenAI shuts this down 2027-01-06.
9
11
  TTS_1_HD = "tts-1-hd"
12
+ # @deprecated OpenAI shuts down both its snapshots 2027-01-06 (the current DEFAULT_MODEL).
10
13
  GPT_4O_MINI_TTS = "gpt-4o-mini-tts"
11
14
  end
12
15
 
13
16
  module Voice
14
17
  ALLOY = "alloy" # https://platform.openai.com/docs/guides/text-to-speech/alloy
15
18
  ASH = "ash" # https://platform.openai.com/docs/guides/text-to-speech/ash
16
- BALLARD = "ballard" # https://platform.openai.com/docs/guides/text-to-speech/ballard
19
+ BALLAD = "ballad" # https://platform.openai.com/docs/guides/text-to-speech/ballad
20
+ CEDAR = "cedar" # https://platform.openai.com/docs/guides/text-to-speech/cedar
17
21
  CORAL = "coral" # https://platform.openai.com/docs/guides/text-to-speech/coral
18
22
  ECHO = "echo" # https://platform.openai.com/docs/guides/text-to-speech/echo
19
23
  FABLE = "fable" # https://platform.openai.com/docs/guides/text-to-speech/fable
24
+ MARIN = "marin" # https://platform.openai.com/docs/guides/text-to-speech/marin
20
25
  NOVA = "nova" # https://platform.openai.com/docs/guides/text-to-speech/nova
21
26
  ONYX = "onyx" # https://platform.openai.com/docs/guides/text-to-speech/onyx
22
27
  SAGE = "sage" # https://platform.openai.com/docs/guides/text-to-speech/sage
23
28
  SHIMMER = "shimmer" # https://platform.openai.com/docs/guides/text-to-speech/shimmer
29
+ VERSE = "verse" # https://platform.openai.com/docs/guides/text-to-speech/verse
24
30
  end
25
31
 
26
32
  DEFAULT_MODEL = Model::GPT_4O_MINI_TTS
@@ -5,9 +5,17 @@ module OmniAI
5
5
  # An OpenAI transcribe implementation.
6
6
  class Transcribe < OmniAI::Transcribe
7
7
  module Model
8
+ # @deprecated OpenAI shuts this down 2027-02-26; use GPT_TRANSCRIBE.
8
9
  WHISPER_1 = "whisper-1"
10
+ # @deprecated OpenAI shuts this down 2027-02-26; use GPT_TRANSCRIBE.
9
11
  GPT_4O_TRANSCRIBE = "gpt-4o-transcribe"
10
- GPT_4O_MINI_TRANSCRIBE = "gpt-4-0-mini-transcribe"
12
+ # @deprecated OpenAI shuts this down 2027-02-26; use GPT_TRANSCRIBE.
13
+ GPT_4O_TRANSCRIBE_DIARIZE = "gpt-4o-transcribe-diarize"
14
+ # @deprecated OpenAI shuts this down 2027-02-26; use GPT_TRANSCRIBE.
15
+ GPT_4O_MINI_TRANSCRIBE = "gpt-4o-mini-transcribe"
16
+ GPT_TRANSCRIBE = "gpt-transcribe"
17
+ GPT_LIVE_TRANSCRIBE = "gpt-live-transcribe"
18
+ # @deprecated OpenAI shuts this down 2027-02-26; use GPT_TRANSCRIBE.
11
19
  WHISPER = WHISPER_1
12
20
  end
13
21
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module OpenAI
5
- VERSION = "3.1.2"
5
+ VERSION = "3.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai-openai
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '3.0'
32
+ version: '3.8'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '3.0'
39
+ version: '3.8'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: openssl
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -89,6 +89,7 @@ files:
89
89
  - lib/omniai/openai/chat/tool_call_serializer.rb
90
90
  - lib/omniai/openai/chat/tool_serializer.rb
91
91
  - lib/omniai/openai/chat/url_serializer.rb
92
+ - lib/omniai/openai/chat/usage_serializer.rb
92
93
  - lib/omniai/openai/client.rb
93
94
  - lib/omniai/openai/config.rb
94
95
  - lib/omniai/openai/embed.rb