Skip to content

Commit 87aee63

Browse files
OllieinCanadaOliver Slapinski
andauthored
fix(python): serialize native values in tool results (#2374)
Signed-off-by: Oliver Slapinski <olliefromcanada@gmail.com> Co-authored-by: Oliver Slapinski <olliefromcanada@gmail.com>
1 parent ff2fc25 commit 87aee63

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

python/copilot/tools.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
import json
1212
from collections.abc import Awaitable, Callable
1313
from dataclasses import dataclass, field
14+
from datetime import date, datetime, time
15+
from decimal import Decimal
16+
from enum import Enum
1417
from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_type_hints, overload
18+
from uuid import UUID
1519

1620
from pydantic import BaseModel, ValidationError
1721

@@ -363,10 +367,18 @@ def _normalize_result(result: Any) -> ToolResult:
363367
result_type="success",
364368
)
365369

366-
# Everything else gets JSON-serialized (with Pydantic model support)
370+
# Everything else gets JSON-serialized (with common Python and Pydantic values)
367371
def default(obj: Any) -> Any:
368372
if isinstance(obj, BaseModel):
369373
return obj.model_dump(mode="json")
374+
if isinstance(obj, (date, datetime, time)):
375+
return obj.isoformat()
376+
if isinstance(obj, (Decimal, UUID)):
377+
return str(obj)
378+
if isinstance(obj, Enum):
379+
return obj.value
380+
if isinstance(obj, set):
381+
return list(obj)
370382
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
371383

372384
try:

python/test_tools.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,39 @@ class Record(BaseModel):
427427
assert set(parsed["tags"]) == {"python", "sdk"}
428428
assert result.result_type == "success"
429429

430+
def test_plain_dict_with_non_primitive_fields_is_serialized(self):
431+
from datetime import date, datetime, time
432+
from decimal import Decimal
433+
from enum import Enum
434+
from uuid import UUID
435+
436+
class Status(Enum):
437+
ACTIVE = "active"
438+
439+
result = _normalize_result(
440+
{
441+
"id": UUID("12345678-1234-5678-1234-567812345678"),
442+
"created": datetime(2026, 1, 15, 10, 30, 0),
443+
"day": date(2026, 1, 15),
444+
"at": time(10, 30, 0),
445+
"score": Decimal("99.5"),
446+
"status": Status.ACTIVE,
447+
"tags": {"python", "sdk"},
448+
}
449+
)
450+
parsed = json.loads(result.text_result_for_llm)
451+
assert parsed == {
452+
"id": "12345678-1234-5678-1234-567812345678",
453+
"created": "2026-01-15T10:30:00",
454+
"day": "2026-01-15",
455+
"at": "10:30:00",
456+
"score": "99.5",
457+
"status": "active",
458+
"tags": parsed["tags"],
459+
}
460+
assert set(parsed["tags"]) == {"python", "sdk"}
461+
assert result.result_type == "success"
462+
430463
def test_raises_for_unserializable_value(self):
431464
# Functions cannot be JSON serialized
432465
with pytest.raises(TypeError, match="Failed to serialize"):

0 commit comments

Comments
 (0)