Repository: pydantic/pydantic-ai
File: tests/test_exceptions.py (Python)

Code before:
    d = {exc: 'value'}

    assert exc in s
    assert d[exc] == 'value'


def test_tool_retry_error_str():
    """Test that ToolRetryError has a meaningful string representation."""
    part = RetryPromptPart(content='Invalid query syntax', tool_name='sql_query')
    error = ToolRetryError(part)
    assert str(error) == "Tool 'sql_query' failed: Invalid query syntax"


def test_tool_retry_error_str_with_error_details():
    """Test that ToolRetryError handles list of ErrorDetails content."""
    error_details: list[ErrorDetails] = [
        {'type': 'missing', 'loc': ('field1',), 'msg': 'Field required', 'input': {}},
        {'type': 'string_type', 'loc': ('field2',), 'msg': 'Input should be a valid string', 'input': 123},
    ]
    part = RetryPromptPart(content=error_details, tool_name='my_tool')
    error = ToolRetryError(part)

    expected = """\
Tool 'my_tool' failed: 2 validation errors
  field1: Field required
  field2: Input should be a valid string"""
    assert str(error) == expected


Review comment (suggestion):
This is a bit lossy because we don't get to see the original input or error type. Can you see if we can use the same rendering that is used when Pydantic `ValidationError` is raised and printed in terminal? We get the `ErrorDetails` from a `ValidationError` here, so maybe we can pass in the string message here where we still have that error:

https://github.com/pydantic/pydantic-ai/blob/d2b08ad12e103e81bd0a9dc0eb7f2a7c3472931d/pydantic_ai_slim/pydantic_ai/_tool_manager.py#L184-L190

Code after:
    s = {exc}
    d = {exc: 'value'}

    assert exc in s
    assert d[exc] == 'value'


def test_tool_retry_error_str_with_string_content():
    """Test that ToolRetryError uses string content as message automatically."""
    part = RetryPromptPart(content='error from tool', tool_name='my_tool')
    error = ToolRetryError(part)
    assert str(error) == 'error from tool'


def test_tool_retry_error_str_with_error_details():
    """Test that ToolRetryError formats ErrorDetails automatically."""
    validation_error = ValidationError.from_exception_data(
        'Test', [{'type': 'string_type', 'loc': ('name',), 'input': 123}]
    )
    part = RetryPromptPart(content=validation_error.errors(include_url=False), tool_name='my_tool')
    error = ToolRetryError(part)

    assert str(error) == (
        "1 validation error for 'my_tool'\nname\n  Input should be a valid string [type=string_type, input_value=123]"
    )


def test_tool_retry_error_str_with_value_error_type():
    """Test that ToolRetryError handles value_error type without ctx.error.

    When ErrorDetails are serialized, the exception object in ctx is stripped.
    This test ensures we handle error types that normally require ctx.error.
    """
    # Simulate serialized ErrorDetails where ctx.error has been stripped
    error_details: list[ErrorDetails] = [
        {
            'type': 'value_error',
            'loc': ('field',),
            'msg': 'Value error, must not be foo',
            'input': 'foo',
        }
    ]
    part = RetryPromptPart(content=error_details, tool_name='my_tool')
    error = ToolRetryError(part)

    assert str(error) == (
        "1 validation error for 'my_tool'\nfield\n  Value error, must not be foo [type=value_error, input_value='foo']"
    )