Repository navigation
Python: Fix nested required properties marked as optional in OpenAPI plugins - #14467
Madan Kumar (winklemad) wants to merge 1 commit into
Conversation
…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.
There was a problem hiding this comment.
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
requiredlist 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.
PRABHU KIRAN VANDRANKI (VANDRANKI)
left a comment
There was a problem hiding this comment.
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.idhasis_required=True. Withenable_payload_namespacing = Trueand arguments{"id": "1"},build_json_objectraisesFunctionExecutionException: No argument is found for the 'id' payload property. - PR:
address.idis 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:
- The change also works in the other direction. Schema with root
required: []andaddress{required: ["street"], properties: {street, city}}: main marksstreetoptional andbuild_json_objectreturns{'address': {'city': 'x'}}whenstreetis missing; the PR marksstreetrequired and the same call raisesFunctionExecutionException: 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. property_schema.get("required", set())is passed straight intoproperty_name in required_properties. If a nested object schema has a non-listrequired(I tried"required": true), the PR raisesTypeError: argument of type 'bool' is not iterablewhere 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.
|
Thanks for testing this so carefully. I added a note about the behavior change to the description. On the non-list |
Motivation and Context
_get_payload_propertiesin the OpenAPI plugin passes the top levelrequiredlist into its own recursion, so a nested object's properties get theiris_requiredchecked against the root schema'srequiredarray 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 ownrequiredlist, so this is an inconsistency in the recursion rather than intended behavior. I reproduced it end to end: parsing such a spec produces astreetparameter withis_required=Falsethat reaches the tool metadata shown to the model.Description
Pass each object's own
requiredlist into the recursion instead of reusing the parent's. Addedtest_get_payload_properties_nested_requiredcovering a required nested field and an optional sibling.Behavior change: a nested field that its own object lists as
requiredis 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 aFunctionExecutionExceptionasking 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:
tests/unit/connectors/openapi_pluginsuite passes (184 tests).ruff check,ruff format --check, andmypyare clean.Contribution Checklist