Skip to content

Commit c496394

Browse files
authored
fix validation errors serialization (#176)
1 parent 33230c4 commit c496394

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations as _annotations
22

3-
import json
43
from dataclasses import dataclass, field
54
from datetime import datetime
65
from typing import Annotated, Any, Literal, Union
@@ -74,6 +73,9 @@ def model_response_object(self) -> dict[str, Any]:
7473
return {'return_value': tool_return_ta.dump_python(self.content, mode='json')}
7574

7675

76+
ErrorDetailsTa = _pydantic.LazyTypeAdapter(list[pydantic_core.ErrorDetails])
77+
78+
7779
@dataclass
7880
class RetryPrompt:
7981
"""A message back to a model asking it to try again.
@@ -109,7 +111,8 @@ def model_response(self) -> str:
109111
if isinstance(self.content, str):
110112
description = self.content
111113
else:
112-
description = f'{len(self.content)} validation errors: {json.dumps(self.content, indent=2)}'
114+
json_errors = ErrorDetailsTa.dump_json(self.content, exclude={'__all__': {'ctx'}}, indent=2)
115+
description = f'{len(self.content)} validation errors: {json_errors.decode()}'
113116
return f'{description}\n\nFix the errors and try again.'
114117

115118

tests/test_agent.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import httpx
66
import pytest
77
from inline_snapshot import snapshot
8-
from pydantic import BaseModel
8+
from pydantic import BaseModel, field_validator
99

1010
from pydantic_ai import Agent, ModelRetry, RunContext, UnexpectedModelBehavior, UserError
1111
from pydantic_ai.messages import (
@@ -107,6 +107,50 @@ def return_model(messages: list[Message], info: AgentInfo) -> ModelAnyResponse:
107107
assert result.all_messages_json().startswith(b'[{"content":"Hello"')
108108

109109

110+
def test_result_pydantic_model_validation_error(set_event_loop: None):
111+
def return_model(messages: list[Message], info: AgentInfo) -> ModelAnyResponse:
112+
assert info.result_tools is not None
113+
if len(messages) == 1:
114+
args_json = '{"a": 1, "b": "foo"}'
115+
else:
116+
args_json = '{"a": 1, "b": "bar"}'
117+
return ModelStructuredResponse(calls=[ToolCall.from_json(info.result_tools[0].name, args_json)])
118+
119+
class Bar(BaseModel):
120+
a: int
121+
b: str
122+
123+
@field_validator('b')
124+
def check_b(cls, v: str) -> str:
125+
if v == 'foo':
126+
raise ValueError('must not be foo')
127+
return v
128+
129+
agent = Agent(FunctionModel(return_model), result_type=Bar)
130+
131+
result = agent.run_sync('Hello')
132+
assert isinstance(result.data, Bar)
133+
assert result.data.model_dump() == snapshot({'a': 1, 'b': 'bar'})
134+
message_roles = [m.role for m in result.all_messages()]
135+
assert message_roles == snapshot(['user', 'model-structured-response', 'retry-prompt', 'model-structured-response'])
136+
137+
retry_prompt = result.all_messages()[2]
138+
assert isinstance(retry_prompt, RetryPrompt)
139+
assert retry_prompt.model_response() == snapshot("""\
140+
1 validation errors: [
141+
{
142+
"type": "value_error",
143+
"loc": [
144+
"b"
145+
],
146+
"msg": "Value error, must not be foo",
147+
"input": "foo"
148+
}
149+
]
150+
151+
Fix the errors and try again.""")
152+
153+
110154
def test_result_validator(set_event_loop: None):
111155
def return_model(messages: list[Message], info: AgentInfo) -> ModelAnyResponse:
112156
assert info.result_tools is not None

0 commit comments

Comments
 (0)