Skip to content

fix(core): strip non-null defaults for strict schema - #4529

Closed
sylvesterkaczmarek wants to merge 6 commits into
openai:mainfrom
sylvesterkaczmarek:fix/strict-schema-defaults-4390-current
Closed

fix(core): strip non-null defaults for strict schema#4529
sylvesterkaczmarek wants to merge 6 commits into
openai:mainfrom
sylvesterkaczmarek:fix/strict-schema-defaults-4390-current

Conversation

@sylvesterkaczmarek

@sylvesterkaczmarek sylvesterkaczmarek commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Strip every JSON Schema default value during strict-schema normalization, not only default: null.

Fixes #4390.

Problem

ensure_strict_json_schema() previously removed a default only when its value was exactly None. Non-null defaults such as strings, enums, integers, and booleans could remain in strict schemas even though strict mode marks every declared property as required.

Structured Outputs can reject those property-level default keywords.

There is one important ordering constraint: for a schema such as {"$ref": "...", "default": "EUR"}, the non-null default must remain present long enough to trigger the existing $ref expansion path. Removing it first leaves a pure $ref; in the MCP strict-conversion path that can be rejected and cause MCPUtil.to_function_tool() to fall back to a non-strict schema.

Fix

  • Preserve the historical early handling of default: None.
  • Preserve non-null defaults until after $ref handling so $ref plus default is expanded correctly.
  • Remove default after expansion/before returning the strict schema.

This does not change Python-side model defaults. It only removes the JSON Schema annotation sent to the model.

Regression coverage

Coverage now verifies:

  • string, integer, boolean, and None defaults are absent from strict function schemas while Python-side Pydantic defaults still apply;
  • a non-empty MCP schema containing $ref plus a non-null default remains strict;
  • that MCP $ref is expanded and the resulting schema is default-free.

Validation

The current branch is mergeable. Repository CI has been triggered for the latest head and is awaiting maintainer workflow approval.

Risk

Low. The change preserves the existing default: None behavior and only delays removal of a non-null default until after $ref expansion. Final strict schemas remain default-free.

@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: cb4dcec3b8

ℹ️ 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 src/agents/strict_schema.py Outdated
@seratch seratch changed the title fix(strict-schema): strip non-null defaults fix(core): strip non-null defaults for strict schema Aug 20, 2026

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution. The underlying issue is valid: strict schemas require every property and default is not part of the supported Structured Outputs subset. Before merging, please update the existing assertions in tests/test_function_schema.py that still require non-null defaults in params_json_schema.

Those tests should assert that strict schemas omit default, while retaining the invocation assertions that prove Python-side Pydantic defaults still apply. The current head otherwise leaves the required test suite failing.

@sylvesterkaczmarek

sylvesterkaczmarek commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the requested test updates in 9ab15dc: the six existing strict-schema assertions now verify that default is omitted, while the invocation checks still confirm Python-side Pydantic defaults apply. The review thread is resolved and the PR is ready for re-review.

Addressed. Updated the existing tests/test_function_schema.py assertions so strict schemas now verify that default is omitted, while retaining the invocation assertions that confirm Python-side Pydantic defaults still apply. The related review thread is resolved. Ready for re-review.

@seratch

seratch commented Aug 20, 2026

Copy link
Copy Markdown
Member

@codex review again

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-20T23:45:59.355911Z 8444e22 New commits
🔒 Security Review Completed 2026-08-20T23:47:03.450266Z 8444e22 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 9ab15dc005

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.

@seratch

seratch commented Aug 20, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 9ab15dc005

ℹ️ 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: 9ab15dc005

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.

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the previous test failures. The six existing function-schema assertions now match the intended strict-schema behavior, and the invocation checks still cover Python-side defaults.

One merge-blocking ordering issue remains. Please preserve a non-null default until after $ref handling, then remove it after expansion, while keeping the existing early default: None behavior. At the current head, {"$ref": "...", "default": "EUR"} becomes a pure $ref before expansion. In the MCP path, _reject_open_objects=True then rejects it, and MCPUtil.to_function_tool() silently falls back to a non-strict schema. Add a regression showing that a non-empty MCP schema with a $ref plus non-null default remains strict and produces an expanded, default-free schema.

Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com>
Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com>
Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com>
@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor Author

Thanks for addressing the previous test failures. The six existing function-schema assertions now match the intended strict-schema behavior, and the invocation checks still cover Python-side defaults.

One merge-blocking ordering issue remains. Please preserve a non-null default until after $ref handling, then remove it after expansion, while keeping the existing early default: None behavior. At the current head, {"$ref": "...", "default": "EUR"} becomes a pure $ref before expansion. In the MCP path, _reject_open_objects=True then rejects it, and MCPUtil.to_function_tool() silently falls back to a non-strict schema. Add a regression showing that a non-empty MCP schema with a $ref plus non-null default remains strict and produces an expanded, default-free schema.

@seratch Addressed the remaining ordering issue on current head 8444e22: default: None keeps the existing early removal, non-null defaults now survive until $ref expansion and are removed afterward, and I added an MCP regression confirming that $ref + non-null default remains strict and produces an expanded, default-free schema.

Ready for re-review. Thanks!

@seratch

seratch commented Aug 21, 2026

Copy link
Copy Markdown
Member

Thanks for addressing the previous review feedback. After tracing every caller of ensure_strict_json_schema(), this change is broader than the reported Azure function-tool case: it also changes strict handoff, structured-output, Realtime, MCP, agent-as-tool, and Codex tool schemas, along with the public params_json_schema value and serialized RunState metadata.

#4390 still lacks the requested Azure comparison where otherwise-identical strict requests differ only by the default keyword. Direct OpenAI requests accept the current schema, and upstream openai-python continues to preserve non-null defaults.

I do not think we should make this global released-behavior change without that evidence. I am going to close this PR for now. If the isolated Azure comparison demonstrates causality, we can revisit a narrower provider-boundary solution.

@seratch seratch closed this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

strict_mode: non-null default values are left in the schema and rejected on Azure

2 participants