fix(responses): decode JSON-string tool schemas before sending to the provider - #39844
Conversation
|
@greptileai can you reply here with a score and why that score? |
|
bugbot run |
|
Confidence score: 4/5 The fix correctly centralizes tool preparation for both Responses and compact requests, decodes JSON strings only when they produce an object, and raises a targeted I’m not giving 5/5 because the required CI check is still pending, and the PR documents two intentionally unhandled paths: nested Codex namespace tools and the Anthropic Messages-to-chat adapter. Those are reasonable follow-ups if they are later included in scope; for the reported top-level Responses/Azure regression, this is a solid 4/5. |
Greptile SummaryThis PR normalizes JSON-encoded Responses API tool schemas into objects before provider dispatch and returns an indexed client error for invalid schemas. The shared preparation path now applies the same behavior to standard and compact requests, with coverage for OpenAI and Azure request shapes, invalid values, null schemas, and omitted schemas Confidence Score: 5/5The PR appears safe to merge, with no outstanding correctness or repository-rule findings The schema conversion accepts objects, decodes only JSON strings containing objects, preserves provider-supported null and absent schemas, and rejects other values before dispatch. Both previous rule findings were manually resolved, and the earlier null-schema concern was withdrawn after the provider compatibility evidence
|
| Filename | Overview |
|---|---|
| litellm/llms/openai/responses/transformation.py | Adds centralized tool-schema decoding and validation shared by standard and compact Responses requests |
| tests/test_litellm/llms/openai/responses/test_openai_responses_transformation.py | Adds focused regression coverage and completes the typing and null-schema follow-ups from the previous review |
| type-discipline-budget.json | Lowers the relevant type-discipline limits to match the typed implementation |
Reviews (2): Last reviewed commit: "test(responses): pin null tool schemas a..." | Re-trigger Greptile
|
Pushed 66ac753. Declined the null finding with a live probe and a pinning test; fixed both P2s. @greptileai can you reply here with a score and why that score? |
|
bugbot run |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Score: 5/5 (high confidence). Commit
No remaining blocking concerns from this review. Confidence score: 5/5. |
|
@greptileai review |
|
The 5 |
|
Merged current staging (ab0fc27). The integrations red was staging's own, fixed upstream by d4fd658 after this branch's base; re-ran the live evidence on the merged tree, all legs still 200. @greptileai can you reply here with a score and why that score? |
|
bugbot run |
|
Confidence score: 5/5. The merged staging tree preserves the fix, and the updated live evidence confirms all reported paths still return 200, including direct Responses requests, Messages input-schema conversion, the 1M Context auto-router/Azure route, the chat-to-Responses bridge, and the object-schema controls. The implementation remains correctly scoped: standard and compact requests share one preparation path, JSON strings are decoded only when they produce an object, valid object/null/omitted schemas are preserved, and malformed or non-object values fail early with an indexed |
… provider A caller that hands a tool schema over already JSON-encoded reached the Responses API with a string `parameters`, and the provider rejected the request with a 400 naming the routed model instead of the offending tool. Decode it at the one place every Responses request converges, and refuse anything that is neither an object nor a string encoding one. Collapses the duplicated input/tool sanitization block shared by the request and compact-request builders into a single owner, so the decode cannot be wired into one path and not the other.
…metrized cases The Responses API serves `parameters: null` and an omitted schema alike, so neither may raise. Pin both against a future tightening, annotate the parametrized inputs, and trim the docstrings back to what the code does not already say.
Lower the type-discipline limits for violations removed by the response transform fix.
ab0fc27 to
6140d88
Compare
|
Rebased onto current litellm_internal_staging at 7573632; final head is 6140d88. @greptileai can you reply here with a score and why that score? |
|
bugbot run |
|
Confidence score: 5/5. The rebased head The focused regression tests cover successful decoding, compact requests, invalid values, accepted edge cases, and Azure behavior. The current Buildkite E2E check is passing, and I found no remaining correctness concerns. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6140d88. Configure here.
TLDR
Problem this solves:
How it solves it:
parametersthat parses to an object before the wiretools[i].parametersUser Flow
Before: a developer whose coding agent defines a tool with a JSON-encoded schema gets an opaque provider error blaming the model tier
tools[0].input_schemaset to the string"{\"type\":\"object\",...}"AzureException BadRequestError - Invalid type for 'tools[0].parameters': expected an object, but got a string instead. Received Model Group=gpt-5.6-lunaAfter: the same request succeeds, and a genuinely malformed schema gets an error that points at the tool
input_schematool_useblock for the tool they definedlitellm.BadRequestError: Invalid type for 'tools[0].parameters': expected an object, but got str insteadfrom the gateway itself, before any provider callRelevant issues
Linear ticket
Resolves LIT-6991
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Local proxy on
:4733from this worktree, real Postgres, deploymentsgpt-5.6-luna/gpt-5.6-terra/gpt-5.6-sol/claude-opus-5registered against a live gateway as upstream, plusmy-1m-router, an auto-router with the 1M Context preset's tiers. Every run below is a real paid model call. The customer's Azure resource is not reachable from this box (every deployment 404s), so the upstream here is the same model family behind OpenAI's Responses validator, which emits the byte-identicalInvalid type for 'tools[0].parameters'error. The only variable between Before and After is the source treeShared payload: a
get_weathertool whose schema is the JSON string"{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"]}", and a control request with the same schema as an object. Both arms were captured with the samerepro.shandchatbridge.sh, differing only in the checked-out sourceBefore (59d42d3)
/v1/responses, string parameters
curl -X POST http://127.0.0.1:4733/v1/responses -d '{"model":"gpt-5.6-luna","input":"weather in Paris","tools":[{"type":"function","name":"get_weather","parameters":"<string schema>"}]}'Invalid type for 'tools[0].parameters': expected an object, but got a string instead./v1/messages, string input_schema
curl -X POST http://127.0.0.1:4733/v1/messages -d '{"model":"gpt-5.6-luna","max_tokens":64,"messages":[{"role":"user","content":"weather in Paris"}],"tools":[{"name":"get_weather","description":"w","input_schema":"<string schema>"}]}'Invalid type for 'tools[0].parameters': expected an object, but got a string instead./v1/messages through the 1M Context auto-router
"model":"my-1m-router"and content"hi"so it classifies SIMPLEInvalid type for 'tools[0].parameters': expected an object, but got a string instead.This is the customer's exact report/v1/chat/completions bridged to Responses, nested string parameters
curl -X POST http://127.0.0.1:4733/v1/chat/completions -d '{"model":"luna-responses-mode","messages":[{"role":"user","content":"weather in Paris"}],"tools":[{"type":"function","function":{"name":"get_weather","parameters":"<string schema>"}}]}'(deployment declaresmodel_info.mode: responses)Invalid type for 'tools[0].parameters': expected an object, but got a string instead.Note the error names the flattools[0].parameterseven though the request nested it underfunction, which is how the customer's report ended up with that shapeControls, object schema
/v1/responsesand/v1/messagesrequests with the schema as an object/v1/messagesreturns atool_useblockAfter (6140d88)
/v1/responses, string parameters
id: resp_84m6-zTsfRUQYDA...,model: gpt-5.6-luna,output: [function_call]/v1/messages, string input_schema
model: gpt-5.6-luna,output: [tool_use]/v1/messages through the 1M Context auto-router
model: my-1m-router,output: [text]/v1/chat/completions bridged to Responses, nested string parameters
id: chatcmpl-a7d710d8-35...,finish_reason: tool_calls, tool callget_weatherControls, object schema
Type
🐛 Bug Fix
Caveats (if any)
Low
toolsarrays are not walked; only top-level entries decodeadapters/transformation.py) copiesinput_schemathe same way and its extra-key merge raisesAttributeErroron a string schema. Same class, different wire shape, not in this report, left as a follow-uptypename is Python's (str,list) rather than the provider's (a string,an array)parameters: nulland an omitted schema are forwarded untouched, since the API accepts both; a live probe returned 200 for null, null withstrict: false, and omittedFinal Attestation
Note
Low Risk
Scoped to Responses API outbound request shaping for tools, with clear validation and unit tests; no auth or persistence changes.
Overview
Fixes Responses API requests where function tools carry
parametersas a JSON string (common when agents pass stringifiedinput_schema), which previously caused opaque provider 400s blaming the model.OpenAI Responses transformation now runs tool prep through a shared
_prepared_input_and_toolspath for both normal and/compactrequests. Before flattening schema combinators, it parses stringparametersinto objects viasafe_json_loads; object,null, and omitted schemas are unchanged. Invalid values raiselitellm.BadRequestErrornamingtools[i].parametersbefore any upstream call. Tool typing is widened toSequencefor the sanitization helpers.Tests cover decode/reject/unchanged cases on standard and compact transforms, plus Azure after chat-shaped tool un-nesting.
type-discipline-budget.jsonlimits are nudged down slightly.Reviewed by Cursor Bugbot for commit 6140d88. Bugbot is set up for automated code reviews on this repo. Configure here.