Skip to content

fix: restore raw BaseModel value contract - #373

Closed
jbeckwith-oai wants to merge 3 commits into
mainfrom
codex/preserve-raw-model-values
Closed

fix: restore raw BaseModel value contract#373
jbeckwith-oai wants to merge 3 commits into
mainfrom
codex/preserve-raw-model-values

Conversation

@jbeckwith-oai

@jbeckwith-oai jbeckwith-oai commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Dependency

This PR must land before #370. PR #370 remains a draft and retains the separate union/discriminator work.

Summary

  • forward-restore the published v0.78 model-coercion behavior across BaseModel, arrays, hashes, and unions
  • keep caller-owned request values shared through field accessors, #[], #to_h, and #deep_to_h
  • serialize request models from their current raw values so later nested hash/array mutations cannot diverge from the sent payload
  • preserve parsed-response symbols, nested models, equality, and hashing from v0.78
  • remove the unshipped fix: coerce nested BaseModel fields #295 nested-coercion behavior and its converted-value cache

Why

#295 changed global model construction and response parsing. In addition to replacing nested request hashes in the public raw-value APIs, the first version of this PR introduced a second converted-value cache that could silently become stale when callers mutated their original nested values.

This revision restores the last published behavior directly. It deliberately contains no new union/discriminator parsing design; that remains in #370.

Coverage

The regression suite now directly covers:

  • raw identity through constructors, setters, accessors, #[], #to_h, and #deep_to_h
  • the common nested fetch chain for Responses and Beta params
  • request serialization after in-place nested hash mutation and array append
  • parsed response enum/const symbols
  • parsed response equality and hash compatibility
  • nested response-model materialization
  • the existing v0.78 collection and union coercion expectations

Generator

No generator change is required. BaseModel and the related runtime tests/signatures are SDK-owned exclusions, and a full generation plus bootstrap overlay preserved these paths.

Validation

  • Ruby 3.3.12: 613 tests / 2,256 assertions
  • Ruby 3.4.10: 613 tests / 2,256 assertions
  • Ruby 4.0.6: 613 tests / 2,256 assertions
  • RuboCop: 2,611 files, no offenses
  • Sorbet typecheck
  • 1,212 RBS files validated
  • gem packaging
  • thermonuclear code-quality review

@openai-sdks

openai-sdks Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

OkTest Summary

237/237 SDK tests passed in 8.57s for Ruby SDK PR #373.

Test results — 42 files
Test Result Time
tests/chat-completions-complex-body.test.ts ✅ Passed 152ms
tests/chat-completions-create.test.ts ✅ Passed 228ms
tests/chat-completions-stream.test.ts ✅ Passed 120ms
tests/files-content-binary.test.ts ✅ Passed 219ms
tests/files-create-multipart.test.ts ✅ Passed 236ms
tests/files-list-pagination.test.ts ✅ Passed 170ms
tests/initialize-config.test.ts ✅ Passed 158ms
tests/instance-isolation.test.ts ✅ Passed 231ms
tests/models-list.test.ts ✅ Passed 173ms
tests/responses-background-lifecycle.test.ts ✅ Passed 204ms
tests/responses-body-method-errors.test.ts ✅ Passed 443ms
tests/responses-cancel-timeout.test.ts ✅ Passed 214ms
tests/responses-cancel.test.ts ✅ Passed 232ms
tests/responses-compact-retries.test.ts ✅ Passed 233ms
tests/responses-compact.test.ts ✅ Passed 251ms
tests/responses-create-advanced-stream.test.ts ✅ Passed 147ms
tests/responses-create-advanced.test.ts ✅ Passed 181ms
tests/responses-create-disconnect.test.ts ✅ Passed 155ms
tests/responses-create-errors.test.ts ✅ Passed 238ms
tests/responses-create-malformed-api-responses.test.ts ✅ Passed 142ms
tests/responses-create-retries.test.ts ✅ Passed 280ms
tests/responses-create-stream-failures.test.ts ✅ Passed 143ms
tests/responses-create-stream-timeout.test.ts ✅ Passed 215ms
tests/responses-create-stream-wire.test.ts ✅ Passed 1.957s
tests/responses-create-stream.test.ts ✅ Passed 147ms
tests/responses-create-terminal-states.test.ts ✅ Passed 282ms
tests/responses-create-timeout.test.ts ✅ Passed 235ms
tests/responses-create.test.ts ✅ Passed 299ms
tests/responses-delete.test.ts ✅ Passed 248ms
tests/responses-input-items-errors.test.ts ✅ Passed 194ms
tests/responses-input-items-list.test.ts ✅ Passed 175ms
tests/responses-input-items-options.test.ts ✅ Passed 250ms
tests/responses-input-tokens-count-timeout.test.ts ✅ Passed 307ms
tests/responses-input-tokens-count.test.ts ✅ Passed 259ms
tests/responses-malformed-inputs.test.ts ✅ Passed 2.143s
tests/responses-not-found-errors.test.ts ✅ Passed 290ms
tests/responses-parse.test.ts ✅ Passed 192ms
tests/responses-retrieve-retries.test.ts ✅ Passed 305ms
tests/responses-retrieve.test.ts ✅ Passed 273ms
tests/responses-stored-method-errors.test.ts ✅ Passed 660ms
tests/retry-behavior.test.ts ✅ Passed 1.934s
tests/sdk-error-shape.test.ts ✅ Passed 348ms

