Skip to content

fix(openapi): let operation parameters override path-level ones - #7209

Open
1aifanatic wants to merge 3 commits into
google:mainfrom
1aifanatic:fix/openapi-operation-params-override-path-params
Open

1aifanatic wants to merge 3 commits into
google:mainfrom
1aifanatic:fix/openapi-operation-params-override-path-params

Conversation

@1aifanatic

Copy link
Copy Markdown

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

Problem:

OpenApiSpecParser._collect_operations() appended every path-level parameter to each operation's own parameters. Per the OpenAPI 3 Path Item Object, an operation-level parameter with the same name and in overrides the path-level one. Keeping both made the tool ask the model for the same path value twice (account_id and account_id_0, both required). Both map to the same {accountId} placeholder, and the path-level value won, so the operation's own, more specific parameter was dropped from the request.

Solution:

Skip path-level parameters whose (name, in) the operation already declares. Parameters with the same name but a different location are still merged. References are resolved before this step, so parameters are plain dicts here; anything without a name is left untouched.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

test_openapi_spec_parser.py::test_parse_spec_operation_parameter_overrides_path_level_parameter: the path accountId is overridden and keeps the operation's description, while a same-named header parameter is still merged. Fails on main.

$ pytest tests/unittests/tools/openapi_tool
342 passed

$ pytest tests/unittests -n 8   # Python 3.12, Windows 11
42 failed, 15146 passed, 102 skipped, 27 xfailed, 2 xpassed

Every failure outside the new tests also fails on an unmodified origin/main (3f4bb8f) on the same machine, where the same run gives 47 failed. Most are deterministic Windows-specific tests (cli/ deploy, conformance, scripts/check_new_py_files, path normalization, the unsafe local code executor, the import allowlist, yaml). The remainder differ between runs: telemetry functional/node-functional schema cases, and streaming/test_streaming_tool_events.py::test_delivered_event_carries_the_message_and_nothing_else, which failed 1/10 in isolation on clean main versus 2/10 on this branch. I ran on Python 3.12 only, not the full tox matrix.

Manual End-to-End (E2E) Tests:

An OpenAPIToolset whose /accounts/{accountId} path declares accountId and whose GET overrides it; HTTP goes to an httpx.MockTransport. The script is in #7205.

Before (main @ 3f4bb8f):

model is asked for: ['account_id', 'account_id_0']
args: {'account_id': 'ACC-123', 'account_id_0': 'ACC-999'} -> request: https://crm.example.com/accounts/ACC-999

After:

model is asked for: ['account_id']
args: {'account_id': 'ACC-123'} -> request: https://crm.example.com/accounts/ACC-123

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

🤖 Generated with Claude Code

https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7

_collect_operations() appended every path-level parameter to each
operation's parameters. An operation-level parameter with the same name
and location overrides the path-level one (OpenAPI Path Item Object), so
keeping both asked the model for the same path value twice, as
account_id and account_id_0. Both map to the same placeholder, and the
path-level value won, dropping the operation's more specific parameter.

Skip path-level parameters whose (name, in) the operation already
declares.

Fixes google#7205

Claude-Session: https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7
OpenAPI mandates `required: true` on every path parameter, but the
parser took the flag as written and defaulted it to false. A spec that
leaves it out, including an operation-level override of a required
path-level parameter, declared the path parameter optional, so a model
could omit it and the request failed with KeyError while building the
URL.

Treat `in: path` parameters as required regardless of the flag;
query, header and cookie parameters keep the operation's own value.

Claude-Session: https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7
@1aifanatic

Copy link
Copy Markdown
Author

@llalitkumarrr thanks for testing, and good catch on that edge case (from #7205).

For a path parameter the flag can't legitimately be false. The OpenAPI spec says required "MUST be true" when in: path, and the URL can't be built without the value. The parser took the flag as written, though, so a spec that leaves it out declared the path parameter optional. That includes an operation-level override like the one you describe. This affects main independently of this PR: if the model omits such a parameter, the call fails with KeyError: 'accountId' while formatting the URL.

I've pushed da60abc so that in: path parameters are always treated as required, whatever the flag says. For query, header and cookie parameters, the operation-level definition replaces the path-level one, as the spec defines. An override replaces the whole parameter object and required defaults to false, so an override that makes one of those optional is honored.

With the override leaving out required, the parameters the model is asked for:

main:                     account_id (optional) + account_id_0 (required)
this PR before da60abc3:  account_id (optional)
this PR now:              account_id (required)

New test: test_parse_spec_path_parameter_is_required_even_when_override_omits_it. pytest tests/unittests/tools gives 2344 passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants