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.
Files changed (60) hide show
  1. oagi/__init__.py +108 -0
  2. oagi/agent/__init__.py +31 -0
  3. oagi/agent/default.py +75 -0
  4. oagi/agent/factories.py +50 -0
  5. oagi/agent/protocol.py +55 -0
  6. oagi/agent/registry.py +155 -0
  7. oagi/agent/tasker/__init__.py +35 -0
  8. oagi/agent/tasker/memory.py +184 -0
  9. oagi/agent/tasker/models.py +83 -0
  10. oagi/agent/tasker/planner.py +385 -0
  11. oagi/agent/tasker/taskee_agent.py +395 -0
  12. oagi/agent/tasker/tasker_agent.py +323 -0
  13. oagi/async_pyautogui_action_handler.py +44 -0
  14. oagi/async_screenshot_maker.py +47 -0
  15. oagi/async_single_step.py +85 -0
  16. oagi/cli/__init__.py +11 -0
  17. oagi/cli/agent.py +125 -0
  18. oagi/cli/main.py +77 -0
  19. oagi/cli/server.py +94 -0
  20. oagi/cli/utils.py +82 -0
  21. oagi/client/__init__.py +12 -0
  22. oagi/client/async_.py +293 -0
  23. oagi/client/base.py +465 -0
  24. oagi/client/sync.py +296 -0
  25. oagi/exceptions.py +118 -0
  26. oagi/logging.py +47 -0
  27. oagi/pil_image.py +102 -0
  28. oagi/pyautogui_action_handler.py +268 -0
  29. oagi/screenshot_maker.py +41 -0
  30. oagi/server/__init__.py +13 -0
  31. oagi/server/agent_wrappers.py +98 -0
  32. oagi/server/config.py +46 -0
  33. oagi/server/main.py +157 -0
  34. oagi/server/models.py +98 -0
  35. oagi/server/session_store.py +116 -0
  36. oagi/server/socketio_server.py +405 -0
  37. oagi/single_step.py +87 -0
  38. oagi/task/__init__.py +14 -0
  39. oagi/task/async_.py +97 -0
  40. oagi/task/async_short.py +64 -0
  41. oagi/task/base.py +121 -0
  42. oagi/task/short.py +64 -0
  43. oagi/task/sync.py +97 -0
  44. oagi/types/__init__.py +28 -0
  45. oagi/types/action_handler.py +30 -0
  46. oagi/types/async_action_handler.py +30 -0
  47. oagi/types/async_image_provider.py +37 -0
  48. oagi/types/image.py +17 -0
  49. oagi/types/image_provider.py +34 -0
  50. oagi/types/models/__init__.py +32 -0
  51. oagi/types/models/action.py +33 -0
  52. oagi/types/models/client.py +64 -0
  53. oagi/types/models/image_config.py +47 -0
  54. oagi/types/models/step.py +17 -0
  55. oagi/types/url_image.py +47 -0
  56. oagi_core-0.9.0.dist-info/METADATA +257 -0
  57. oagi_core-0.9.0.dist-info/RECORD +60 -0
  58. oagi_core-0.9.0.dist-info/WHEEL +4 -0
  59. oagi_core-0.9.0.dist-info/entry_points.txt +2 -0
  60. 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
+ )