Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion pydantic_ai_slim/pydantic_ai/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,23 @@ class ToolRetryError(Exception):

def __init__(self, tool_retry: RetryPromptPart):
self.tool_retry = tool_retry
super().__init__()
super().__init__(_format_tool_retry_message(tool_retry))


def _format_tool_retry_message(tool_retry: RetryPromptPart) -> str:
"""Format a human-readable error message for ToolRetryError."""
content = tool_retry.content
if isinstance(content, str):
return f"Tool '{tool_retry.tool_name}' failed: {content}"

# Format list of ErrorDetails in a human-readable way
error_count = len(content)
error_word = 'error' if error_count == 1 else 'errors'
lines = [f"Tool '{tool_retry.tool_name}' failed: {error_count} validation {error_word}"]
for error in content:
loc = '.'.join(str(loc) for loc in error['loc'])
lines.append(f' {loc}: {error["msg"]}')
return '\n'.join(lines)


class IncompleteToolCall(UnexpectedModelBehavior):
Expand Down
28 changes: 28 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any

import pytest
from pydantic_core import ErrorDetails

from pydantic_ai import ModelRetry
from pydantic_ai.exceptions import (
Expand All @@ -13,10 +14,12 @@
IncompleteToolCall,
ModelAPIError,
ModelHTTPError,
ToolRetryError,
UnexpectedModelBehavior,
UsageLimitExceeded,
UserError,
)
from pydantic_ai.messages import RetryPromptPart


@pytest.mark.parametrize(
Expand All @@ -32,6 +35,7 @@
lambda: ModelAPIError('model', 'test message'),
lambda: ModelHTTPError(500, 'model'),
lambda: IncompleteToolCall('test'),
lambda: ToolRetryError(RetryPromptPart(content='test', tool_name='test')),
],
ids=[
'ModelRetry',
Expand All @@ -44,6 +48,7 @@
'ModelAPIError',
'ModelHTTPError',
'IncompleteToolCall',
'ToolRetryError',
],
)
def test_exceptions_hashable(exc_factory: Callable[[], Any]):
Expand All @@ -59,3 +64,26 @@ def test_exceptions_hashable(exc_factory: Callable[[], Any]):

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