omniai-openai 3.1.2 → 3.2.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: 231ff4c15352d0e13f870683f437e7e05b41b78e58c6d5b343f3bd827a7874aa
4
+ data.tar.gz: 06e2e6d651eb56384ede3176920abbd51f771ba17ea066ab6f1842fc3e6a06ca
5
5
  SHA512:
6
- metadata.gz: 8b789a962479764a1de021ee658cf27d82550e8d2699777bf7cc8294ffe099c9748ec4411d2296ece309e3dbd039685d0d2da0ed1f4380c573180fe042a7aa94
7
- data.tar.gz: 180e1daaebcddb697f9e63d2b41e2adcd25a3e22e2845a184d43669497d415e350afeced8aa8cc99ec55c19dd67233685d034e138e80dcc0a26b7a089d216ca0
6
+ metadata.gz: c17675c981946547d0c19c0f885d347d13b44a11a4344fa6e08cd35e930f4ca97987e66c69eb600091bb7dc71139ceaf510cbf1cbf017dec848a3105fa3c084e
7
+ data.tar.gz: e68ce52cfbdc2230d7690a75d5e81f35682502e18284aaaa72622abe5ee93fb4cdd59aa042791e6c7e39ddfce2011ab2b5c1f07c5dfbe713d79efb2353dab1d2
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
@@ -184,6 +199,23 @@ client.chat("What are the prime factors of 1234567?", model: "o3-mini", thinking
184
199
 
185
200
  [OpenAI API Reference `reasoning`](https://platform.openai.com/docs/guides/reasoning)
186
201
 
202
+ #### Other Responses API Options
203
+
204
+ Any option that is not modelled explicitly is forwarded to the Responses API verbatim, so parameters the gem
205
+ does not wrap are still reachable:
206
+
207
+ ```ruby
208
+ completion = client.chat('Summarize this.', model: 'gpt-5.5', max_output_tokens: 512, store: false)
209
+
210
+ # continue a stored conversation
211
+ completion = client.chat('And the next one?', previous_response_id: 'resp_123')
212
+ ```
213
+
214
+ This covers `max_output_tokens`, `previous_response_id`, `store`, `parallel_tool_calls`, `metadata`,
215
+ `truncation`, `top_p`, `service_tier`, and `prompt_cache_key`, among others.
216
+
217
+ [OpenAI API Reference `responses`](https://platform.openai.com/docs/api-reference/responses/create)
218
+
187
219
  ### Transcribe
188
220
 
189
221
  A transcription is generated by passing in a path to a file:
@@ -269,7 +301,7 @@ client.speak('She sells seashells by the seashore.', voice: OmniAI::OpenAI::Spea
269
301
 
270
302
  #### Model
271
303
 
272
- `model` is optional and must be either `tts-1` or `tts-1-hd` (default):
304
+ `model` is optional and is one of `tts-1`, `tts-1-hd`, or `gpt-4o-mini-tts` (default):
273
305
 
274
306
  ```ruby
275
307
  client.speak('I saw a kitten eating chicken in the kitchen.', format: OmniAI::OpenAI::Speak::Model::TTS_1)
@@ -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
@@ -32,11 +32,23 @@ module OmniAI
32
32
  end
33
33
 
34
34
  module Model
35
+ GPT_5_6_LUNA = "gpt-5.6-luna"
36
+ GPT_5_6_SOL = "gpt-5.6-sol"
37
+ GPT_5_6_TERRA = "gpt-5.6-terra"
38
+ GPT_5_5 = "gpt-5.5"
39
+ GPT_5_5_PRO = "gpt-5.5-pro"
40
+ GPT_5_4 = "gpt-5.4"
41
+ GPT_5_4_MINI = "gpt-5.4-mini"
42
+ GPT_5_4_NANO = "gpt-5.4-nano"
43
+ GPT_5_4_PRO = "gpt-5.4-pro"
44
+ GPT_5_3_CODEX = "gpt-5.3-codex"
35
45
  GPT_5_2 = "gpt-5.2"
46
+ GPT_5_2_PRO = "gpt-5.2-pro"
36
47
  GPT_5_1 = "gpt-5.1"
37
48
  GPT_5 = "gpt-5"
38
49
  GPT_5_MINI = "gpt-5-mini"
39
50
  GPT_5_NANO = "gpt-5-nano"
51
+ GPT_5_PRO = "gpt-5-pro"
40
52
  GPT_4_1 = "gpt-4.1"
41
53
  GPT_4_1_NANO = "gpt-4.1-nano"
42
54
  GPT_4_1_MINI = "gpt-4.1-mini"
@@ -54,6 +66,38 @@ module OmniAI
54
66
 
55
67
  DEFAULT_MODEL = Model::GPT_5_2
56
68
 
69
+ # Models that reject `temperature` on the Responses API.
70
+ #
71
+ # OpenAI's support here is not patterned by version, so this cannot be a prefix rule: `gpt-5` and its
72
+ # mini/nano/pro variants reject `temperature`, `gpt-5.1` through `gpt-5.4` accept it, and `gpt-5.5`
73
+ # onwards rejects it again. Every entry below was verified live against the API on 2026-08-16 by issuing
74
+ # a request with `temperature` and checking for `Unsupported parameter`.
75
+ #
76
+ # Models absent from this list pass `temperature` through unchanged. That is deliberate: an unlisted
77
+ # model that rejects it surfaces a loud API error the caller can act on, whereas over-applying the guard
78
+ # would silently discard a caller's temperature.
79
+ TEMPERATURE_UNSUPPORTED_MODELS = [
80
+ Model::GPT_5_6_LUNA,
81
+ Model::GPT_5_6_SOL,
82
+ Model::GPT_5_6_TERRA,
83
+ Model::GPT_5_5,
84
+ Model::GPT_5_5_PRO,
85
+ Model::GPT_5_4_PRO,
86
+ Model::GPT_5_2_PRO,
87
+ Model::GPT_5,
88
+ Model::GPT_5_MINI,
89
+ Model::GPT_5_NANO,
90
+ Model::GPT_5_PRO,
91
+ Model::O1_MINI,
92
+ Model::O1,
93
+ Model::O3,
94
+ Model::O3_MINI,
95
+ Model::O4_MINI,
96
+ ].freeze
97
+
98
+ # Keys in `@options` consumed by dedicated builders below rather than sent verbatim.
99
+ RESERVED_OPTIONS = %i[text reasoning thinking].freeze
100
+
57
101
  # @return [Context]
58
102
  CONTEXT = Context.build do |context|
59
103
  context.serializers[:choice] = ChoiceSerializer.method(:serialize)
@@ -65,6 +109,7 @@ module OmniAI
65
109
  context.serializers[:response] = ResponseSerializer.method(:serialize)
66
110
  context.deserializers[:response] = ResponseSerializer.method(:deserialize)
67
111
  context.deserializers[:content] = ContentSerializer.method(:deserialize)
112
+ context.deserializers[:usage] = UsageSerializer.method(:deserialize)
68
113
  context.serializers[:file] = FileSerializer.method(:serialize)
69
114
  context.serializers[:url] = URLSerializer.method(:serialize)
70
115
  context.serializers[:tool] = ToolSerializer.method(:serialize)
@@ -106,36 +151,38 @@ module OmniAI
106
151
  parts.join("\n\n")
107
152
  end
108
153
 
154
+ # @return [String]
155
+ def model
156
+ @model || DEFAULT_MODEL
157
+ end
158
+
109
159
  # @return [Float, nil]
110
160
  def temperature
111
161
  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)
162
+ return if TEMPERATURE_UNSUPPORTED_MODELS.include?(model)
123
163
 
124
164
  @temperature
125
165
  end
126
166
 
167
+ # Unrecognized options are passed through verbatim so callers can reach Responses API parameters the gem
168
+ # does not model explicitly (e.g. `max_output_tokens`, `previous_response_id`, `store`,
169
+ # `parallel_tool_calls`, `metadata`, `truncation`, `top_p`). Keys built by dedicated methods are excluded
170
+ # so they are not sent twice, and the explicit keys below win over any same-named passthrough.
171
+ #
127
172
  # @return [Hash]
128
173
  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
174
+ OmniAI::OpenAI.config.chat_options
175
+ .merge(@options.except(*RESERVED_OPTIONS))
176
+ .merge({
177
+ instructions:,
178
+ input:,
179
+ model:,
180
+ stream: stream? || nil,
181
+ temperature:,
182
+ tools:,
183
+ text:,
184
+ reasoning:,
185
+ }).compact
139
186
  end
140
187
 
141
188
  # @return [Array<Hash>]
@@ -13,14 +13,17 @@ module OmniAI
13
13
  module Voice
14
14
  ALLOY = "alloy" # https://platform.openai.com/docs/guides/text-to-speech/alloy
15
15
  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
16
+ BALLAD = "ballad" # https://platform.openai.com/docs/guides/text-to-speech/ballad
17
+ CEDAR = "cedar" # https://platform.openai.com/docs/guides/text-to-speech/cedar
17
18
  CORAL = "coral" # https://platform.openai.com/docs/guides/text-to-speech/coral
18
19
  ECHO = "echo" # https://platform.openai.com/docs/guides/text-to-speech/echo
19
20
  FABLE = "fable" # https://platform.openai.com/docs/guides/text-to-speech/fable
21
+ MARIN = "marin" # https://platform.openai.com/docs/guides/text-to-speech/marin
20
22
  NOVA = "nova" # https://platform.openai.com/docs/guides/text-to-speech/nova
21
23
  ONYX = "onyx" # https://platform.openai.com/docs/guides/text-to-speech/onyx
22
24
  SAGE = "sage" # https://platform.openai.com/docs/guides/text-to-speech/sage
23
25
  SHIMMER = "shimmer" # https://platform.openai.com/docs/guides/text-to-speech/shimmer
26
+ VERSE = "verse" # https://platform.openai.com/docs/guides/text-to-speech/verse
24
27
  end
25
28
 
26
29
  DEFAULT_MODEL = Model::GPT_4O_MINI_TTS
@@ -7,7 +7,10 @@ module OmniAI
7
7
  module Model
8
8
  WHISPER_1 = "whisper-1"
9
9
  GPT_4O_TRANSCRIBE = "gpt-4o-transcribe"
10
- GPT_4O_MINI_TRANSCRIBE = "gpt-4-0-mini-transcribe"
10
+ GPT_4O_TRANSCRIBE_DIARIZE = "gpt-4o-transcribe-diarize"
11
+ GPT_4O_MINI_TRANSCRIBE = "gpt-4o-mini-transcribe"
12
+ GPT_TRANSCRIBE = "gpt-transcribe"
13
+ GPT_LIVE_TRANSCRIBE = "gpt-live-transcribe"
11
14
  WHISPER = WHISPER_1
12
15
  end
13
16
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module OpenAI
5
- VERSION = "3.1.2"
5
+ VERSION = "3.2.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.2.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