Skip to content

Commit dfc5e6f

Browse files
refactor: reimplement to avoid code duplication
1 parent a085a21 commit dfc5e6f

1 file changed

Lines changed: 71 additions & 112 deletions

File tree

src/askui/multi_device_agent.py

Lines changed: 71 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,104 @@
1-
import logging
21
from typing import Annotated
32

43
from pydantic import Field
54

5+
from askui.agent import ComputerAgent
66
from askui.agent_base import Agent
7+
from askui.agent_settings import AgentSettings
8+
from askui.android_agent import AndroidAgent
79
from askui.models.shared.tools import Tool
810
from askui.prompts.act_prompts import create_multidevice_agent_prompt
911
from askui.reporting import CompositeReporter, Reporter
1012
from askui.retry import Retry
11-
from askui.tools import AgentToolbox, ComputerAgentOsFacade
12-
from askui.tools.android.agent_os_facade import AndroidAgentOsFacade
13-
from askui.tools.android.ppadb_agent_os import PpadbAgentOs
14-
from askui.tools.android.tools import (
15-
AndroidDragAndDropTool,
16-
AndroidGetConnectedDevicesSerialNumbersTool,
17-
AndroidGetConnectedDisplaysInfosTool,
18-
AndroidGetCurrentConnectedDeviceInfosTool,
19-
AndroidKeyCombinationTool,
20-
AndroidKeyTapEventTool,
21-
AndroidScreenshotTool,
22-
AndroidSelectDeviceBySerialNumberTool,
23-
AndroidSelectDisplayByUniqueIDTool,
24-
AndroidShellTool,
25-
AndroidSwipeTool,
26-
AndroidTapTool,
27-
AndroidTypeTool,
28-
)
29-
from askui.tools.askui import AskUiControllerClient
30-
from askui.tools.computer import (
31-
ComputerGetMousePositionTool,
32-
ComputerGetSystemInfoTool,
33-
ComputerKeyboardPressedTool,
34-
ComputerKeyboardReleaseTool,
35-
ComputerKeyboardTapTool,
36-
ComputerListDisplaysTool,
37-
ComputerMouseClickTool,
38-
ComputerMouseHoldDownTool,
39-
ComputerMouseReleaseTool,
40-
ComputerMouseScrollTool,
41-
ComputerMoveMouseTool,
42-
ComputerRetrieveActiveDisplayTool,
43-
ComputerScreenshotTool,
44-
ComputerSetActiveDisplayTool,
45-
ComputerTypeTool,
46-
)
47-
from askui.tools.exception_tool import ExceptionTool
48-
49-
logger = logging.getLogger(__name__)
5013

5114

5215
class MultiDeviceAgent(Agent):
16+
"""
17+
Multi device agent that combines a computer and an Android agent.
18+
It can be used to perform actions on both devices simultaneously.
19+
20+
Args:
21+
display (int, optional): The display number for computer screen
22+
interactions. Defaults to `1`.
23+
reporters (list[Reporter] | None, optional): List of reporter instances.
24+
tools (AgentToolbox | None, optional): Not supported; use `act_tools`.
25+
retry (Retry | None, optional): Retry instance for failed actions.
26+
act_tools (list[Tool] | None, optional): Additional tools for `act()`.
27+
android_device_sn (str | None, optional): Android device serial number
28+
to select on open.
29+
30+
Example:
31+
```python
32+
from askui import MultiDeviceAgent
33+
34+
with MultiDeviceAgent(android_device_sn="emulator-5554") as agent:
35+
agent.computer.click("Start")
36+
agent.android.tap("OK")
37+
agent.act("Fill the form on the phone and submit from the desktop")
38+
```
39+
"""
40+
5341
def __init__(
5442
self,
55-
display: Annotated[int, Field(ge=1)] = 1,
43+
desktop_display: Annotated[int, Field(ge=1)] = 1,
44+
android_device_sn: str | int = 0,
5645
reporters: list[Reporter] | None = None,
57-
tools: AgentToolbox | None = None,
5846
retry: Retry | None = None,
5947
act_tools: list[Tool] | None = None,
60-
android_device_sn: str | None = None,
61-
):
62-
self.android_device_sn = android_device_sn
48+
settings: AgentSettings | None = None,
49+
) -> None:
6350
reporter = CompositeReporter(reporters=reporters)
64-
self.android_os = PpadbAgentOs(reporter=reporter)
65-
self.android_agent_os_facade = AndroidAgentOsFacade(self.android_os)
66-
self.computer_agent_os_tool = AgentToolbox(
67-
AskUiControllerClient(
68-
display=display,
69-
reporter=reporter,
70-
)
71-
)
72-
73-
self.android_tools: list[Tool] = [
74-
AndroidScreenshotTool(self.android_agent_os_facade),
75-
AndroidTapTool(self.android_agent_os_facade),
76-
AndroidTypeTool(self.android_agent_os_facade),
77-
AndroidDragAndDropTool(self.android_agent_os_facade),
78-
AndroidKeyTapEventTool(self.android_agent_os_facade),
79-
AndroidSwipeTool(self.android_agent_os_facade),
80-
AndroidKeyCombinationTool(self.android_agent_os_facade),
81-
AndroidShellTool(self.android_agent_os_facade),
82-
AndroidSelectDeviceBySerialNumberTool(self.android_agent_os_facade),
83-
AndroidSelectDisplayByUniqueIDTool(self.android_agent_os_facade),
84-
AndroidGetConnectedDevicesSerialNumbersTool(self.android_agent_os_facade),
85-
AndroidGetConnectedDisplaysInfosTool(self.android_agent_os_facade),
86-
AndroidGetCurrentConnectedDeviceInfosTool(self.android_agent_os_facade),
87-
]
88-
self.computer_tools: list[Tool] = [
89-
ComputerGetSystemInfoTool(),
90-
ComputerGetMousePositionTool(),
91-
ComputerKeyboardPressedTool(),
92-
ComputerKeyboardReleaseTool(),
93-
ComputerKeyboardTapTool(),
94-
ComputerMouseClickTool(),
95-
ComputerMouseHoldDownTool(),
96-
ComputerMouseReleaseTool(),
97-
ComputerMouseScrollTool(),
98-
ComputerMoveMouseTool(),
99-
ComputerScreenshotTool(),
100-
ComputerTypeTool(),
101-
ComputerListDisplaysTool(),
102-
ComputerRetrieveActiveDisplayTool(),
103-
ComputerSetActiveDisplayTool(),
104-
]
105-
106-
act_tools = act_tools or []
107-
108-
multi_device_tools: list[Tool] = (
109-
act_tools + self.android_tools + self.computer_tools + [ExceptionTool()]
110-
)
111-
112-
if tools:
113-
# After all, I don't even know why we actually have both parameters in
114-
# the constructor.
115-
msg = (
116-
"'tools' parameter is not supported for MultiDeviceAgent and will"
117-
" be ignored. Please set tools via the 'act_tools' parameter"
118-
)
119-
logger.warning(msg)
12051

52+
# Initialize the base agent
12153
super().__init__(
12254
reporter=reporter,
123-
tools=multi_device_tools,
12455
retry=retry,
125-
agent_os=self.computer_agent_os_tool.os,
56+
settings=settings,
12657
)
12758

128-
self.computer_agent_os_facade: ComputerAgentOsFacade = ComputerAgentOsFacade(
129-
self.computer_agent_os_tool.os
59+
# Initialize the computer agent
60+
self._computer_agent = ComputerAgent(
61+
display=desktop_display,
62+
reporters=[reporter],
63+
settings=settings,
13064
)
131-
self.act_tool_collection.add_agent_os(self.computer_agent_os_facade)
65+
66+
# Initialize the Android agent
67+
self._android_agent = AndroidAgent(
68+
device=android_device_sn,
69+
reporters=[reporter],
70+
settings=settings,
71+
)
72+
73+
# Combine the tool collections of the computer and Android agents
74+
self.act_tool_collection = (
75+
self._computer_agent.act_tool_collection
76+
+ self._android_agent.act_tool_collection
77+
)
78+
79+
self.act_tool_collection.append_tool(*(act_tools or []))
13280

13381
self.act_settings.messages.system = create_multidevice_agent_prompt()
13482

83+
@property
84+
def computer(self) -> ComputerAgent:
85+
"""The composed computer agent."""
86+
return self._computer_agent
87+
88+
@property
89+
def android(self) -> AndroidAgent:
90+
"""The composed Android agent."""
91+
return self._android_agent
92+
13593
def close(self) -> None:
136-
self.android_os.disconnect()
94+
self._computer_agent.act_agent_os_facade.disconnect()
95+
self._android_agent.act_agent_os_facade.disconnect()
13796
super().close()
13897

13998
def open(self) -> None:
140-
self.android_os.connect()
141-
if self.android_device_sn is not None:
142-
self.android_os.set_device_by_serial_number(self.android_device_sn)
143-
if self._agent_os is not None:
144-
self._agent_os.connect()
99+
self._computer_agent.open()
100+
self._android_agent.open()
145101
super().open()
102+
103+
# Get and locate functions must be overridden and throw please use
104+
# .computer_agent and .android_agent instead.

0 commit comments

Comments
 (0)