fix(core): strip non-null defaults for strict schema - #4529
fix(core): strip non-null defaults for strict schema#4529sylvesterkaczmarek wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
💡 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".
seratch
left a comment
There was a problem hiding this comment.
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.
|
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. |
|
@codex review again |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Security review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Security review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
seratch
left a comment
There was a problem hiding this comment.
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>
@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! |
|
Thanks for addressing the previous review feedback. After tracing every caller of #4390 still lacks the requested Azure comparison where otherwise-identical strict requests differ only by the 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. |
Summary
Strip every JSON Schema
defaultvalue during strict-schema normalization, not onlydefault: null.Fixes #4390.
Problem
ensure_strict_json_schema()previously removed a default only when its value was exactlyNone. 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
defaultkeywords.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$refexpansion path. Removing it first leaves a pure$ref; in the MCP strict-conversion path that can be rejected and causeMCPUtil.to_function_tool()to fall back to a non-strict schema.Fix
default: None.$refhandling so$refplusdefaultis expanded correctly.defaultafter 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:
Nonedefaults are absent from strict function schemas while Python-side Pydantic defaults still apply;$refplus a non-null default remains strict;$refis 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: Nonebehavior and only delays removal of a non-null default until after$refexpansion. Final strict schemas remain default-free.