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
oagi/__init__.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
import importlib
|
|
9
|
+
|
|
10
|
+
from oagi.async_single_step import async_single_step
|
|
11
|
+
from oagi.client import AsyncClient, SyncClient
|
|
12
|
+
from oagi.exceptions import (
|
|
13
|
+
APIError,
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
ConfigurationError,
|
|
16
|
+
NetworkError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
OAGIError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
RequestTimeoutError,
|
|
21
|
+
ServerError,
|
|
22
|
+
ValidationError,
|
|
23
|
+
)
|
|
24
|
+
from oagi.single_step import single_step
|
|
25
|
+
from oagi.task import AsyncShortTask, AsyncTask, ShortTask, Task
|
|
26
|
+
from oagi.types import (
|
|
27
|
+
AsyncActionHandler,
|
|
28
|
+
AsyncImageProvider,
|
|
29
|
+
ImageConfig,
|
|
30
|
+
)
|
|
31
|
+
from oagi.types.models import ErrorDetail, ErrorResponse, LLMResponse
|
|
32
|
+
|
|
33
|
+
# Lazy imports for pyautogui-dependent modules
|
|
34
|
+
# These will only be imported when actually accessed
|
|
35
|
+
_LAZY_IMPORTS = {
|
|
36
|
+
"AsyncPyautoguiActionHandler": "oagi.async_pyautogui_action_handler",
|
|
37
|
+
"AsyncScreenshotMaker": "oagi.async_screenshot_maker",
|
|
38
|
+
"PILImage": "oagi.pil_image",
|
|
39
|
+
"PyautoguiActionHandler": "oagi.pyautogui_action_handler",
|
|
40
|
+
"PyautoguiConfig": "oagi.pyautogui_action_handler",
|
|
41
|
+
"ScreenshotMaker": "oagi.screenshot_maker",
|
|
42
|
+
# Agent modules (to avoid circular imports)
|
|
43
|
+
"TaskerAgent": "oagi.agent.tasker",
|
|
44
|
+
# Server modules (optional - requires server dependencies)
|
|
45
|
+
"create_app": "oagi.server.main",
|
|
46
|
+
"ServerConfig": "oagi.server.config",
|
|
47
|
+
"sio": "oagi.server.socketio_server",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def __getattr__(name: str):
|
|
52
|
+
"""Lazy import for pyautogui-dependent modules."""
|
|
53
|
+
if name in _LAZY_IMPORTS:
|
|
54
|
+
module_name = _LAZY_IMPORTS[name]
|
|
55
|
+
module = importlib.import_module(module_name)
|
|
56
|
+
return getattr(module, name)
|
|
57
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
# Core sync classes
|
|
62
|
+
"Task",
|
|
63
|
+
"ShortTask",
|
|
64
|
+
"SyncClient",
|
|
65
|
+
# Core async classes
|
|
66
|
+
"AsyncTask",
|
|
67
|
+
"AsyncShortTask",
|
|
68
|
+
"AsyncClient",
|
|
69
|
+
# Agent classes
|
|
70
|
+
"TaskerAgent",
|
|
71
|
+
# Functions
|
|
72
|
+
"single_step",
|
|
73
|
+
"async_single_step",
|
|
74
|
+
# Async protocols
|
|
75
|
+
"AsyncActionHandler",
|
|
76
|
+
"AsyncImageProvider",
|
|
77
|
+
# Configuration
|
|
78
|
+
"ImageConfig",
|
|
79
|
+
# Response models
|
|
80
|
+
"LLMResponse",
|
|
81
|
+
"ErrorResponse",
|
|
82
|
+
"ErrorDetail",
|
|
83
|
+
# Exceptions
|
|
84
|
+
"OAGIError",
|
|
85
|
+
"APIError",
|
|
86
|
+
"AuthenticationError",
|
|
87
|
+
"ConfigurationError",
|
|
88
|
+
"NetworkError",
|
|
89
|
+
"NotFoundError",
|
|
90
|
+
"RateLimitError",
|
|
91
|
+
"ServerError",
|
|
92
|
+
"RequestTimeoutError",
|
|
93
|
+
"ValidationError",
|
|
94
|
+
# Lazy imports
|
|
95
|
+
# Image classes
|
|
96
|
+
"PILImage",
|
|
97
|
+
# Handler classes
|
|
98
|
+
"PyautoguiActionHandler",
|
|
99
|
+
"PyautoguiConfig",
|
|
100
|
+
"ScreenshotMaker",
|
|
101
|
+
# Async handler classes
|
|
102
|
+
"AsyncPyautoguiActionHandler",
|
|
103
|
+
"AsyncScreenshotMaker",
|
|
104
|
+
# Server modules (optional)
|
|
105
|
+
"create_app",
|
|
106
|
+
"ServerConfig",
|
|
107
|
+
"sio",
|
|
108
|
+
]
|
oagi/agent/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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 factories to trigger registration
|
|
10
|
+
from . import factories # noqa: F401
|
|
11
|
+
from .default import AsyncDefaultAgent
|
|
12
|
+
from .protocol import Agent, AsyncAgent
|
|
13
|
+
from .registry import (
|
|
14
|
+
async_agent_register,
|
|
15
|
+
create_agent,
|
|
16
|
+
get_agent_factory,
|
|
17
|
+
list_agent_modes,
|
|
18
|
+
)
|
|
19
|
+
from .tasker import TaskeeAgent, TaskerAgent
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Agent",
|
|
23
|
+
"AsyncAgent",
|
|
24
|
+
"AsyncDefaultAgent",
|
|
25
|
+
"TaskerAgent",
|
|
26
|
+
"TaskeeAgent",
|
|
27
|
+
"async_agent_register",
|
|
28
|
+
"create_agent",
|
|
29
|
+
"get_agent_factory",
|
|
30
|
+
"list_agent_modes",
|
|
31
|
+
]
|
oagi/agent/default.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
|
|
11
|
+
from .. import AsyncTask
|
|
12
|
+
from ..types import (
|
|
13
|
+
AsyncActionHandler,
|
|
14
|
+
AsyncImageProvider,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AsyncDefaultAgent:
|
|
21
|
+
"""Default asynchronous agent implementation using OAGI client."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
api_key: str | None = None,
|
|
26
|
+
base_url: str | None = None,
|
|
27
|
+
model: str = "lux-v1",
|
|
28
|
+
max_steps: int = 30,
|
|
29
|
+
temperature: float | None = None,
|
|
30
|
+
):
|
|
31
|
+
self.api_key = api_key
|
|
32
|
+
self.base_url = base_url
|
|
33
|
+
self.model = model
|
|
34
|
+
self.max_steps = max_steps
|
|
35
|
+
self.temperature = temperature
|
|
36
|
+
|
|
37
|
+
async def execute(
|
|
38
|
+
self,
|
|
39
|
+
instruction: str,
|
|
40
|
+
action_handler: AsyncActionHandler,
|
|
41
|
+
image_provider: AsyncImageProvider,
|
|
42
|
+
) -> bool:
|
|
43
|
+
async with AsyncTask(
|
|
44
|
+
api_key=self.api_key, base_url=self.base_url, model=self.model
|
|
45
|
+
) as self.task:
|
|
46
|
+
logger.info(f"Starting async task execution: {instruction}")
|
|
47
|
+
await self.task.init_task(instruction, max_steps=self.max_steps)
|
|
48
|
+
|
|
49
|
+
for i in range(self.max_steps):
|
|
50
|
+
logger.debug(f"Executing step {i + 1}/{self.max_steps}")
|
|
51
|
+
|
|
52
|
+
# Capture current state
|
|
53
|
+
image = await image_provider()
|
|
54
|
+
|
|
55
|
+
# Get next step from OAGI
|
|
56
|
+
step = await self.task.step(image, temperature=self.temperature)
|
|
57
|
+
|
|
58
|
+
# Log reasoning
|
|
59
|
+
if step.reason:
|
|
60
|
+
logger.debug(f"Step {i + 1} reasoning: {step.reason}")
|
|
61
|
+
|
|
62
|
+
# Execute actions if any
|
|
63
|
+
if step.actions:
|
|
64
|
+
logger.debug(f"Executing {len(step.actions)} actions")
|
|
65
|
+
await action_handler(step.actions)
|
|
66
|
+
|
|
67
|
+
# Check if task is complete
|
|
68
|
+
if step.stop:
|
|
69
|
+
logger.info(f"Task completed successfully after {i + 1} steps")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
logger.warning(
|
|
73
|
+
f"Task reached max steps ({self.max_steps}) without completion"
|
|
74
|
+
)
|
|
75
|
+
return False
|
oagi/agent/factories.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
from oagi.agent.tasker import TaskerAgent
|
|
9
|
+
|
|
10
|
+
from .default import AsyncDefaultAgent
|
|
11
|
+
from .protocol import AsyncAgent
|
|
12
|
+
from .registry import async_agent_register
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@async_agent_register(mode="actor")
|
|
16
|
+
def create_default_agent(
|
|
17
|
+
api_key: str | None = None,
|
|
18
|
+
base_url: str | None = None,
|
|
19
|
+
model: str = "lux-v1",
|
|
20
|
+
max_steps: int = 20,
|
|
21
|
+
temperature: float = 0.1,
|
|
22
|
+
) -> AsyncAgent:
|
|
23
|
+
return AsyncDefaultAgent(
|
|
24
|
+
api_key=api_key,
|
|
25
|
+
base_url=base_url,
|
|
26
|
+
model=model,
|
|
27
|
+
max_steps=max_steps,
|
|
28
|
+
temperature=temperature,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@async_agent_register(mode="tasker")
|
|
33
|
+
def create_planner_agent(
|
|
34
|
+
api_key: str | None = None,
|
|
35
|
+
base_url: str | None = None,
|
|
36
|
+
model: str = "lux-v1",
|
|
37
|
+
max_steps: int = 30,
|
|
38
|
+
temperature: float = 0.0,
|
|
39
|
+
reflection_interval: int = 20,
|
|
40
|
+
) -> AsyncAgent:
|
|
41
|
+
tasker = TaskerAgent(
|
|
42
|
+
api_key=api_key,
|
|
43
|
+
base_url=base_url,
|
|
44
|
+
model=model,
|
|
45
|
+
max_steps=max_steps,
|
|
46
|
+
temperature=temperature,
|
|
47
|
+
reflection_interval=reflection_interval,
|
|
48
|
+
)
|
|
49
|
+
# tasker.set_task()
|
|
50
|
+
return tasker
|
oagi/agent/protocol.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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 typing import Protocol
|
|
10
|
+
|
|
11
|
+
from ..types import ActionHandler, AsyncActionHandler, AsyncImageProvider, ImageProvider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Agent(Protocol):
|
|
15
|
+
"""Protocol for synchronous task execution agents."""
|
|
16
|
+
|
|
17
|
+
def execute(
|
|
18
|
+
self,
|
|
19
|
+
instruction: str,
|
|
20
|
+
action_handler: ActionHandler,
|
|
21
|
+
image_provider: ImageProvider,
|
|
22
|
+
) -> bool:
|
|
23
|
+
"""Execute a task with the given handlers.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
instruction: Task instruction to execute
|
|
27
|
+
action_handler: Handler for executing actions
|
|
28
|
+
image_provider: Provider for capturing images
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
True if task completed successfully, False otherwise
|
|
32
|
+
"""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncAgent(Protocol):
|
|
37
|
+
"""Protocol for asynchronous task execution agents."""
|
|
38
|
+
|
|
39
|
+
async def execute(
|
|
40
|
+
self,
|
|
41
|
+
instruction: str,
|
|
42
|
+
action_handler: AsyncActionHandler,
|
|
43
|
+
image_provider: AsyncImageProvider,
|
|
44
|
+
) -> bool:
|
|
45
|
+
"""Asynchronously execute a task with the given handlers.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
instruction: Task instruction to execute
|
|
49
|
+
action_handler: Handler for executing actions
|
|
50
|
+
image_provider: Provider for capturing images
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if task completed successfully, False otherwise
|
|
54
|
+
"""
|
|
55
|
+
...
|
oagi/agent/registry.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
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 inspect
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .protocol import AsyncAgent
|
|
14
|
+
|
|
15
|
+
# Type alias for agent factory functions
|
|
16
|
+
AgentFactory = Callable[..., AsyncAgent]
|
|
17
|
+
|
|
18
|
+
# Global registry mapping mode names to factory functions
|
|
19
|
+
_agent_registry: dict[str, AgentFactory] = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def async_agent_register(mode: str) -> Callable[[AgentFactory], AgentFactory]:
|
|
23
|
+
"""Decorator to register agent factory functions for specific modes.
|
|
24
|
+
|
|
25
|
+
The decorator performs the following:
|
|
26
|
+
1. Registers the factory function under the specified mode name
|
|
27
|
+
2. Validates that duplicate modes are not registered
|
|
28
|
+
3. Enables runtime validation of returned AsyncAgent instances
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
mode: The agent mode identifier (e.g., "actor", "planner", "todo")
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Decorator function that registers the factory
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If the mode is already registered
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def decorator(func: AgentFactory) -> AgentFactory:
|
|
41
|
+
# Check if mode is already registered
|
|
42
|
+
if mode in _agent_registry:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Agent mode '{mode}' is already registered. "
|
|
45
|
+
f"Cannot register the same mode twice."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Register the factory
|
|
49
|
+
_agent_registry[mode] = func
|
|
50
|
+
return func
|
|
51
|
+
|
|
52
|
+
return decorator
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_agent_factory(mode: str) -> AgentFactory:
|
|
56
|
+
"""Get the registered agent factory for a mode.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
mode: The agent mode identifier
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The registered factory function
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If the mode is not registered
|
|
66
|
+
"""
|
|
67
|
+
if mode not in _agent_registry:
|
|
68
|
+
available_modes = list(_agent_registry.keys())
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Unknown agent mode: '{mode}'. Available modes: {available_modes}"
|
|
71
|
+
)
|
|
72
|
+
return _agent_registry[mode]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def list_agent_modes() -> list[str]:
|
|
76
|
+
"""List all registered agent modes.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List of registered mode names
|
|
80
|
+
"""
|
|
81
|
+
return list(_agent_registry.keys())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def create_agent(mode: str, **kwargs: Any) -> AsyncAgent:
|
|
85
|
+
"""Create an agent instance using the registered factory for the given mode.
|
|
86
|
+
|
|
87
|
+
This function automatically introspects the factory's signature and only passes
|
|
88
|
+
parameters that the factory accepts. This allows factories to have flexible
|
|
89
|
+
signatures while callers can provide a standard set of parameters.
|
|
90
|
+
|
|
91
|
+
Standard parameters typically include:
|
|
92
|
+
- api_key: OAGI API key
|
|
93
|
+
- base_url: OAGI API base URL
|
|
94
|
+
- model: Model identifier (e.g., "lux-v1")
|
|
95
|
+
- max_steps: Maximum number of steps to execute
|
|
96
|
+
- temperature: Sampling temperature
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
mode: The agent mode identifier
|
|
100
|
+
**kwargs: Parameters to pass to the factory function
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
AsyncAgent instance created by the factory
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If the mode is not registered
|
|
107
|
+
TypeError: If the factory returns an object that doesn't implement AsyncAgent
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
agent = create_agent(
|
|
111
|
+
mode="actor",
|
|
112
|
+
api_key="...",
|
|
113
|
+
base_url="...",
|
|
114
|
+
model="lux-v1",
|
|
115
|
+
max_steps=30,
|
|
116
|
+
temperature=0.0,
|
|
117
|
+
)
|
|
118
|
+
"""
|
|
119
|
+
factory = get_agent_factory(mode)
|
|
120
|
+
|
|
121
|
+
# Introspect factory signature to determine which parameters it accepts
|
|
122
|
+
sig = inspect.signature(factory)
|
|
123
|
+
|
|
124
|
+
# Check if factory has **kwargs parameter (VAR_KEYWORD)
|
|
125
|
+
has_var_keyword = any(
|
|
126
|
+
param.kind == inspect.Parameter.VAR_KEYWORD for param in sig.parameters.values()
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if has_var_keyword:
|
|
130
|
+
# If factory has **kwargs, pass all parameters
|
|
131
|
+
filtered_kwargs = kwargs
|
|
132
|
+
else:
|
|
133
|
+
# Otherwise, filter kwargs to only include parameters the factory accepts
|
|
134
|
+
accepted_params = set(sig.parameters.keys())
|
|
135
|
+
filtered_kwargs = {
|
|
136
|
+
key: value for key, value in kwargs.items() if key in accepted_params
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
agent = factory(**filtered_kwargs)
|
|
140
|
+
|
|
141
|
+
if not hasattr(agent, "execute"):
|
|
142
|
+
raise TypeError(
|
|
143
|
+
f"Factory for mode '{mode}' returned an object that doesn't "
|
|
144
|
+
f"implement AsyncAgent protocol. Expected an object with an "
|
|
145
|
+
f"'execute' method, got {type(agent).__name__}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if not inspect.iscoroutinefunction(agent.execute):
|
|
149
|
+
raise TypeError(
|
|
150
|
+
f"Factory for mode '{mode}' returned an object with a non-async "
|
|
151
|
+
f"'execute' method. AsyncAgent protocol requires 'execute' to be "
|
|
152
|
+
f"an async method."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return agent
|
|
@@ -0,0 +1,35 @@
|
|
|
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 .memory import PlannerMemory
|
|
10
|
+
from .models import (
|
|
11
|
+
Action,
|
|
12
|
+
Deliverable,
|
|
13
|
+
PlannerOutput,
|
|
14
|
+
ReflectionOutput,
|
|
15
|
+
Todo,
|
|
16
|
+
TodoHistory,
|
|
17
|
+
TodoStatus,
|
|
18
|
+
)
|
|
19
|
+
from .planner import Planner
|
|
20
|
+
from .taskee_agent import TaskeeAgent
|
|
21
|
+
from .tasker_agent import TaskerAgent
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"TaskerAgent",
|
|
25
|
+
"TaskeeAgent",
|
|
26
|
+
"PlannerMemory",
|
|
27
|
+
"Planner",
|
|
28
|
+
"Todo",
|
|
29
|
+
"TodoStatus",
|
|
30
|
+
"Deliverable",
|
|
31
|
+
"Action",
|
|
32
|
+
"TodoHistory",
|
|
33
|
+
"PlannerOutput",
|
|
34
|
+
"ReflectionOutput",
|
|
35
|
+
]
|
|
@@ -0,0 +1,184 @@
|
|
|
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 typing import Any
|
|
10
|
+
|
|
11
|
+
from .models import Action, Deliverable, Todo, TodoHistory, TodoStatus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PlannerMemory:
|
|
15
|
+
"""In-memory state management for the planner agent.
|
|
16
|
+
|
|
17
|
+
This class manages the hierarchical task execution state for TaskerAgent.
|
|
18
|
+
It provides methods for:
|
|
19
|
+
- Task/todo/deliverable management
|
|
20
|
+
- Execution history tracking
|
|
21
|
+
- Memory state serialization
|
|
22
|
+
|
|
23
|
+
Context formatting for backend API calls is handled by the backend.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Initialize empty memory."""
|
|
28
|
+
self.task_description: str = ""
|
|
29
|
+
self.todos: list[Todo] = []
|
|
30
|
+
self.deliverables: list[Deliverable] = []
|
|
31
|
+
self.history: list[TodoHistory] = []
|
|
32
|
+
self.task_execution_summary: str = ""
|
|
33
|
+
self.todo_execution_summaries: dict[int, str] = {}
|
|
34
|
+
|
|
35
|
+
def set_task(
|
|
36
|
+
self,
|
|
37
|
+
task_description: str,
|
|
38
|
+
todos: list[str] | list[Todo],
|
|
39
|
+
deliverables: list[str] | list[Deliverable] | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Set the task, todos, and deliverables.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
task_description: Overall task description
|
|
45
|
+
todos: List of todo items (strings or Todo objects)
|
|
46
|
+
deliverables: Optional list of deliverables (strings or Deliverable objects)
|
|
47
|
+
"""
|
|
48
|
+
self.task_description = task_description
|
|
49
|
+
|
|
50
|
+
# Convert todos
|
|
51
|
+
self.todos = []
|
|
52
|
+
for todo in todos:
|
|
53
|
+
if isinstance(todo, str):
|
|
54
|
+
self.todos.append(Todo(description=todo))
|
|
55
|
+
else:
|
|
56
|
+
self.todos.append(todo)
|
|
57
|
+
|
|
58
|
+
# Convert deliverables
|
|
59
|
+
self.deliverables = []
|
|
60
|
+
if deliverables:
|
|
61
|
+
for deliverable in deliverables:
|
|
62
|
+
if isinstance(deliverable, str):
|
|
63
|
+
self.deliverables.append(Deliverable(description=deliverable))
|
|
64
|
+
else:
|
|
65
|
+
self.deliverables.append(deliverable)
|
|
66
|
+
|
|
67
|
+
def get_current_todo(self) -> tuple[Todo | None, int]:
|
|
68
|
+
"""Get the next pending or in-progress todo.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Tuple of (Todo object, index) or (None, -1) if no todos remain
|
|
72
|
+
"""
|
|
73
|
+
for idx, todo in enumerate(self.todos):
|
|
74
|
+
if todo.status in [TodoStatus.PENDING, TodoStatus.IN_PROGRESS]:
|
|
75
|
+
return todo, idx
|
|
76
|
+
return None, -1
|
|
77
|
+
|
|
78
|
+
def update_todo(
|
|
79
|
+
self,
|
|
80
|
+
index: int,
|
|
81
|
+
status: TodoStatus | str,
|
|
82
|
+
summary: str | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Update a todo's status and optionally its summary.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
index: Index of the todo to update
|
|
88
|
+
status: New status for the todo
|
|
89
|
+
summary: Optional execution summary
|
|
90
|
+
"""
|
|
91
|
+
if 0 <= index < len(self.todos):
|
|
92
|
+
if isinstance(status, str):
|
|
93
|
+
status = TodoStatus(status)
|
|
94
|
+
self.todos[index].status = status
|
|
95
|
+
if summary:
|
|
96
|
+
self.todo_execution_summaries[index] = summary
|
|
97
|
+
|
|
98
|
+
def add_history(
|
|
99
|
+
self,
|
|
100
|
+
todo_index: int,
|
|
101
|
+
actions: list[Action],
|
|
102
|
+
summary: str | None = None,
|
|
103
|
+
completed: bool = False,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Add execution history for a todo.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
todo_index: Index of the todo
|
|
109
|
+
actions: List of actions taken
|
|
110
|
+
summary: Optional execution summary
|
|
111
|
+
completed: Whether the todo was completed
|
|
112
|
+
"""
|
|
113
|
+
if 0 <= todo_index < len(self.todos):
|
|
114
|
+
self.history.append(
|
|
115
|
+
TodoHistory(
|
|
116
|
+
todo_index=todo_index,
|
|
117
|
+
todo=self.todos[todo_index].description,
|
|
118
|
+
actions=actions,
|
|
119
|
+
summary=summary,
|
|
120
|
+
completed=completed,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def get_context(self) -> dict[str, Any]:
|
|
125
|
+
"""Get the full context for planning/reflection.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Dictionary containing all memory state
|
|
129
|
+
"""
|
|
130
|
+
return {
|
|
131
|
+
"task_description": self.task_description,
|
|
132
|
+
"todos": [
|
|
133
|
+
{"index": i, "description": t.description, "status": t.status}
|
|
134
|
+
for i, t in enumerate(self.todos)
|
|
135
|
+
],
|
|
136
|
+
"deliverables": [
|
|
137
|
+
{"description": d.description, "achieved": d.achieved}
|
|
138
|
+
for d in self.deliverables
|
|
139
|
+
],
|
|
140
|
+
"history": [
|
|
141
|
+
{
|
|
142
|
+
"todo_index": h.todo_index,
|
|
143
|
+
"todo": h.todo,
|
|
144
|
+
"action_count": len(h.actions),
|
|
145
|
+
"summary": h.summary,
|
|
146
|
+
"completed": h.completed,
|
|
147
|
+
}
|
|
148
|
+
for h in self.history
|
|
149
|
+
],
|
|
150
|
+
"task_execution_summary": self.task_execution_summary,
|
|
151
|
+
"todo_execution_summaries": self.todo_execution_summaries,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
def get_todo_status_summary(self) -> dict[str, int]:
|
|
155
|
+
"""Get a summary of todo statuses.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary with counts for each status
|
|
159
|
+
"""
|
|
160
|
+
summary = {
|
|
161
|
+
TodoStatus.PENDING: 0,
|
|
162
|
+
TodoStatus.IN_PROGRESS: 0,
|
|
163
|
+
TodoStatus.COMPLETED: 0,
|
|
164
|
+
TodoStatus.SKIPPED: 0,
|
|
165
|
+
}
|
|
166
|
+
for todo in self.todos:
|
|
167
|
+
summary[todo.status] += 1
|
|
168
|
+
return summary
|
|
169
|
+
|
|
170
|
+
def append_todo(self, description: str) -> None:
|
|
171
|
+
"""Append a new todo to the list.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
description: Description of the new todo
|
|
175
|
+
"""
|
|
176
|
+
self.todos.append(Todo(description=description))
|
|
177
|
+
|
|
178
|
+
def append_deliverable(self, description: str) -> None:
|
|
179
|
+
"""Append a new deliverable to the list.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
description: Description of the new deliverable
|
|
183
|
+
"""
|
|
184
|
+
self.deliverables.append(Deliverable(description=description))
|