Skip to content

release: 0.10.0 #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/create-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ jobs:
env:
# `RUBYGEMS_HOST` is only required for private gem repositories, not https://rubygems.org
RUBYGEMS_HOST: ${{ secrets.OPENAI_RUBYGEMS_HOST || secrets.RUBYGEMS_HOST }}
GEM_HOST_API_KEY: ${{ secrets.OPENAI_GEM_HOST_API_KEY || secrets.GEM_HOST_API_KEY }}
GEM_HOST_API_KEY: ${{ secrets.OPENAI_GEM_HOST_API_KEY || secrets.GEM_HOST_API_KEY }}
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.9.0"
".": "0.10.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 109
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-9e41d2d5471d2c28bff0d616f4476f5b0e6c541ef4cb51bdaaef5fdf5e13c8b2.yml
openapi_spec_hash: 86f765e18d00e32cf2ce9db7ab84d946
config_hash: dc5515e257676a27cb1ace1784aa92b3
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-ef4ecb19eb61e24c49d77fef769ee243e5279bc0bdbaee8d0f8dba4da8722559.yml
openapi_spec_hash: 1b8a9767c9f04e6865b06c41948cdc24
config_hash: fd2af1d5eff0995bb7dc02ac9a34851d
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.10.0 (2025-06-23)

