lybic-guiagents 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of lybic-guiagents might be problematic. Click here for more details.
- desktop_env/__init__.py +1 -0
- desktop_env/actions.py +203 -0
- desktop_env/controllers/__init__.py +0 -0
- desktop_env/controllers/python.py +471 -0
- desktop_env/controllers/setup.py +882 -0
- desktop_env/desktop_env.py +509 -0
- desktop_env/evaluators/__init__.py +5 -0
- desktop_env/evaluators/getters/__init__.py +41 -0
- desktop_env/evaluators/getters/calc.py +15 -0
- desktop_env/evaluators/getters/chrome.py +1774 -0
- desktop_env/evaluators/getters/file.py +154 -0
- desktop_env/evaluators/getters/general.py +42 -0
- desktop_env/evaluators/getters/gimp.py +38 -0
- desktop_env/evaluators/getters/impress.py +126 -0
- desktop_env/evaluators/getters/info.py +24 -0
- desktop_env/evaluators/getters/misc.py +406 -0
- desktop_env/evaluators/getters/replay.py +20 -0
- desktop_env/evaluators/getters/vlc.py +86 -0
- desktop_env/evaluators/getters/vscode.py +35 -0
- desktop_env/evaluators/metrics/__init__.py +160 -0
- desktop_env/evaluators/metrics/basic_os.py +68 -0
- desktop_env/evaluators/metrics/chrome.py +493 -0
- desktop_env/evaluators/metrics/docs.py +1011 -0
- desktop_env/evaluators/metrics/general.py +665 -0
- desktop_env/evaluators/metrics/gimp.py +637 -0
- desktop_env/evaluators/metrics/libreoffice.py +28 -0
- desktop_env/evaluators/metrics/others.py +92 -0
- desktop_env/evaluators/metrics/pdf.py +31 -0
- desktop_env/evaluators/metrics/slides.py +957 -0
- desktop_env/evaluators/metrics/table.py +585 -0
- desktop_env/evaluators/metrics/thunderbird.py +176 -0
- desktop_env/evaluators/metrics/utils.py +719 -0
- desktop_env/evaluators/metrics/vlc.py +524 -0
- desktop_env/evaluators/metrics/vscode.py +283 -0
- desktop_env/providers/__init__.py +35 -0
- desktop_env/providers/aws/__init__.py +0 -0
- desktop_env/providers/aws/manager.py +278 -0
- desktop_env/providers/aws/provider.py +186 -0
- desktop_env/providers/aws/provider_with_proxy.py +315 -0
- desktop_env/providers/aws/proxy_pool.py +193 -0
- desktop_env/providers/azure/__init__.py +0 -0
- desktop_env/providers/azure/manager.py +87 -0
- desktop_env/providers/azure/provider.py +207 -0
- desktop_env/providers/base.py +97 -0
- desktop_env/providers/gcp/__init__.py +0 -0
- desktop_env/providers/gcp/manager.py +0 -0
- desktop_env/providers/gcp/provider.py +0 -0
- desktop_env/providers/virtualbox/__init__.py +0 -0
- desktop_env/providers/virtualbox/manager.py +463 -0
- desktop_env/providers/virtualbox/provider.py +124 -0
- desktop_env/providers/vmware/__init__.py +0 -0
- desktop_env/providers/vmware/manager.py +455 -0
- desktop_env/providers/vmware/provider.py +105 -0
- gui_agents/__init__.py +0 -0
- gui_agents/agents/Action.py +209 -0
- gui_agents/agents/__init__.py +0 -0
- gui_agents/agents/agent_s.py +832 -0
- gui_agents/agents/global_state.py +610 -0
- gui_agents/agents/grounding.py +651 -0
- gui_agents/agents/hardware_interface.py +129 -0
- gui_agents/agents/manager.py +568 -0
- gui_agents/agents/translator.py +132 -0
- gui_agents/agents/worker.py +355 -0
- gui_agents/cli_app.py +560 -0
- gui_agents/core/__init__.py +0 -0
- gui_agents/core/engine.py +1496 -0
- gui_agents/core/knowledge.py +449 -0
- gui_agents/core/mllm.py +555 -0
- gui_agents/tools/__init__.py +0 -0
- gui_agents/tools/tools.py +727 -0
- gui_agents/unit_test/__init__.py +0 -0
- gui_agents/unit_test/run_tests.py +65 -0
- gui_agents/unit_test/test_manager.py +330 -0
- gui_agents/unit_test/test_worker.py +269 -0
- gui_agents/utils/__init__.py +0 -0
- gui_agents/utils/analyze_display.py +301 -0
- gui_agents/utils/common_utils.py +263 -0
- gui_agents/utils/display_viewer.py +281 -0
- gui_agents/utils/embedding_manager.py +53 -0
- gui_agents/utils/image_axis_utils.py +27 -0
- lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
- lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
- lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
- lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
- lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
# Add project root directory to Python path
|
|
9
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..')))
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
|
|
14
|
+
def load_env_variables():
|
|
15
|
+
env_path = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) / '.env'
|
|
16
|
+
if env_path.exists():
|
|
17
|
+
load_dotenv(dotenv_path=env_path)
|
|
18
|
+
return True
|
|
19
|
+
else:
|
|
20
|
+
print(f".env file not found: {env_path}")
|
|
21
|
+
return False
|
|
22
|
+
load_env_variables()
|
|
23
|
+
|
|
24
|
+
def run_all_tests():
|
|
25
|
+
"""Run all unit tests"""
|
|
26
|
+
# Discover all tests in the current directory
|
|
27
|
+
test_loader = unittest.TestLoader()
|
|
28
|
+
test_suite = test_loader.discover(os.path.dirname(__file__), pattern='test_*.py')
|
|
29
|
+
|
|
30
|
+
# Run tests
|
|
31
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
32
|
+
runner.run(test_suite)
|
|
33
|
+
|
|
34
|
+
def run_specific_test(test_name):
|
|
35
|
+
"""Run specified unit tests
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
test_name: Test module name, e.g. 'test_manager' or 'test_worker'
|
|
39
|
+
"""
|
|
40
|
+
if not test_name.startswith('test_'):
|
|
41
|
+
test_name = f'test_{test_name}'
|
|
42
|
+
|
|
43
|
+
# Import test module
|
|
44
|
+
try:
|
|
45
|
+
test_module = __import__(test_name)
|
|
46
|
+
except ImportError:
|
|
47
|
+
print(f"Test module not found: {test_name}")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
# Run tests
|
|
51
|
+
test_loader = unittest.TestLoader()
|
|
52
|
+
test_suite = test_loader.loadTestsFromModule(test_module)
|
|
53
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
54
|
+
runner.run(test_suite)
|
|
55
|
+
|
|
56
|
+
if __name__ == '__main__':
|
|
57
|
+
"""
|
|
58
|
+
python -m gui_agents.unit_test.run_tests
|
|
59
|
+
"""
|
|
60
|
+
if len(sys.argv) > 1:
|
|
61
|
+
# Run specified tests
|
|
62
|
+
run_specific_test(sys.argv[1])
|
|
63
|
+
else:
|
|
64
|
+
# Run all tests
|
|
65
|
+
run_all_tests()
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
from gui_agents.agents.manager import Manager
|
|
11
|
+
from gui_agents.utils.common_utils import Node, Dag
|
|
12
|
+
|
|
13
|
+
# Configure colored logging
|
|
14
|
+
class ColoredFormatter(logging.Formatter):
|
|
15
|
+
"""Custom colored logging formatter"""
|
|
16
|
+
COLORS = {
|
|
17
|
+
'DEBUG': '\033[94m', # Blue
|
|
18
|
+
'INFO': '\033[92m', # Green
|
|
19
|
+
'WARNING': '\033[93m', # Yellow
|
|
20
|
+
'ERROR': '\033[91m', # Red
|
|
21
|
+
'CRITICAL': '\033[91m\033[1m', # Red bold
|
|
22
|
+
'RESET': '\033[0m' # Reset
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def format(self, record):
|
|
26
|
+
log_message = super().format(record)
|
|
27
|
+
return f"{self.COLORS.get(record.levelname, self.COLORS['RESET'])}{log_message}{self.COLORS['RESET']}"
|
|
28
|
+
|
|
29
|
+
# Configure logging - Clear all handlers and reconfigure
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
logger.handlers = [] # Clear all existing handlers
|
|
32
|
+
logger.propagate = False # Prevent logging from propagating to root logger
|
|
33
|
+
|
|
34
|
+
# Add single handler
|
|
35
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
36
|
+
console_handler.setFormatter(ColoredFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
|
37
|
+
logger.addHandler(console_handler)
|
|
38
|
+
logger.setLevel(logging.INFO)
|
|
39
|
+
|
|
40
|
+
# Define colored separator
|
|
41
|
+
def print_test_header(test_name):
|
|
42
|
+
"""Print test header, using colored and prominent separator"""
|
|
43
|
+
separator = "="*80
|
|
44
|
+
logger.info(separator)
|
|
45
|
+
logger.info(test_name.center(80))
|
|
46
|
+
logger.info(separator)
|
|
47
|
+
|
|
48
|
+
def print_test_section(section_name):
|
|
49
|
+
"""Print test section, using colored and prominent separator"""
|
|
50
|
+
separator = "-"*60
|
|
51
|
+
logger.info("\n" + separator)
|
|
52
|
+
logger.info(section_name.center(60))
|
|
53
|
+
logger.info(separator)
|
|
54
|
+
|
|
55
|
+
class TestManager(unittest.TestCase):
|
|
56
|
+
def setUp(self):
|
|
57
|
+
"""Set up test environment"""
|
|
58
|
+
print_test_header("Set up test environment")
|
|
59
|
+
|
|
60
|
+
# Load tools configuration file
|
|
61
|
+
tools_config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "tools", "tools_config.json")
|
|
62
|
+
with open(tools_config_path, "r") as f:
|
|
63
|
+
tools_config = json.load(f)
|
|
64
|
+
self.Tools_dict = {}
|
|
65
|
+
for tool in tools_config["tools"]:
|
|
66
|
+
tool_name = tool["tool_name"]
|
|
67
|
+
self.Tools_dict[tool_name] = {
|
|
68
|
+
"provider": tool["provider"],
|
|
69
|
+
"model": tool["model_name"]
|
|
70
|
+
}
|
|
71
|
+
logger.info(f"Loaded {len(self.Tools_dict)} tool configurations")
|
|
72
|
+
|
|
73
|
+
# Create test directory structure
|
|
74
|
+
self.test_kb_path = "test_kb"
|
|
75
|
+
self.platform = "darwin"
|
|
76
|
+
self.test_platform_path = os.path.join(self.test_kb_path, self.platform)
|
|
77
|
+
os.makedirs(self.test_platform_path, exist_ok=True)
|
|
78
|
+
logger.info(f"Created test directory: {self.test_platform_path}")
|
|
79
|
+
|
|
80
|
+
# Create test files
|
|
81
|
+
with open(os.path.join(self.test_platform_path, "narrative_memory.json"), "w") as f:
|
|
82
|
+
json.dump({}, f)
|
|
83
|
+
with open(os.path.join(self.test_platform_path, "episodic_memory.json"), "w") as f:
|
|
84
|
+
json.dump({}, f)
|
|
85
|
+
with open(os.path.join(self.test_platform_path, "embeddings.pkl"), "wb") as f:
|
|
86
|
+
f.write(b"")
|
|
87
|
+
logger.info("Created test files")
|
|
88
|
+
|
|
89
|
+
# Create Manager instance - use actual Manager instead of mock
|
|
90
|
+
self.manager = Manager(
|
|
91
|
+
Tools_dict=self.Tools_dict,
|
|
92
|
+
local_kb_path=self.test_kb_path,
|
|
93
|
+
platform=self.platform
|
|
94
|
+
)
|
|
95
|
+
logger.info("Manager instance created")
|
|
96
|
+
|
|
97
|
+
# Create test observation data
|
|
98
|
+
import pyautogui
|
|
99
|
+
self.test_image = pyautogui.screenshot()
|
|
100
|
+
buffered = BytesIO()
|
|
101
|
+
self.test_image.save(buffered, format="PNG")
|
|
102
|
+
self.test_screenshot_bytes = buffered.getvalue()
|
|
103
|
+
|
|
104
|
+
self.test_observation = {
|
|
105
|
+
"screenshot": self.test_screenshot_bytes
|
|
106
|
+
}
|
|
107
|
+
logger.info("Test observation data created")
|
|
108
|
+
|
|
109
|
+
# Test instruction
|
|
110
|
+
self.test_instruction = "在系统中打开设置并更改显示分辨率"
|
|
111
|
+
logger.info(f"Test instruction: {self.test_instruction}")
|
|
112
|
+
|
|
113
|
+
def tearDown(self):
|
|
114
|
+
"""Clean up test environment"""
|
|
115
|
+
print_test_header("Clean up test environment")
|
|
116
|
+
import shutil
|
|
117
|
+
if os.path.exists(self.test_kb_path):
|
|
118
|
+
shutil.rmtree(self.test_kb_path)
|
|
119
|
+
logger.info(f"Deleted test directory: {self.test_kb_path}")
|
|
120
|
+
|
|
121
|
+
def test_generate_step_by_step_plan(self):
|
|
122
|
+
"""Test _generate_step_by_step_plan method"""
|
|
123
|
+
print_test_header("Test _generate_step_by_step_plan method")
|
|
124
|
+
logger.info(f"Input parameters: observation={type(self.test_observation)}, instruction={self.test_instruction}")
|
|
125
|
+
|
|
126
|
+
# Test initial plan generation
|
|
127
|
+
print_test_section("Initial plan generation")
|
|
128
|
+
planner_info, plan = self.manager._generate_step_by_step_plan(
|
|
129
|
+
self.test_observation,
|
|
130
|
+
self.test_instruction
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Output results
|
|
134
|
+
logger.info(f"Output results: planner_info={planner_info}")
|
|
135
|
+
logger.info(f"Output results: plan(first 100 characters)={plan[:100]}...")
|
|
136
|
+
|
|
137
|
+
# Verify results
|
|
138
|
+
self.assertIsNotNone(plan)
|
|
139
|
+
self.assertIsInstance(plan, str)
|
|
140
|
+
self.assertGreater(len(plan), 0)
|
|
141
|
+
self.assertIn("search_query", planner_info)
|
|
142
|
+
self.assertIn("goal_plan", planner_info)
|
|
143
|
+
self.assertEqual(planner_info["goal_plan"], plan)
|
|
144
|
+
|
|
145
|
+
# Test re-planning (failed subtask)
|
|
146
|
+
print_test_section("Test re-planning (failed subtask)")
|
|
147
|
+
failed_subtask = Node(name="Failed subtask", info="Failed subtask information")
|
|
148
|
+
completed_subtasks = [Node(name="Completed subtask", info="Completed subtask information")]
|
|
149
|
+
|
|
150
|
+
logger.info(f"Input parameters: failed_subtask={failed_subtask}, completed_subtasks={completed_subtasks}")
|
|
151
|
+
|
|
152
|
+
self.manager.turn_count = 1 # Set to non-initial state
|
|
153
|
+
planner_info, plan = self.manager._generate_step_by_step_plan(
|
|
154
|
+
self.test_observation,
|
|
155
|
+
self.test_instruction,
|
|
156
|
+
failed_subtask,
|
|
157
|
+
completed_subtasks,
|
|
158
|
+
[]
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Output results
|
|
162
|
+
logger.info(f"Output results: planner_info={planner_info}")
|
|
163
|
+
logger.info(f"Output results: plan(first 100 characters)={plan[:100]}...")
|
|
164
|
+
|
|
165
|
+
# Verify results
|
|
166
|
+
self.assertIsNotNone(plan)
|
|
167
|
+
self.assertIsInstance(plan, str)
|
|
168
|
+
self.assertGreater(len(plan), 0)
|
|
169
|
+
self.assertIn("goal_plan", planner_info)
|
|
170
|
+
self.assertEqual(planner_info["goal_plan"], plan)
|
|
171
|
+
|
|
172
|
+
def test_generate_dag(self):
|
|
173
|
+
"""Test _generate_dag method"""
|
|
174
|
+
print_test_header("Test _generate_dag method")
|
|
175
|
+
|
|
176
|
+
# First generate plan
|
|
177
|
+
print_test_section("Generate plan")
|
|
178
|
+
logger.info("First generate plan")
|
|
179
|
+
_, plan = self.manager._generate_step_by_step_plan(
|
|
180
|
+
self.test_observation,
|
|
181
|
+
self.test_instruction
|
|
182
|
+
)
|
|
183
|
+
logger.info(f"Generated plan(first 100 characters): {plan[:100]}...")
|
|
184
|
+
|
|
185
|
+
# Use generated plan to create DAG
|
|
186
|
+
print_test_section("Create DAG")
|
|
187
|
+
logger.info(f"Input parameters: instruction={self.test_instruction}, plan(first 100 characters)={plan[:100]}...")
|
|
188
|
+
dag_raw = self.manager.dag_translator_agent.execute_tool("dag_translator", {"str_input": f"Instruction: {self.test_instruction}\nPlan: {plan}"})
|
|
189
|
+
logger.info(f"Raw DAG output: {dag_raw}")
|
|
190
|
+
|
|
191
|
+
# Manually parse DAG
|
|
192
|
+
print_test_section("Parse DAG")
|
|
193
|
+
from gui_agents.utils.common_utils import parse_dag
|
|
194
|
+
dag = parse_dag(dag_raw)
|
|
195
|
+
|
|
196
|
+
if dag is None:
|
|
197
|
+
logger.error("DAG parsing failed, create a simple test DAG")
|
|
198
|
+
# Create a simple test DAG
|
|
199
|
+
nodes = [
|
|
200
|
+
Node(name="Open settings", info="Open settings application"),
|
|
201
|
+
Node(name="Navigate to display settings", info="Find and click on display settings option"),
|
|
202
|
+
Node(name="Change resolution", info="Change screen resolution")
|
|
203
|
+
]
|
|
204
|
+
edges = [
|
|
205
|
+
[nodes[0], nodes[1]],
|
|
206
|
+
[nodes[1], nodes[2]]
|
|
207
|
+
]
|
|
208
|
+
dag = Dag(nodes=nodes, edges=edges)
|
|
209
|
+
|
|
210
|
+
dag_info = {"dag": dag_raw}
|
|
211
|
+
|
|
212
|
+
logger.info(f"Parsed DAG: nodes={[node.name for node in dag.nodes]}, edges number={len(dag.edges)}")
|
|
213
|
+
|
|
214
|
+
# Verify results
|
|
215
|
+
self.assertIsNotNone(dag)
|
|
216
|
+
self.assertIsInstance(dag, Dag)
|
|
217
|
+
self.assertGreater(len(dag.nodes), 0)
|
|
218
|
+
self.assertGreaterEqual(len(dag.edges), 0)
|
|
219
|
+
self.assertIn("dag", dag_info)
|
|
220
|
+
|
|
221
|
+
def test_topological_sort(self):
|
|
222
|
+
"""Test _topological_sort method"""
|
|
223
|
+
print_test_header("Test _topological_sort method")
|
|
224
|
+
|
|
225
|
+
# Create test DAG
|
|
226
|
+
print_test_section("Create test DAG")
|
|
227
|
+
nodes = [
|
|
228
|
+
Node(name="A", info="Task A"),
|
|
229
|
+
Node(name="B", info="Task B"),
|
|
230
|
+
Node(name="C", info="Task C"),
|
|
231
|
+
Node(name="D", info="Task D")
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
edges = [
|
|
235
|
+
[nodes[0], nodes[1]], # A -> B
|
|
236
|
+
[nodes[0], nodes[2]], # A -> C
|
|
237
|
+
[nodes[1], nodes[3]], # B -> D
|
|
238
|
+
[nodes[2], nodes[3]] # C -> D
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
dag = Dag(nodes=nodes, edges=edges)
|
|
242
|
+
logger.info(f"Input parameters: dag.nodes={[node.name for node in dag.nodes]}, dag.edges number={len(dag.edges)}")
|
|
243
|
+
|
|
244
|
+
# Execute topological sort
|
|
245
|
+
print_test_section("Execute topological sort")
|
|
246
|
+
sorted_nodes = self.manager._topological_sort(dag)
|
|
247
|
+
logger.info(f"Output results: sorted_nodes={[node.name for node in sorted_nodes]}")
|
|
248
|
+
|
|
249
|
+
# Verify results
|
|
250
|
+
print_test_section("Verify sorting results")
|
|
251
|
+
self.assertEqual(len(sorted_nodes), 4)
|
|
252
|
+
self.assertEqual(sorted_nodes[0].name, "A")
|
|
253
|
+
|
|
254
|
+
# Verify B and C's order may be uncertain, but they are both after A and before D
|
|
255
|
+
self.assertIn(sorted_nodes[1].name, ["B", "C"])
|
|
256
|
+
self.assertIn(sorted_nodes[2].name, ["B", "C"])
|
|
257
|
+
self.assertNotEqual(sorted_nodes[1].name, sorted_nodes[2].name)
|
|
258
|
+
|
|
259
|
+
self.assertEqual(sorted_nodes[3].name, "D")
|
|
260
|
+
|
|
261
|
+
def test_get_action_queue(self):
|
|
262
|
+
"""Test get_action_queue method"""
|
|
263
|
+
print_test_header("Test get_action_queue method")
|
|
264
|
+
|
|
265
|
+
# Modify Manager's _generate_dag method to avoid parsing failure
|
|
266
|
+
print_test_section("Modify _generate_dag method")
|
|
267
|
+
def mock_generate_dag(self, instruction, plan):
|
|
268
|
+
logger.info("Use modified _generate_dag method")
|
|
269
|
+
dag_raw = self.dag_translator_agent.execute_tool("dag_translator", {"str_input": f"Instruction: {instruction}\nPlan: {plan}"})
|
|
270
|
+
logger.info(f"Raw DAG output: {dag_raw}")
|
|
271
|
+
|
|
272
|
+
# Try to parse DAG
|
|
273
|
+
from gui_agents.utils.common_utils import parse_dag
|
|
274
|
+
dag = parse_dag(dag_raw)
|
|
275
|
+
|
|
276
|
+
# If parsing fails, create a simple test DAG
|
|
277
|
+
if dag is None:
|
|
278
|
+
logger.warning("DAG parsing failed, create a simple test DAG")
|
|
279
|
+
nodes = [
|
|
280
|
+
Node(name="Open settings", info="Open settings application"),
|
|
281
|
+
Node(name="Navigate to display settings", info="Find and click on display settings option"),
|
|
282
|
+
Node(name="Change resolution", info="Change screen resolution")
|
|
283
|
+
]
|
|
284
|
+
edges = [
|
|
285
|
+
[nodes[0], nodes[1]],
|
|
286
|
+
[nodes[1], nodes[2]]
|
|
287
|
+
]
|
|
288
|
+
dag = Dag(nodes=nodes, edges=edges)
|
|
289
|
+
|
|
290
|
+
dag_info = {"dag": dag_raw}
|
|
291
|
+
return dag_info, dag
|
|
292
|
+
|
|
293
|
+
# Replace original method
|
|
294
|
+
original_generate_dag = self.manager._generate_dag
|
|
295
|
+
self.manager._generate_dag = lambda instruction, plan: mock_generate_dag(self.manager, instruction, plan)
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
# Call get_action_queue method
|
|
299
|
+
print_test_section("Call get_action_queue method")
|
|
300
|
+
logger.info(f"Input parameters: Tu={self.test_instruction}, Screenshot=Image(100x100), Running_state='初始状态'")
|
|
301
|
+
planner_info, action_queue = self.manager.get_action_queue(
|
|
302
|
+
Tu=self.test_instruction,
|
|
303
|
+
Screenshot=self.test_image,
|
|
304
|
+
Running_state="初始状态"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Output results
|
|
308
|
+
print_test_section("Verify results")
|
|
309
|
+
logger.info(f"Output results: planner_info={planner_info}")
|
|
310
|
+
logger.info(f"Output results: action_queue={[action.name for action in action_queue]}")
|
|
311
|
+
|
|
312
|
+
# Verify results
|
|
313
|
+
self.assertIsNotNone(planner_info)
|
|
314
|
+
self.assertIsNotNone(action_queue)
|
|
315
|
+
self.assertIn("search_query", planner_info)
|
|
316
|
+
self.assertIn("goal_plan", planner_info)
|
|
317
|
+
self.assertIn("dag", planner_info)
|
|
318
|
+
self.assertGreater(len(action_queue), 0)
|
|
319
|
+
|
|
320
|
+
# Verify that the elements in action_queue are Node types
|
|
321
|
+
for action in action_queue:
|
|
322
|
+
self.assertIsInstance(action, Node)
|
|
323
|
+
self.assertIsNotNone(action.name)
|
|
324
|
+
self.assertIsNotNone(action.info)
|
|
325
|
+
finally:
|
|
326
|
+
# Restore original method
|
|
327
|
+
self.manager._generate_dag = original_generate_dag
|
|
328
|
+
|
|
329
|
+
if __name__ == '__main__':
|
|
330
|
+
unittest.main()
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
from gui_agents.agents.worker import Worker
|
|
11
|
+
from gui_agents.utils.common_utils import Node
|
|
12
|
+
|
|
13
|
+
# 配置彩色日志
|
|
14
|
+
class ColoredFormatter(logging.Formatter):
|
|
15
|
+
"""Custom colored log formatter"""
|
|
16
|
+
COLORS = {
|
|
17
|
+
'DEBUG': '\033[94m', # Blue
|
|
18
|
+
'INFO': '\033[92m', # Green
|
|
19
|
+
'WARNING': '\033[93m', # Yellow
|
|
20
|
+
'ERROR': '\033[91m', # Red
|
|
21
|
+
'CRITICAL': '\033[91m\033[1m', # Red bold
|
|
22
|
+
'RESET': '\033[0m' # Reset
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def format(self, record):
|
|
26
|
+
log_message = super().format(record)
|
|
27
|
+
return f"{self.COLORS.get(record.levelname, self.COLORS['RESET'])}{log_message}{self.COLORS['RESET']}"
|
|
28
|
+
|
|
29
|
+
# Configure logging - Clear all handlers and reconfigure
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
logger.handlers = [] # Clear all existing handlers
|
|
32
|
+
logger.propagate = False # Prevent logging from propagating to root logger
|
|
33
|
+
|
|
34
|
+
# Add single handler
|
|
35
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
36
|
+
console_handler.setFormatter(ColoredFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
|
37
|
+
logger.addHandler(console_handler)
|
|
38
|
+
logger.setLevel(logging.INFO)
|
|
39
|
+
|
|
40
|
+
# Define colored separator
|
|
41
|
+
def print_test_header(test_name):
|
|
42
|
+
"""Print test title, using colored and prominent separator"""
|
|
43
|
+
separator = "="*80
|
|
44
|
+
logger.info(separator)
|
|
45
|
+
logger.info(test_name.center(80))
|
|
46
|
+
logger.info(separator)
|
|
47
|
+
|
|
48
|
+
def print_test_section(section_name):
|
|
49
|
+
"""Print test section, using colored and prominent separator"""
|
|
50
|
+
separator = "-"*60
|
|
51
|
+
logger.info("\n" + separator)
|
|
52
|
+
logger.info(section_name.center(60))
|
|
53
|
+
logger.info(separator)
|
|
54
|
+
|
|
55
|
+
class TestWorker(unittest.TestCase):
|
|
56
|
+
def setUp(self):
|
|
57
|
+
"""Set up test environment"""
|
|
58
|
+
print_test_header("Start setting up test environment")
|
|
59
|
+
|
|
60
|
+
# Load tools configuration from tools_config.json
|
|
61
|
+
tools_config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "tools", "tools_config.json")
|
|
62
|
+
with open(tools_config_path, "r") as f:
|
|
63
|
+
tools_config = json.load(f)
|
|
64
|
+
self.Tools_dict = {}
|
|
65
|
+
for tool in tools_config["tools"]:
|
|
66
|
+
tool_name = tool["tool_name"]
|
|
67
|
+
self.Tools_dict[tool_name] = {
|
|
68
|
+
"provider": tool["provider"],
|
|
69
|
+
"model": tool["model_name"]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Create test directory structure
|
|
73
|
+
self.test_kb_path = "test_kb"
|
|
74
|
+
self.platform = "darwin"
|
|
75
|
+
self.test_platform_path = os.path.join(self.test_kb_path, self.platform)
|
|
76
|
+
os.makedirs(self.test_platform_path, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
# Create test files
|
|
79
|
+
with open(os.path.join(self.test_platform_path, "episodic_memory.json"), "w") as f:
|
|
80
|
+
json.dump({}, f)
|
|
81
|
+
with open(os.path.join(self.test_platform_path, "embeddings.pkl"), "wb") as f:
|
|
82
|
+
f.write(b"")
|
|
83
|
+
|
|
84
|
+
# Create Worker instance
|
|
85
|
+
self.worker = Worker(
|
|
86
|
+
Tools_dict=self.Tools_dict,
|
|
87
|
+
local_kb_path=self.test_kb_path,
|
|
88
|
+
platform=self.platform,
|
|
89
|
+
enable_reflection=True,
|
|
90
|
+
use_subtask_experience=True
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Create test observation data
|
|
94
|
+
import pyautogui
|
|
95
|
+
self.test_image = pyautogui.screenshot()
|
|
96
|
+
buffered = BytesIO()
|
|
97
|
+
self.test_image.save(buffered, format="PNG")
|
|
98
|
+
self.test_screenshot_bytes = buffered.getvalue()
|
|
99
|
+
|
|
100
|
+
self.test_observation = {
|
|
101
|
+
"screenshot": self.test_screenshot_bytes
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Initialize planner_history, avoid accessing empty list when turn_count > 0
|
|
105
|
+
self.worker.planner_history = ["Test plan history"]
|
|
106
|
+
|
|
107
|
+
# Record log
|
|
108
|
+
logger.info("Test environment setup completed, using real screenshot")
|
|
109
|
+
logger.info(f"Screenshot size: {self.test_image.size}")
|
|
110
|
+
|
|
111
|
+
def tearDown(self):
|
|
112
|
+
"""Clean up test environment"""
|
|
113
|
+
print_test_header("Clean up test environment")
|
|
114
|
+
import shutil
|
|
115
|
+
if os.path.exists(self.test_kb_path):
|
|
116
|
+
shutil.rmtree(self.test_kb_path)
|
|
117
|
+
|
|
118
|
+
def test_reset(self):
|
|
119
|
+
"""Test reset method"""
|
|
120
|
+
print_test_header("Test RESET method")
|
|
121
|
+
|
|
122
|
+
# Set some initial states
|
|
123
|
+
self.worker.turn_count = 5
|
|
124
|
+
self.worker.worker_history = ["History 1", "History 2"]
|
|
125
|
+
self.worker.reflections = ["Reflection 1", "Reflection 2"]
|
|
126
|
+
|
|
127
|
+
# Call reset method
|
|
128
|
+
self.worker.reset()
|
|
129
|
+
|
|
130
|
+
# Verify if the state is reset
|
|
131
|
+
self.assertEqual(self.worker.turn_count, 0)
|
|
132
|
+
self.assertEqual(self.worker.worker_history, [])
|
|
133
|
+
self.assertEqual(self.worker.reflections, [])
|
|
134
|
+
|
|
135
|
+
# Verify if a new agent instance is created
|
|
136
|
+
self.assertIsNotNone(self.worker.generator_agent)
|
|
137
|
+
self.assertIsNotNone(self.worker.reflection_agent)
|
|
138
|
+
self.assertIsNotNone(self.worker.knowledge_base)
|
|
139
|
+
|
|
140
|
+
def test_generate_next_action_first_turn(self):
|
|
141
|
+
"""Test generate_next_action method for the first turn (turn_count=0)"""
|
|
142
|
+
print_test_header("Test GENERATE_NEXT_ACTION for the first turn")
|
|
143
|
+
|
|
144
|
+
# Prepare test data
|
|
145
|
+
instruction = "Open settings and change display resolution"
|
|
146
|
+
search_query = "How to open settings and change display resolution"
|
|
147
|
+
subtask = "Open settings"
|
|
148
|
+
subtask_info = "Open settings application"
|
|
149
|
+
future_tasks = [
|
|
150
|
+
Node(name="Navigate to display settings", info="Find and click on display settings option"),
|
|
151
|
+
Node(name="Change resolution", info="Change screen resolution")
|
|
152
|
+
]
|
|
153
|
+
done_tasks = []
|
|
154
|
+
|
|
155
|
+
self.worker.turn_count = 0
|
|
156
|
+
|
|
157
|
+
# Call generate_next_action method
|
|
158
|
+
executor_info = self.worker.generate_next_action(
|
|
159
|
+
instruction=instruction,
|
|
160
|
+
search_query=search_query,
|
|
161
|
+
subtask=subtask,
|
|
162
|
+
subtask_info=subtask_info,
|
|
163
|
+
future_tasks=future_tasks,
|
|
164
|
+
done_task=done_tasks,
|
|
165
|
+
obs=self.test_observation
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Print results for debugging
|
|
169
|
+
logger.info(f"Executor information: {executor_info}")
|
|
170
|
+
|
|
171
|
+
# Verify results
|
|
172
|
+
self.assertIn("executor_plan", executor_info)
|
|
173
|
+
# No longer assert specific operations, because the output may vary using real models
|
|
174
|
+
self.assertIsInstance(executor_info["executor_plan"], str)
|
|
175
|
+
self.assertGreater(len(executor_info["executor_plan"]), 0)
|
|
176
|
+
|
|
177
|
+
# Verify turn_count increased
|
|
178
|
+
self.assertEqual(self.worker.turn_count, 1)
|
|
179
|
+
|
|
180
|
+
def test_generate_next_action_second_turn(self):
|
|
181
|
+
"""Test generate_next_action method for the second turn (turn_count>0)"""
|
|
182
|
+
print_test_header("Test GENERATE_NEXT_ACTION for the second turn")
|
|
183
|
+
|
|
184
|
+
# Prepare test data
|
|
185
|
+
instruction = "Open settings and change display resolution"
|
|
186
|
+
search_query = "How to open settings and change display resolution"
|
|
187
|
+
subtask = "Open settings"
|
|
188
|
+
subtask_info = "Open settings application"
|
|
189
|
+
future_tasks = [
|
|
190
|
+
Node(name="Navigate to display settings", info="Find and click on display settings option"),
|
|
191
|
+
Node(name="Change resolution", info="Change screen resolution")
|
|
192
|
+
]
|
|
193
|
+
done_tasks = []
|
|
194
|
+
|
|
195
|
+
# Set to the second turn
|
|
196
|
+
self.worker.turn_count = 1
|
|
197
|
+
|
|
198
|
+
# Ensure planner_history has content
|
|
199
|
+
if len(self.worker.planner_history) == 0:
|
|
200
|
+
self.worker.planner_history = ["Test plan history"]
|
|
201
|
+
|
|
202
|
+
# Call generate_next_action method
|
|
203
|
+
executor_info = self.worker.generate_next_action(
|
|
204
|
+
instruction=instruction,
|
|
205
|
+
search_query=search_query,
|
|
206
|
+
subtask=subtask,
|
|
207
|
+
subtask_info=subtask_info,
|
|
208
|
+
future_tasks=future_tasks,
|
|
209
|
+
done_task=done_tasks,
|
|
210
|
+
obs=self.test_observation
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Print results for debugging
|
|
214
|
+
logger.info(f"Executor information (second turn): {executor_info}")
|
|
215
|
+
|
|
216
|
+
# Verify results
|
|
217
|
+
self.assertIn("executor_plan", executor_info)
|
|
218
|
+
self.assertIsInstance(executor_info["executor_plan"], str)
|
|
219
|
+
self.assertGreater(len(executor_info["executor_plan"]), 0)
|
|
220
|
+
|
|
221
|
+
# Verify turn_count increased
|
|
222
|
+
self.assertEqual(self.worker.turn_count, 2)
|
|
223
|
+
|
|
224
|
+
def test_clean_worker_generation_for_reflection(self):
|
|
225
|
+
"""Test clean_worker_generation_for_reflection method"""
|
|
226
|
+
print_test_header("Test CLEAN_WORKER_GENERATION_FOR_REFLECTION method")
|
|
227
|
+
|
|
228
|
+
# Prepare test data
|
|
229
|
+
worker_generation = """(Previous Action Verification)
|
|
230
|
+
The previous action has been successfully executed.
|
|
231
|
+
|
|
232
|
+
(Screenshot Analysis)
|
|
233
|
+
I see the settings application is open, with multiple options.
|
|
234
|
+
|
|
235
|
+
(Reasoning)
|
|
236
|
+
I need to find and click on the display settings option.
|
|
237
|
+
|
|
238
|
+
(Grounded Action)
|
|
239
|
+
```python
|
|
240
|
+
agent.click("Display settings")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
(Additional Grounded Action)
|
|
244
|
+
```python
|
|
245
|
+
agent.wait(1.0)
|
|
246
|
+
```
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
# Call clean_worker_generation_for_reflection method
|
|
250
|
+
cleaned_text = self.worker.clean_worker_generation_for_reflection(worker_generation)
|
|
251
|
+
|
|
252
|
+
# Print results for debugging
|
|
253
|
+
logger.info(f"Text before cleaning: \n{worker_generation}")
|
|
254
|
+
logger.info(f"Text after cleaning: \n{cleaned_text}")
|
|
255
|
+
|
|
256
|
+
# Verify results
|
|
257
|
+
self.assertIn("(Screenshot Analysis)", cleaned_text)
|
|
258
|
+
self.assertIn("agent.click(\"Display settings\")", cleaned_text)
|
|
259
|
+
self.assertNotIn("(Previous Action Verification)", cleaned_text)
|
|
260
|
+
# Note: Depending on the actual implementation of clean_worker_generation_for_reflection, the following assertions may need to be adjusted
|
|
261
|
+
# If the method implementation has changed, these assertions may need to be modified
|
|
262
|
+
try:
|
|
263
|
+
self.assertNotIn("(Additional Grounded Action)", cleaned_text)
|
|
264
|
+
self.assertNotIn("agent.wait(1.0)", cleaned_text)
|
|
265
|
+
except AssertionError as e:
|
|
266
|
+
logger.warning(f"Assertion failed, but this may be because the implementation of clean_worker_generation_for_reflection has changed: {e}")
|
|
267
|
+
|
|
268
|
+
if __name__ == '__main__':
|
|
269
|
+
unittest.main()
|
|
File without changes
|