View OkTest run #31648046159

SDK merge (27a66086ad9b) · head (52bcbaf20fa7) · base (919977919667) · OkTest (a845fa206fa4)

@jbeckwith-oai jbeckwith-oai added the generator Touches generated SDK files label Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: b7c0124b6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: b7c0124b6f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@jbeckwith-oai

Copy link
Copy Markdown
Contributor Author

Generator compatibility audit complete: no Castiron PR is required for this fix.

  • The current Ruby generator does not render BaseModel; lib/openai/internal/**, the matching RBI/RBS runtime signatures, and broad runtime tests are SDK-owned exclusions.
  • A full current Ruby generation (3,644 rendered files) plus bootstrap overlay against this head left base_model.rb, base_model.rbi, base_model.rbs, and base_model_raw_value_contract_test.rb byte-for-byte unchanged.
  • The targeted Castiron runtime-exclusion test and SDK-side three-way-overlay preservation test both pass.
  • The generation run did report unrelated RBI/RBS drift from the newer compiler branch, but none of those conflicts touched the raw-value contract paths.

This means integrated future regenerations preserve the implementation and its regression suite as repository-owned runtime code; adding a generator template or compatibility branch would duplicate ownership and is unnecessary.

@apcha-oai apcha-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together. Restoring the raw-value contract is directionally right, but after comparing this branch against the last published release (v0.78.0), I don't think this is safe to merge as a compatibility fix.

1. Request serialization can silently use stale cached values.

input = [{content: "before", role: "user", type: "message"}]

params = OpenAI::Responses::ResponseCreateParams.new(input: input, model: "gpt-4o")



input[0][:content] = "after"

input << {content: "second", role: "user", type: "message"}



params.to_h[:input]

# => [{content: "after", ...}, {content: "second", ...}]



dumped, = OpenAI::Responses::ResponseCreateParams.dump_request(params)

dumped[:input]

# => [{content: "before", ...}]

On v0.78.0, both mutations are included in the serialized request. Here the raw input and converted cache diverge, so the SDK silently sends a different payload from the one exposed by to_h.

2. Parsed response behavior also changes relative to the published SDK. Fields such as response[:type] and response[:role] that were symbols on parsed response models in v0.78.0 become strings here. The change to the existing streaming assertion from c[:type] == :output_text to c[:type] == "output_text" demonstrates the compatibility break directly. Equality and hashing of otherwise equivalent parsed models change as well.

3. This does not undo the broader parsing changes from #295. The changes to union selection, collection coercion, and conversion-error propagation remain, so fixing to_h alone does not restore the previously published parsing behavior.

My honest recommendation is to revert #295, since it has not shipped, and revisit nested model coercion as a separate, deliberately scoped compatibility change. I don't think layering a second global model-conversion/cache rewrite on top is warranted here.

apcha-oai commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

One additional point on the original motivation for #295: if a caller wants typed nested models, they can construct them explicitly instead of relying on global implicit coercion:

item = Item.new(name: "example")
container = Container.new(item: item)

container.item.name # => "example"

Passing a hash and passing a typed model are meaningfully different choices. Automatically converting every nested hash changes existing behavior across model construction, response deserialization, unions, collections, raw accessors, and request serialization.

My recommendation remains to revert #295 and revisit this separately. Globally changing deserialization/coercion to address this is too risky, and we should decide much more carefully whether the right answer is explicit typed constructors, a dedicated conversion API, or a narrowly scoped accessor change.

@jbeckwith-oai

Copy link
Copy Markdown
Contributor Author

@apcha-oai Addressed all three points in 19d44c1.

  1. Removed the converted-value cache entirely. Request models again retain the caller-owned raw objects, and dump_request reads that same current data. The regression test reproduces the exact nested mutation case: changing the first message content and appending a second message are both present in the dumped request.
  2. Restored the v0.78 parsed-response path. Enum and const fields remain symbols, the streaming assertion is back to :output_text, and regression coverage pins parsed-model equality and hash behavior.
  3. Forward-restored the broader pre-fix: coerce nested BaseModel fields #295 behavior in BaseModel, ArrayOf, HashOf, Converter, and Union, and removed the fix: coerce nested BaseModel fields #295-only coercion tests. This is a forward compatibility commit rather than a git revert, while producing the requested published behavior.

I also rewrote the PR description to match the new implementation. The change does not add or redesign discriminator behavior; #370 remains the separate draft for that work.

Local validation is green on Ruby 3.3, 3.4, and 4.0 (613 tests / 2,256 assertions each), plus RuboCop, Sorbet, RBS, packaging, and the thermonuclear code-quality review. Replacement CI is running now. Please take another look when convenient.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19d44c13c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/openai/internal/type/base_model.rb Outdated
Comment thread lib/openai/internal/type/base_model.rb Outdated
Comment thread lib/openai/internal/type/base_model.rb
Comment thread lib/openai/internal/type/union.rb
Comment thread lib/openai/internal/type/union.rb
Comment thread lib/openai/internal/type/base_model.rb
Comment thread lib/openai/internal/type/array_of.rb
@jbeckwith-oai jbeckwith-oai changed the title fix: restore raw BaseModel value contract revert: coerce nested BaseModel fields (#295) Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 52bcbaf2, which makes this a literal rollback of #295. Every production/test path touched by #295 now matches the result of git revert -m 1 e5152a92962251ce76ceb3d89ec0b6b37fa4efdb on main, byte-for-byte; the only retained addition is the focused raw-request / parsed-response compatibility suite.

Validation is green locally across Ruby 3.3.12, 3.4.10, and 4.0.6 (613 runs / 2,256 assertions each), plus lint, gem build, and the thermonuclear review. This intentionally does not attempt a replacement union/discriminator design.

@apcha-oai, once the replacement CI is green, could you please take a fresh look at the exact-revert version?

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 52bcbaf20f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 52bcbaf20f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Copy link
Copy Markdown
Contributor Author

Closing this PR rather than repurposing it. The forward-fix history and review discussion should remain intact. I’ll open a separate, clean PR from current main containing the literal #295 revert plus only the compatibility characterization tests, and link it here once filed.

Copy link
Copy Markdown
Contributor Author

Replacement opened as a new standalone PR: #375. It starts from current main and contains two clean commits only: the literal #295 revert, followed by the compatibility characterization tests.

@jbeckwith-oai jbeckwith-oai changed the title revert: coerce nested BaseModel fields (#295) fix: restore raw BaseModel value contract Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Cleanup complete: this closed PR’s original forward-fix title/body have been restored, and codex/preserve-raw-model-values again points to its last forward-fix head, 19d44c13. The standalone revert remains exclusively in #375.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

generator Touches generated SDK files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants