From ae84ca1132292a0bb014332476a6777698063dce Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Sat, 8 Aug 2026 08:28:21 +0900 Subject: [PATCH] fix: suppress pydantic serializer warnings (#4291) --- src/agents/_tool_invocation.py | 7 ++- tests/test_tool_approval_call_id_reuse.py | 65 ++++++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/agents/_tool_invocation.py b/src/agents/_tool_invocation.py index 969b98f9..eb360687 100644 --- a/src/agents/_tool_invocation.py +++ b/src/agents/_tool_invocation.py @@ -5,6 +5,8 @@ import json from collections.abc import Mapping, Sequence from typing import Any, TypeGuard +from pydantic import BaseModel + from ._tool_identity import ( FunctionToolLookupKey, get_function_tool_lookup_key_for_call, @@ -67,7 +69,10 @@ def _as_mapping(value: Any) -> Mapping[str, Any] | None: return value model_dump = getattr(value, "model_dump", None) if callable(model_dump): - dumped = model_dump(exclude_none=True, exclude_unset=True) + kwargs = {"exclude_none": True, "exclude_unset": True} + if isinstance(value, BaseModel): + kwargs["warnings"] = False + dumped = model_dump(**kwargs) return dumped if isinstance(dumped, Mapping) else None return None diff --git a/tests/test_tool_approval_call_id_reuse.py b/tests/test_tool_approval_call_id_reuse.py index 45426d36..5b6a4317 100644 --- a/tests/test_tool_approval_call_id_reuse.py +++ b/tests/test_tool_approval_call_id_reuse.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import json +import warnings from types import SimpleNamespace from typing import Any, Literal, cast @@ -12,6 +13,11 @@ from openai.types.responses.response_computer_tool_call import ( PendingSafetyCheck, ResponseComputerToolCall, ) +from openai.types.responses.response_function_web_search import ( + ActionSearch, + ActionSearchSource, + ResponseFunctionWebSearch, +) from openai.types.responses.response_output_item import McpApprovalRequest from openai.types.responses.response_reasoning_item import ResponseReasoningItem @@ -33,7 +39,11 @@ from agents import ( handoff, tool_output_guardrail, ) -from agents._tool_invocation import tool_invocation_identity, tool_invocation_identity_and_scope +from agents._tool_invocation import ( + tool_invocation_call_id, + tool_invocation_identity, + tool_invocation_identity_and_scope, +) from agents.editor import ApplyPatchOperation, ApplyPatchResult from agents.exceptions import ModelBehaviorError, UserError from agents.items import ModelResponse, ToolApprovalItem @@ -79,6 +89,59 @@ def test_canonical_shell_identity_ignores_stripped_provider_metadata() -> None: assert tool_invocation_identity(provider_call) == tool_invocation_identity(persisted_call) +def test_web_search_source_schema_drift_does_not_warn_during_invocation_lookup() -> None: + source = ActionSearchSource.model_construct(type="api", name="oai-calculator") + output_item = ResponseFunctionWebSearch( + id="ws_123", + action=ActionSearch(type="search", query="current market data", sources=[source]), + status="completed", + type="web_search_call", + ) + + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always", UserWarning) + call_id = tool_invocation_call_id(output_item) + + serializer_warnings = [ + warning + for warning in caught_warnings + if "Pydantic serializer warnings" in str(warning.message) + ] + assert call_id is None + assert not serializer_warnings + + +def test_invocation_lookup_preserves_legacy_model_dump_signature() -> None: + class LegacyModel: + def model_dump(self, *, exclude_none: bool, exclude_unset: bool) -> dict[str, str]: + assert exclude_none is True + assert exclude_unset is True + return {"type": "function_call", "call_id": "call_legacy"} + + assert tool_invocation_call_id(LegacyModel()) == ("function_call", "call_legacy") + + +def test_invocation_lookup_propagates_internal_model_dump_type_error() -> None: + class FailingModel: + def __init__(self) -> None: + self.calls = 0 + + def model_dump( + self, + *, + exclude_none: bool, + exclude_unset: bool, + warnings: bool = True, + ) -> dict[str, str]: + self.calls += 1 + raise TypeError("internal serialization failure") + + model = FailingModel() + with pytest.raises(TypeError, match="internal serialization failure"): + tool_invocation_call_id(model) + assert model.calls == 1 + + def test_canonical_shell_identity_treats_optional_nulls_as_omitted() -> None: provider_call = { "type": "shell_call",