chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
ComputerProvider,
|
||||
ComputerTool,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
dispose_resolved_computers,
|
||||
resolve_computer,
|
||||
)
|
||||
from agents.computer import Button, Computer, Environment
|
||||
from tests.fake_model import FakeModel
|
||||
|
||||
|
||||
class FakeComputer(Computer):
|
||||
def __init__(self, label: str = "computer") -> None:
|
||||
self.label = label
|
||||
|
||||
@property
|
||||
def environment(self) -> Environment:
|
||||
return "mac"
|
||||
|
||||
@property
|
||||
def dimensions(self) -> tuple[int, int]:
|
||||
return (1, 1)
|
||||
|
||||
def screenshot(self) -> str:
|
||||
return "img"
|
||||
|
||||
def click(self, x: int, y: int, button: Button) -> None:
|
||||
return None
|
||||
|
||||
def double_click(self, x: int, y: int) -> None:
|
||||
return None
|
||||
|
||||
def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
|
||||
return None
|
||||
|
||||
def type(self, text: str) -> None:
|
||||
return None
|
||||
|
||||
def wait(self) -> None:
|
||||
return None
|
||||
|
||||
def move(self, x: int, y: int) -> None:
|
||||
return None
|
||||
|
||||
def keypress(self, keys: list[str]) -> None:
|
||||
return None
|
||||
|
||||
def drag(self, path: list[tuple[int, int]]) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def _make_message(text: str) -> ResponseOutputMessage:
|
||||
return ResponseOutputMessage(
|
||||
id="msg-1",
|
||||
content=[ResponseOutputText(annotations=[], text=text, type="output_text")],
|
||||
role="assistant",
|
||||
status="completed",
|
||||
type="message",
|
||||
)
|
||||
|
||||
|
||||
def test_fake_computer_implements_interface() -> None:
|
||||
computer = FakeComputer("iface")
|
||||
|
||||
computer.screenshot()
|
||||
computer.click(0, 0, "left")
|
||||
computer.double_click(0, 0)
|
||||
computer.scroll(0, 0, 1, 1)
|
||||
computer.type("hello")
|
||||
computer.wait()
|
||||
computer.move(1, 1)
|
||||
computer.keypress(["enter"])
|
||||
computer.drag([(0, 0), (1, 1)])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_computer_per_run_context() -> None:
|
||||
counter = 0
|
||||
|
||||
async def create_computer(*_: Any, **__: Any) -> FakeComputer:
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return FakeComputer(label=f"computer-{counter}")
|
||||
|
||||
tool = ComputerTool(computer=create_computer)
|
||||
ctx_a = RunContextWrapper(context=None)
|
||||
ctx_b = RunContextWrapper(context=None)
|
||||
|
||||
comp_a1 = await resolve_computer(tool=tool, run_context=ctx_a)
|
||||
comp_a2 = await resolve_computer(tool=tool, run_context=ctx_a)
|
||||
comp_b1 = await resolve_computer(tool=tool, run_context=ctx_b)
|
||||
|
||||
assert comp_a1 is comp_a2
|
||||
assert comp_a1 is not comp_b1
|
||||
assert tool.computer is comp_b1
|
||||
assert counter == 2
|
||||
|
||||
await dispose_resolved_computers(run_context=ctx_a)
|
||||
comp_a3 = await resolve_computer(tool=tool, run_context=ctx_a)
|
||||
|
||||
assert comp_a3 is not comp_a1
|
||||
assert counter == 3
|
||||
await dispose_resolved_computers(run_context=ctx_b)
|
||||
await dispose_resolved_computers(run_context=ctx_a)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runner_disposes_computer_after_run() -> None:
|
||||
created = FakeComputer("created")
|
||||
create = AsyncMock(return_value=created)
|
||||
dispose = AsyncMock()
|
||||
|
||||
tool = ComputerTool(computer=ComputerProvider[FakeComputer](create=create, dispose=dispose))
|
||||
model = FakeModel(initial_output=[_make_message("done")])
|
||||
agent = Agent(name="ComputerAgent", model=model, tools=[tool])
|
||||
|
||||
result = await Runner.run(agent, "hello")
|
||||
|
||||
assert result.final_output == "done"
|
||||
create.assert_awaited_once()
|
||||
dispose.assert_awaited_once()
|
||||
dispose.assert_awaited_with(run_context=result.context_wrapper, computer=created)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_computer_with_create_attribute_returns_instance() -> None:
|
||||
"""A Computer subclass with a callable `create` attribute is not a provider."""
|
||||
|
||||
class ComputerWithCreate(FakeComputer):
|
||||
def create(self, *args: Any, **kwargs: Any) -> str:
|
||||
return "user-helper"
|
||||
|
||||
computer = ComputerWithCreate("with-create")
|
||||
tool = ComputerTool(computer=computer)
|
||||
ctx = RunContextWrapper(context=None)
|
||||
|
||||
resolved = await resolve_computer(tool=tool, run_context=ctx)
|
||||
|
||||
assert resolved is computer
|
||||
await dispose_resolved_computers(run_context=ctx)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streamed_run_disposes_computer_after_completion() -> None:
|
||||
created = FakeComputer("streaming")
|
||||
create = AsyncMock(return_value=created)
|
||||
dispose = AsyncMock()
|
||||
|
||||
tool = ComputerTool(computer=ComputerProvider[FakeComputer](create=create, dispose=dispose))
|
||||
model = FakeModel(initial_output=[_make_message("done")])
|
||||
agent = Agent(name="ComputerAgent", model=model, tools=[tool])
|
||||
|
||||
streamed_result = Runner.run_streamed(agent, "hello")
|
||||
async for _ in streamed_result.stream_events():
|
||||
pass
|
||||
|
||||
assert streamed_result.final_output == "done"
|
||||
create.assert_awaited_once()
|
||||
dispose.assert_awaited_once()
|
||||
dispose.assert_awaited_with(run_context=streamed_result.context_wrapper, computer=created)
|
||||
Reference in New Issue
Block a user