oagi-core 0.9.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.
- oagi/__init__.py +108 -0
- oagi/agent/__init__.py +31 -0
- oagi/agent/default.py +75 -0
- oagi/agent/factories.py +50 -0
- oagi/agent/protocol.py +55 -0
- oagi/agent/registry.py +155 -0
- oagi/agent/tasker/__init__.py +35 -0
- oagi/agent/tasker/memory.py +184 -0
- oagi/agent/tasker/models.py +83 -0
- oagi/agent/tasker/planner.py +385 -0
- oagi/agent/tasker/taskee_agent.py +395 -0
- oagi/agent/tasker/tasker_agent.py +323 -0
- oagi/async_pyautogui_action_handler.py +44 -0
- oagi/async_screenshot_maker.py +47 -0
- oagi/async_single_step.py +85 -0
- oagi/cli/__init__.py +11 -0
- oagi/cli/agent.py +125 -0
- oagi/cli/main.py +77 -0
- oagi/cli/server.py +94 -0
- oagi/cli/utils.py +82 -0
- oagi/client/__init__.py +12 -0
- oagi/client/async_.py +293 -0
- oagi/client/base.py +465 -0
- oagi/client/sync.py +296 -0
- oagi/exceptions.py +118 -0
- oagi/logging.py +47 -0
- oagi/pil_image.py +102 -0
- oagi/pyautogui_action_handler.py +268 -0
- oagi/screenshot_maker.py +41 -0
- oagi/server/__init__.py +13 -0
- oagi/server/agent_wrappers.py +98 -0
- oagi/server/config.py +46 -0
- oagi/server/main.py +157 -0
- oagi/server/models.py +98 -0
- oagi/server/session_store.py +116 -0
- oagi/server/socketio_server.py +405 -0
- oagi/single_step.py +87 -0
- oagi/task/__init__.py +14 -0
- oagi/task/async_.py +97 -0
- oagi/task/async_short.py +64 -0
- oagi/task/base.py +121 -0
- oagi/task/short.py +64 -0
- oagi/task/sync.py +97 -0
- oagi/types/__init__.py +28 -0
- oagi/types/action_handler.py +30 -0
- oagi/types/async_action_handler.py +30 -0
- oagi/types/async_image_provider.py +37 -0
- oagi/types/image.py +17 -0
- oagi/types/image_provider.py +34 -0
- oagi/types/models/__init__.py +32 -0
- oagi/types/models/action.py +33 -0
- oagi/types/models/client.py +64 -0
- oagi/types/models/image_config.py +47 -0
- oagi/types/models/step.py +17 -0
- oagi/types/url_image.py +47 -0
- oagi_core-0.9.0.dist-info/METADATA +257 -0
- oagi_core-0.9.0.dist-info/RECORD +60 -0
- oagi_core-0.9.0.dist-info/WHEEL +4 -0
- oagi_core-0.9.0.dist-info/entry_points.txt +2 -0
- oagi_core-0.9.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from oagi import AsyncTask
|
|
14
|
+
from oagi.types import AsyncActionHandler, AsyncImageProvider
|
|
15
|
+
|
|
16
|
+
from ..protocol import AsyncAgent
|
|
17
|
+
from .memory import PlannerMemory
|
|
18
|
+
from .models import Action, ExecutionResult
|
|
19
|
+
from .planner import Planner
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TaskeeAgent(AsyncAgent):
|
|
25
|
+
"""Executes a single todo with planning and reflection capabilities.
|
|
26
|
+
|
|
27
|
+
This agent uses a Planner to:
|
|
28
|
+
1. Convert a todo into a clear actionable instruction
|
|
29
|
+
2. Execute the instruction using OAGI API
|
|
30
|
+
3. Periodically reflect on progress and adjust approach
|
|
31
|
+
4. Generate execution summaries
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: str | None = None,
|
|
37
|
+
base_url: str | None = None,
|
|
38
|
+
model: str = "lux-v1",
|
|
39
|
+
max_steps_per_subtask: int = 10,
|
|
40
|
+
reflection_interval: int = 20,
|
|
41
|
+
temperature: float = 0.0,
|
|
42
|
+
planner: Planner | None = None,
|
|
43
|
+
external_memory: PlannerMemory | None = None,
|
|
44
|
+
todo_index: int | None = None,
|
|
45
|
+
):
|
|
46
|
+
"""Initialize the taskee agent.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
api_key: OAGI API key
|
|
50
|
+
base_url: OAGI API base URL
|
|
51
|
+
model: Model to use for vision tasks
|
|
52
|
+
max_steps_per_subtask: Maximum steps before reinitializing task
|
|
53
|
+
reflection_interval: Number of actions before triggering reflection
|
|
54
|
+
temperature: Sampling temperature
|
|
55
|
+
planner: Planner for planning and reflection
|
|
56
|
+
external_memory: External memory from parent agent
|
|
57
|
+
todo_index: Index of the todo being executed
|
|
58
|
+
"""
|
|
59
|
+
self.api_key = api_key
|
|
60
|
+
self.base_url = base_url
|
|
61
|
+
self.model = model
|
|
62
|
+
self.max_steps_per_subtask = max_steps_per_subtask
|
|
63
|
+
self.reflection_interval = reflection_interval
|
|
64
|
+
self.temperature = temperature
|
|
65
|
+
self.planner = planner or Planner()
|
|
66
|
+
self.external_memory = external_memory
|
|
67
|
+
self.todo_index = todo_index
|
|
68
|
+
|
|
69
|
+
# Internal state
|
|
70
|
+
self.task: AsyncTask | None = None
|
|
71
|
+
self.current_todo: str = ""
|
|
72
|
+
self.current_instruction: str = ""
|
|
73
|
+
self.actions: list[Action] = []
|
|
74
|
+
self.total_actions = 0
|
|
75
|
+
self.since_reflection = 0
|
|
76
|
+
self.success = False
|
|
77
|
+
|
|
78
|
+
async def execute(
|
|
79
|
+
self,
|
|
80
|
+
instruction: str,
|
|
81
|
+
action_handler: AsyncActionHandler,
|
|
82
|
+
image_provider: AsyncImageProvider,
|
|
83
|
+
) -> bool:
|
|
84
|
+
"""Execute the todo using planning and reflection.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
instruction: The todo description to execute
|
|
88
|
+
action_handler: Handler for executing actions
|
|
89
|
+
image_provider: Provider for capturing screenshots
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if successful, False otherwise
|
|
93
|
+
"""
|
|
94
|
+
self.current_todo = instruction
|
|
95
|
+
self.actions = []
|
|
96
|
+
self.total_actions = 0
|
|
97
|
+
self.since_reflection = 0
|
|
98
|
+
self.success = False
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Initial planning
|
|
102
|
+
await self._initial_plan(image_provider)
|
|
103
|
+
|
|
104
|
+
# Main execution loop with reinitializations
|
|
105
|
+
max_total_steps = self.max_steps_per_subtask * 3 # Allow up to 3 reinits
|
|
106
|
+
remaining_steps = max_total_steps
|
|
107
|
+
|
|
108
|
+
while remaining_steps > 0 and not self.success:
|
|
109
|
+
# Execute subtask
|
|
110
|
+
steps_taken = await self._execute_subtask(
|
|
111
|
+
min(self.max_steps_per_subtask, remaining_steps),
|
|
112
|
+
action_handler,
|
|
113
|
+
image_provider,
|
|
114
|
+
)
|
|
115
|
+
remaining_steps -= steps_taken
|
|
116
|
+
|
|
117
|
+
# Check if we should continue
|
|
118
|
+
if not self.success and remaining_steps > 0:
|
|
119
|
+
# Reflect and potentially get new instruction
|
|
120
|
+
should_continue = await self._reflect_and_decide(image_provider)
|
|
121
|
+
if not should_continue:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
# Generate final summary
|
|
125
|
+
await self._generate_summary()
|
|
126
|
+
|
|
127
|
+
return self.success
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error executing todo: {e}")
|
|
131
|
+
self._record_action(
|
|
132
|
+
action_type="error",
|
|
133
|
+
target=None,
|
|
134
|
+
reasoning=str(e),
|
|
135
|
+
)
|
|
136
|
+
return False
|
|
137
|
+
finally:
|
|
138
|
+
# Clean up task
|
|
139
|
+
if self.task:
|
|
140
|
+
await self.task.close()
|
|
141
|
+
self.task = None
|
|
142
|
+
|
|
143
|
+
async def _initial_plan(self, image_provider: AsyncImageProvider) -> None:
|
|
144
|
+
"""Generate initial plan for the todo.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
image_provider: Provider for capturing screenshots
|
|
148
|
+
"""
|
|
149
|
+
logger.info("Generating initial plan for todo")
|
|
150
|
+
|
|
151
|
+
# Capture initial screenshot
|
|
152
|
+
screenshot = await image_provider()
|
|
153
|
+
|
|
154
|
+
# Get context from external memory if available
|
|
155
|
+
context = self._get_context()
|
|
156
|
+
|
|
157
|
+
# Generate plan using LLM planner
|
|
158
|
+
plan_output = await self.planner.initial_plan(
|
|
159
|
+
self.current_todo,
|
|
160
|
+
context,
|
|
161
|
+
screenshot,
|
|
162
|
+
memory=self.external_memory,
|
|
163
|
+
todo_index=self.todo_index,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Record planning action
|
|
167
|
+
self._record_action(
|
|
168
|
+
action_type="plan",
|
|
169
|
+
target="initial",
|
|
170
|
+
reasoning=plan_output.reasoning,
|
|
171
|
+
result=plan_output.instruction,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Set current instruction
|
|
175
|
+
self.current_instruction = plan_output.instruction
|
|
176
|
+
logger.info(f"Initial instruction: {self.current_instruction}")
|
|
177
|
+
|
|
178
|
+
# Handle subtodos if any
|
|
179
|
+
if plan_output.subtodos:
|
|
180
|
+
logger.info(f"Planner created {len(plan_output.subtodos)} subtodos")
|
|
181
|
+
# Could potentially add these to memory for tracking
|
|
182
|
+
|
|
183
|
+
async def _execute_subtask(
|
|
184
|
+
self,
|
|
185
|
+
max_steps: int,
|
|
186
|
+
action_handler: AsyncActionHandler,
|
|
187
|
+
image_provider: AsyncImageProvider,
|
|
188
|
+
) -> int:
|
|
189
|
+
"""Execute a subtask with the current instruction.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
max_steps: Maximum steps for this subtask
|
|
193
|
+
action_handler: Handler for executing actions
|
|
194
|
+
image_provider: Provider for capturing screenshots
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Number of steps taken
|
|
198
|
+
"""
|
|
199
|
+
logger.info(f"Executing subtask with max {max_steps} steps")
|
|
200
|
+
|
|
201
|
+
# Use async with for automatic resource management
|
|
202
|
+
async with AsyncTask(
|
|
203
|
+
api_key=self.api_key,
|
|
204
|
+
base_url=self.base_url,
|
|
205
|
+
model=self.model,
|
|
206
|
+
temperature=self.temperature,
|
|
207
|
+
) as task:
|
|
208
|
+
# Store reference for potential cleanup in execute's finally block
|
|
209
|
+
self.task = task
|
|
210
|
+
|
|
211
|
+
# Initialize task with current instruction
|
|
212
|
+
await task.init_task(self.current_instruction)
|
|
213
|
+
|
|
214
|
+
steps_taken = 0
|
|
215
|
+
for step_num in range(max_steps):
|
|
216
|
+
# Capture screenshot
|
|
217
|
+
screenshot = await image_provider()
|
|
218
|
+
|
|
219
|
+
# Get next step from OAGI
|
|
220
|
+
try:
|
|
221
|
+
step = await task.step(screenshot, instruction=None)
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.error(f"Error getting step from OAGI: {e}")
|
|
224
|
+
self._record_action(
|
|
225
|
+
action_type="error",
|
|
226
|
+
target="oagi_step",
|
|
227
|
+
reasoning=str(e),
|
|
228
|
+
)
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
# Record OAGI actions
|
|
232
|
+
if step.actions:
|
|
233
|
+
for action in step.actions:
|
|
234
|
+
self._record_action(
|
|
235
|
+
action_type=action.type.lower(),
|
|
236
|
+
target=action.argument,
|
|
237
|
+
reasoning=step.reason,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Execute actions
|
|
241
|
+
await action_handler(step.actions)
|
|
242
|
+
self.total_actions += len(step.actions)
|
|
243
|
+
self.since_reflection += len(step.actions)
|
|
244
|
+
|
|
245
|
+
steps_taken += 1
|
|
246
|
+
|
|
247
|
+
# Check if task is complete
|
|
248
|
+
if step.stop:
|
|
249
|
+
logger.info("OAGI signaled task completion")
|
|
250
|
+
self.success = True
|
|
251
|
+
break
|
|
252
|
+
|
|
253
|
+
# Check if reflection is needed
|
|
254
|
+
if self.since_reflection >= self.reflection_interval:
|
|
255
|
+
logger.info("Reflection interval reached")
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
# Task will be automatically closed by async with context manager
|
|
259
|
+
# Clear reference after context manager closes it
|
|
260
|
+
self.task = None
|
|
261
|
+
return steps_taken
|
|
262
|
+
|
|
263
|
+
async def _reflect_and_decide(self, image_provider: AsyncImageProvider) -> bool:
|
|
264
|
+
"""Reflect on progress and decide whether to continue.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
image_provider: Provider for capturing screenshots
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True to continue, False to stop
|
|
271
|
+
"""
|
|
272
|
+
logger.info("Reflecting on progress")
|
|
273
|
+
|
|
274
|
+
# Capture current screenshot
|
|
275
|
+
screenshot = await image_provider()
|
|
276
|
+
|
|
277
|
+
# Get context
|
|
278
|
+
context = self._get_context()
|
|
279
|
+
context["current_todo"] = self.current_todo
|
|
280
|
+
|
|
281
|
+
# Get recent actions for reflection
|
|
282
|
+
recent_actions = self.actions[-self.since_reflection :]
|
|
283
|
+
|
|
284
|
+
# Reflect using planner
|
|
285
|
+
reflection = await self.planner.reflect(
|
|
286
|
+
recent_actions,
|
|
287
|
+
context,
|
|
288
|
+
screenshot,
|
|
289
|
+
memory=self.external_memory,
|
|
290
|
+
todo_index=self.todo_index,
|
|
291
|
+
current_instruction=self.current_instruction,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Record reflection
|
|
295
|
+
self._record_action(
|
|
296
|
+
action_type="reflect",
|
|
297
|
+
target=None,
|
|
298
|
+
reasoning=reflection.reasoning,
|
|
299
|
+
result=("continue" if reflection.continue_current else "pivot"),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Update success assessment
|
|
303
|
+
if reflection.success_assessment:
|
|
304
|
+
self.success = True
|
|
305
|
+
logger.info("Reflection indicates task is successful")
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
# Reset reflection counter
|
|
309
|
+
self.since_reflection = 0
|
|
310
|
+
|
|
311
|
+
# Update instruction if needed
|
|
312
|
+
if not reflection.continue_current and reflection.new_instruction:
|
|
313
|
+
logger.info(f"Pivoting to new instruction: {reflection.new_instruction}")
|
|
314
|
+
self.current_instruction = reflection.new_instruction
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
return reflection.continue_current
|
|
318
|
+
|
|
319
|
+
async def _generate_summary(self) -> None:
|
|
320
|
+
"""Generate execution summary."""
|
|
321
|
+
logger.info("Generating execution summary")
|
|
322
|
+
|
|
323
|
+
context = self._get_context()
|
|
324
|
+
context["current_todo"] = self.current_todo
|
|
325
|
+
|
|
326
|
+
summary = await self.planner.summarize(
|
|
327
|
+
self.actions,
|
|
328
|
+
context,
|
|
329
|
+
memory=self.external_memory,
|
|
330
|
+
todo_index=self.todo_index,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Record summary
|
|
334
|
+
self._record_action(
|
|
335
|
+
action_type="summary",
|
|
336
|
+
target=None,
|
|
337
|
+
reasoning=summary,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
logger.info(f"Execution summary: {summary}")
|
|
341
|
+
|
|
342
|
+
def _record_action(
|
|
343
|
+
self,
|
|
344
|
+
action_type: str,
|
|
345
|
+
target: str | None,
|
|
346
|
+
reasoning: str | None = None,
|
|
347
|
+
result: str | None = None,
|
|
348
|
+
) -> None:
|
|
349
|
+
"""Record an action to the history.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
action_type: Type of action
|
|
353
|
+
target: Target of the action
|
|
354
|
+
reasoning: Reasoning for the action
|
|
355
|
+
result: Result of the action
|
|
356
|
+
"""
|
|
357
|
+
action = Action(
|
|
358
|
+
timestamp=datetime.now().isoformat(),
|
|
359
|
+
action_type=action_type,
|
|
360
|
+
target=target,
|
|
361
|
+
reasoning=reasoning,
|
|
362
|
+
result=result,
|
|
363
|
+
details={},
|
|
364
|
+
)
|
|
365
|
+
self.actions.append(action)
|
|
366
|
+
|
|
367
|
+
def _get_context(self) -> dict[str, Any]:
|
|
368
|
+
"""Get execution context.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Dictionary with context information
|
|
372
|
+
"""
|
|
373
|
+
if self.external_memory:
|
|
374
|
+
return self.external_memory.get_context()
|
|
375
|
+
return {}
|
|
376
|
+
|
|
377
|
+
def return_execution_results(self) -> ExecutionResult:
|
|
378
|
+
"""Return the execution results.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
ExecutionResult with success status, actions, and summary
|
|
382
|
+
"""
|
|
383
|
+
# Find summary in actions
|
|
384
|
+
summary = ""
|
|
385
|
+
for action in reversed(self.actions):
|
|
386
|
+
if action.action_type == "summary":
|
|
387
|
+
summary = action.reasoning or ""
|
|
388
|
+
break
|
|
389
|
+
|
|
390
|
+
return ExecutionResult(
|
|
391
|
+
success=self.success,
|
|
392
|
+
actions=self.actions,
|
|
393
|
+
summary=summary,
|
|
394
|
+
total_steps=self.total_actions,
|
|
395
|
+
)
|