fix: coerce JSON-stringified dict/list tool params before Pydantic validation#1882
Open
giulio-leone wants to merge 1 commit intostrands-agents:mainfrom
Open
fix: coerce JSON-stringified dict/list tool params before Pydantic validation#1882giulio-leone wants to merge 1 commit intostrands-agents:mainfrom
giulio-leone wants to merge 1 commit intostrands-agents:mainfrom
Conversation
Bedrock/Claude sometimes serializes nested object or array tool-call
parameters as JSON strings instead of native Python dicts/lists. This
causes Pydantic validation errors like:
Input should be a valid dictionary [type=dict_type, input_value='{...}', input_type=str]
Root cause: the Converse API returns tool_use.input as a parsed dict,
but for nested object/array parameters the model may stringify the
value before embedding it in the outer dict. The SDK's Pydantic-based
validate_input() then receives a str where it expects a dict/list and
rejects it.
The fix adds a pre-validation coercion step in
FunctionToolMetadata.validate_input() that:
1. Inspects each field's type annotation on the input_model
2. If the annotation accepts dict or list (including Optional/Union)
and the incoming value is a string, attempts json.loads()
3. Only replaces the value if deserialization produces a dict or list
4. Leaves non-JSON strings untouched (they still fail validation)
This is model-provider-agnostic and has zero impact on correctly-typed
inputs — the coercion only activates when a string value is received
for a dict/list field.
Closes strands-agents#1285
8bd3314 to
c9370f9
Compare
Contributor
Author
|
Friendly ping — coerces JSON-stringified dict/list tool parameters back to native types before Pydantic validation, fixing crashes when LLMs return string-wrapped JSON. |
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.
Issue
Closes #1285
Problem
When using strands with Bedrock/Claude and MCP tools that have nested object parameters (e.g.,
parameters: dict[str, Any] | None), the model incorrectly serializes the parameter value as a JSON string instead of a native Python dictionary:This causes a Pydantic validation error:
Root Cause
The Bedrock Converse API returns
tool_use.inputas a parsed dict, but for nested object/array parameters the model may stringify the value before embedding it in the outer dict. The SDK's Pydantic-basedvalidate_input()then receives astrwhere it expects adict/listand rejects it.Solution
Added a pre-validation coercion step in
FunctionToolMetadata.validate_input()via_coerce_json_string_params()that:dictorlist(includingOptional/Union/Any) and the incoming value is a string, attemptsjson.loads()dictorlistDesign Notes
dictorlist— won't coerce a string thatjson.loadsto an int, bool, etc.Testing
test_validate_input_coerces_json_string_to_dict: Tests dict coercion withOptional[dict[str, Any]], verifies dict values still work, None still works, and non-JSON strings still raiseValueErrortest_validate_input_coerces_json_string_to_list: Tests list coercion withlist[str]Changes
src/strands/tools/decorator.py: Added_coerce_json_string_params(),_annotation_accepts_mapping_or_sequence()methods toFunctionToolMetadata; updatedvalidate_input()to call coercion before Pydantic validationtests/strands/tools/test_decorator.py: Added 2 test cases for JSON string coercion