droidrun 0.2.0__py3-none-any.whl → 0.3.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.
- droidrun/__init__.py +16 -11
- droidrun/__main__.py +1 -1
- droidrun/adb/__init__.py +3 -3
- droidrun/adb/device.py +1 -1
- droidrun/adb/manager.py +2 -2
- droidrun/agent/__init__.py +6 -0
- droidrun/agent/codeact/__init__.py +2 -4
- droidrun/agent/codeact/codeact_agent.py +321 -235
- droidrun/agent/codeact/events.py +12 -20
- droidrun/agent/codeact/prompts.py +0 -52
- droidrun/agent/common/default.py +5 -0
- droidrun/agent/common/events.py +4 -0
- droidrun/agent/context/__init__.py +23 -0
- droidrun/agent/context/agent_persona.py +15 -0
- droidrun/agent/context/context_injection_manager.py +66 -0
- droidrun/agent/context/episodic_memory.py +15 -0
- droidrun/agent/context/personas/__init__.py +11 -0
- droidrun/agent/context/personas/app_starter.py +44 -0
- droidrun/agent/context/personas/default.py +95 -0
- droidrun/agent/context/personas/extractor.py +52 -0
- droidrun/agent/context/personas/ui_expert.py +107 -0
- droidrun/agent/context/reflection.py +20 -0
- droidrun/agent/context/task_manager.py +124 -0
- droidrun/agent/context/todo.txt +4 -0
- droidrun/agent/droid/__init__.py +2 -2
- droidrun/agent/droid/droid_agent.py +264 -325
- droidrun/agent/droid/events.py +28 -0
- droidrun/agent/oneflows/reflector.py +265 -0
- droidrun/agent/planner/__init__.py +2 -4
- droidrun/agent/planner/events.py +9 -13
- droidrun/agent/planner/planner_agent.py +268 -0
- droidrun/agent/planner/prompts.py +33 -53
- droidrun/agent/utils/__init__.py +3 -0
- droidrun/agent/utils/async_utils.py +1 -40
- droidrun/agent/utils/chat_utils.py +268 -48
- droidrun/agent/utils/executer.py +49 -14
- droidrun/agent/utils/llm_picker.py +14 -10
- droidrun/agent/utils/trajectory.py +184 -0
- droidrun/cli/__init__.py +1 -1
- droidrun/cli/logs.py +283 -0
- droidrun/cli/main.py +333 -439
- droidrun/run.py +105 -0
- droidrun/tools/__init__.py +5 -10
- droidrun/tools/{actions.py → adb.py} +279 -238
- droidrun/tools/ios.py +594 -0
- droidrun/tools/tools.py +99 -0
- droidrun-0.3.0.dist-info/METADATA +149 -0
- droidrun-0.3.0.dist-info/RECORD +52 -0
- droidrun/agent/planner/task_manager.py +0 -355
- droidrun/agent/planner/workflow.py +0 -371
- droidrun/tools/device.py +0 -29
- droidrun/tools/loader.py +0 -60
- droidrun-0.2.0.dist-info/METADATA +0 -373
- droidrun-0.2.0.dist-info/RECORD +0 -32
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/WHEEL +0 -0
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/entry_points.txt +0 -0
- {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,40 +3,70 @@ DroidAgent - A wrapper class that coordinates the planning and execution of task
|
|
3
3
|
to achieve a user's goal on an Android device.
|
4
4
|
"""
|
5
5
|
|
6
|
-
import asyncio
|
7
6
|
import logging
|
8
|
-
from typing import
|
7
|
+
from typing import List
|
9
8
|
|
10
|
-
from llama_index.core.base.llms.types import ChatMessage
|
11
9
|
from llama_index.core.llms.llm import LLM
|
12
|
-
from llama_index.core.
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
10
|
+
from llama_index.core.workflow import step, StartEvent, StopEvent, Workflow, Context
|
11
|
+
from droidrun.agent.droid.events import *
|
12
|
+
from droidrun.agent.codeact import CodeActAgent
|
13
|
+
from droidrun.agent.codeact.events import EpisodicMemoryEvent
|
14
|
+
from droidrun.agent.planner import PlannerAgent
|
15
|
+
from droidrun.agent.context.task_manager import TaskManager
|
16
|
+
from droidrun.agent.utils.trajectory import Trajectory
|
17
|
+
from droidrun.tools import Tools, describe_tools
|
18
|
+
from droidrun.agent.common.events import ScreenshotEvent
|
19
|
+
from droidrun.agent.common.default import MockWorkflow
|
20
|
+
from droidrun.agent.context import ContextInjectionManager
|
21
|
+
from droidrun.agent.context.agent_persona import AgentPersona
|
22
|
+
from droidrun.agent.context.personas import DEFAULT
|
23
|
+
from droidrun.agent.oneflows.reflector import Reflector
|
24
|
+
|
17
25
|
|
18
26
|
logger = logging.getLogger("droidrun")
|
19
27
|
|
20
|
-
class DroidAgent:
|
28
|
+
class DroidAgent(Workflow):
|
21
29
|
"""
|
22
|
-
|
30
|
+
A wrapper class that coordinates between PlannerAgent (creates plans) and
|
23
31
|
CodeActAgent (executes tasks) to achieve a user's goal.
|
24
32
|
"""
|
25
33
|
|
34
|
+
@staticmethod
|
35
|
+
def _configure_default_logging(debug: bool = False):
|
36
|
+
"""
|
37
|
+
Configure default logging for DroidAgent if no handlers are present.
|
38
|
+
This ensures logs are visible when using DroidAgent directly.
|
39
|
+
"""
|
40
|
+
# Only configure if no handlers exist (avoid duplicate configuration)
|
41
|
+
if not logger.handlers:
|
42
|
+
# Create a console handler
|
43
|
+
handler = logging.StreamHandler()
|
44
|
+
|
45
|
+
# Set format
|
46
|
+
if debug:
|
47
|
+
formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")
|
48
|
+
else:
|
49
|
+
formatter = logging.Formatter("%(message)s")
|
50
|
+
|
51
|
+
handler.setFormatter(formatter)
|
52
|
+
logger.addHandler(handler)
|
53
|
+
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
54
|
+
logger.propagate = False
|
55
|
+
|
26
56
|
def __init__(
|
27
57
|
self,
|
28
58
|
goal: str,
|
29
59
|
llm: LLM,
|
30
|
-
|
31
|
-
|
60
|
+
tools: Tools,
|
61
|
+
personas: List[AgentPersona] = [DEFAULT],
|
32
62
|
max_steps: int = 15,
|
33
|
-
vision: bool = False,
|
34
63
|
timeout: int = 1000,
|
35
|
-
|
36
|
-
|
64
|
+
reasoning: bool = False,
|
65
|
+
reflection: bool = False,
|
37
66
|
enable_tracing: bool = False,
|
38
67
|
debug: bool = False,
|
39
|
-
|
68
|
+
save_trajectories: bool = False,
|
69
|
+
*args,
|
40
70
|
**kwargs
|
41
71
|
):
|
42
72
|
"""
|
@@ -45,184 +75,83 @@ class DroidAgent:
|
|
45
75
|
Args:
|
46
76
|
goal: The user's goal or command to execute
|
47
77
|
llm: The language model to use for both agents
|
48
|
-
tools_instance: An instance of the Tools class
|
49
|
-
tool_list: Dictionary of available tools
|
50
78
|
max_steps: Maximum number of steps for both agents
|
51
|
-
vision: Whether to enable vision capabilities
|
52
79
|
timeout: Timeout for agent execution in seconds
|
53
|
-
max_retries: Maximum number of retries for failed tasks
|
54
80
|
reasoning: Whether to use the PlannerAgent for complex reasoning (True)
|
55
81
|
or send tasks directly to CodeActAgent (False)
|
82
|
+
reflection: Whether to reflect on steps the CodeActAgent did to give the PlannerAgent advice
|
56
83
|
enable_tracing: Whether to enable Arize Phoenix tracing
|
57
84
|
debug: Whether to enable verbose debug logging
|
58
|
-
device_serial: Target Android device serial number
|
59
85
|
**kwargs: Additional keyword arguments to pass to the agents
|
60
86
|
"""
|
87
|
+
super().__init__(timeout=timeout ,*args,**kwargs)
|
88
|
+
|
89
|
+
# Configure default logging if not already configured
|
90
|
+
self._configure_default_logging(debug=debug)
|
91
|
+
|
92
|
+
# Setup global tracing first if enabled
|
93
|
+
if enable_tracing:
|
94
|
+
try:
|
95
|
+
from llama_index.core import set_global_handler
|
96
|
+
set_global_handler("arize_phoenix")
|
97
|
+
logger.info("🔍 Arize Phoenix tracing enabled globally")
|
98
|
+
except ImportError:
|
99
|
+
logger.warning("⚠️ Arize Phoenix package not found, tracing disabled")
|
100
|
+
enable_tracing = False
|
101
|
+
|
61
102
|
self.goal = goal
|
62
103
|
self.llm = llm
|
63
104
|
self.max_steps = max_steps
|
64
|
-
self.
|
105
|
+
self.max_codeact_steps = max_steps
|
65
106
|
self.timeout = timeout
|
66
|
-
self.max_retries = max_retries
|
67
|
-
self.task_manager = TaskManager()
|
68
107
|
self.reasoning = reasoning
|
108
|
+
self.reflection = reflection
|
69
109
|
self.debug = debug
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
self.tools_instance = tools_instance
|
75
|
-
self.tool_list = tool_list
|
76
|
-
|
77
|
-
# Ensure remember tool is in the tool_list if available
|
78
|
-
if hasattr(tools_instance, 'remember') and 'remember' not in tool_list:
|
79
|
-
logger.debug("📝 Adding 'remember' tool to the available tools")
|
80
|
-
tool_list['remember'] = tools_instance.remember
|
81
|
-
|
82
|
-
# Create code executor
|
83
|
-
logger.debug("🔧 Initializing Code Executor...")
|
84
|
-
loop = asyncio.get_event_loop()
|
85
|
-
self.executor = SimpleCodeExecutor(
|
86
|
-
loop=loop,
|
87
|
-
locals={},
|
88
|
-
tools=tool_list,
|
89
|
-
globals={"__builtins__": __builtins__}
|
90
|
-
)
|
91
|
-
|
92
|
-
# Create memory buffer for the planning agent if reasoning is enabled
|
93
|
-
if self.reasoning:
|
94
|
-
self.planning_memory = ChatMemoryBuffer.from_defaults(llm=llm)
|
110
|
+
|
111
|
+
self.event_counter = 0
|
112
|
+
self.save_trajectories = save_trajectories
|
95
113
|
|
96
|
-
|
97
|
-
|
98
|
-
self.
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
max_steps=999999,
|
104
|
-
vision=vision,
|
105
|
-
debug=debug,
|
106
|
-
timeout=timeout
|
107
|
-
)
|
114
|
+
self.trajectory = Trajectory()
|
115
|
+
self.task_manager = TaskManager()
|
116
|
+
self.task_iter = None
|
117
|
+
self.cim = ContextInjectionManager(personas=personas)
|
118
|
+
self.current_episodic_memory = None
|
119
|
+
|
120
|
+
logger.info("🤖 Initializing DroidAgent...")
|
108
121
|
|
122
|
+
self.tool_list = describe_tools(tools)
|
123
|
+
self.tools_instance = tools
|
124
|
+
|
125
|
+
|
109
126
|
if self.reasoning:
|
110
127
|
logger.info("📝 Initializing Planner Agent...")
|
111
128
|
self.planner_agent = PlannerAgent(
|
112
129
|
goal=goal,
|
113
130
|
llm=llm,
|
114
|
-
|
115
|
-
|
131
|
+
personas=personas,
|
132
|
+
task_manager=self.task_manager,
|
133
|
+
tools_instance=tools,
|
116
134
|
timeout=timeout,
|
117
|
-
max_retries=max_retries,
|
118
|
-
enable_tracing=enable_tracing,
|
119
135
|
debug=debug
|
120
136
|
)
|
137
|
+
self.add_workflows(planner_agent=self.planner_agent)
|
138
|
+
self.max_codeact_steps = 5
|
139
|
+
|
140
|
+
if self.reflection:
|
141
|
+
self.reflector = Reflector(llm=llm, debug=debug)
|
121
142
|
|
122
|
-
# Give task manager to the planner
|
123
|
-
self.planner_agent.task_manager = self.task_manager
|
124
143
|
else:
|
125
144
|
logger.debug("🚫 Planning disabled - will execute tasks directly with CodeActAgent")
|
126
145
|
self.planner_agent = None
|
127
146
|
|
128
147
|
logger.info("✅ DroidAgent initialized successfully.")
|
129
148
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
"""
|
137
|
-
logger.info("📋 Planning steps to accomplish the goal...")
|
138
|
-
|
139
|
-
# Create system and user messages
|
140
|
-
system_msg = ChatMessage(role="system", content=self.planner_agent.system_prompt)
|
141
|
-
user_msg = ChatMessage(role="user", content=self.planner_agent.user_prompt)
|
142
|
-
|
143
|
-
# Check if we have task history to add to the prompt
|
144
|
-
task_history = ""
|
145
|
-
# Use the persistent task history methods to get ALL completed and failed tasks
|
146
|
-
completed_tasks = self.task_manager.get_all_completed_tasks()
|
147
|
-
failed_tasks = self.task_manager.get_all_failed_tasks()
|
148
|
-
|
149
|
-
# Show any remembered information in task history
|
150
|
-
remembered_info = ""
|
151
|
-
if hasattr(self.tools_instance, 'memory') and self.tools_instance.memory:
|
152
|
-
remembered_info = "\n### Remembered Information:\n"
|
153
|
-
for idx, item in enumerate(self.tools_instance.memory, 1):
|
154
|
-
remembered_info += f"{idx}. {item}\n"
|
155
|
-
|
156
|
-
if completed_tasks or failed_tasks or remembered_info:
|
157
|
-
task_history = "### Task Execution History:\n"
|
158
|
-
|
159
|
-
if completed_tasks:
|
160
|
-
task_history += "✅ Completed Tasks:\n"
|
161
|
-
for task in completed_tasks:
|
162
|
-
task_history += f"- {task['description']}\n"
|
163
|
-
|
164
|
-
if failed_tasks:
|
165
|
-
task_history += "\n❌ Failed Tasks:\n"
|
166
|
-
for task in failed_tasks:
|
167
|
-
failure_reason = task.get('failure_reason', 'Unknown reason')
|
168
|
-
task_history += f"- {task['description']} (Failed: {failure_reason})\n"
|
169
|
-
|
170
|
-
if remembered_info:
|
171
|
-
task_history += remembered_info
|
172
|
-
|
173
|
-
# Add a reminder to use this information
|
174
|
-
task_history += "\n⚠️ Please use the above information in your planning. For example, if specific dates or locations were found, include them explicitly in your next tasks instead of just referring to 'the dates' or 'the location'.\n"
|
175
|
-
|
176
|
-
# Append task history to user prompt
|
177
|
-
user_msg = ChatMessage(
|
178
|
-
role="user",
|
179
|
-
content=f"{self.planner_agent.user_prompt}\n\n{task_history}\n\nPlease consider the above task history and discovered information when creating your next plan. Incorporate specific data (dates, locations, etc.) directly into tasks rather than referring to them generally. Remember that previously completed or failed tasks will not be repeated."
|
180
|
-
)
|
181
|
-
|
182
|
-
# Create message list
|
183
|
-
messages = [system_msg, user_msg]
|
184
|
-
if self.debug:
|
185
|
-
logger.debug(f"Sending {len(messages)} messages to planner: {[msg.role for msg in messages]}")
|
186
|
-
|
187
|
-
# Get response from LLM
|
188
|
-
llm_response = await self.planner_agent._get_llm_response(messages)
|
189
|
-
code, thoughts = self.planner_agent._extract_code_and_thought(llm_response.message.content)
|
190
|
-
|
191
|
-
# Execute the planning code (which should call set_tasks)
|
192
|
-
if code:
|
193
|
-
try:
|
194
|
-
planning_tools = {
|
195
|
-
"set_tasks": self.task_manager.set_tasks,
|
196
|
-
"add_task": self.task_manager.add_task,
|
197
|
-
"get_all_tasks": self.task_manager.get_all_tasks,
|
198
|
-
"clear_tasks": self.task_manager.clear_tasks,
|
199
|
-
"complete_goal": self.task_manager.complete_goal
|
200
|
-
}
|
201
|
-
planning_executor = SimpleCodeExecutor(
|
202
|
-
loop=asyncio.get_event_loop(),
|
203
|
-
globals={},
|
204
|
-
locals={},
|
205
|
-
tools=planning_tools
|
206
|
-
)
|
207
|
-
await planning_executor.execute(code)
|
208
|
-
except Exception as e:
|
209
|
-
logger.error(f"Error executing planning code: {e}")
|
210
|
-
# If there's an error, create a simple default task
|
211
|
-
self.task_manager.set_tasks([f"Achieve the goal: {self.goal}"])
|
212
|
-
|
213
|
-
# Get and display the tasks
|
214
|
-
tasks = self.task_manager.get_all_tasks()
|
215
|
-
if tasks:
|
216
|
-
logger.info("📝 Plan created:")
|
217
|
-
for i, task in enumerate(tasks, 1):
|
218
|
-
if task["status"] == self.task_manager.STATUS_PENDING:
|
219
|
-
logger.info(f" {i}. {task['description']}")
|
220
|
-
else:
|
221
|
-
logger.warning("No tasks were generated in the plan")
|
222
|
-
|
223
|
-
return tasks
|
224
|
-
|
225
|
-
async def _execute_task_with_codeact(self, task: Dict) -> Tuple[bool, str]:
|
149
|
+
@step
|
150
|
+
async def execute_task(
|
151
|
+
self,
|
152
|
+
ctx: Context,
|
153
|
+
ev: CodeActExecuteEvent
|
154
|
+
) -> CodeActResultEvent:
|
226
155
|
"""
|
227
156
|
Execute a single task using the CodeActAgent.
|
228
157
|
|
@@ -232,59 +161,144 @@ class DroidAgent:
|
|
232
161
|
Returns:
|
233
162
|
Tuple of (success, reason)
|
234
163
|
"""
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
# Run the CodeActAgent
|
164
|
+
task: Task = ev.task
|
165
|
+
reflection = ev.reflection if ev.reflection is not None else None
|
166
|
+
persona = self.cim.get_persona(task.agent_type)
|
167
|
+
|
168
|
+
logger.info(f"🔧 Executing task: {task.description}")
|
169
|
+
|
242
170
|
try:
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
171
|
+
codeact_agent = CodeActAgent(
|
172
|
+
llm=self.llm,
|
173
|
+
persona=persona,
|
174
|
+
max_steps=self.max_codeact_steps,
|
175
|
+
all_tools_list=self.tool_list,
|
176
|
+
tools_instance=self.tools_instance,
|
177
|
+
debug=self.debug,
|
178
|
+
timeout=self.timeout,
|
179
|
+
)
|
180
|
+
|
181
|
+
handler = codeact_agent.run(
|
182
|
+
input=task.description,
|
183
|
+
remembered_info=self.tools_instance.memory,
|
184
|
+
reflection=reflection
|
185
|
+
)
|
251
186
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
logger.debug(f"Task completed successfully: {self.tools_instance.reason}")
|
258
|
-
return True, self.tools_instance.reason or "Task completed successfully"
|
259
|
-
else:
|
260
|
-
task["status"] = self.task_manager.STATUS_FAILED
|
261
|
-
task["failure_reason"] = self.tools_instance.reason or "Task failed without specific reason"
|
262
|
-
logger.warning(f"Task failed: {task['failure_reason']}")
|
263
|
-
return False, self.tools_instance.reason or "Task failed without specific reason"
|
187
|
+
async for nested_ev in handler.stream_events():
|
188
|
+
self.handle_stream_event(nested_ev, ctx)
|
189
|
+
|
190
|
+
result = await handler
|
191
|
+
|
264
192
|
|
265
|
-
|
266
|
-
|
267
|
-
task["status"] = self.task_manager.STATUS_COMPLETED
|
268
|
-
if self.debug:
|
269
|
-
logger.debug(f"Task completed with result: {result}")
|
270
|
-
return True, result.get("reason", "Task completed successfully")
|
193
|
+
if "success" in result and result["success"]:
|
194
|
+
return CodeActResultEvent(success=True, reason=result["reason"], task=task, steps=result["codeact_steps"])
|
271
195
|
else:
|
272
|
-
|
273
|
-
task["status"] = self.task_manager.STATUS_FAILED
|
274
|
-
task["failure_reason"] = failure_reason
|
275
|
-
logger.warning(f"Task failed: {failure_reason}")
|
276
|
-
return False, failure_reason
|
196
|
+
return CodeActResultEvent(success=False, reason=result["reason"], task=task, steps=result["codeact_steps"])
|
277
197
|
|
278
198
|
except Exception as e:
|
279
199
|
logger.error(f"Error during task execution: {e}")
|
280
200
|
if self.debug:
|
281
201
|
import traceback
|
282
202
|
logger.error(traceback.format_exc())
|
283
|
-
task["
|
284
|
-
|
285
|
-
|
203
|
+
return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task, steps=result["codeact_steps"])
|
204
|
+
|
205
|
+
@step
|
206
|
+
async def handle_codeact_execute(self, ctx: Context, ev: CodeActResultEvent) -> FinalizeEvent | ReflectionEvent:
|
207
|
+
try:
|
208
|
+
task = ev.task
|
209
|
+
if not self.reasoning:
|
210
|
+
return FinalizeEvent(success=ev.success, reason=ev.reason, task=[task], steps=ev.steps)
|
211
|
+
|
212
|
+
if self.reflection:
|
213
|
+
return ReflectionEvent(task=task)
|
214
|
+
|
215
|
+
return ReasoningLogicEvent()
|
286
216
|
|
287
|
-
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(f"❌ Error during DroidAgent execution: {e}")
|
219
|
+
if self.debug:
|
220
|
+
import traceback
|
221
|
+
logger.error(traceback.format_exc())
|
222
|
+
return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
|
223
|
+
|
224
|
+
|
225
|
+
@step
|
226
|
+
async def reflect(
|
227
|
+
self,
|
228
|
+
ctx: Context,
|
229
|
+
ev: ReflectionEvent
|
230
|
+
) -> ReasoningLogicEvent | CodeActExecuteEvent:
|
231
|
+
|
232
|
+
|
233
|
+
task = ev.task
|
234
|
+
if ev.task.agent_type == "AppStarterExpert":
|
235
|
+
self.task_manager.complete_task(task)
|
236
|
+
return ReasoningLogicEvent()
|
237
|
+
|
238
|
+
reflection = await self.reflector.reflect_on_episodic_memory(episodic_memory=self.current_episodic_memory, goal=task.description)
|
239
|
+
|
240
|
+
if reflection.goal_achieved:
|
241
|
+
self.task_manager.complete_task(task)
|
242
|
+
return ReasoningLogicEvent()
|
243
|
+
|
244
|
+
else:
|
245
|
+
self.task_manager.fail_task(task)
|
246
|
+
return ReasoningLogicEvent(reflection=reflection)
|
247
|
+
|
248
|
+
|
249
|
+
@step
|
250
|
+
async def handle_reasoning_logic(
|
251
|
+
self,
|
252
|
+
ctx: Context,
|
253
|
+
ev: ReasoningLogicEvent,
|
254
|
+
planner_agent: Workflow = MockWorkflow()
|
255
|
+
) -> FinalizeEvent | CodeActExecuteEvent:
|
256
|
+
try:
|
257
|
+
if self.step_counter >= self.max_steps:
|
258
|
+
return FinalizeEvent(success=False, reason=f"Reached maximum number of steps ({self.max_steps})", task=self.task_manager.get_task_history(), steps=self.step_counter)
|
259
|
+
self.step_counter += 1
|
260
|
+
|
261
|
+
if ev.reflection:
|
262
|
+
handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=ev.reflection)
|
263
|
+
else:
|
264
|
+
if self.task_iter:
|
265
|
+
try:
|
266
|
+
task = next(self.task_iter)
|
267
|
+
return CodeActExecuteEvent(task=task, reflection=None)
|
268
|
+
except StopIteration as e:
|
269
|
+
logger.info("Planning next steps...")
|
270
|
+
|
271
|
+
logger.debug(f"Planning step {self.step_counter}/{self.max_steps}")
|
272
|
+
|
273
|
+
handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=None)
|
274
|
+
|
275
|
+
async for nested_ev in handler.stream_events():
|
276
|
+
self.handle_stream_event(nested_ev, ctx)
|
277
|
+
|
278
|
+
result = await handler
|
279
|
+
|
280
|
+
self.tasks = self.task_manager.get_all_tasks()
|
281
|
+
self.task_iter = iter(self.tasks)
|
282
|
+
|
283
|
+
if self.task_manager.goal_completed:
|
284
|
+
logger.info(f"✅ Goal completed: {self.task_manager.message}")
|
285
|
+
return FinalizeEvent(success=True, reason=self.task_manager.message, task=self.task_manager.get_task_history(), steps=self.step_counter)
|
286
|
+
if not self.tasks:
|
287
|
+
logger.warning("No tasks generated by planner")
|
288
|
+
return FinalizeEvent(success=False, reason="Planner did not generate any tasks", task=self.task_manager.get_task_history(), steps=self.step_counter)
|
289
|
+
|
290
|
+
return CodeActExecuteEvent(task=next(self.task_iter), reflection=None)
|
291
|
+
|
292
|
+
except Exception as e:
|
293
|
+
logger.error(f"❌ Error during DroidAgent execution: {e}")
|
294
|
+
if self.debug:
|
295
|
+
import traceback
|
296
|
+
logger.error(traceback.format_exc())
|
297
|
+
return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
|
298
|
+
|
299
|
+
|
300
|
+
@step
|
301
|
+
async def start_handler(self, ctx: Context, ev: StartEvent) -> CodeActExecuteEvent | ReasoningLogicEvent:
|
288
302
|
"""
|
289
303
|
Main execution loop that coordinates between planning and execution.
|
290
304
|
|
@@ -293,126 +307,51 @@ class DroidAgent:
|
|
293
307
|
"""
|
294
308
|
logger.info(f"🚀 Running DroidAgent to achieve goal: {self.goal}")
|
295
309
|
|
296
|
-
step_counter = 0
|
297
|
-
retry_counter = 0
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
task = {
|
307
|
-
"description": self.goal,
|
308
|
-
"status": self.task_manager.STATUS_PENDING
|
309
|
-
}
|
310
|
-
|
311
|
-
# Execute the task directly with CodeActAgent
|
312
|
-
success, reason = await self._execute_task_with_codeact(task)
|
313
|
-
|
314
|
-
return {
|
315
|
-
"success": success,
|
316
|
-
"reason": reason,
|
317
|
-
"steps": 1,
|
318
|
-
"task_history": [task] # Single task history
|
319
|
-
}
|
310
|
+
self.step_counter = 0
|
311
|
+
self.retry_counter = 0
|
312
|
+
|
313
|
+
if not self.reasoning:
|
314
|
+
logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
|
315
|
+
task = Task(
|
316
|
+
description=self.goal,
|
317
|
+
status=self.task_manager.STATUS_PENDING,
|
318
|
+
agent_type="Default"
|
319
|
+
)
|
320
320
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
if self.debug:
|
325
|
-
logger.debug(f"Planning step {step_counter}/{self.max_steps}")
|
326
|
-
|
327
|
-
# 1. Get a plan from the planner
|
328
|
-
tasks = await self._get_plan_from_planner()
|
329
|
-
|
330
|
-
if self.task_manager.task_completed:
|
331
|
-
# Task is marked as complete by the planner
|
332
|
-
logger.info(f"✅ Goal completed: {self.task_manager.message}")
|
333
|
-
overall_success = True
|
334
|
-
final_message = self.task_manager.message
|
335
|
-
break
|
336
|
-
|
337
|
-
if not tasks:
|
338
|
-
logger.warning("No tasks generated by planner")
|
339
|
-
final_message = "Planner did not generate any tasks"
|
340
|
-
break
|
341
|
-
|
342
|
-
# 2. Execute each task in the plan sequentially
|
343
|
-
for task in tasks:
|
344
|
-
if task["status"] == self.task_manager.STATUS_PENDING:
|
345
|
-
# Reset the CodeActAgent's step counter for this task
|
346
|
-
self.codeact_agent.steps_counter = 0
|
347
|
-
|
348
|
-
# Execute the task
|
349
|
-
success, reason = await self._execute_task_with_codeact(task)
|
350
|
-
|
351
|
-
# Update task info with detailed result for the planner
|
352
|
-
task_idx = tasks.index(task)
|
353
|
-
result_info = {
|
354
|
-
"execution_details": reason,
|
355
|
-
"step_executed": step_counter,
|
356
|
-
"codeact_steps": self.codeact_agent.steps_counter
|
357
|
-
}
|
358
|
-
|
359
|
-
# Only update if not already updated in _execute_task_with_codeact
|
360
|
-
if success:
|
361
|
-
self.task_manager.update_status(
|
362
|
-
task_idx,
|
363
|
-
self.task_manager.STATUS_COMPLETED,
|
364
|
-
result_info
|
365
|
-
)
|
366
|
-
logger.info(f"✅ Task completed: {task['description']}")
|
367
|
-
|
368
|
-
if not success:
|
369
|
-
# Store detailed failure information if not already set
|
370
|
-
if "failure_reason" not in task:
|
371
|
-
self.task_manager.update_status(
|
372
|
-
task_idx,
|
373
|
-
self.task_manager.STATUS_FAILED,
|
374
|
-
{"failure_reason": reason, **result_info}
|
375
|
-
)
|
376
|
-
|
377
|
-
# Handle retries
|
378
|
-
if retry_counter < self.max_retries:
|
379
|
-
retry_counter += 1
|
380
|
-
logger.info(f"Retrying... ({retry_counter}/{self.max_retries})")
|
381
|
-
# Next iteration will generate a new plan based on current state
|
382
|
-
break
|
383
|
-
else:
|
384
|
-
logger.error(f"Max retries exceeded for task")
|
385
|
-
final_message = f"Failed after {self.max_retries} retries. Reason: {reason}"
|
386
|
-
return {"success": False, "reason": final_message}
|
387
|
-
|
388
|
-
# Reset retry counter for new task sequence
|
389
|
-
retry_counter = 0
|
321
|
+
return CodeActExecuteEvent(task=task, reflection=None)
|
322
|
+
|
323
|
+
return ReasoningLogicEvent()
|
390
324
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
325
|
+
|
326
|
+
@step
|
327
|
+
async def finalize(self, ctx: Context, ev: FinalizeEvent) -> StopEvent:
|
328
|
+
ctx.write_event_to_stream(ev)
|
329
|
+
|
330
|
+
result = {
|
331
|
+
"success": ev.success,
|
332
|
+
"reason": ev.reason,
|
333
|
+
"steps": ev.steps,
|
334
|
+
}
|
335
|
+
|
336
|
+
if self.trajectory and self.save_trajectories:
|
337
|
+
self.trajectory.save_trajectory()
|
338
|
+
|
339
|
+
return StopEvent(result)
|
340
|
+
|
341
|
+
def handle_stream_event(self, ev: Event, ctx: Context):
|
342
|
+
|
343
|
+
if isinstance(ev, EpisodicMemoryEvent):
|
344
|
+
self.current_episodic_memory = ev.episodic_memory
|
345
|
+
return
|
346
|
+
|
347
|
+
if not isinstance(ev, StopEvent):
|
348
|
+
ctx.write_event_to_stream(ev)
|
396
349
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
"steps": step_counter,
|
406
|
-
"task_history": self.task_manager.get_task_history()
|
407
|
-
}
|
408
|
-
|
409
|
-
except Exception as e:
|
410
|
-
logger.error(f"❌ Error during DroidAgent execution: {e}")
|
411
|
-
if self.debug:
|
412
|
-
import traceback
|
413
|
-
logger.error(traceback.format_exc())
|
414
|
-
return {
|
415
|
-
"success": False,
|
416
|
-
"reason": str(e),
|
417
|
-
"task_history": self.task_manager.get_task_history()
|
418
|
-
}
|
350
|
+
if isinstance(ev, ScreenshotEvent):
|
351
|
+
self.trajectory.screenshots.append(ev.screenshot)
|
352
|
+
|
353
|
+
else:
|
354
|
+
self.trajectory.events.append(ev)
|
355
|
+
|
356
|
+
|
357
|
+
|