from __future__ import annotations import dataclasses import gc import weakref from typing import Any, cast import pytest from openai.types.responses import ResponseOutputMessage, ResponseOutputText from pydantic import BaseModel, ConfigDict from agents import ( Agent, AgentToolInvocation, MessageOutputItem, RunContextWrapper, RunItem, RunResult, RunResultStreaming, ) from agents.exceptions import AgentsException from agents.tool_context import ToolContext def create_run_result( final_output: Any | None, *, new_items: list[RunItem] | None = None, last_agent: Agent[Any] | None = None, ) -> RunResult: return RunResult( input="test", new_items=new_items or [], raw_responses=[], final_output=final_output, input_guardrail_results=[], output_guardrail_results=[], tool_input_guardrail_results=[], tool_output_guardrail_results=[], _last_agent=last_agent or Agent(name="test"), context_wrapper=RunContextWrapper(context=None), interruptions=[], ) class Foo(BaseModel): bar: int def test_run_result_streaming_supports_pydantic_model_rebuild() -> None: class StreamingRunContainer(BaseModel): query_id: str run_stream: RunResultStreaming | None model_config = ConfigDict(arbitrary_types_allowed=True) StreamingRunContainer.model_rebuild() def _create_message(text: str) -> ResponseOutputMessage: return ResponseOutputMessage( id="msg", content=[ResponseOutputText(annotations=[], text=text, type="output_text")], role="assistant", status="completed", type="message", ) def test_result_cast_typechecks(): """Correct casts should work fine.""" result = create_run_result(1) assert result.final_output_as(int) == 1 result = create_run_result("test") assert result.final_output_as(str) == "test" result = create_run_result(Foo(bar=1)) assert result.final_output_as(Foo) == Foo(bar=1) def test_bad_cast_doesnt_raise(): """Bad casts shouldn't error unless we ask for it.""" result = create_run_result(1) result.final_output_as(str) result = create_run_result("test") result.final_output_as(Foo) def test_bad_cast_with_param_raises(): """Bad casts should raise a TypeError when we ask for it.""" result = create_run_result(1) with pytest.raises(TypeError): result.final_output_as(str, raise_if_incorrect_type=True) result = create_run_result("test") with pytest.raises(TypeError): result.final_output_as(Foo, raise_if_incorrect_type=True) result = create_run_result(Foo(bar=1)) with pytest.raises(TypeError): result.final_output_as(int, raise_if_incorrect_type=True) def test_run_result_release_agents_breaks_strong_refs() -> None: message = _create_message("hello") agent = Agent(name="leak-test-agent") item = MessageOutputItem(agent=agent, raw_item=message) result = create_run_result(None, new_items=[item], last_agent=agent) assert item.agent is not None assert item.agent.name == "leak-test-agent" agent_ref = weakref.ref(agent) result.release_agents() del agent gc.collect() assert agent_ref() is None assert item.agent is None with pytest.raises(AgentsException): _ = result.last_agent def test_run_item_retains_agent_when_result_is_garbage_collected() -> None: def build_item() -> tuple[MessageOutputItem, weakref.ReferenceType[RunResult]]: message = _create_message("persist") agent = Agent(name="persisted-agent") item = MessageOutputItem(agent=agent, raw_item=message) result = create_run_result(None, new_items=[item], last_agent=agent) return item, weakref.ref(result) item, result_ref = build_item() gc.collect() assert result_ref() is None assert item.agent is not None assert item.agent.name == "persisted-agent" def test_run_item_repr_and_asdict_after_release() -> None: message = _create_message("repr") agent = Agent(name="repr-agent") item = MessageOutputItem(agent=agent, raw_item=message) item.release_agent() assert item.agent is agent text = repr(item) assert "MessageOutputItem" in text serialized = dataclasses.asdict(item) assert isinstance(serialized["agent"], dict) assert serialized["agent"]["name"] == "repr-agent" agent_ref = weakref.ref(agent) del agent gc.collect() assert agent_ref() is None assert item.agent is None serialized_after_gc = dataclasses.asdict(item) assert serialized_after_gc["agent"] is None def test_run_result_repr_and_asdict_after_release_agents() -> None: agent = Agent(name="repr-result-agent") result = create_run_result(None, last_agent=agent) result.release_agents() text = repr(result) assert "RunResult" in text serialized = dataclasses.asdict(result) assert serialized["_last_agent"] is None def test_run_result_release_agents_without_releasing_new_items() -> None: message = _create_message("keep") item_agent = Agent(name="item-agent") last_agent = Agent(name="last-agent") item = MessageOutputItem(agent=item_agent, raw_item=message) result = create_run_result(None, new_items=[item], last_agent=last_agent) result.release_agents(release_new_items=False) assert item.agent is item_agent last_agent_ref = weakref.ref(last_agent) del last_agent gc.collect() assert last_agent_ref() is None with pytest.raises(AgentsException): _ = result.last_agent def test_run_result_release_agents_is_idempotent() -> None: message = _create_message("idempotent") agent = Agent(name="idempotent-agent") item = MessageOutputItem(agent=agent, raw_item=message) result = RunResult( input="test", new_items=[item], raw_responses=[], final_output=None, input_guardrail_results=[], output_guardrail_results=[], tool_input_guardrail_results=[], tool_output_guardrail_results=[], _last_agent=agent, context_wrapper=RunContextWrapper(context=None), interruptions=[], ) result.release_agents() result.release_agents() assert item.agent is agent agent_ref = weakref.ref(agent) del agent gc.collect() assert agent_ref() is None assert item.agent is None with pytest.raises(AgentsException): _ = result.last_agent def test_run_result_streaming_release_agents_releases_current_agent() -> None: agent = Agent(name="streaming-agent") streaming_result = RunResultStreaming( input="stream", new_items=[], raw_responses=[], final_output=None, input_guardrail_results=[], output_guardrail_results=[], tool_input_guardrail_results=[], tool_output_guardrail_results=[], context_wrapper=RunContextWrapper(context=None), current_agent=agent, current_turn=0, max_turns=1, _current_agent_output_schema=None, trace=None, interruptions=[], ) streaming_result.release_agents(release_new_items=False) agent_ref = weakref.ref(agent) del agent gc.collect() assert agent_ref() is None with pytest.raises(AgentsException): _ = streaming_result.last_agent def test_run_result_agent_tool_invocation_returns_none_for_plain_context() -> None: result = create_run_result("ok") assert result.agent_tool_invocation is None def test_run_result_agent_tool_invocation_returns_immutable_metadata() -> None: tool_ctx = ToolContext( context=None, tool_name="my_tool", tool_call_id="call_xyz", tool_arguments="{}", ) result = RunResult( input="test", new_items=[], raw_responses=[], final_output="ok", input_guardrail_results=[], output_guardrail_results=[], tool_input_guardrail_results=[], tool_output_guardrail_results=[], _last_agent=Agent(name="test"), context_wrapper=tool_ctx, interruptions=[], ) assert result.agent_tool_invocation == AgentToolInvocation( tool_name="my_tool", tool_call_id="call_xyz", tool_arguments="{}", ) invocation = result.agent_tool_invocation assert invocation is not None with pytest.raises(dataclasses.FrozenInstanceError): cast(Any, invocation).tool_name = "other" def test_run_result_streaming_agent_tool_invocation_returns_metadata() -> None: agent = Agent(name="streaming-tool-agent") tool_ctx = ToolContext( context=None, tool_name="stream_tool", tool_call_id="call_stream", tool_arguments='{"input":"stream"}', ) result = RunResultStreaming( input="stream", new_items=[], raw_responses=[], final_output="done", input_guardrail_results=[], output_guardrail_results=[], tool_input_guardrail_results=[], tool_output_guardrail_results=[], context_wrapper=tool_ctx, current_agent=agent, current_turn=0, max_turns=1, _current_agent_output_schema=None, trace=None, interruptions=[], ) assert result.agent_tool_invocation == AgentToolInvocation( tool_name="stream_tool", tool_call_id="call_stream", tool_arguments='{"input":"stream"}', )