From ae734e2254fda58e044defd46986a48c4227c95a Mon Sep 17 00:00:00 2001 From: David Montague <35119617+dmontagu@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:52:48 -0600 Subject: [PATCH] Improve validation error retry message --- pydantic_ai_slim/pydantic_ai/messages.py | 5 ++++- .../test_gemini/test_gemini_drop_exclusive_maximum.yaml | 2 +- tests/test_agent.py | 9 ++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 6e91b78bda..0e9ba35871 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -882,7 +882,10 @@ def model_response(self) -> str: description = self.content else: json_errors = error_details_ta.dump_json(self.content, exclude={'__all__': {'ctx'}}, indent=2) - description = f'{len(self.content)} validation errors: {json_errors.decode()}' + plural = isinstance(self.content, list) and len(self.content) != 1 + description = ( + f'{len(self.content)} validation error{"s" if plural else ""}:\n```json\n{json_errors.decode()}\n```' + ) return f'{description}\n\nFix the errors and try again.' def otel_event(self, settings: InstrumentationSettings) -> Event: diff --git a/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml b/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml index fe2ce34d2f..7bd4c0a19c 100644 --- a/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml +++ b/tests/models/cassettes/test_gemini/test_gemini_drop_exclusive_maximum.yaml @@ -259,7 +259,7 @@ interactions: name: get_chinese_zodiac response: call_error: |- - 1 validation errors: [ + 1 validation error: [ { "type": "greater_than", "loc": [ diff --git a/tests/test_agent.py b/tests/test_agent.py index c8beb08312..01daabc0cf 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -185,7 +185,7 @@ def return_model(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse ), ModelResponse( parts=[ToolCallPart(tool_name='final_result', args='{"a": 42, "b": "foo"}', tool_call_id=IsStr())], - usage=RequestUsage(input_tokens=87, output_tokens=14), + usage=RequestUsage(input_tokens=89, output_tokens=14), model_name='function:return_model:', timestamp=IsNow(tz=timezone.utc), ), @@ -244,7 +244,9 @@ def check_b(cls, v: str) -> str: retry_prompt = user_retry.parts[0] assert isinstance(retry_prompt, RetryPromptPart) assert retry_prompt.model_response() == snapshot("""\ -1 validation errors: [ +1 validation error: +```json +[ { "type": "value_error", "loc": [ @@ -254,6 +256,7 @@ def check_b(cls, v: str) -> str: "input": "foo" } ] +``` Fix the errors and try again.""") @@ -1762,7 +1765,7 @@ class CityLocation(BaseModel): ), ModelResponse( parts=[TextPart(content='{"city": "Mexico City", "country": "Mexico"}')], - usage=RequestUsage(input_tokens=85, output_tokens=12), + usage=RequestUsage(input_tokens=87, output_tokens=12), model_name='function:return_city_location:', timestamp=IsDatetime(), ),