Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6c9fd46

Browse files
committed
Python: fix Foundry handoff argument serialization
1 parent 97eaef0 commit 6c9fd46

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import os
1010
import tempfile
1111
import threading
12-
from collections.abc import AsyncIterable, AsyncIterator, Generator, Mapping, Sequence
12+
from collections.abc import AsyncIterable, AsyncIterator, Generator, Sequence
1313
from contextlib import suppress
14+
from dataclasses import asdict, is_dataclass
1415
from pathlib import Path
1516
from typing import Protocol, cast
1617

@@ -1394,11 +1395,20 @@ def _convert_message_content(content: MessageContent) -> Content:
13941395
# region Output Item Conversion
13951396

13961397

1397-
def _arguments_to_str(arguments: str | Mapping[str, Any] | None) -> str:
1398+
def _argument_json_default(value: Any) -> Any:
1399+
if is_dataclass(value) and not isinstance(value, type):
1400+
return asdict(value)
1401+
to_dict = getattr(value, "to_dict", None)
1402+
if callable(to_dict):
1403+
return to_dict()
1404+
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")
1405+
1406+
1407+
def _arguments_to_str(arguments: Any | None) -> str:
13981408
"""Convert arguments to a JSON string.
13991409
14001410
Args:
1401-
arguments: The arguments to convert, can be a string, mapping, or None.
1411+
arguments: The arguments to convert, can be a string, JSON-like object, or None.
14021412
14031413
Returns:
14041414
The arguments as a JSON string.
@@ -1407,7 +1417,7 @@ def _arguments_to_str(arguments: str | Mapping[str, Any] | None) -> str:
14071417
return ""
14081418
if isinstance(arguments, str):
14091419
return arguments
1410-
return json.dumps(arguments)
1420+
return json.dumps(arguments, default=_argument_json_default)
14111421

14121422

14131423
async def _to_outputs(

python/packages/foundry_hosting/tests/test_responses.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import json
1414
from collections.abc import AsyncIterator, Callable
15+
from dataclasses import dataclass
1516
from unittest.mock import AsyncMock, MagicMock
1617

1718
import httpx
@@ -401,6 +402,36 @@ async def test_function_call_streaming(self) -> None:
401402
assert len(args_done) == 1
402403
assert args_done[0]["data"]["arguments"] == '{"q": "hello"}'
403404

405+
async def test_function_call_streaming_serializes_dataclass_arguments(self) -> None:
406+
@dataclass
407+
class HandoffLikeRequest:
408+
agent_response: AgentResponse
409+
410+
request = HandoffLikeRequest(
411+
agent_response=AgentResponse(
412+
messages=[Message(role="assistant", contents=[Content.from_text("Need more details")])]
413+
)
414+
)
415+
agent = _make_agent(
416+
stream_updates=[
417+
AgentResponseUpdate(
418+
contents=[Content.from_function_call("call_1", "handoff_to_refund", arguments=request)],
419+
role="assistant",
420+
),
421+
]
422+
)
423+
server = _make_server(agent)
424+
resp = await _post(server, stream=True)
425+
426+
assert resp.status_code == 200
427+
events = _parse_sse_events(resp.text)
428+
args_done = [e for e in events if e["event"] == "response.function_call_arguments.done"]
429+
assert len(args_done) == 1
430+
431+
payload = json.loads(args_done[0]["data"]["arguments"])
432+
assert payload["agent_response"]["type"] == "agent_response"
433+
assert payload["agent_response"]["messages"][0]["contents"][0]["text"] == "Need more details"
434+
404435
async def test_alternating_text_and_function_call(self) -> None:
405436
agent = _make_agent(
406437
stream_updates=[

0 commit comments

Comments
 (0)