oagi-core 0.9.1__py3-none-any.whl → 0.10.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 (42) hide show
  1. oagi/__init__.py +76 -33
  2. oagi/agent/__init__.py +2 -0
  3. oagi/agent/default.py +45 -12
  4. oagi/agent/factories.py +22 -3
  5. oagi/agent/observer/__init__.py +38 -0
  6. oagi/agent/observer/agent_observer.py +99 -0
  7. oagi/agent/observer/events.py +28 -0
  8. oagi/agent/observer/exporters.py +445 -0
  9. oagi/agent/observer/protocol.py +12 -0
  10. oagi/agent/registry.py +2 -2
  11. oagi/agent/tasker/models.py +1 -0
  12. oagi/agent/tasker/planner.py +41 -9
  13. oagi/agent/tasker/taskee_agent.py +178 -86
  14. oagi/agent/tasker/tasker_agent.py +25 -14
  15. oagi/cli/agent.py +50 -9
  16. oagi/cli/tracking.py +27 -17
  17. oagi/cli/utils.py +11 -4
  18. oagi/client/base.py +3 -7
  19. oagi/handler/_macos.py +55 -0
  20. oagi/handler/pyautogui_action_handler.py +19 -2
  21. oagi/server/agent_wrappers.py +5 -5
  22. oagi/server/config.py +3 -3
  23. oagi/server/models.py +2 -2
  24. oagi/server/session_store.py +2 -2
  25. oagi/server/socketio_server.py +1 -1
  26. oagi/task/async_.py +13 -34
  27. oagi/task/async_short.py +2 -2
  28. oagi/task/base.py +41 -7
  29. oagi/task/short.py +2 -2
  30. oagi/task/sync.py +11 -34
  31. oagi/types/__init__.py +24 -4
  32. oagi/types/async_image_provider.py +3 -2
  33. oagi/types/image_provider.py +3 -2
  34. oagi/types/step_observer.py +75 -16
  35. oagi/types/url.py +3 -0
  36. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/METADATA +38 -25
  37. oagi_core-0.10.0.dist-info/RECORD +68 -0
  38. oagi/types/url_image.py +0 -47
  39. oagi_core-0.9.1.dist-info/RECORD +0 -62
  40. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/WHEEL +0 -0
  41. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/entry_points.txt +0 -0
  42. {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
oagi/__init__.py CHANGED
@@ -6,6 +6,7 @@
6
6
  # Licensed under the MIT License.
7
7
  # -----------------------------------------------------------------------------
8
8
  import importlib
9
+ from typing import TYPE_CHECKING
9
10
 
10
11
  from oagi.client import AsyncClient, SyncClient
11
12
  from oagi.exceptions import (
@@ -19,40 +20,82 @@ from oagi.exceptions import (
19
20
  RequestTimeoutError,
20
21
  ServerError,
21
22
  ValidationError,
23
+ check_optional_dependency,
22
24
  )
23
25
  from oagi.task import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
24
- from oagi.types import (
25
- AsyncActionHandler,
26
- AsyncImageProvider,
27
- ImageConfig,
26
+ from oagi.types import ImageConfig
27
+ from oagi.types.models import (
28
+ ErrorDetail,
29
+ ErrorResponse,
30
+ GenerateResponse,
31
+ LLMResponse,
32
+ UploadFileResponse,
28
33
  )
29
- from oagi.types.models import ErrorDetail, ErrorResponse, LLMResponse
30
34
 
31
- # Lazy imports for pyautogui-dependent modules
32
- # These will only be imported when actually accessed
33
- _LAZY_IMPORTS = {
34
- "AsyncPyautoguiActionHandler": "oagi.handler.async_pyautogui_action_handler",
35
- "AsyncScreenshotMaker": "oagi.handler.async_screenshot_maker",
36
- "PILImage": "oagi.handler.pil_image",
37
- "PyautoguiActionHandler": "oagi.handler.pyautogui_action_handler",
38
- "PyautoguiConfig": "oagi.handler.pyautogui_action_handler",
39
- "ScreenshotMaker": "oagi.handler.screenshot_maker",
40
- # Agent modules (to avoid circular imports)
41
- "TaskerAgent": "oagi.agent.tasker",
42
- # Server modules (optional - requires server dependencies)
43
- "create_app": "oagi.server.main",
44
- "ServerConfig": "oagi.server.config",
45
- "sio": "oagi.server.socketio_server",
35
+ # Lazy imports for optional dependency modules
36
+ # Format: name -> (module_path, package_to_check, extra_name)
37
+ # package_to_check is None if no optional dependency is required
38
+ _LAZY_IMPORTS_DATA: dict[str, tuple[str, str | None, str | None]] = {
39
+ # Desktop handlers (require pyautogui/PIL)
40
+ "AsyncPyautoguiActionHandler": (
41
+ "oagi.handler.async_pyautogui_action_handler",
42
+ "pyautogui",
43
+ "desktop",
44
+ ),
45
+ "AsyncScreenshotMaker": ("oagi.handler.async_screenshot_maker", "PIL", "desktop"),
46
+ "PILImage": ("oagi.handler.pil_image", "PIL", "desktop"),
47
+ "PyautoguiActionHandler": (
48
+ "oagi.handler.pyautogui_action_handler",
49
+ "pyautogui",
50
+ "desktop",
51
+ ),
52
+ "PyautoguiConfig": (
53
+ "oagi.handler.pyautogui_action_handler",
54
+ "pyautogui",
55
+ "desktop",
56
+ ),
57
+ "ScreenshotMaker": ("oagi.handler.screenshot_maker", "PIL", "desktop"),
58
+ # Agent modules (lazy to avoid circular imports)
59
+ "AsyncDefaultAgent": ("oagi.agent.default", None, None),
60
+ "TaskerAgent": ("oagi.agent.tasker", None, None),
61
+ "AsyncAgentObserver": ("oagi.agent.observer.agent_observer", None, None),
62
+ # Server modules (require server dependencies)
63
+ "create_app": ("oagi.server.main", "socketio", "server"),
64
+ "ServerConfig": ("oagi.server.config", "pydantic_settings", "server"),
65
+ "sio": ("oagi.server.socketio_server", "socketio", "server"),
46
66
  }
47
67
 
68
+ if TYPE_CHECKING:
69
+ from oagi.agent.default import AsyncDefaultAgent
70
+ from oagi.agent.observer.agent_observer import AsyncAgentObserver
71
+ from oagi.agent.tasker import TaskerAgent
72
+ from oagi.handler.async_pyautogui_action_handler import AsyncPyautoguiActionHandler
73
+ from oagi.handler.async_screenshot_maker import AsyncScreenshotMaker
74
+ from oagi.handler.pil_image import PILImage
75
+ from oagi.handler.pyautogui_action_handler import (
76
+ PyautoguiActionHandler,
77
+ PyautoguiConfig,
78
+ )
79
+ from oagi.handler.screenshot_maker import ScreenshotMaker
80
+ from oagi.server.config import ServerConfig
81
+ from oagi.server.main import create_app
82
+ from oagi.server.socketio_server import sio
83
+
48
84
 
49
85
  def __getattr__(name: str):
50
- """Lazy import for pyautogui-dependent modules."""
51
- if name in _LAZY_IMPORTS:
52
- module_name = _LAZY_IMPORTS[name]
53
- module = importlib.import_module(module_name)
86
+ """Lazy import for optional dependency modules with helpful error messages."""
87
+ if name in _LAZY_IMPORTS_DATA:
88
+ module_path, package, extra = _LAZY_IMPORTS_DATA[name]
89
+ if package is not None:
90
+ check_optional_dependency(package, name, extra)
91
+ module = importlib.import_module(module_path)
54
92
  return getattr(module, name)
55
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
93
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
94
+
95
+
96
+ def __dir__() -> list[str]:
97
+ """Return all public names including lazy imports."""
98
+ return sorted(set(globals().keys()) | set(_LAZY_IMPORTS_DATA.keys()))
56
99
 
57
100
 
58
101
  __all__ = [
@@ -67,14 +110,15 @@ __all__ = [
67
110
  "AsyncShortTask", # Deprecated
68
111
  "AsyncClient",
69
112
  # Agent classes
113
+ "AsyncDefaultAgent",
70
114
  "TaskerAgent",
71
- # Async protocols
72
- "AsyncActionHandler",
73
- "AsyncImageProvider",
115
+ "AsyncAgentObserver",
74
116
  # Configuration
75
117
  "ImageConfig",
76
118
  # Response models
77
119
  "LLMResponse",
120
+ "GenerateResponse",
121
+ "UploadFileResponse",
78
122
  "ErrorResponse",
79
123
  "ErrorDetail",
80
124
  # Exceptions
@@ -88,17 +132,16 @@ __all__ = [
88
132
  "ServerError",
89
133
  "RequestTimeoutError",
90
134
  "ValidationError",
91
- # Lazy imports
92
- # Image classes
135
+ # Lazy imports - Image classes
93
136
  "PILImage",
94
- # Handler classes
137
+ # Lazy imports - Handler classes
95
138
  "PyautoguiActionHandler",
96
139
  "PyautoguiConfig",
97
140
  "ScreenshotMaker",
98
- # Async handler classes
141
+ # Lazy imports - Async handler classes
99
142
  "AsyncPyautoguiActionHandler",
100
143
  "AsyncScreenshotMaker",
101
- # Server modules (optional)
144
+ # Lazy imports - Server modules (optional)
102
145
  "create_app",
103
146
  "ServerConfig",
104
147
  "sio",
oagi/agent/__init__.py CHANGED
@@ -9,6 +9,7 @@
9
9
  # Import factories to trigger registration
10
10
  from . import factories # noqa: F401
11
11
  from .default import AsyncDefaultAgent
12
+ from .observer import AsyncAgentObserver
12
13
  from .protocol import Agent, AsyncAgent
13
14
  from .registry import (
14
15
  async_agent_register,
@@ -21,6 +22,7 @@ from .tasker import TaskeeAgent, TaskerAgent
21
22
  __all__ = [
22
23
  "Agent",
23
24
  "AsyncAgent",
25
+ "AsyncAgentObserver",
24
26
  "AsyncDefaultAgent",
25
27
  "TaskerAgent",
26
28
  "TaskeeAgent",
oagi/agent/default.py CHANGED
@@ -10,14 +10,24 @@ import logging
10
10
 
11
11
  from .. import AsyncActor
12
12
  from ..types import (
13
+ ActionEvent,
13
14
  AsyncActionHandler,
14
15
  AsyncImageProvider,
15
- AsyncStepObserver,
16
+ AsyncObserver,
17
+ Image,
18
+ StepEvent,
16
19
  )
17
20
 
18
21
  logger = logging.getLogger(__name__)
19
22
 
20
23
 
24
+ def _serialize_image(image: Image | str) -> bytes | str:
25
+ """Convert an image to bytes or keep URL as string."""
26
+ if isinstance(image, str):
27
+ return image
28
+ return image.read()
29
+
30
+
21
31
  class AsyncDefaultAgent:
22
32
  """Default asynchronous agent implementation using OAGI client."""
23
33
 
@@ -25,10 +35,10 @@ class AsyncDefaultAgent:
25
35
  self,
26
36
  api_key: str | None = None,
27
37
  base_url: str | None = None,
28
- model: str = "lux-v1",
29
- max_steps: int = 30,
30
- temperature: float | None = None,
31
- step_observer: AsyncStepObserver | None = None,
38
+ model: str = "lux-actor-1",
39
+ max_steps: int = 20,
40
+ temperature: float | None = 0.5,
41
+ step_observer: AsyncObserver | None = None,
32
42
  ):
33
43
  self.api_key = api_key
34
44
  self.base_url = base_url
@@ -50,7 +60,8 @@ class AsyncDefaultAgent:
50
60
  await self.actor.init_task(instruction, max_steps=self.max_steps)
51
61
 
52
62
  for i in range(self.max_steps):
53
- logger.debug(f"Executing step {i + 1}/{self.max_steps}")
63
+ step_num = i + 1
64
+ logger.debug(f"Executing step {step_num}/{self.max_steps}")
54
65
 
55
66
  # Capture current state
56
67
  image = await image_provider()
@@ -60,11 +71,17 @@ class AsyncDefaultAgent:
60
71
 
61
72
  # Log reasoning
62
73
  if step.reason:
63
- logger.info(f"Step {i + 1}: {step.reason}")
74
+ logger.info(f"Step {step_num}: {step.reason}")
64
75
 
65
- # Notify observer if present
66
- if self.step_observer and step.actions:
67
- await self.step_observer.on_step(i + 1, step.reason, step.actions)
76
+ # Emit step event
77
+ if self.step_observer:
78
+ await self.step_observer.on_event(
79
+ StepEvent(
80
+ step_num=step_num,
81
+ image=_serialize_image(image),
82
+ step=step,
83
+ )
84
+ )
68
85
 
69
86
  # Execute actions if any
70
87
  if step.actions:
@@ -78,11 +95,27 @@ class AsyncDefaultAgent:
78
95
  logger.info(
79
96
  f" [{action.type.value}] {action.argument}{count_suffix}"
80
97
  )
81
- await action_handler(step.actions)
98
+
99
+ error = None
100
+ try:
101
+ await action_handler(step.actions)
102
+ except Exception as e:
103
+ error = str(e)
104
+ raise
105
+
106
+ # Emit action event
107
+ if self.step_observer:
108
+ await self.step_observer.on_event(
109
+ ActionEvent(
110
+ step_num=step_num,
111
+ actions=step.actions,
112
+ error=error,
113
+ )
114
+ )
82
115
 
83
116
  # Check if task is complete
84
117
  if step.stop:
85
- logger.info(f"Task completed successfully after {i + 1} steps")
118
+ logger.info(f"Task completed successfully after {step_num} steps")
86
119
  return True
87
120
 
88
121
  logger.warning(
oagi/agent/factories.py CHANGED
@@ -17,7 +17,7 @@ from .registry import async_agent_register
17
17
  def create_default_agent(
18
18
  api_key: str | None = None,
19
19
  base_url: str | None = None,
20
- model: str = "lux-v1",
20
+ model: str = "lux-actor-1",
21
21
  max_steps: int = 20,
22
22
  temperature: float = 0.1,
23
23
  step_observer: AsyncStepObserver | None = None,
@@ -32,13 +32,32 @@ def create_default_agent(
32
32
  )
33
33
 
34
34
 
35
+ @async_agent_register(mode="thinker")
36
+ def create_thinker_agent(
37
+ api_key: str | None = None,
38
+ base_url: str | None = None,
39
+ model: str = "lux-thinker-1",
40
+ max_steps: int = 100,
41
+ temperature: float = 0.1,
42
+ step_observer: AsyncStepObserver | None = None,
43
+ ) -> AsyncAgent:
44
+ return AsyncDefaultAgent(
45
+ api_key=api_key,
46
+ base_url=base_url,
47
+ model=model,
48
+ max_steps=max_steps,
49
+ temperature=temperature,
50
+ step_observer=step_observer,
51
+ )
52
+
53
+
35
54
  @async_agent_register(mode="tasker")
36
55
  def create_planner_agent(
37
56
  api_key: str | None = None,
38
57
  base_url: str | None = None,
39
- model: str = "lux-v1",
58
+ model: str = "lux-actor-1",
40
59
  max_steps: int = 30,
41
- temperature: float = 0.0,
60
+ temperature: float = 0.1,
42
61
  reflection_interval: int = 20,
43
62
  step_observer: AsyncStepObserver | None = None,
44
63
  ) -> AsyncAgent:
@@ -0,0 +1,38 @@
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
+ from ...types import (
10
+ ActionEvent,
11
+ AsyncObserver,
12
+ BaseEvent,
13
+ ImageEvent,
14
+ LogEvent,
15
+ ObserverEvent,
16
+ PlanEvent,
17
+ SplitEvent,
18
+ StepEvent,
19
+ )
20
+ from .agent_observer import AsyncAgentObserver, ExportFormat
21
+ from .exporters import export_to_html, export_to_json, export_to_markdown
22
+
23
+ __all__ = [
24
+ "ActionEvent",
25
+ "AsyncAgentObserver",
26
+ "AsyncObserver",
27
+ "BaseEvent",
28
+ "ExportFormat",
29
+ "ImageEvent",
30
+ "LogEvent",
31
+ "ObserverEvent",
32
+ "PlanEvent",
33
+ "SplitEvent",
34
+ "StepEvent",
35
+ "export_to_html",
36
+ "export_to_json",
37
+ "export_to_markdown",
38
+ ]
@@ -0,0 +1,99 @@
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
+ from enum import Enum
10
+
11
+ from ...types import LogEvent, ObserverEvent, SplitEvent
12
+ from .exporters import export_to_html, export_to_json, export_to_markdown
13
+
14
+
15
+ class ExportFormat(str, Enum):
16
+ """Supported export formats."""
17
+
18
+ MARKDOWN = "markdown"
19
+ HTML = "html"
20
+ JSON = "json"
21
+
22
+
23
+ class AsyncAgentObserver:
24
+ """Records agent execution events and exports to various formats.
25
+
26
+ This class implements the AsyncObserver protocol and provides
27
+ functionality for recording events during agent execution and
28
+ exporting them to Markdown or HTML formats.
29
+ """
30
+
31
+ def __init__(self) -> None:
32
+ self.events: list[ObserverEvent] = []
33
+
34
+ async def on_event(self, event: ObserverEvent) -> None:
35
+ """Record an event.
36
+
37
+ Args:
38
+ event: The event to record.
39
+ """
40
+ self.events.append(event)
41
+
42
+ def add_log(self, message: str) -> None:
43
+ """Add a custom log message.
44
+
45
+ Args:
46
+ message: The log message to add.
47
+ """
48
+ self.events.append(LogEvent(message=message))
49
+
50
+ def add_split(self, label: str = "") -> None:
51
+ """Add a visual separator.
52
+
53
+ Args:
54
+ label: Optional label for the separator.
55
+ """
56
+ self.events.append(SplitEvent(label=label))
57
+
58
+ def clear(self) -> None:
59
+ """Clear all recorded events."""
60
+ self.events.clear()
61
+
62
+ def get_events_by_step(self, step_num: int) -> list[ObserverEvent]:
63
+ """Get all events for a specific step.
64
+
65
+ Args:
66
+ step_num: The step number to filter by.
67
+
68
+ Returns:
69
+ List of events for the specified step.
70
+ """
71
+ return [
72
+ event
73
+ for event in self.events
74
+ if hasattr(event, "step_num") and event.step_num == step_num
75
+ ]
76
+
77
+ def export(
78
+ self,
79
+ format: ExportFormat | str,
80
+ path: str,
81
+ images_dir: str | None = None,
82
+ ) -> None:
83
+ """Export recorded events to a file.
84
+
85
+ Args:
86
+ format: Export format (markdown, html, json)
87
+ path: Path to the output file.
88
+ images_dir: Directory to save images (markdown only).
89
+ """
90
+ if isinstance(format, str):
91
+ format = ExportFormat(format.lower())
92
+
93
+ match format:
94
+ case ExportFormat.MARKDOWN:
95
+ export_to_markdown(self.events, path, images_dir)
96
+ case ExportFormat.HTML:
97
+ export_to_html(self.events, path)
98
+ case ExportFormat.JSON:
99
+ export_to_json(self.events, path)
@@ -0,0 +1,28 @@
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
+ # Re-export from types for convenience
10
+ from ...types import (
11
+ ActionEvent,
12
+ BaseEvent,
13
+ ImageEvent,
14
+ LogEvent,
15
+ ObserverEvent,
16
+ SplitEvent,
17
+ StepEvent,
18
+ )
19
+
20
+ __all__ = [
21
+ "ActionEvent",
22
+ "BaseEvent",
23
+ "ImageEvent",
24
+ "LogEvent",
25
+ "ObserverEvent",
26
+ "SplitEvent",
27
+ "StepEvent",
28
+ ]