openai 0.8.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +115 -4
- data/lib/openai/errors.rb +22 -0
- data/lib/openai/internal/type/array_of.rb +6 -1
- data/lib/openai/internal/type/base_model.rb +76 -24
- data/lib/openai/internal/type/boolean.rb +7 -1
- data/lib/openai/internal/type/converter.rb +42 -34
- data/lib/openai/internal/type/enum.rb +10 -2
- data/lib/openai/internal/type/file_input.rb +6 -1
- data/lib/openai/internal/type/hash_of.rb +6 -1
- data/lib/openai/internal/type/union.rb +12 -7
- data/lib/openai/internal/type/unknown.rb +7 -1
- data/lib/openai/models/audio/speech_create_params.rb +23 -2
- data/lib/openai/models/audio/transcription.rb +118 -1
- data/lib/openai/models/audio/transcription_text_done_event.rb +80 -1
- data/lib/openai/models/audio/transcription_verbose.rb +31 -1
- data/lib/openai/models/chat/chat_completion.rb +1 -0
- data/lib/openai/models/chat/chat_completion_chunk.rb +1 -0
- data/lib/openai/models/chat/completion_create_params.rb +1 -0
- data/lib/openai/models/fine_tuning/job_create_params.rb +4 -2
- data/lib/openai/models/image_edit_params.rb +35 -1
- data/lib/openai/models/responses/response.rb +41 -6
- data/lib/openai/models/responses/response_code_interpreter_call_code_delta_event.rb +17 -8
- data/lib/openai/models/responses/response_code_interpreter_call_code_done_event.rb +14 -10
- data/lib/openai/models/responses/response_code_interpreter_call_completed_event.rb +11 -10
- data/lib/openai/models/responses/response_code_interpreter_call_in_progress_event.rb +11 -10
- data/lib/openai/models/responses/response_code_interpreter_call_interpreting_event.rb +11 -10
- data/lib/openai/models/responses/response_code_interpreter_tool_call.rb +49 -78
- data/lib/openai/models/responses/response_create_params.rb +41 -32
- data/lib/openai/models/responses/response_output_text.rb +18 -2
- data/lib/openai/models/responses/response_prompt.rb +63 -0
- data/lib/openai/models/responses/response_stream_event.rb +2 -2
- data/lib/openai/resources/audio/speech.rb +3 -1
- data/lib/openai/resources/chat/completions.rb +8 -0
- data/lib/openai/resources/fine_tuning/jobs.rb +2 -2
- data/lib/openai/resources/images.rb +5 -1
- data/lib/openai/resources/responses.rb +18 -14
- data/lib/openai/version.rb +1 -1
- data/lib/openai.rb +1 -0
- data/rbi/openai/errors.rbi +16 -0
- data/rbi/openai/internal/type/boolean.rbi +2 -0
- data/rbi/openai/internal/type/converter.rbi +15 -15
- data/rbi/openai/internal/type/union.rbi +5 -0
- data/rbi/openai/internal/type/unknown.rbi +2 -0
- data/rbi/openai/models/audio/speech_create_params.rbi +59 -2
- data/rbi/openai/models/audio/transcription.rbi +213 -3
- data/rbi/openai/models/audio/transcription_text_done_event.rbi +146 -1
- data/rbi/openai/models/audio/transcription_verbose.rbi +47 -0
- data/rbi/openai/models/chat/chat_completion.rbi +5 -0
- data/rbi/openai/models/chat/chat_completion_chunk.rbi +5 -0
- data/rbi/openai/models/chat/completion_create_params.rbi +5 -0
- data/rbi/openai/models/fine_tuning/job_create_params.rbi +8 -4
- data/rbi/openai/models/image_edit_params.rbi +51 -0
- data/rbi/openai/models/responses/response.rbi +66 -7
- data/rbi/openai/models/responses/response_code_interpreter_call_code_delta_event.rbi +17 -7
- data/rbi/openai/models/responses/response_code_interpreter_call_code_done_event.rbi +13 -5
- data/rbi/openai/models/responses/response_code_interpreter_call_completed_event.rbi +13 -21
- data/rbi/openai/models/responses/response_code_interpreter_call_in_progress_event.rbi +13 -21
- data/rbi/openai/models/responses/response_code_interpreter_call_interpreting_event.rbi +13 -21
- data/rbi/openai/models/responses/response_code_interpreter_tool_call.rbi +83 -125
- data/rbi/openai/models/responses/response_create_params.rbi +107 -64
- data/rbi/openai/models/responses/response_output_text.rbi +26 -4
- data/rbi/openai/models/responses/response_prompt.rbi +120 -0
- data/rbi/openai/resources/audio/speech.rbi +6 -1
- data/rbi/openai/resources/fine_tuning/jobs.rbi +6 -4
- data/rbi/openai/resources/images.rbi +11 -0
- data/rbi/openai/resources/responses.rbi +56 -50
- data/sig/openai/errors.rbs +9 -0
- data/sig/openai/internal/type/converter.rbs +7 -1
- data/sig/openai/models/audio/speech_create_params.rbs +21 -1
- data/sig/openai/models/audio/transcription.rbs +95 -3
- data/sig/openai/models/audio/transcription_text_done_event.rbs +72 -2
- data/sig/openai/models/audio/transcription_verbose.rbs +21 -0
- data/sig/openai/models/chat/chat_completion.rbs +2 -1
- data/sig/openai/models/chat/chat_completion_chunk.rbs +2 -1
- data/sig/openai/models/chat/completion_create_params.rbs +2 -1
- data/sig/openai/models/image_edit_params.rbs +22 -0
- data/sig/openai/models/responses/response.rbs +22 -5
- data/sig/openai/models/responses/response_code_interpreter_call_code_delta_event.rbs +5 -0
- data/sig/openai/models/responses/response_code_interpreter_call_code_done_event.rbs +5 -0
- data/sig/openai/models/responses/response_code_interpreter_call_completed_event.rbs +4 -4
- data/sig/openai/models/responses/response_code_interpreter_call_in_progress_event.rbs +4 -4
- data/sig/openai/models/responses/response_code_interpreter_call_interpreting_event.rbs +4 -4
- data/sig/openai/models/responses/response_code_interpreter_tool_call.rbs +31 -52
- data/sig/openai/models/responses/response_create_params.rbs +25 -11
- data/sig/openai/models/responses/response_output_text.rbs +15 -1
- data/sig/openai/models/responses/response_prompt.rbs +44 -0
- data/sig/openai/resources/audio/speech.rbs +1 -0
- data/sig/openai/resources/images.rbs +2 -0
- data/sig/openai/resources/responses.rbs +6 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8ca71d5671f3a20cde2fda0eec819c5305ebd9e60aabcc55abf471f1de63b53
|
4
|
+
data.tar.gz: 1de944c9a0ee804f9d15567fec8038368458132183468c81ae42609c036e05ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 822226e8ebc9b077c242ae0366ec3f5e4b0b413054cbc3e4d9e971347c79ccb9b64006a6af5cc5a88b25472103be1a5c9a2b3c9a5e25396951f454f9f15f2dc4
|
7
|
+
data.tar.gz: 6ed344972114e5c0f098d3e5619dc1548ac61557e7b3b23384d558e2f9ccbe1ee1dd00ee0ffa0f77b17027554c1c46d33bc6eb552695615bf0fd289cf0fb4dc1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,46 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.10.0 (2025-06-23)
|
4
|
+
|
5
|
+
Full Changelog: [v0.9.0...v0.10.0](https://github.com/openai/openai-ruby/compare/v0.9.0...v0.10.0)
|
6
|
+
|
7
|
+
### Features
|
8
|
+
|
9
|
+
* **api:** make model and inputs not required to create response ([2087fb5](https://github.com/openai/openai-ruby/commit/2087fb53d775f6481dd34737f6d554c5c35f65e7))
|
10
|
+
* **api:** update api shapes for usage and code interpreter ([733ebfb](https://github.com/openai/openai-ruby/commit/733ebfbafe14d9733149b174c99d41d471a42865))
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* **internal:** fix: should publish to ruby gems when a release is created ([aebd8eb](https://github.com/openai/openai-ruby/commit/aebd8eb2855d6a8f4fe685bdb5a458346d509e50))
|
16
|
+
* issue where we cannot mutate arrays on base model derivatives ([266d072](https://github.com/openai/openai-ruby/commit/266d072946c75f93abeff45eec9787ce4e7fea56))
|
17
|
+
|
18
|
+
|
19
|
+
### Chores
|
20
|
+
|
21
|
+
* allow more free formatted json response input ([#726](https://github.com/openai/openai-ruby/issues/726)) ([69fb0af](https://github.com/openai/openai-ruby/commit/69fb0afabf86ecc3d1ca469d9700c42447569f3b))
|
22
|
+
|
23
|
+
## 0.9.0 (2025-06-17)
|
24
|
+
|
25
|
+
Full Changelog: [v0.8.0...v0.9.0](https://github.com/openai/openai-ruby/compare/v0.8.0...v0.9.0)
|
26
|
+
|
27
|
+
### Features
|
28
|
+
|
29
|
+
* **api:** add reusable prompt IDs ([72e35ad](https://github.com/openai/openai-ruby/commit/72e35ad4a677a70a98db291a20aa212e53c367ea))
|
30
|
+
* **api:** manual updates ([a4bcab7](https://github.com/openai/openai-ruby/commit/a4bcab736d59404c61b148a468d3bf0bc570fa39))
|
31
|
+
|
32
|
+
|
33
|
+
### Chores
|
34
|
+
|
35
|
+
* **ci:** enable for pull requests ([e8dfcf9](https://github.com/openai/openai-ruby/commit/e8dfcf97f3af426d3ad83472fa6eaac718acbd4d))
|
36
|
+
* **ci:** link to correct github repo ([7b34316](https://github.com/openai/openai-ruby/commit/7b3431612ea66d123bc114ec55bdf07f6081439e))
|
37
|
+
|
38
|
+
|
39
|
+
### Documentation
|
40
|
+
|
41
|
+
* structured outputs in README ([#723](https://github.com/openai/openai-ruby/issues/723)) ([7212e61](https://github.com/openai/openai-ruby/commit/7212e61ee2fb9ebff0576b0bff4424f43ae54af2))
|
42
|
+
* use image edit example in readme ([#722](https://github.com/openai/openai-ruby/issues/722)) ([eaa5055](https://github.com/openai/openai-ruby/commit/eaa5055eebca620c261c749ae4945845532c012d))
|
43
|
+
|
3
44
|
## 0.8.0 (2025-06-10)
|
4
45
|
|
5
46
|
Full Changelog: [v0.7.0...v0.8.0](https://github.com/openai/openai-ruby/compare/v0.7.0...v0.8.0)
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
|
|
15
15
|
<!-- x-release-please-start-version -->
|
16
16
|
|
17
17
|
```ruby
|
18
|
-
gem "openai", "~> 0.
|
18
|
+
gem "openai", "~> 0.10.0"
|
19
19
|
```
|
20
20
|
|
21
21
|
<!-- x-release-please-end -->
|
@@ -96,15 +96,126 @@ file_object = openai.files.create(file: Pathname("input.jsonl"), purpose: "fine-
|
|
96
96
|
# Alternatively, pass file contents or a `StringIO` directly:
|
97
97
|
file_object = openai.files.create(file: File.read("input.jsonl"), purpose: "fine-tune")
|
98
98
|
|
99
|
+
puts(file_object.id)
|
100
|
+
|
99
101
|
# Or, to control the filename and/or content type:
|
100
|
-
|
101
|
-
|
102
|
+
image = OpenAI::FilePart.new(Pathname('dog.jpg'), content_type: 'image/jpeg')
|
103
|
+
edited = openai.images.edit(
|
104
|
+
prompt: "make this image look like a painting",
|
105
|
+
model: "gpt-image-1",
|
106
|
+
size: '1024x1024',
|
107
|
+
image: image
|
108
|
+
)
|
102
109
|
|
103
|
-
puts(
|
110
|
+
puts(edited.data.first)
|
104
111
|
```
|
105
112
|
|
106
113
|
Note that you can also pass a raw `IO` descriptor, but this disables retries, as the library can't be sure if the descriptor is a file or pipe (which cannot be rewound).
|
107
114
|
|
115
|
+
### [Structured outputs](https://platform.openai.com/docs/guides/structured-outputs) and function calling
|
116
|
+
|
117
|
+
This SDK ships with helpers in `OpenAI::BaseModel`, `OpenAI::ArrayOf`, `OpenAI::EnumOf`, and `OpenAI::UnionOf` to help you define the supported JSON schemas used in making structured outputs and function calling requests.
|
118
|
+
|
119
|
+
<details>
|
120
|
+
<summary>Snippet</summary>
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
# Participant model with an optional last_name and an enum for status
|
124
|
+
class Participant < OpenAI::BaseModel
|
125
|
+
required :first_name, String
|
126
|
+
required :last_name, String, nil?: true
|
127
|
+
required :status, OpenAI::EnumOf[:confirmed, :unconfirmed, :tentative]
|
128
|
+
end
|
129
|
+
|
130
|
+
# CalendarEvent model with a list of participants.
|
131
|
+
class CalendarEvent < OpenAI::BaseModel
|
132
|
+
required :name, String
|
133
|
+
required :date, String
|
134
|
+
required :participants, OpenAI::ArrayOf[Participant]
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
client = OpenAI::Client.new
|
139
|
+
|
140
|
+
response = client.responses.create(
|
141
|
+
model: "gpt-4o-2024-08-06",
|
142
|
+
input: [
|
143
|
+
{role: :system, content: "Extract the event information."},
|
144
|
+
{
|
145
|
+
role: :user,
|
146
|
+
content: <<~CONTENT
|
147
|
+
Alice Shah and Lena are going to a science fair on Friday at 123 Main St. in San Diego.
|
148
|
+
They have also invited Jasper Vellani and Talia Groves - Jasper has not responded and Talia said she is thinking about it.
|
149
|
+
CONTENT
|
150
|
+
}
|
151
|
+
],
|
152
|
+
text: CalendarEvent
|
153
|
+
)
|
154
|
+
|
155
|
+
response
|
156
|
+
.output
|
157
|
+
.flat_map { _1.content }
|
158
|
+
# filter out refusal responses
|
159
|
+
.grep_v(OpenAI::Models::Responses::ResponseOutputRefusal)
|
160
|
+
.each do |content|
|
161
|
+
# parsed is an instance of `CalendarEvent`
|
162
|
+
pp(content.parsed)
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
</details>
|
167
|
+
|
168
|
+
See the [examples](https://github.com/openai/openai-ruby/tree/main/examples) directory for more usage examples for helper usage.
|
169
|
+
|
170
|
+
To make the equivalent request using raw JSON schema format, you would do the following:
|
171
|
+
|
172
|
+
<details>
|
173
|
+
<summary>Snippet</summary>
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
response = client.responses.create(
|
177
|
+
model: "gpt-4o-2024-08-06",
|
178
|
+
input: [
|
179
|
+
{role: :system, content: "Extract the event information."},
|
180
|
+
{
|
181
|
+
role: :user,
|
182
|
+
content: "..."
|
183
|
+
}
|
184
|
+
],
|
185
|
+
text: {
|
186
|
+
format: {
|
187
|
+
type: :json_schema,
|
188
|
+
name: "CalendarEvent",
|
189
|
+
strict: true,
|
190
|
+
schema: {
|
191
|
+
type: "object",
|
192
|
+
properties: {
|
193
|
+
name: {type: "string"},
|
194
|
+
date: {type: "string"},
|
195
|
+
participants: {
|
196
|
+
type: "array",
|
197
|
+
items: {
|
198
|
+
type: "object",
|
199
|
+
properties: {
|
200
|
+
first_name: {type: "string"},
|
201
|
+
last_name: {type: %w[string null]},
|
202
|
+
status: {type: "string", enum: %w[confirmed unconfirmed tentative]}
|
203
|
+
},
|
204
|
+
required: %w[first_name last_name status],
|
205
|
+
additionalProperties: false
|
206
|
+
}
|
207
|
+
}
|
208
|
+
},
|
209
|
+
required: %w[name date participants],
|
210
|
+
additionalProperties: false
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
)
|
215
|
+
```
|
216
|
+
|
217
|
+
</details>
|
218
|
+
|
108
219
|
### Handling errors
|
109
220
|
|
110
221
|
When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `OpenAI::Errors::APIError` will be thrown:
|
data/lib/openai/errors.rb
CHANGED
@@ -9,6 +9,28 @@ module OpenAI
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class ConversionError < OpenAI::Errors::Error
|
12
|
+
# @return [StandardError, nil]
|
13
|
+
def cause = @cause.nil? ? super : @cause
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
#
|
17
|
+
# @param on [Class<StandardError>]
|
18
|
+
# @param method [Symbol]
|
19
|
+
# @param target [Object]
|
20
|
+
# @param value [Object]
|
21
|
+
# @param cause [StandardError, nil]
|
22
|
+
def initialize(on:, method:, target:, value:, cause: nil)
|
23
|
+
cls = on.name.split("::").last
|
24
|
+
|
25
|
+
message = [
|
26
|
+
"Failed to parse #{cls}.#{method} from #{value.class} to #{target.inspect}.",
|
27
|
+
"To get the unparsed API response, use #{cls}[#{method.inspect}].",
|
28
|
+
cause && "Cause: #{cause.message}"
|
29
|
+
].filter(&:itself).join(" ")
|
30
|
+
|
31
|
+
@cause = cause
|
32
|
+
super(message)
|
33
|
+
end
|
12
34
|
end
|
13
35
|
|
14
36
|
class APIError < OpenAI::Errors::Error
|
@@ -62,10 +62,14 @@ module OpenAI
|
|
62
62
|
#
|
63
63
|
# @param state [Hash{Symbol=>Object}] .
|
64
64
|
#
|
65
|
-
# @option state [Boolean
|
65
|
+
# @option state [Boolean] :translate_names
|
66
|
+
#
|
67
|
+
# @option state [Boolean] :strictness
|
66
68
|
#
|
67
69
|
# @option state [Hash{Symbol=>Object}] :exactness
|
68
70
|
#
|
71
|
+
# @option state [Class<StandardError>] :error
|
72
|
+
#
|
69
73
|
# @option state [Integer] :branched
|
70
74
|
#
|
71
75
|
# @return [Array<Object>, Object]
|
@@ -74,6 +78,7 @@ module OpenAI
|
|
74
78
|
|
75
79
|
unless value.is_a?(Array)
|
76
80
|
exactness[:no] += 1
|
81
|
+
state[:error] = TypeError.new("#{value.class} can't be coerced into #{Array}")
|
77
82
|
return value
|
78
83
|
end
|
79
84
|
|
@@ -60,7 +60,7 @@ module OpenAI
|
|
60
60
|
[OpenAI::Internal::Type::Converter.type_info(type_info), type_info]
|
61
61
|
end
|
62
62
|
|
63
|
-
setter = "#{name_sym}="
|
63
|
+
setter = :"#{name_sym}="
|
64
64
|
api_name = info.fetch(:api_name, name_sym)
|
65
65
|
nilable = info.fetch(:nil?, false)
|
66
66
|
const = required && !nilable ? info.fetch(:const, OpenAI::Internal::OMIT) : OpenAI::Internal::OMIT
|
@@ -77,30 +77,61 @@ module OpenAI
|
|
77
77
|
type_fn: type_fn
|
78
78
|
}
|
79
79
|
|
80
|
-
define_method(setter)
|
80
|
+
define_method(setter) do |value|
|
81
|
+
target = type_fn.call
|
82
|
+
state = OpenAI::Internal::Type::Converter.new_coerce_state(translate_names: false)
|
83
|
+
coerced = OpenAI::Internal::Type::Converter.coerce(target, value, state: state)
|
84
|
+
error = @coerced.store(name_sym, state.fetch(:error) || true)
|
85
|
+
stored =
|
86
|
+
case [target, error]
|
87
|
+
in [OpenAI::Internal::Type::Converter | Symbol, nil]
|
88
|
+
coerced
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
@data.store(name_sym, stored)
|
93
|
+
end
|
81
94
|
|
95
|
+
# rubocop:disable Style/CaseEquality
|
96
|
+
# rubocop:disable Metrics/BlockLength
|
82
97
|
define_method(name_sym) do
|
83
98
|
target = type_fn.call
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
OpenAI::
|
90
|
-
|
91
|
-
|
92
|
-
|
99
|
+
|
100
|
+
case @coerced[name_sym]
|
101
|
+
in true | false if OpenAI::Internal::Type::Converter === target
|
102
|
+
@data.fetch(name_sym)
|
103
|
+
in ::StandardError => e
|
104
|
+
raise OpenAI::Errors::ConversionError.new(
|
105
|
+
on: self.class,
|
106
|
+
method: __method__,
|
107
|
+
target: target,
|
108
|
+
value: @data.fetch(name_sym),
|
109
|
+
cause: e
|
93
110
|
)
|
111
|
+
else
|
112
|
+
Kernel.then do
|
113
|
+
value = @data.fetch(name_sym) { const == OpenAI::Internal::OMIT ? nil : const }
|
114
|
+
state = OpenAI::Internal::Type::Converter.new_coerce_state(translate_names: false)
|
115
|
+
if (nilable || !required) && value.nil?
|
116
|
+
nil
|
117
|
+
else
|
118
|
+
OpenAI::Internal::Type::Converter.coerce(
|
119
|
+
target, value, state: state
|
120
|
+
)
|
121
|
+
end
|
122
|
+
rescue StandardError => e
|
123
|
+
raise OpenAI::Errors::ConversionError.new(
|
124
|
+
on: self.class,
|
125
|
+
method: __method__,
|
126
|
+
target: target,
|
127
|
+
value: value,
|
128
|
+
cause: e
|
129
|
+
)
|
130
|
+
end
|
94
131
|
end
|
95
|
-
rescue StandardError => e
|
96
|
-
cls = self.class.name.split("::").last
|
97
|
-
message = [
|
98
|
-
"Failed to parse #{cls}.#{__method__} from #{value.class} to #{target.inspect}.",
|
99
|
-
"To get the unparsed API response, use #{cls}[#{__method__.inspect}].",
|
100
|
-
"Cause: #{e.message}"
|
101
|
-
].join(" ")
|
102
|
-
raise OpenAI::Errors::ConversionError.new(message)
|
103
132
|
end
|
133
|
+
# rubocop:enable Metrics/BlockLength
|
134
|
+
# rubocop:enable Style/CaseEquality
|
104
135
|
end
|
105
136
|
|
106
137
|
# @api private
|
@@ -200,10 +231,14 @@ module OpenAI
|
|
200
231
|
#
|
201
232
|
# @param state [Hash{Symbol=>Object}] .
|
202
233
|
#
|
203
|
-
# @option state [Boolean
|
234
|
+
# @option state [Boolean] :translate_names
|
235
|
+
#
|
236
|
+
# @option state [Boolean] :strictness
|
204
237
|
#
|
205
238
|
# @option state [Hash{Symbol=>Object}] :exactness
|
206
239
|
#
|
240
|
+
# @option state [Class<StandardError>] :error
|
241
|
+
#
|
207
242
|
# @option state [Integer] :branched
|
208
243
|
#
|
209
244
|
# @return [self, Object]
|
@@ -217,6 +252,7 @@ module OpenAI
|
|
217
252
|
|
218
253
|
unless (val = OpenAI::Internal::Util.coerce_hash(value)).is_a?(Hash)
|
219
254
|
exactness[:no] += 1
|
255
|
+
state[:error] = TypeError.new("#{value.class} can't be coerced into #{Hash}")
|
220
256
|
return value
|
221
257
|
end
|
222
258
|
exactness[:yes] += 1
|
@@ -224,13 +260,15 @@ module OpenAI
|
|
224
260
|
keys = val.keys.to_set
|
225
261
|
instance = new
|
226
262
|
data = instance.to_h
|
263
|
+
viability = instance.instance_variable_get(:@coerced)
|
227
264
|
|
228
265
|
# rubocop:disable Metrics/BlockLength
|
229
266
|
fields.each do |name, field|
|
230
267
|
mode, required, target = field.fetch_values(:mode, :required, :type)
|
231
268
|
api_name, nilable, const = field.fetch_values(:api_name, :nilable, :const)
|
269
|
+
src_name = state.fetch(:translate_names) ? api_name : name
|
232
270
|
|
233
|
-
unless val.key?(
|
271
|
+
unless val.key?(src_name)
|
234
272
|
if required && mode != :dump && const == OpenAI::Internal::OMIT
|
235
273
|
exactness[nilable ? :maybe : :no] += 1
|
236
274
|
else
|
@@ -239,9 +277,10 @@ module OpenAI
|
|
239
277
|
next
|
240
278
|
end
|
241
279
|
|
242
|
-
item = val.fetch(
|
243
|
-
keys.delete(
|
280
|
+
item = val.fetch(src_name)
|
281
|
+
keys.delete(src_name)
|
244
282
|
|
283
|
+
state[:error] = nil
|
245
284
|
converted =
|
246
285
|
if item.nil? && (nilable || !required)
|
247
286
|
exactness[nilable ? :yes : :maybe] += 1
|
@@ -255,6 +294,8 @@ module OpenAI
|
|
255
294
|
item
|
256
295
|
end
|
257
296
|
end
|
297
|
+
|
298
|
+
viability.store(name, state.fetch(:error) || true)
|
258
299
|
data.store(name, converted)
|
259
300
|
end
|
260
301
|
# rubocop:enable Metrics/BlockLength
|
@@ -430,7 +471,18 @@ module OpenAI
|
|
430
471
|
# Create a new instance of a model.
|
431
472
|
#
|
432
473
|
# @param data [Hash{Symbol=>Object}, self]
|
433
|
-
def initialize(data = {})
|
474
|
+
def initialize(data = {})
|
475
|
+
@data = {}
|
476
|
+
@coerced = {}
|
477
|
+
OpenAI::Internal::Util.coerce_hash!(data).each do
|
478
|
+
if self.class.known_fields.key?(_1)
|
479
|
+
public_send(:"#{_1}=", _2)
|
480
|
+
else
|
481
|
+
@data.store(_1, _2)
|
482
|
+
@coerced.store(_1, false)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
434
486
|
|
435
487
|
class << self
|
436
488
|
# @api private
|
@@ -31,14 +31,20 @@ module OpenAI
|
|
31
31
|
class << self
|
32
32
|
# @api private
|
33
33
|
#
|
34
|
+
# Coerce value to Boolean if possible, otherwise return the original value.
|
35
|
+
#
|
34
36
|
# @param value [Boolean, Object]
|
35
37
|
#
|
36
38
|
# @param state [Hash{Symbol=>Object}] .
|
37
39
|
#
|
38
|
-
# @option state [Boolean
|
40
|
+
# @option state [Boolean] :translate_names
|
41
|
+
#
|
42
|
+
# @option state [Boolean] :strictness
|
39
43
|
#
|
40
44
|
# @option state [Hash{Symbol=>Object}] :exactness
|
41
45
|
#
|
46
|
+
# @option state [Class<StandardError>] :error
|
47
|
+
#
|
42
48
|
# @option state [Integer] :branched
|
43
49
|
#
|
44
50
|
# @return [Boolean, Object]
|
@@ -15,10 +15,14 @@ module OpenAI
|
|
15
15
|
#
|
16
16
|
# @param state [Hash{Symbol=>Object}] .
|
17
17
|
#
|
18
|
-
# @option state [Boolean
|
18
|
+
# @option state [Boolean] :translate_names
|
19
|
+
#
|
20
|
+
# @option state [Boolean] :strictness
|
19
21
|
#
|
20
22
|
# @option state [Hash{Symbol=>Object}] :exactness
|
21
23
|
#
|
24
|
+
# @option state [Class<StandardError>] :error
|
25
|
+
#
|
22
26
|
# @option state [Integer] :branched
|
23
27
|
#
|
24
28
|
# @return [Object]
|
@@ -94,6 +98,21 @@ module OpenAI
|
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
101
|
+
# @api private
|
102
|
+
#
|
103
|
+
# @param translate_names [Boolean]
|
104
|
+
#
|
105
|
+
# @return [Hash{Symbol=>Object}]
|
106
|
+
def new_coerce_state(translate_names: true)
|
107
|
+
{
|
108
|
+
translate_names: translate_names,
|
109
|
+
strictness: true,
|
110
|
+
exactness: {yes: 0, no: 0, maybe: 0},
|
111
|
+
error: nil,
|
112
|
+
branched: 0
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
97
116
|
# @api private
|
98
117
|
#
|
99
118
|
# Based on `target`, transform `value` into `target`, to the extent possible:
|
@@ -110,14 +129,11 @@ module OpenAI
|
|
110
129
|
#
|
111
130
|
# @param value [Object]
|
112
131
|
#
|
113
|
-
# @param state [Hash{Symbol=>Object}] The `strictness` is one of `true`, `false
|
114
|
-
#
|
115
|
-
# targets:
|
132
|
+
# @param state [Hash{Symbol=>Object}] The `strictness` is one of `true`, `false`. This informs the coercion strategy
|
133
|
+
# when we have to decide between multiple possible conversion targets:
|
116
134
|
#
|
117
135
|
# - `true`: the conversion must be exact, with minimum coercion.
|
118
136
|
# - `false`: the conversion can be approximate, with some coercion.
|
119
|
-
# - `:strong`: the conversion must be exact, with no coercion, and raise an error
|
120
|
-
# if not possible.
|
121
137
|
#
|
122
138
|
# The `exactness` is `Hash` with keys being one of `yes`, `no`, or `maybe`. For
|
123
139
|
# any given conversion attempt, the exactness will be updated based on how closely
|
@@ -130,21 +146,20 @@ module OpenAI
|
|
130
146
|
#
|
131
147
|
# See implementation below for more details.
|
132
148
|
#
|
133
|
-
# @option state [Boolean
|
149
|
+
# @option state [Boolean] :translate_names
|
150
|
+
#
|
151
|
+
# @option state [Boolean] :strictness
|
134
152
|
#
|
135
153
|
# @option state [Hash{Symbol=>Object}] :exactness
|
136
154
|
#
|
155
|
+
# @option state [Class<StandardError>] :error
|
156
|
+
#
|
137
157
|
# @option state [Integer] :branched
|
138
158
|
#
|
139
159
|
# @return [Object]
|
140
|
-
def coerce(
|
141
|
-
target,
|
142
|
-
value,
|
143
|
-
state: {strictness: true, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}
|
144
|
-
)
|
145
|
-
# rubocop:disable Lint/SuppressedException
|
160
|
+
def coerce(target, value, state: OpenAI::Internal::Type::Converter.new_coerce_state)
|
146
161
|
# rubocop:disable Metrics/BlockNesting
|
147
|
-
|
162
|
+
exactness = state.fetch(:exactness)
|
148
163
|
|
149
164
|
case target
|
150
165
|
in OpenAI::Internal::Type::Converter
|
@@ -160,29 +175,26 @@ module OpenAI
|
|
160
175
|
exactness[value.nil? ? :yes : :maybe] += 1
|
161
176
|
return nil
|
162
177
|
in -> { _1 <= Integer }
|
163
|
-
|
178
|
+
case value
|
179
|
+
in Integer
|
164
180
|
exactness[:yes] += 1
|
165
181
|
return value
|
166
|
-
elsif strictness == :strong && Integer(value, exception: false) != value
|
167
|
-
message = "no implicit conversion of #{value.class} into #{target.inspect}"
|
168
|
-
raise value.is_a?(Numeric) ? ArgumentError.new(message) : TypeError.new(message)
|
169
182
|
else
|
170
183
|
Kernel.then do
|
171
184
|
return Integer(value).tap { exactness[:maybe] += 1 }
|
172
|
-
rescue ArgumentError, TypeError
|
185
|
+
rescue ArgumentError, TypeError => e
|
186
|
+
state[:error] = e
|
173
187
|
end
|
174
188
|
end
|
175
189
|
in -> { _1 <= Float }
|
176
190
|
if value.is_a?(Numeric)
|
177
191
|
exactness[:yes] += 1
|
178
192
|
return Float(value)
|
179
|
-
elsif strictness == :strong
|
180
|
-
message = "no implicit conversion of #{value.class} into #{target.inspect}"
|
181
|
-
raise TypeError.new(message)
|
182
193
|
else
|
183
194
|
Kernel.then do
|
184
195
|
return Float(value).tap { exactness[:maybe] += 1 }
|
185
|
-
rescue ArgumentError, TypeError
|
196
|
+
rescue ArgumentError, TypeError => e
|
197
|
+
state[:error] = e
|
186
198
|
end
|
187
199
|
end
|
188
200
|
in -> { _1 <= String }
|
@@ -194,16 +206,13 @@ module OpenAI
|
|
194
206
|
exactness[:yes] += 1
|
195
207
|
return value.string
|
196
208
|
else
|
197
|
-
|
198
|
-
message = "no implicit conversion of #{value.class} into #{target.inspect}"
|
199
|
-
raise TypeError.new(message)
|
200
|
-
end
|
209
|
+
state[:error] = TypeError.new("#{value.class} can't be coerced into #{String}")
|
201
210
|
end
|
202
211
|
in -> { _1 <= Date || _1 <= Time }
|
203
212
|
Kernel.then do
|
204
213
|
return target.parse(value).tap { exactness[:yes] += 1 }
|
205
214
|
rescue ArgumentError, TypeError => e
|
206
|
-
|
215
|
+
state[:error] = e
|
207
216
|
end
|
208
217
|
in -> { _1 <= StringIO } if value.is_a?(String)
|
209
218
|
exactness[:yes] += 1
|
@@ -221,10 +230,8 @@ module OpenAI
|
|
221
230
|
return value
|
222
231
|
end
|
223
232
|
else
|
224
|
-
|
225
|
-
|
226
|
-
raise ArgumentError.new(message)
|
227
|
-
end
|
233
|
+
message = "cannot convert non-matching #{value.class} into #{target.inspect}"
|
234
|
+
state[:error] = ArgumentError.new(message)
|
228
235
|
end
|
229
236
|
else
|
230
237
|
end
|
@@ -232,7 +239,6 @@ module OpenAI
|
|
232
239
|
exactness[:no] += 1
|
233
240
|
value
|
234
241
|
# rubocop:enable Metrics/BlockNesting
|
235
|
-
# rubocop:enable Lint/SuppressedException
|
236
242
|
end
|
237
243
|
|
238
244
|
# @api private
|
@@ -277,8 +283,10 @@ module OpenAI
|
|
277
283
|
define_sorbet_constant!(:CoerceState) do
|
278
284
|
T.type_alias do
|
279
285
|
{
|
280
|
-
|
286
|
+
translate_names: T::Boolean,
|
287
|
+
strictness: T::Boolean,
|
281
288
|
exactness: {yes: Integer, no: Integer, maybe: Integer},
|
289
|
+
error: T::Class[StandardError],
|
282
290
|
branched: Integer
|
283
291
|
}
|
284
292
|
end
|
@@ -81,10 +81,14 @@ module OpenAI
|
|
81
81
|
#
|
82
82
|
# @param state [Hash{Symbol=>Object}] .
|
83
83
|
#
|
84
|
-
# @option state [Boolean
|
84
|
+
# @option state [Boolean] :translate_names
|
85
|
+
#
|
86
|
+
# @option state [Boolean] :strictness
|
85
87
|
#
|
86
88
|
# @option state [Hash{Symbol=>Object}] :exactness
|
87
89
|
#
|
90
|
+
# @option state [Class<StandardError>] :error
|
91
|
+
#
|
88
92
|
# @option state [Integer] :branched
|
89
93
|
#
|
90
94
|
# @return [Symbol, Object]
|
@@ -95,8 +99,12 @@ module OpenAI
|
|
95
99
|
if values.include?(val)
|
96
100
|
exactness[:yes] += 1
|
97
101
|
val
|
102
|
+
elsif values.first&.class == val.class
|
103
|
+
exactness[:maybe] += 1
|
104
|
+
value
|
98
105
|
else
|
99
|
-
exactness[
|
106
|
+
exactness[:no] += 1
|
107
|
+
state[:error] = TypeError.new("#{value.class} can't be coerced into #{self}")
|
100
108
|
value
|
101
109
|
end
|
102
110
|
end
|
@@ -45,10 +45,14 @@ module OpenAI
|
|
45
45
|
#
|
46
46
|
# @param state [Hash{Symbol=>Object}] .
|
47
47
|
#
|
48
|
-
# @option state [Boolean
|
48
|
+
# @option state [Boolean] :translate_names
|
49
|
+
#
|
50
|
+
# @option state [Boolean] :strictness
|
49
51
|
#
|
50
52
|
# @option state [Hash{Symbol=>Object}] :exactness
|
51
53
|
#
|
54
|
+
# @option state [Class<StandardError>] :error
|
55
|
+
#
|
52
56
|
# @option state [Integer] :branched
|
53
57
|
#
|
54
58
|
# @return [StringIO, Object]
|
@@ -62,6 +66,7 @@ module OpenAI
|
|
62
66
|
exactness[:yes] += 1
|
63
67
|
value
|
64
68
|
else
|
69
|
+
state[:error] = TypeError.new("#{value.class} can't be coerced into #{StringIO}")
|
65
70
|
exactness[:no] += 1
|
66
71
|
value
|
67
72
|
end
|