Repository navigation
Conversation
Contributor
|
@strandly-the-agent review |
strandly-the-agent
approved these changes
Sep 18, 2026
strandly-the-agent
left a comment
Collaborator
There was a problem hiding this comment.
Approve. Correct, minimal fix for #4379: the inner loop was rebinding the outer is_required, so a required nested object was dropped from required whenever the nested model's last field was optional. Renaming the loop-local fixes exactly that and nothing else. No findings.
What I verified
- Head
7a1fbc4, diff vs merge-base is 2 files / +37 −2 (the source change is the 2-line rename atstructured_output_utils.py:57-58). - New test fails on
mainwith the fix reverted (KeyError: 'required') and passes on the PR head. pytest tests/strands/tools/structured_output tests/strands/tools/test_structured_output.py tests/strands/agent/test_agent_structured_output.py -n auto→ 65 passed.ruff check/ruff format --checkon both changed files: clean.- Side-effect probe (required-nested/optional-last, nested-with-default/required-last, nullable-nested/required-last) — only the first case changes (
None→['inner']); the other two areNoneon bothmainand the PR head, so the fix doesn't flip any optional nested object to required. - No TS counterpart:
strands-tsbuilds structured-output schemas from zod and has no$ref-flattening step, so no parity change needed.
Skipped the full specialist fan-out — a two-line local rename with a deterministic before/after test doesn't warrant it.
`_flatten_schema` reused the `is_required` loop variable while processing the sub-properties of an expanded nested object, so the outer property's required-ness was left holding the value of the last nested sub-property. A required nested model whose last field was optional was then dropped from the tool spec's `required` list. Use a separate local for the nested sub-property required-ness.
BlueX888
force-pushed
the
fix/prep-structured-output-drops-required-nested-field
branch
from
October 8, 2026 10:14
7a1fbc4 to
915dd1d
Compare
8 of 9 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
_flatten_schemainstrands-py/src/strands/tools/structured_output/structured_output_utils.pykeeps the outer property's required-ness inis_required(line 44). The loop that processes the sub-properties of an expanded nested object wrote its own result into that same local (line 57):The inner assignment rebinds the outer local, so once the loop finishes
is_requiredholds the required-ness of the nested model's last declared field. The outer check at line 71 then reads that value. When the nested model's last field is optional, the check is false and the required nested object is left out of the tool spec'srequiredlist, so the model is never told the field is required.Reproduced on
main:Changes:
structured_output_utils.py: bind the nested sub-property's required-ness to a separatenested_is_requiredlocal, so the outer property'sis_requiredsurvives the inner loop.test_structured_output.py: addtest_convert_pydantic_required_nested_object_stays_required, which converts a model with a required nested object under both field orders of the nested model and assertsrequired == ["inner"]in both cases.The emitted schema is unchanged except where the bug suppressed a required entry; nothing else reads the local.
Related Issues
Fixes #4379
Documentation PR
No documentation change is needed — this is an internal fix to the schema converter; the public API is unchanged.
Type of Change
Bug fix
Testing
New test,
pytest tests/strands/tools/test_structured_output.py -k stays_required:mainwithout the source change:1 failed—KeyError: 'required'at the first assertion.1 passed.pytest tests/strands/tools/test_structured_output.py: 14 passed.pytest tests/strands/tools/: 692 passed, 31 skipped.Broader run over the structured-output call sites (
tests/strands/tools/,test_agent_structured_output.py,test_streaming_structured_output.py,test_event_loop_structured_output.py,test_agent_as_tool.py): 766 passed, 31 skipped. The same selection with the source change reverted and the new test deselected gives 765 passed, 31 skipped, 1 deselected — the same set, so the only behavior that changed is the one the new test covers.pytest tests/strands/agent tests/strands/event_loop tests/strands/tools(ignoringtest_a2a_agent.py, which needs the optionala2aextra not installed here): 1648 passed, 31 skipped.ruff check: All checks passed.ruff format --checkon the two changed files: already formatted.mypy src/strands/tools/structured_output/structured_output_utils.py: no errors in the changed file.I ran
hatch run prepare(thehatch-static-analysisenv build in my environment spent ~50 minutes compiling the optional native dependencies and did not finish; I ran the equivalent ruff/mypy/pytest commands above)Checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.