Files
openai--openai-agents-python/tests/sandbox/test_runtime_agent_preparation.py
T
2026-07-13 12:39:17 +08:00

252 lines
8.5 KiB
Python

from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable, Coroutine
from pathlib import Path
from types import SimpleNamespace
from typing import Any, cast
import pytest
from agents import UserError
from agents.models.default_models import get_default_model
from agents.run_context import RunContextWrapper
from agents.sandbox import MemoryReadConfig, runtime_agent_preparation as sandbox_prep
from agents.sandbox.capabilities import Capability, Compaction, Memory
from agents.sandbox.entries import BaseEntry, File
from agents.sandbox.manifest import Manifest
from agents.sandbox.sandbox_agent import SandboxAgent
from agents.sandbox.session.base_sandbox_session import BaseSandboxSession
class _Capability:
def __init__(self, fragment: str | None, *, type: str = "test") -> None:
self.type = type
self.fragment = fragment
self.manifests: list[Manifest] = []
self.sampling_params_calls: list[dict[str, object]] = []
def tools(self) -> list[object]:
return []
def sampling_params(self, sampling_params: dict[str, object]) -> dict[str, object]:
self.sampling_params_calls.append(dict(sampling_params))
return {}
def required_capability_types(self) -> set[str]:
return set()
async def instructions(self, manifest: Manifest) -> str | None:
self.manifests.append(manifest)
return self.fragment
def _session_with_manifest(manifest: Manifest | None) -> object:
return SimpleNamespace(state=SimpleNamespace(manifest=manifest))
def test_prepare_sandbox_agent_passes_session_manifest_to_capability_instructions():
manifest = Manifest(root="/workspace")
capability = _Capability("capability fragment")
prepared = sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
base_instructions="base instructions",
instructions="additional instructions",
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=cast(list[Capability], [capability]),
)
instructions = cast(
Callable[[RunContextWrapper[object], SandboxAgent[object]], Awaitable[str | None]],
prepared.instructions,
)
result: str | None = asyncio.run(
cast(
Coroutine[Any, Any, str | None],
instructions(
cast(RunContextWrapper[object], None),
cast(SandboxAgent[object], prepared),
),
)
)
assert result == (
"base instructions\n\n"
"# Agent instructions\n\n"
"additional instructions\n\n"
"# Sandbox capability instructions\n\n"
"capability fragment\n\n"
f"{sandbox_prep._filesystem_instructions(manifest)}"
)
assert capability.manifests == [manifest]
def test_prepare_sandbox_agent_wraps_capabilities_without_agent_instructions():
manifest = Manifest(root="/workspace")
capability = _Capability("capability fragment")
prepared = sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
base_instructions="base instructions",
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=cast(list[Capability], [capability]),
)
instructions = cast(
Callable[[RunContextWrapper[object], SandboxAgent[object]], Awaitable[str | None]],
prepared.instructions,
)
result: str | None = asyncio.run(
cast(
Coroutine[Any, Any, str | None],
instructions(
cast(RunContextWrapper[object], None),
cast(SandboxAgent[object], prepared),
),
)
)
assert result == (
"base instructions\n\n"
"# Sandbox capability instructions\n\n"
"capability fragment\n\n"
f"{sandbox_prep._filesystem_instructions(manifest)}"
)
assert capability.manifests == [manifest]
def test_prepare_sandbox_agent_passes_default_model_to_capability_sampling_params() -> None:
manifest = Manifest(root="/workspace")
capability = _Capability(None)
sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="base instructions",
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=cast(list[Capability], [capability]),
)
assert capability.sampling_params_calls == [{"model": get_default_model()}]
def test_prepare_sandbox_agent_prepares_default_compaction_policy() -> None:
manifest = Manifest(root="/workspace")
prepared = sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="base instructions",
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=[Compaction()],
)
extra_args = prepared.model_settings.extra_args
assert extra_args is not None
assert "context_management" in extra_args
assert "model" not in extra_args
def test_prepare_sandbox_agent_uses_default_sandbox_instructions_when_base_missing():
manifest = Manifest(root="/workspace")
capability = _Capability("capability fragment")
prepared = sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="additional instructions",
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=cast(list[Capability], [capability]),
)
instructions = cast(
Callable[[RunContextWrapper[object], SandboxAgent[object]], Awaitable[str | None]],
prepared.instructions,
)
result: str | None = asyncio.run(
cast(
Coroutine[Any, Any, str | None],
instructions(
cast(RunContextWrapper[object], None),
cast(SandboxAgent[object], prepared),
),
)
)
default_instructions = sandbox_prep.get_default_sandbox_instructions()
assert default_instructions is not None
assert result == (
f"{default_instructions}\n\n"
"# Agent instructions\n\n"
"additional instructions\n\n"
"# Sandbox capability instructions\n\n"
"capability fragment\n\n"
f"{sandbox_prep._filesystem_instructions(manifest)}"
)
assert capability.manifests == [manifest]
def test_filesystem_instructions_tell_model_to_ls_when_manifest_tree_is_truncated() -> None:
entries: dict[str | Path, BaseEntry] = {
f"file_{index:03}.txt": File(content=b"", description="x" * 40) for index in range(200)
}
manifest = Manifest(root="/workspace", entries=entries)
result = sandbox_prep._filesystem_instructions(manifest)
assert "... (truncated " in result
assert (
"The filesystem layout above was truncated. "
"Use `ls` to explore specific directories before relying on omitted paths."
) in result
def test_prepare_sandbox_agent_validates_required_capabilities() -> None:
manifest = Manifest(root="/workspace")
with pytest.raises(UserError, match="Memory requires missing capabilities: filesystem, shell"):
sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="base instructions",
capabilities=[Memory()],
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=[Memory()],
)
with pytest.raises(UserError, match="Memory requires missing capabilities: shell"):
sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="base instructions",
capabilities=[Memory(read=MemoryReadConfig(live_update=False), generate=None)],
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=[Memory(read=MemoryReadConfig(live_update=False), generate=None)],
)
prepared = sandbox_prep.prepare_sandbox_agent(
agent=SandboxAgent(
name="sandbox",
instructions="base instructions",
capabilities=[Memory()],
),
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
capabilities=cast(
list[Capability],
[
Memory(),
_Capability(None, type="filesystem"),
_Capability(None, type="shell"),
],
),
)
assert prepared.name == "sandbox"