Skip to content

Commit b445f0b

Browse files
lionpelouxclaude
andcommitted
Fix tool name sanitization to preserve uppercase letters
The previous regex pattern `[^a-z0-9-_]` was removing uppercase letters from class names, causing test failures. For example, "Foo" became "oo". This fix changes the pattern to `[^a-zA-Z0-9-_]` to preserve both uppercase and lowercase letters while still removing invalid characters like brackets from generic type names (e.g., `Result[StringData]`). Also adds a test case to verify that generic class names with brackets are properly sanitized while preserving valid characters. Fixes test failures in test_response_multiple_return_tools and related tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent a53d87a commit b445f0b

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

pydantic_ai_slim/pydantic_ai/_output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
7373
DEFAULT_OUTPUT_TOOL_DESCRIPTION = 'The final response which ends this conversation'
74-
OUTPUT_TOOL_NAME_SANITIZER = re.compile(r'[^a-z0-9-_]')
74+
OUTPUT_TOOL_NAME_SANITIZER = re.compile(r'[^a-zA-Z0-9-_]')
7575

7676

7777
async def execute_traced_output_function(

tests/test_agent.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,46 @@ def validate_output(ctx: RunContext[None], o: Any) -> Any:
632632
assert got_tool_call_name == snapshot('final_result_Bar')
633633

634634

635+
def test_output_type_generic_class_name_sanitization(create_module: Callable[[str], Any]):
636+
"""Test that generic class names with brackets are properly sanitized."""
637+
module_code = '''
638+
from pydantic import BaseModel
639+
from typing import Generic, TypeVar
640+
641+
T = TypeVar('T')
642+
643+
class Result(BaseModel, Generic[T]):
644+
"""A generic result class."""
645+
value: T
646+
success: bool
647+
648+
class StringData(BaseModel):
649+
text: str
650+
651+
# This will have a name like "Result[StringData]" which needs sanitization
652+
OutputType = [Result[StringData], Result[int]]
653+
'''
654+
655+
mod = create_module(module_code)
656+
657+
m = TestModel()
658+
agent = Agent(m, output_type=mod.OutputType)
659+
agent.run_sync('Hello')
660+
661+
# The sanitizer should remove brackets from the generic type name
662+
assert m.last_model_request_parameters is not None
663+
assert m.last_model_request_parameters.output_tools is not None
664+
assert len(m.last_model_request_parameters.output_tools) == 2
665+
666+
# Check that tool names don't contain brackets
667+
tool_names = [tool.name for tool in m.last_model_request_parameters.output_tools]
668+
for tool_name in tool_names:
669+
assert '[' not in tool_name, f"Tool name '{tool_name}' contains brackets"
670+
assert ']' not in tool_name, f"Tool name '{tool_name}' contains brackets"
671+
# Verify the name follows the pattern [a-zA-Z0-9_-]
672+
assert re.match(r'^[a-zA-Z0-9_-]+$', tool_name), f"Tool name '{tool_name}' contains invalid characters"
673+
674+
635675
def test_output_type_with_two_descriptions():
636676
class MyOutput(BaseModel):
637677
"""Description from docstring"""

0 commit comments

Comments
 (0)