diff --git a/src/agents/tracing/span_data.py b/src/agents/tracing/span_data.py index cb3e8491d3..6f440fbd39 100644 --- a/src/agents/tracing/span_data.py +++ b/src/agents/tracing/span_data.py @@ -88,7 +88,7 @@ def export(self) -> dict[str, Any]: "type": self.type, "name": self.name, "input": self.input, - "output": str(self.output) if self.output else None, + "output": self.output, "mcp_data": self.mcp_data, } diff --git a/tests/mcp/test_mcp_tracing.py b/tests/mcp/test_mcp_tracing.py index 9cb3454b1b..11b801419a 100644 --- a/tests/mcp/test_mcp_tracing.py +++ b/tests/mcp/test_mcp_tracing.py @@ -62,7 +62,7 @@ async def test_mcp_tracing(): "data": { "name": "test_tool_1", "input": "", - "output": "{'type': 'text', 'text': 'result_test_tool_1_{}'}", # noqa: E501 + "output": {"type": "text", "text": "result_test_tool_1_{}"}, # noqa: E501 "mcp_data": {"server": "fake_mcp_server"}, }, }, @@ -133,7 +133,7 @@ async def test_mcp_tracing(): "data": { "name": "test_tool_2", "input": "", - "output": "{'type': 'text', 'text': 'result_test_tool_2_{}'}", # noqa: E501 + "output": {"type": "text", "text": "result_test_tool_2_{}"}, # noqa: E501 "mcp_data": {"server": "fake_mcp_server"}, }, }, @@ -197,7 +197,7 @@ async def test_mcp_tracing(): "data": { "name": "test_tool_3", "input": "", - "output": "{'type': 'text', 'text': 'result_test_tool_3_{}'}", # noqa: E501 + "output": {"type": "text", "text": "result_test_tool_3_{}"}, # noqa: E501 "mcp_data": {"server": "fake_mcp_server"}, }, }, diff --git a/tests/tracing/test_span_data.py b/tests/tracing/test_span_data.py new file mode 100644 index 0000000000..2b4cfe5704 --- /dev/null +++ b/tests/tracing/test_span_data.py @@ -0,0 +1,67 @@ +"""Tests for span data export methods.""" + +from __future__ import annotations + +import pytest + +from agents.tracing.span_data import FunctionSpanData + + +class TestFunctionSpanDataExport: + """FunctionSpanData.export() must preserve output values faithfully.""" + + def test_dict_output_preserved_as_dict(self) -> None: + """Dict outputs should stay as dicts, not be converted to Python repr strings.""" + span = FunctionSpanData(name="my_tool", input="query", output={"key": "value", "n": 42}) + exported = span.export() + assert exported["output"] == {"key": "value", "n": 42} + assert isinstance(exported["output"], dict) + + def test_string_output_preserved(self) -> None: + span = FunctionSpanData(name="my_tool", input="query", output="hello world") + exported = span.export() + assert exported["output"] == "hello world" + + def test_none_output_preserved(self) -> None: + span = FunctionSpanData(name="my_tool", input="query", output=None) + exported = span.export() + assert exported["output"] is None + + @pytest.mark.parametrize( + "output", + [0, False, "", []], + ids=["zero", "false", "empty_str", "empty_list"], + ) + def test_falsy_output_not_converted_to_none(self, output: object) -> None: + """Falsy but valid outputs (0, False, '', []) must not become None.""" + span = FunctionSpanData(name="my_tool", input="query", output=output) + exported = span.export() + assert exported["output"] is not None + assert exported["output"] == output + + def test_list_output_preserved(self) -> None: + span = FunctionSpanData(name="my_tool", input="query", output=[1, 2, 3]) + exported = span.export() + assert exported["output"] == [1, 2, 3] + assert isinstance(exported["output"], list) + + def test_numeric_output_preserved(self) -> None: + span = FunctionSpanData(name="my_tool", input="query", output=42) + exported = span.export() + assert exported["output"] == 42 + + def test_export_includes_all_fields(self) -> None: + span = FunctionSpanData( + name="my_tool", + input="query", + output="result", + mcp_data={"server": "test"}, + ) + exported = span.export() + assert exported == { + "type": "function", + "name": "my_tool", + "input": "query", + "output": "result", + "mcp_data": {"server": "test"}, + }