Repository navigation
fix(openapi): support non-object request bodies and stop body data loss on parameter name collisions - #344
Closed
K4bain wants to merge 1 commit into
Closed
fix(openapi): support non-object request bodies and stop body data loss on parameter name collisions#344K4bain wants to merge 1 commit into
K4bain wants to merge 1 commit into
Conversation
…ss on parameter name collisions (tadata-org#319) Two related defects in how requestBody is projected and executed: 1. Non-object request bodies (top-level arrays or primitives, e.g. from a Pydantic __root__ model) have no 'properties' field, so the conversion loop added no arguments for them — the tool was generated with no way to supply the body, making those endpoints unreachable. Such bodies are now mapped to a single 'payload' argument carrying the body schema verbatim. 2. path/query/header parameters and body fields are flattened into one inputSchema; on a name collision (path 'id' + body 'id') the executor's arguments.pop() deleted the value while building the path, so the body payload lost the field and FastAPI answered 422. The operation map now records which argument names are body fields ('body_param_names') and optionally the raw-body argument name; the executor rebuilds the body from that list BEFORE consuming routing parameters, so a colliding body field reaches the body as well as the path. On schema conversion a collision logs a warning and the route parameter keeps the inputSchema slot (the LLM cannot express the two independently — that would require a breaking nesting change, out of scope here). Operation maps without the new metadata (hand-constructed or from older versions) keep the exact historical leftover-arguments behavior. Also fixes test_execute_api_tool_with_body, which asserted the old inconsistent contract (caller supplying {'item': {...}} although the advertised inputSchema flattens the Item model's fields); the test now sends what the schema advertises and the endpoint actually expects. Regression tests: schema-level (array body -> payload argument, collision metadata recorded) and executor-level (body survives path pop, raw body passthrough, legacy map fallback). 19/19 in touched files, full non-transport suite green, ruff + mypy clean.
Author
|
Closing for housekeeping - the repo had no reviewer engagement and we are re-approaching these areas through a narrower pipeline. Happy to reopen on request. |
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.
Fixes #319 (both parts).
Root cause
Two related defects in how
requestBodyis projected and executed:schema["properties"]— a top-level array (List[Item] = Body(...)) or a primitive body (Pydantic__root__model) has noproperties, so the tool was generated with no body arguments at all. The endpoint was unreachable: the LLM had no way to supply the payload.item_idin both/orders/{item_id}and the body model), the executor'sarguments.pop(param_name)consumed the value while building the path URL — and the body payload then went out without the field, so FastAPI answered422 Unprocessable Entity.The fix
Schema conversion (
convert.py)payloadargument carrying the body schema verbatim (falls back torequestBodyif a route parameter is namedpayload). Purely additive: these tools previously had no body input at all.body_param_names(which flattened arguments belong to the body) andraw_body_argument(set for non-object bodies).Execution (
server.py)body_param_namesbefore routing parameters are popped, so a colliding body field reaches the body as well as the path.json=<the array/primitive>, not nested).operation_map.Test contract fix
test_execute_api_tool_with_bodypreviously asserted the old inconsistent behavior: the tool's advertised inputSchema flattens theItemmodel's fields, but the test supplied{"item": {...}}and expected it forwarded verbatim — a payload the real endpoint would reject with 422. The test now sends what the schema advertises (which is also whathttp://localhostFastAPI actually expects), and the executor forwards exactly the declared body fields.Verification
json == {"item_id": 42, "quantity": 7}while URL becomes/orders/42), raw array body passes through unwrapped, legacy map keeps leftover behavior.payloadargument withbody_param_names/raw_body_argumentmetadata; collision → route param keeps the slot + metadata records both body fields.main— subprocess fork contexts, unrelated to this change.)