Skip to content
Open
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
10 changes: 8 additions & 2 deletions phone_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,16 @@ def _execute_step(
# Parse action from response
try:
action = parse_action(response.action)
except ValueError:
except ValueError as e:
if self.agent_config.verbose:
traceback.print_exc()
action = finish(message=response.action)
return StepResult(
success=False,
finished=True,
action=None,
thinking=response.thinking,
message=f"Failed to parse action: {response.action}. Error: {e}",
)

if self.agent_config.verbose:
# Print thinking process
Expand Down
12 changes: 9 additions & 3 deletions phone_agent/agent_ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class IOSAgentConfig:
max_steps: int = 100
wda_url: str = "http://localhost:8100"
session_id: str | None = None
device_id: str | None = None # iOS device UDID
device_id: str | None = None # iOS device identifier
lang: str = "cn"
system_prompt: str | None = None
verbose: bool = True
Expand Down Expand Up @@ -208,10 +208,16 @@ def _execute_step(
# Parse action from response
try:
action = parse_action(response.action)
except ValueError:
except ValueError as e:
if self.agent_config.verbose:
traceback.print_exc()
action = finish(message=response.action)
return StepResult(
success=False,
finished=True,
action=None,
thinking=response.thinking,
message=f"Failed to parse action: {response.action}. Error: {e}",
)

if self.agent_config.verbose:
# Print thinking process
Expand Down
85 changes: 85 additions & 0 deletions tests/test_agent_parse_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from dataclasses import dataclass

import phone_agent.agent as agent_module
import phone_agent.agent_ios as agent_ios_module
from phone_agent.agent import AgentConfig, PhoneAgent
from phone_agent.agent_ios import IOSAgentConfig, IOSPhoneAgent
from phone_agent.model.client import ModelResponse


@dataclass
class FakeScreenshot:
base64_data: str = ""
width: int = 1080
height: int = 2400


class FakeDeviceFactory:
def get_screenshot(self, device_id=None):
return FakeScreenshot()

def get_current_app(self, device_id=None):
return "test.app"


class FakeModelClient:
def request(self, messages):
return ModelResponse(
thinking="bad action",
action="[{'Type': 'Type'}]",
raw_content="[{'Type': 'Type'}]",
)


def test_parse_error_returns_failure_without_executing_action(monkeypatch) -> None:
monkeypatch.setattr(agent_module, "get_device_factory", lambda: FakeDeviceFactory())

agent = PhoneAgent(agent_config=AgentConfig(verbose=False))
agent.model_client = FakeModelClient()

def fail_if_called(*args, **kwargs):
raise AssertionError("malformed action should not be executed")

monkeypatch.setattr(agent.action_handler, "execute", fail_if_called)

result = agent.step("do something")

assert result.success is False
assert result.finished is True
assert result.action is None
assert "Failed to parse action" in result.message


class FakeWDAConnection:
def __init__(self, wda_url):
self.wda_url = wda_url

def start_wda_session(self):
return False, None


def test_ios_parse_error_returns_failure_without_executing_action(
monkeypatch,
) -> None:
monkeypatch.setattr(agent_ios_module, "XCTestConnection", FakeWDAConnection)
monkeypatch.setattr(
agent_ios_module, "get_screenshot", lambda **kwargs: FakeScreenshot()
)
monkeypatch.setattr(
agent_ios_module, "get_current_app", lambda **kwargs: "test.app"
)

agent = IOSPhoneAgent(agent_config=IOSAgentConfig(verbose=False))
agent.model_client = FakeModelClient()

def fail_if_called(*args, **kwargs):
raise AssertionError("malformed action should not be executed")

monkeypatch.setattr(agent.action_handler, "execute", fail_if_called)

result = agent.step("do something")

assert result.success is False
assert result.finished is True
assert result.action is None
assert "Failed to parse action" in result.message