Full Changelog: [v0.9.0...v0.10.0](https://github.com/openai/openai-ruby/compare/v0.9.0...v0.10.0)

### Features

* **api:** make model and inputs not required to create response ([2087fb5](https://github.com/openai/openai-ruby/commit/2087fb53d775f6481dd34737f6d554c5c35f65e7))
* **api:** update api shapes for usage and code interpreter ([733ebfb](https://github.com/openai/openai-ruby/commit/733ebfbafe14d9733149b174c99d41d471a42865))


### Bug Fixes

* **internal:** fix: should publish to ruby gems when a release is created ([aebd8eb](https://github.com/openai/openai-ruby/commit/aebd8eb2855d6a8f4fe685bdb5a458346d509e50))
* issue where we cannot mutate arrays on base model derivatives ([266d072](https://github.com/openai/openai-ruby/commit/266d072946c75f93abeff45eec9787ce4e7fea56))


### Chores

* 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))

## 0.9.0 (2025-06-17)

Full Changelog: [v0.8.0...v0.9.0](https://github.com/openai/openai-ruby/compare/v0.8.0...v0.9.0)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ GIT
PATH
remote: .
specs:
openai (0.9.0)
openai (0.10.0)
connection_pool

GEM
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
<!-- x-release-please-start-version -->

```ruby
gem "openai", "~> 0.9.0"
gem "openai", "~> 0.10.0"
```

<!-- x-release-please-end -->
Expand Down
22 changes: 22 additions & 0 deletions lib/openai/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ class Error < StandardError
end

class ConversionError < OpenAI::Errors::Error
# @return [StandardError, nil]
def cause = @cause.nil? ? super : @cause

# @api private
#
# @param on [Class<StandardError>]
# @param method [Symbol]
# @param target [Object]
# @param value [Object]
# @param cause [StandardError, nil]
def initialize(on:, method:, target:, value:, cause: nil)
cls = on.name.split("::").last

message = [
"Failed to parse #{cls}.#{method} from #{value.class} to #{target.inspect}.",
"To get the unparsed API response, use #{cls}[#{method.inspect}].",
cause && "Cause: #{cause.message}"
].filter(&:itself).join(" ")

@cause = cause
super(message)
end
end

class APIError < OpenAI::Errors::Error
Expand Down
7 changes: 6 additions & 1 deletion lib/openai/internal/type/array_of.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ def hash = [self.class, item_type].hash
#
# @param state [Hash{Symbol=>Object}] .
#
# @option state [Boolean, :strong] :strictness
# @option state [Boolean] :translate_names
#
# @option state [Boolean] :strictness
#
# @option state [Hash{Symbol=>Object}] :exactness
#
# @option state [Class<StandardError>] :error
#
# @option state [Integer] :branched
#
# @return [Array<Object>, Object]
Expand All @@ -74,6 +78,7 @@ def coerce(value, state:)

unless value.is_a?(Array)
exactness[:no] += 1
state[:error] = TypeError.new("#{value.class} can't be coerced into #{Array}")
return value
end

Expand Down
100 changes: 76 additions & 24 deletions lib/openai/internal/type/base_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def fields
[OpenAI::Internal::Type::Converter.type_info(type_info), type_info]
end

setter = "#{name_sym}="
setter = :"#{name_sym}="
api_name = info.fetch(:api_name, name_sym)
nilable = info.fetch(:nil?, false)
const = required && !nilable ? info.fetch(:const, OpenAI::Internal::OMIT) : OpenAI::Internal::OMIT
Expand All @@ -77,30 +77,61 @@ def fields
type_fn: type_fn
}

define_method(setter) { @data.store(name_sym, _1) }
define_method(setter) do |value|
target = type_fn.call
state = OpenAI::Internal::Type::Converter.new_coerce_state(translate_names: false)
coerced = OpenAI::Internal::Type::Converter.coerce(target, value, state: state)
error = @coerced.store(name_sym, state.fetch(:error) || true)
stored =
case [target, error]
in [OpenAI::Internal::Type::Converter | Symbol, nil]
coerced
else
value
end
@data.store(name_sym, stored)
end

# rubocop:disable Style/CaseEquality
# rubocop:disable Metrics/BlockLength
define_method(name_sym) do
target = type_fn.call
value = @data.fetch(name_sym) { const == OpenAI::Internal::OMIT ? nil : const }
state = {strictness: :strong, exactness: {yes: 0, no: 0, maybe: 0}, branched: 0}
if (nilable || !required) && value.nil?
nil
else
OpenAI::Internal::Type::Converter.coerce(
target,
value,
state: state

case @coerced[name_sym]
in true | false if OpenAI::Internal::Type::Converter === target
@data.fetch(name_sym)
in ::StandardError => e
raise OpenAI::Errors::ConversionError.new(
on: self.class,
method: __method__,
target: target,
value: @data.fetch(name_sym),
cause: e
)
else
Kernel.then do
value = @data.fetch(name_sym) { const == OpenAI::Internal::OMIT ? nil : const }
state = OpenAI::Internal::Type::Converter.new_coerce_state(translate_names: false)
if (nilable || !required) && value.nil?
nil
else
OpenAI::Internal::Type::Converter.coerce(
target, value, state: state
)
end
rescue StandardError => e
raise OpenAI::Errors::ConversionError.new(
on: self.class,
method: __method__,
target: target,
value: value,
cause: e
)
end
end
rescue StandardError => e
cls = self.class.name.split("::").last
message = [
"Failed to parse #{cls}.#{__method__} from #{value.class} to #{target.inspect}.",
"To get the unparsed API response, use #{cls}[#{__method__.inspect}].",
"Cause: #{e.message}"
].join(" ")
raise OpenAI::Errors::ConversionError.new(message)
end
# rubocop:enable Metrics/BlockLength
# rubocop:enable Style/CaseEquality
end

# @api private
Expand Down Expand Up @@ -200,10 +231,14 @@ class << self
#
# @param state [Hash{Symbol=>Object}] .
#
# @option state [Boolean, :strong] :strictness
# @option state [Boolean] :translate_names
#
# @option state [Boolean] :strictness
#
# @option state [Hash{Symbol=>Object}] :exactness
#
# @option state [Class<StandardError>] :error
#
# @option state [Integer] :branched
#
# @return [self, Object]
Expand All @@ -217,20 +252,23 @@ def coerce(value, state:)

unless (val = OpenAI::Internal::Util.coerce_hash(value)).is_a?(Hash)
exactness[:no] += 1
state[:error] = TypeError.new("#{value.class} can't be coerced into #{Hash}")
return value
end
exactness[:yes] += 1

keys = val.keys.to_set
instance = new
data = instance.to_h
viability = instance.instance_variable_get(:@coerced)

# rubocop:disable Metrics/BlockLength
fields.each do |name, field|
mode, required, target = field.fetch_values(:mode, :required, :type)
api_name, nilable, const = field.fetch_values(:api_name, :nilable, :const)
src_name = state.fetch(:translate_names) ? api_name : name

unless val.key?(api_name)
unless val.key?(src_name)
if required && mode != :dump && const == OpenAI::Internal::OMIT
exactness[nilable ? :maybe : :no] += 1
else
Expand All @@ -239,9 +277,10 @@ def coerce(value, state:)
next
end

item = val.fetch(api_name)
keys.delete(api_name)
item = val.fetch(src_name)
keys.delete(src_name)

state[:error] = nil
converted =
if item.nil? && (nilable || !required)
exactness[nilable ? :yes : :maybe] += 1
Expand All @@ -255,6 +294,8 @@ def coerce(value, state:)
item
end
end

viability.store(name, state.fetch(:error) || true)
data.store(name, converted)
end
# rubocop:enable Metrics/BlockLength
Expand Down Expand Up @@ -430,7 +471,18 @@ def to_yaml(*a) = OpenAI::Internal::Type::Converter.dump(self.class, self).to_ya
# Create a new instance of a model.
#
# @param data [Hash{Symbol=>Object}, self]
def initialize(data = {}) = (@data = OpenAI::Internal::Util.coerce_hash!(data).to_h)
def initialize(data = {})
@data = {}
@coerced = {}
OpenAI::Internal::Util.coerce_hash!(data).each do
if self.class.known_fields.key?(_1)
public_send(:"#{_1}=", _2)
else
@data.store(_1, _2)
@coerced.store(_1, false)
end
end
end

class << self
# @api private
Expand Down
8 changes: 7 additions & 1 deletion lib/openai/internal/type/boolean.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@ def self.==(other) = other.is_a?(Class) && other <= OpenAI::Internal::Type::Bool
class << self
# @api private
#
# Coerce value to Boolean if possible, otherwise return the original value.
#
# @param value [Boolean, Object]
#
# @param state [Hash{Symbol=>Object}] .
#
# @option state [Boolean, :strong] :strictness
# @option state [Boolean] :translate_names
#
# @option state [Boolean] :strictness
#
# @option state [Hash{Symbol=>Object}] :exactness
#
# @option state [Class<StandardError>] :error
#
# @option state [Integer] :branched
#
# @return [Boolean, Object]
Expand Down
Loading