From 1bb3bb8d2e1cc0003e6ace50abdfae28d3c04633 Mon Sep 17 00:00:00 2001 From: j-zhangyiyuan Date: Mon, 3 Aug 2026 10:30:35 +0800 Subject: [PATCH 1/2] fix(python): serialize Pydantic models with mode='json' in tool results model_dump() defaults to mode='python', which leaves datetime, UUID, Decimal, Enum and set as native Python objects. json.dumps then fails on these, and the error is swallowed as a generic tool failure. Use model_dump(mode='json') so Pydantic converts all fields to JSON-safe types before serialization. Closes #2203 --- python/copilot/tools.py | 2 +- python/test_tools.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/python/copilot/tools.py b/python/copilot/tools.py index 762b79c45b..de81fe7fd8 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -351,7 +351,7 @@ def _normalize_result(result: Any) -> ToolResult: # Everything else gets JSON-serialized (with Pydantic model support) def default(obj: Any) -> Any: if isinstance(obj, BaseModel): - return obj.model_dump() + return obj.model_dump(mode="json") raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") try: diff --git a/python/test_tools.py b/python/test_tools.py index 646f17c03a..f751082679 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -389,6 +389,36 @@ class Item(BaseModel): assert parsed == [{"name": "a", "value": 1}, {"name": "b", "value": 2}] assert result.result_type == "success" + def test_pydantic_model_with_non_primitive_fields_is_serialized(self): + from datetime import datetime, date + from decimal import Decimal + from enum import Enum + from uuid import UUID + + class Status(Enum): + ACTIVE = "active" + + class Record(BaseModel): + id: UUID + created: datetime + day: date + score: Decimal + status: Status + + record = Record( + id=UUID("12345678-1234-5678-1234-567812345678"), + created=datetime(2026, 1, 15, 10, 30, 0), + day=date(2026, 1, 15), + score=Decimal("99.5"), + status=Status.ACTIVE, + ) + result = _normalize_result(record) + parsed = json.loads(result.text_result_for_llm) + assert parsed["id"] == "12345678-1234-5678-1234-567812345678" + assert parsed["status"] == "active" + assert parsed["score"] == "99.5" + assert result.result_type == "success" + def test_raises_for_unserializable_value(self): # Functions cannot be JSON serialized with pytest.raises(TypeError, match="Failed to serialize"): From 6488526fa25518db4a4c9c7a66097a0c2d6b7ed2 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 3 Aug 2026 13:01:34 +0000 Subject: [PATCH 2/2] test(python): strengthen Pydantic result coverage Assert the JSON representation of date and datetime fields, cover set serialization, and fix import ordering for Ruff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/test_tools.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/python/test_tools.py b/python/test_tools.py index f751082679..97de41df42 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -390,7 +390,7 @@ class Item(BaseModel): assert result.result_type == "success" def test_pydantic_model_with_non_primitive_fields_is_serialized(self): - from datetime import datetime, date + from datetime import date, datetime from decimal import Decimal from enum import Enum from uuid import UUID @@ -404,6 +404,7 @@ class Record(BaseModel): day: date score: Decimal status: Status + tags: set[str] record = Record( id=UUID("12345678-1234-5678-1234-567812345678"), @@ -411,12 +412,19 @@ class Record(BaseModel): day=date(2026, 1, 15), score=Decimal("99.5"), status=Status.ACTIVE, + tags={"python", "sdk"}, ) result = _normalize_result(record) parsed = json.loads(result.text_result_for_llm) - assert parsed["id"] == "12345678-1234-5678-1234-567812345678" - assert parsed["status"] == "active" - assert parsed["score"] == "99.5" + assert parsed == { + "id": "12345678-1234-5678-1234-567812345678", + "created": "2026-01-15T10:30:00", + "day": "2026-01-15", + "score": "99.5", + "status": "active", + "tags": parsed["tags"], + } + assert set(parsed["tags"]) == {"python", "sdk"} assert result.result_type == "success" def test_raises_for_unserializable_value(self):