Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/agents/sandbox/memory/rollouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ def terminal_metadata_for_exception(exc: BaseException) -> RolloutTerminalMetada
)


def _serialize_interruption_raw_item(raw_item: Any) -> Any:
if isinstance(raw_item, BaseModel):
return _to_dump_compatible(raw_item.model_dump(exclude_unset=True))
if isinstance(raw_item, dict):
return dict(raw_item)
return _to_dump_compatible(raw_item)


def build_rollout_payload(
*,
input: str | list[TResponseInputItem],
Expand All @@ -210,10 +218,7 @@ def build_rollout_payload(
)

serialized_interruptions = [
_to_dump_compatible(interruption.raw_item)
if not isinstance(interruption.raw_item, dict)
else dict(interruption.raw_item)
for interruption in interruptions
_serialize_interruption_raw_item(interruption.raw_item) for interruption in interruptions
]

payload: dict[str, Any] = {
Expand Down
37 changes: 35 additions & 2 deletions tests/sandbox/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Any, cast

import pytest
from openai.types.responses import ResponseCustomToolCall
from openai.types.responses import ResponseCustomToolCall, ResponseFunctionToolCall
from openai.types.responses.response_output_message import ResponseOutputMessage
from openai.types.responses.response_reasoning_item import ResponseReasoningItem

Expand All @@ -24,7 +24,12 @@
TResponseInputItem,
)
from agents.exceptions import UserError
from agents.items import CompactionItem, MessageOutputItem, TResponseOutputItem
from agents.items import (
CompactionItem,
MessageOutputItem,
ToolApprovalItem,
TResponseOutputItem,
)
from agents.result import RunResultStreaming
from agents.run import _sandbox_memory_input
from agents.run_context import RunContextWrapper
Expand Down Expand Up @@ -258,6 +263,34 @@ def test_build_rollout_payload_filters_developer_and_noisy_items() -> None:
assert payload["final_output"] == "done"


def test_build_rollout_payload_serializes_model_interruptions_as_dicts() -> None:
agent = Agent(name="test")
raw = ResponseFunctionToolCall(
id="fc_1",
call_id="call_1",
name="get_weather",
arguments='{"city":"Paris"}',
type="function_call",
)
approval = ToolApprovalItem(agent=agent, raw_item=raw)

payload = build_rollout_payload(
input="hello",
new_items=[],
final_output=None,
interruptions=[approval],
terminal_metadata=RolloutTerminalMetadata(terminal_state="interrupted"),
)

interruption = payload["interruptions"][0]
assert isinstance(interruption, dict)
assert interruption == raw.model_dump(exclude_unset=True)
assert interruption["type"] == "function_call"
assert interruption["call_id"] == "call_1"
assert interruption["name"] == "get_weather"
assert interruption["arguments"] == '{"city":"Paris"}'


def test_render_phase_one_prompt_truncates_large_rollout_contents() -> None:
payload = {
"input": [{"role": "user", "content": f"start{'a' * 700_000}middle{'z' * 700_000}end"}],
Expand Down