From e59ed5bae2a0ff3cd1038fb4c5acff2d1174ea04 Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Thu, 24 Jul 2025 16:15:17 -0400 Subject: [PATCH 1/5] feat: update structured_output tool name to reduce user confusion --- src/strands/tools/structured_output.py | 4 +- tests/strands/tools/test_structured_output.py | 43 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/strands/tools/structured_output.py b/src/strands/tools/structured_output.py index 6f2739d88..c055cf89b 100644 --- a/src/strands/tools/structured_output.py +++ b/src/strands/tools/structured_output.py @@ -275,7 +275,7 @@ def convert_pydantic_to_tool_spec( Returns: ToolSpec: Dict containing the Bedrock tool specification """ - name = model.__name__ + name = f"{model.__name__}OutputStructurer" # Get the JSON schema input_schema = model.model_json_schema() @@ -300,7 +300,7 @@ def convert_pydantic_to_tool_spec( # Construct the tool specification return ToolSpec( name=name, - description=model_description or f"{name} structured output tool", + description=model_description or f"{model.__name__} structured output tool", inputSchema={"json": final_schema}, ) diff --git a/tests/strands/tools/test_structured_output.py b/tests/strands/tools/test_structured_output.py index 97b68a34c..68dec068b 100644 --- a/tests/strands/tools/test_structured_output.py +++ b/tests/strands/tools/test_structured_output.py @@ -41,7 +41,7 @@ def test_convert_pydantic_to_tool_spec_basic(): tool_spec = convert_pydantic_to_tool_spec(User) expected_spec = { - "name": "User", + "name": "UserOutputStructurer", "description": "User model with name and age.", "inputSchema": { "json": { @@ -73,7 +73,7 @@ def test_convert_pydantic_to_tool_spec_complex(): tool_spec = convert_pydantic_to_tool_spec(ListOfUsersWithPlanet) expected_spec = { - "name": "ListOfUsersWithPlanet", + "name": "ListOfUsersWithPlanetOutputStructurer", "description": "List of users model with planet.", "inputSchema": { "json": { @@ -127,7 +127,7 @@ def test_convert_pydantic_to_tool_spec_multiple_same_type(): tool_spec = convert_pydantic_to_tool_spec(TwoUsersWithPlanet) expected_spec = { - "name": "TwoUsersWithPlanet", + "name": "TwoUsersWithPlanetOutputStructurer", "description": "Two users model with planet.", "inputSchema": { "json": { @@ -286,7 +286,7 @@ class Person(BaseModel): "type": "object", } }, - "name": "Person", + "name": "PersonOutputStructurer", } assert tool_spec == expected_spec @@ -340,6 +340,39 @@ class Person(BaseModel): "type": "object", } }, - "name": "Person", + "name": "PersonOutputStructurer", } assert tool_spec == expected_spec + + +def test_tool_name_validation(): + """Test that tool names follow proper naming conventions.""" + import re + + # Tool names should start with a letter and contain only alphanumeric characters and underscores + tool_name_pattern = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$') + + # Test with various model names + class SimpleModel(BaseModel): + value: str + + class Model_With_Underscores(BaseModel): + value: str + + class Model123(BaseModel): + value: str + + # Test all models produce valid tool names + for model_class in [SimpleModel, Model_With_Underscores, Model123, User, UserWithPlanet]: + tool_spec = convert_pydantic_to_tool_spec(model_class) + tool_name = tool_spec["name"] + + # Verify the tool name matches the expected pattern + assert tool_name_pattern.match(tool_name), f"Tool name '{tool_name}' does not match valid pattern" + + # Verify the tool name ends with OutputStructurer + assert tool_name.endswith("OutputStructurer"), f"Tool name '{tool_name}' should end with 'OutputStructurer'" + + # Verify the tool name starts with the model name + expected_prefix = model_class.__name__ + assert tool_name.startswith(expected_prefix), f"Tool name '{tool_name}' should start with '{expected_prefix}'" From 2351fe5ffea058c0a9832510253019ecaff203d6 Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Thu, 24 Jul 2025 16:37:33 -0400 Subject: [PATCH 2/5] fix: linting --- tests/strands/tools/test_structured_output.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/strands/tools/test_structured_output.py b/tests/strands/tools/test_structured_output.py index 68dec068b..20402a28d 100644 --- a/tests/strands/tools/test_structured_output.py +++ b/tests/strands/tools/test_structured_output.py @@ -348,31 +348,31 @@ class Person(BaseModel): def test_tool_name_validation(): """Test that tool names follow proper naming conventions.""" import re - + # Tool names should start with a letter and contain only alphanumeric characters and underscores - tool_name_pattern = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$') - + tool_name_pattern = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$") + # Test with various model names class SimpleModel(BaseModel): value: str - + class Model_With_Underscores(BaseModel): value: str - + class Model123(BaseModel): value: str - + # Test all models produce valid tool names for model_class in [SimpleModel, Model_With_Underscores, Model123, User, UserWithPlanet]: tool_spec = convert_pydantic_to_tool_spec(model_class) tool_name = tool_spec["name"] - + # Verify the tool name matches the expected pattern assert tool_name_pattern.match(tool_name), f"Tool name '{tool_name}' does not match valid pattern" - + # Verify the tool name ends with OutputStructurer assert tool_name.endswith("OutputStructurer"), f"Tool name '{tool_name}' should end with 'OutputStructurer'" - + # Verify the tool name starts with the model name expected_prefix = model_class.__name__ assert tool_name.startswith(expected_prefix), f"Tool name '{tool_name}' should start with '{expected_prefix}'" From f7bde18e80330182c043c86b26b44c3ff10c69fa Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Thu, 24 Jul 2025 16:38:41 -0400 Subject: [PATCH 3/5] fix: remove extra test --- tests/strands/tools/test_structured_output.py | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/tests/strands/tools/test_structured_output.py b/tests/strands/tools/test_structured_output.py index 20402a28d..e68ae55f9 100644 --- a/tests/strands/tools/test_structured_output.py +++ b/tests/strands/tools/test_structured_output.py @@ -343,36 +343,3 @@ class Person(BaseModel): "name": "PersonOutputStructurer", } assert tool_spec == expected_spec - - -def test_tool_name_validation(): - """Test that tool names follow proper naming conventions.""" - import re - - # Tool names should start with a letter and contain only alphanumeric characters and underscores - tool_name_pattern = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$") - - # Test with various model names - class SimpleModel(BaseModel): - value: str - - class Model_With_Underscores(BaseModel): - value: str - - class Model123(BaseModel): - value: str - - # Test all models produce valid tool names - for model_class in [SimpleModel, Model_With_Underscores, Model123, User, UserWithPlanet]: - tool_spec = convert_pydantic_to_tool_spec(model_class) - tool_name = tool_spec["name"] - - # Verify the tool name matches the expected pattern - assert tool_name_pattern.match(tool_name), f"Tool name '{tool_name}' does not match valid pattern" - - # Verify the tool name ends with OutputStructurer - assert tool_name.endswith("OutputStructurer"), f"Tool name '{tool_name}' should end with 'OutputStructurer'" - - # Verify the tool name starts with the model name - expected_prefix = model_class.__name__ - assert tool_name.startswith(expected_prefix), f"Tool name '{tool_name}' should start with '{expected_prefix}'" From 3ec87859de97c376e97f800f361b0a2821b51520 Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Fri, 25 Jul 2025 10:24:25 -0400 Subject: [PATCH 4/5] fix: update broken unit tests referencing old tool name --- tests/strands/models/test_anthropic.py | 2 +- tests/strands/models/test_bedrock.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/strands/models/test_anthropic.py b/tests/strands/models/test_anthropic.py index 5e8d69ea7..b7236890c 100644 --- a/tests/strands/models/test_anthropic.py +++ b/tests/strands/models/test_anthropic.py @@ -724,7 +724,7 @@ async def test_structured_output(anthropic_client, model, test_output_model_cls, return_value={ "type": "content_block_start", "index": 0, - "content_block": {"type": "tool_use", "id": "123", "name": "TestOutputModel"}, + "content_block": {"type": "tool_use", "id": "123", "name": "TestOutputModelOutputStructurer"}, } ), ), diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 47e028cb9..4dca3bc52 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1084,7 +1084,7 @@ async def test_structured_output(bedrock_client, model, test_output_model_cls, a bedrock_client.converse_stream.return_value = { "stream": [ {"messageStart": {"role": "assistant"}}, - {"contentBlockStart": {"start": {"toolUse": {"toolUseId": "123", "name": "TestOutputModel"}}}}, + {"contentBlockStart": {"start": {"toolUse": {"toolUseId": "123", "name": "TestOutputModelOutputStructurer"}}}}, {"contentBlockDelta": {"delta": {"toolUse": {"input": '{"name": "John", "age": 30}'}}}}, {"contentBlockStop": {}}, {"messageStop": {"stopReason": "tool_use"}}, From 4d4f0a19c82311be6a4818bd101a05f18394ad44 Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Fri, 25 Jul 2025 10:36:22 -0400 Subject: [PATCH 5/5] fix: linting --- tests/strands/models/test_bedrock.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 4dca3bc52..c45b1b2ca 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1084,7 +1084,11 @@ async def test_structured_output(bedrock_client, model, test_output_model_cls, a bedrock_client.converse_stream.return_value = { "stream": [ {"messageStart": {"role": "assistant"}}, - {"contentBlockStart": {"start": {"toolUse": {"toolUseId": "123", "name": "TestOutputModelOutputStructurer"}}}}, + { + "contentBlockStart": { + "start": {"toolUse": {"toolUseId": "123", "name": "TestOutputModelOutputStructurer"}} + } + }, {"contentBlockDelta": {"delta": {"toolUse": {"input": '{"name": "John", "age": 30}'}}}}, {"contentBlockStop": {}}, {"messageStop": {"stopReason": "tool_use"}},