Skip to content

Commit a86b8ac

Browse files
authored
fix(core): #772 avoid noisy pydantic serialization warnings for model_dump items (#2443)
1 parent 7f59aa5 commit a86b8ac

File tree

2 files changed

+56
-7
lines changed

2 files changed

+56
-7
lines changed

src/agents/run_internal/items.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ def fingerprint_input_item(item: Any, *, ignore_ids_for_matching: bool = False)
116116
return None
117117

118118
try:
119+
payload: Any
119120
if hasattr(item, "model_dump"):
120-
payload = item.model_dump(exclude_unset=True)
121+
payload = _model_dump_without_warnings(item)
122+
if payload is None:
123+
return None
121124
elif isinstance(item, dict):
122125
payload = dict(item)
123126
if ignore_ids_for_matching:
@@ -314,13 +317,25 @@ def _coerce_to_dict(value: object) -> dict[str, Any] | None:
314317
if isinstance(value, dict):
315318
return dict(value)
316319
if isinstance(value, BaseModel):
317-
try:
318-
return value.model_dump(exclude_unset=True)
319-
except Exception:
320-
return None
320+
return _model_dump_without_warnings(value)
321321
if hasattr(value, "model_dump"):
322+
return _model_dump_without_warnings(value)
323+
return None
324+
325+
326+
def _model_dump_without_warnings(value: object) -> dict[str, Any] | None:
327+
"""Best-effort model_dump that avoids noisy serialization warnings from third-party models."""
328+
if not hasattr(value, "model_dump"):
329+
return None
330+
331+
model_dump = cast(Any, value).model_dump
332+
try:
333+
return cast(dict[str, Any], model_dump(exclude_unset=True, warnings=False))
334+
except TypeError:
335+
# Some model_dump-compatible objects only accept exclude_unset.
322336
try:
323-
return cast(dict[str, Any], value.model_dump(exclude_unset=True))
337+
return cast(dict[str, Any], model_dump(exclude_unset=True))
324338
except Exception:
325339
return None
326-
return None
340+
except Exception:
341+
return None

tests/test_agent_runner.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
import json
55
import tempfile
6+
import warnings
67
from pathlib import Path
78
from typing import Any, cast
89
from unittest.mock import patch
@@ -11,6 +12,7 @@
1112
import pytest
1213
from openai import BadRequestError
1314
from openai.types.responses import ResponseFunctionToolCall
15+
from openai.types.responses.response_output_text import AnnotationFileCitation, ResponseOutputText
1416
from typing_extensions import TypedDict
1517

1618
from agents import (
@@ -48,6 +50,7 @@
4850
from agents.run_internal.items import (
4951
drop_orphan_function_calls,
5052
ensure_input_item_format,
53+
fingerprint_input_item,
5154
normalize_input_items_for_api,
5255
normalize_resumed_input,
5356
)
@@ -329,6 +332,14 @@ def testnormalize_input_items_for_api_preserves_provider_data():
329332
assert second["provider_data"] == {"trace": "remove"}
330333

331334

335+
def test_fingerprint_input_item_returns_none_when_model_dump_fails():
336+
class _BrokenModelDump:
337+
def model_dump(self, *_args: Any, **_kwargs: Any) -> dict[str, Any]:
338+
raise RuntimeError("model_dump failed")
339+
340+
assert fingerprint_input_item(_BrokenModelDump()) is None
341+
342+
332343
def test_server_conversation_tracker_tracks_previous_response_id():
333344
tracker = OpenAIServerConversationTracker(conversation_id=None, previous_response_id="resp_a")
334345
response = ModelResponse(
@@ -1310,6 +1321,29 @@ def model_dump(self, exclude_unset: bool = True) -> dict[str, Any]:
13101321
assert converted["output"] == "dumped"
13111322

13121323

1324+
def test_ensure_api_input_item_avoids_pydantic_serialization_warnings():
1325+
annotation = AnnotationFileCitation.model_construct(
1326+
type="container_file_citation",
1327+
file_id="file_123",
1328+
filename="result.txt",
1329+
index=0,
1330+
)
1331+
output_text = ResponseOutputText.model_construct(
1332+
type="output_text",
1333+
text="done",
1334+
annotations=[annotation],
1335+
)
1336+
1337+
with warnings.catch_warnings(record=True) as captured:
1338+
warnings.simplefilter("always")
1339+
converted = ensure_input_item_format(cast(Any, output_text))
1340+
1341+
converted_payload = cast(dict[str, Any], converted)
1342+
assert captured == []
1343+
assert converted_payload["type"] == "output_text"
1344+
assert converted_payload["annotations"][0]["type"] == "container_file_citation"
1345+
1346+
13131347
def test_ensure_api_input_item_preserves_object_output():
13141348
payload = cast(
13151349
TResponseInputItem,

0 commit comments

Comments
 (0)