Skip to content

Python: Fix nested required properties marked as optional in OpenAPI plugins - #14467

Open
Madan Kumar (winklemad) wants to merge 1 commit into
microsoft:mainfrom
winklemad:fix/openapi-nested-required
Open

Madan Kumar (winklemad) wants to merge 1 commit into
microsoft:mainfrom
winklemad:fix/openapi-nested-required

Conversation

@winklemad

@winklemad Madan Kumar (winklemad) commented Sep 22, 2026 •

Copy link
Copy Markdown

Motivation and Context

_get_payload_properties in the OpenAPI plugin passes the top level required list into its own recursion, so a nested object's properties get their is_required checked against the root schema's required array instead of the nested object's own. A required nested field, for a body like {required: [address], address: {required: [street], properties: {street, city}}}, ends up marked optional in the generated function schema. The model can then leave it out and the real API call fails validation.

The top level caller (_create_rest_api_operation_payload) already seeds each object with its own required list, so this is an inconsistency in the recursion rather than intended behavior. I reproduced it end to end: parsing such a spec produces a street parameter with is_required=False that reaches the tool metadata shown to the model.

Description

Pass each object's own required list into the recursion instead of reusing the parent's. Added test_get_payload_properties_nested_required covering a required nested field and an optional sibling.

Behavior change: a nested field that its own object lists as required is now treated as required. Plugins that called an operation without such a field used to get a request with the field missing; they now get a FunctionExecutionException asking for it, which matches the spec. In the other direction, a nested field that only shares a name with a root-level required field is no longer wrongly required.

Verified locally:

  • New test fails before the change and passes after.
  • Full tests/unit/connectors/openapi_plugin suite passes (184 tests).
  • ruff check, ruff format --check, and mypy are clean.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the SK Contribution Guidelines and the pre-submission formatting script raises no violations
  • All unit tests pass, and I have added new tests where possible
  • I didn't break anyone 😄

…plugins

When building the function schema from an OpenAPI spec, _get_payload_properties
recursed into nested object schemas but kept passing the top level required list
down at every level. A nested property's is_required was checked against the root
object's required array instead of the nested object's own one, so a required
nested field like address.street ended up marked optional. The generated tool
then advertises it as optional, the model can omit it, and the request fails
schema validation on the API side.

The top level caller already seeds each object with its own required list, so do
the same in the recursion. Adds a unit test for the nested case.

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The reviewed change and regression test address the nested required-property issue with no unresolved blocking concerns.

Review effort: Lite
Findings: None

What changed in this PR

Fixes nested OpenAPI payload properties being incorrectly marked as optional.

Changes:

  • Uses each schema’s own required list during recursion.
  • Adds regression coverage for nested required and optional fields.
File Description
python/​tests/​unit/​connectors/​openapi_plugin/​test_openapi_parser.py Adds nested required-property regression coverage.
python/​semantic_kernel/​connectors/​openapi_plugin/​openapi_parser.py Corrects recursive required-property handling.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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.

Community review (does not clear the merge gate).

I read the full diff, applied it on upstream main (5b4a326) in a separate worktree and ran it from python/ with Python 3.13 and PYTHONPATH set to that tree. I did not run a live OpenAPI call; the checks below call OpenApiParser._get_payload_properties and OpenApiRunner.build_json_object directly.

The bug is real and the fix is right. On main, _get_payload_properties passes the root required list down to every nested object, so a nested property is marked required if it merely shares a name with a root-required property. Example schema: root required: ["id"], properties id and address{id, street}.

  • main: address.id has is_required=True. With enable_payload_namespacing = True and arguments {"id": "1"}, build_json_object raises FunctionExecutionException: No argument is found for the 'id' payload property.
  • PR: address.id is not required and the result is {'id': '1', 'address': {}}.

Tests: test_get_payload_properties_nested_required fails on main and passes with the PR; tests/unit/connectors/openapi_plugin gives 223 passed with the PR applied.

Two behavior notes, both traced:

  1. The change also works in the other direction. Schema with root required: [] and address{required: ["street"], properties: {street, city}}: main marks street optional and build_json_object returns {'address': {'city': 'x'}} when street is missing; the PR marks street required and the same call raises FunctionExecutionException: No argument is found for the 'street' payload property. That is what the spec says, but it is a runtime behavior change for existing plugins that omitted a nested required field. Worth a line in the PR description.
  2. property_schema.get("required", set()) is passed straight into property_name in required_properties. If a nested object schema has a non-list required (I tried "required": true), the PR raises TypeError: argument of type 'bool' is not iterable where main did not. I called the private method directly; I did not check whether the spec loader (ResolvingParser) already rejects such a document before this point, so this may be unreachable in practice.

Not run: array items (the function does not descend into items on either side), and any real HTTP request through the plugin.

@winklemad

Copy link
Copy Markdown
Author

Thanks for testing this so carefully. I added a note about the behavior change to the description. On the non-list required, JSON Schema requires it to be an array, so I left that path as is rather than guard against an invalid spec.

This branch had an error being deployed

1 failed deployment
github-app-auth — 83cfb578 Deployed Sep 22, 2026 by winklemad via add_label #29048
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants