oagi-core 0.10.3__py3-none-any.whl → 0.12.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 +1 -3
- oagi/actor/__init__.py +21 -0
- oagi/{task → actor}/async_.py +23 -7
- oagi/{task → actor}/async_short.py +1 -1
- oagi/actor/base.py +222 -0
- oagi/{task → actor}/short.py +1 -1
- oagi/{task → actor}/sync.py +21 -5
- oagi/agent/default.py +5 -0
- oagi/agent/factories.py +75 -3
- oagi/agent/observer/exporters.py +6 -0
- oagi/agent/observer/report_template.html +19 -0
- oagi/agent/tasker/planner.py +31 -19
- oagi/agent/tasker/taskee_agent.py +26 -7
- oagi/agent/tasker/tasker_agent.py +4 -0
- oagi/cli/agent.py +54 -30
- oagi/client/async_.py +54 -96
- oagi/client/base.py +81 -133
- oagi/client/sync.py +52 -99
- oagi/constants.py +7 -2
- oagi/handler/__init__.py +16 -0
- oagi/handler/_macos.py +137 -0
- oagi/handler/_windows.py +101 -0
- oagi/handler/async_pyautogui_action_handler.py +8 -0
- oagi/handler/capslock_manager.py +55 -0
- oagi/handler/pyautogui_action_handler.py +21 -39
- oagi/server/session_store.py +3 -3
- oagi/server/socketio_server.py +4 -4
- oagi/task/__init__.py +22 -8
- oagi/types/__init__.py +2 -1
- oagi/types/models/__init__.py +0 -2
- oagi/types/models/action.py +4 -1
- oagi/types/models/client.py +1 -17
- oagi/types/step_observer.py +2 -0
- oagi/types/url.py +25 -0
- oagi/utils/__init__.py +12 -0
- oagi/utils/output_parser.py +166 -0
- oagi/utils/prompt_builder.py +44 -0
- {oagi_core-0.10.3.dist-info → oagi_core-0.12.0.dist-info}/METADATA +90 -10
- oagi_core-0.12.0.dist-info/RECORD +76 -0
- oagi/task/base.py +0 -158
- oagi_core-0.10.3.dist-info/RECORD +0 -70
- {oagi_core-0.10.3.dist-info → oagi_core-0.12.0.dist-info}/WHEEL +0 -0
- {oagi_core-0.10.3.dist-info → oagi_core-0.12.0.dist-info}/entry_points.txt +0 -0
- {oagi_core-0.10.3.dist-info → oagi_core-0.12.0.dist-info}/licenses/LICENSE +0 -0
oagi/__init__.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import importlib
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
+
from oagi.actor import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
|
|
11
12
|
from oagi.client import AsyncClient, SyncClient
|
|
12
13
|
from oagi.exceptions import (
|
|
13
14
|
APIError,
|
|
@@ -22,13 +23,11 @@ from oagi.exceptions import (
|
|
|
22
23
|
ValidationError,
|
|
23
24
|
check_optional_dependency,
|
|
24
25
|
)
|
|
25
|
-
from oagi.task import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
|
|
26
26
|
from oagi.types import ImageConfig
|
|
27
27
|
from oagi.types.models import (
|
|
28
28
|
ErrorDetail,
|
|
29
29
|
ErrorResponse,
|
|
30
30
|
GenerateResponse,
|
|
31
|
-
LLMResponse,
|
|
32
31
|
UploadFileResponse,
|
|
33
32
|
)
|
|
34
33
|
|
|
@@ -116,7 +115,6 @@ __all__ = [
|
|
|
116
115
|
# Configuration
|
|
117
116
|
"ImageConfig",
|
|
118
117
|
# Response models
|
|
119
|
-
"LLMResponse",
|
|
120
118
|
"GenerateResponse",
|
|
121
119
|
"UploadFileResponse",
|
|
122
120
|
"ErrorResponse",
|
oagi/actor/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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 .async_ import AsyncActor, AsyncTask
|
|
10
|
+
from .async_short import AsyncShortTask
|
|
11
|
+
from .short import ShortTask
|
|
12
|
+
from .sync import Actor, Task
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Actor",
|
|
16
|
+
"AsyncActor",
|
|
17
|
+
"Task", # Deprecated: Use Actor instead
|
|
18
|
+
"AsyncTask", # Deprecated: Use AsyncActor instead
|
|
19
|
+
"ShortTask", # Deprecated
|
|
20
|
+
"AsyncShortTask", # Deprecated
|
|
21
|
+
]
|
oagi/{task → actor}/async_.py
RENAMED
|
@@ -10,9 +10,12 @@ import warnings
|
|
|
10
10
|
|
|
11
11
|
from ..client import AsyncClient
|
|
12
12
|
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
13
|
+
from ..logging import get_logger
|
|
13
14
|
from ..types import URL, Image, Step
|
|
14
15
|
from .base import BaseActor
|
|
15
16
|
|
|
17
|
+
logger = get_logger("actor.async")
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class AsyncActor(BaseActor):
|
|
18
21
|
"""Async base class for task automation with the OAGI API."""
|
|
@@ -51,20 +54,33 @@ class AsyncActor(BaseActor):
|
|
|
51
54
|
"""Send screenshot to the server and get the next actions.
|
|
52
55
|
|
|
53
56
|
Args:
|
|
54
|
-
screenshot: Screenshot as Image object or raw bytes
|
|
55
|
-
instruction: Optional additional instruction for this step
|
|
57
|
+
screenshot: Screenshot as Image object, URL string, or raw bytes
|
|
58
|
+
instruction: Optional additional instruction for this step (currently unused)
|
|
56
59
|
temperature: Sampling temperature for this step (overrides task default if provided)
|
|
57
60
|
|
|
58
61
|
Returns:
|
|
59
62
|
Step: The actions and reasoning for this step
|
|
60
63
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
+
self._validate_and_increment_step()
|
|
65
|
+
self._log_step_execution(prefix="async ")
|
|
64
66
|
|
|
65
67
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
screenshot_url = await self._ensure_screenshot_url_async(
|
|
69
|
+
screenshot, self.client
|
|
70
|
+
)
|
|
71
|
+
self._add_user_message_to_history(screenshot_url, self._build_step_prompt())
|
|
72
|
+
|
|
73
|
+
step, raw_output, usage = await self.client.chat_completion(
|
|
74
|
+
model=self.model,
|
|
75
|
+
messages=self.message_history,
|
|
76
|
+
temperature=self._get_temperature(temperature),
|
|
77
|
+
task_id=self.task_id,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._add_assistant_message_to_history(raw_output)
|
|
81
|
+
self._log_step_completion(step, prefix="Async ")
|
|
82
|
+
return step
|
|
83
|
+
|
|
68
84
|
except Exception as e:
|
|
69
85
|
self._handle_step_error(e, prefix="async ")
|
|
70
86
|
|
|
@@ -14,7 +14,7 @@ from ..types import AsyncActionHandler, AsyncImageProvider
|
|
|
14
14
|
from .async_ import AsyncActor
|
|
15
15
|
from .base import BaseAutoMode
|
|
16
16
|
|
|
17
|
-
logger = get_logger("
|
|
17
|
+
logger = get_logger("async_short_actor")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class AsyncShortTask(AsyncActor, BaseAutoMode):
|
oagi/actor/base.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
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 uuid import uuid4
|
|
10
|
+
|
|
11
|
+
from ..constants import (
|
|
12
|
+
DEFAULT_MAX_STEPS,
|
|
13
|
+
MAX_STEPS_ACTOR,
|
|
14
|
+
MAX_STEPS_THINKER,
|
|
15
|
+
MODEL_THINKER,
|
|
16
|
+
)
|
|
17
|
+
from ..logging import get_logger
|
|
18
|
+
from ..types import URL, Image, Step
|
|
19
|
+
from ..utils.prompt_builder import build_prompt
|
|
20
|
+
|
|
21
|
+
logger = get_logger("actor.base")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseActor:
|
|
25
|
+
"""Base class with shared task management logic for sync/async actors."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
api_key: str | None,
|
|
30
|
+
base_url: str | None,
|
|
31
|
+
model: str,
|
|
32
|
+
temperature: float | None,
|
|
33
|
+
):
|
|
34
|
+
self.task_id: str = uuid4().hex # Client-side generated UUID
|
|
35
|
+
self.task_description: str | None = None
|
|
36
|
+
self.model = model
|
|
37
|
+
self.temperature = temperature
|
|
38
|
+
self.message_history: list = [] # OpenAI-compatible message history
|
|
39
|
+
self.max_steps: int = DEFAULT_MAX_STEPS
|
|
40
|
+
self.current_step: int = 0 # Current step counter
|
|
41
|
+
# Client will be set by subclasses
|
|
42
|
+
self.api_key: str | None = None
|
|
43
|
+
self.base_url: str | None = None
|
|
44
|
+
|
|
45
|
+
def _validate_max_steps(self, max_steps: int) -> int:
|
|
46
|
+
"""Validate and cap max_steps based on model type.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
max_steps: Requested maximum number of steps
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Validated max_steps (capped to model limit if exceeded)
|
|
53
|
+
"""
|
|
54
|
+
limit = MAX_STEPS_THINKER if self.model == MODEL_THINKER else MAX_STEPS_ACTOR
|
|
55
|
+
if max_steps > limit:
|
|
56
|
+
logger.warning(
|
|
57
|
+
f"max_steps ({max_steps}) exceeds limit for model '{self.model}'. "
|
|
58
|
+
f"Capping to {limit}."
|
|
59
|
+
)
|
|
60
|
+
return limit
|
|
61
|
+
return max_steps
|
|
62
|
+
|
|
63
|
+
def _prepare_init_task(
|
|
64
|
+
self,
|
|
65
|
+
task_desc: str,
|
|
66
|
+
max_steps: int,
|
|
67
|
+
):
|
|
68
|
+
"""Prepare task initialization.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
task_desc: Task description
|
|
72
|
+
max_steps: Maximum number of steps
|
|
73
|
+
"""
|
|
74
|
+
self.task_id = uuid4().hex
|
|
75
|
+
self.task_description = task_desc
|
|
76
|
+
self.message_history = []
|
|
77
|
+
self.max_steps = self._validate_max_steps(max_steps)
|
|
78
|
+
self.current_step = 0
|
|
79
|
+
logger.info(f"Task initialized: '{task_desc}' (max_steps: {self.max_steps})")
|
|
80
|
+
|
|
81
|
+
def _validate_and_increment_step(self):
|
|
82
|
+
if not self.task_description:
|
|
83
|
+
raise ValueError("Task description must be set. Call init_task() first.")
|
|
84
|
+
if self.current_step >= self.max_steps:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Max steps limit ({self.max_steps}) reached. "
|
|
87
|
+
"Call init_task() to start a new task."
|
|
88
|
+
)
|
|
89
|
+
self.current_step += 1
|
|
90
|
+
|
|
91
|
+
def _get_temperature(self, temperature: float | None) -> float | None:
|
|
92
|
+
return temperature if temperature is not None else self.temperature
|
|
93
|
+
|
|
94
|
+
def _prepare_screenshot(self, screenshot: Image | bytes) -> bytes:
|
|
95
|
+
if isinstance(screenshot, Image):
|
|
96
|
+
return screenshot.read()
|
|
97
|
+
return screenshot
|
|
98
|
+
|
|
99
|
+
def _get_screenshot_url(self, screenshot: Image | URL | bytes) -> str | None:
|
|
100
|
+
"""Get screenshot URL if it's a string, otherwise return None."""
|
|
101
|
+
if isinstance(screenshot, str):
|
|
102
|
+
return screenshot
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def _ensure_screenshot_url_sync(
|
|
106
|
+
self, screenshot: Image | URL | bytes, client
|
|
107
|
+
) -> str:
|
|
108
|
+
"""Get screenshot URL, uploading to S3 if needed (sync version).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
screenshot: Screenshot as Image object, URL string, or raw bytes
|
|
112
|
+
client: SyncClient instance for S3 upload
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Screenshot URL (either direct or from S3 upload)
|
|
116
|
+
"""
|
|
117
|
+
screenshot_url = self._get_screenshot_url(screenshot)
|
|
118
|
+
if screenshot_url is None:
|
|
119
|
+
screenshot_bytes = self._prepare_screenshot(screenshot)
|
|
120
|
+
upload_response = client.put_s3_presigned_url(screenshot_bytes)
|
|
121
|
+
screenshot_url = upload_response.download_url
|
|
122
|
+
return screenshot_url
|
|
123
|
+
|
|
124
|
+
async def _ensure_screenshot_url_async(
|
|
125
|
+
self, screenshot: Image | URL | bytes, client
|
|
126
|
+
) -> str:
|
|
127
|
+
"""Get screenshot URL, uploading to S3 if needed (async version).
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
screenshot: Screenshot as Image object, URL string, or raw bytes
|
|
131
|
+
client: AsyncClient instance for S3 upload
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Screenshot URL (either direct or from S3 upload)
|
|
135
|
+
"""
|
|
136
|
+
screenshot_url = self._get_screenshot_url(screenshot)
|
|
137
|
+
if screenshot_url is None:
|
|
138
|
+
screenshot_bytes = self._prepare_screenshot(screenshot)
|
|
139
|
+
upload_response = await client.put_s3_presigned_url(screenshot_bytes)
|
|
140
|
+
screenshot_url = upload_response.download_url
|
|
141
|
+
return screenshot_url
|
|
142
|
+
|
|
143
|
+
def _add_user_message_to_history(
|
|
144
|
+
self, screenshot_url: str, prompt: str | None = None
|
|
145
|
+
):
|
|
146
|
+
"""Add user message with screenshot to message history.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
screenshot_url: URL of the screenshot
|
|
150
|
+
prompt: Optional prompt text (for first message only)
|
|
151
|
+
"""
|
|
152
|
+
content = []
|
|
153
|
+
if prompt:
|
|
154
|
+
content.append({"type": "text", "text": prompt})
|
|
155
|
+
content.append({"type": "image_url", "image_url": {"url": screenshot_url}})
|
|
156
|
+
|
|
157
|
+
self.message_history.append(
|
|
158
|
+
{
|
|
159
|
+
"role": "user",
|
|
160
|
+
"content": content,
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _add_assistant_message_to_history(self, raw_output: str):
|
|
165
|
+
"""Add assistant response to message history.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
raw_output: Raw model output string
|
|
169
|
+
"""
|
|
170
|
+
if raw_output:
|
|
171
|
+
self.message_history.append(
|
|
172
|
+
{
|
|
173
|
+
"role": "assistant",
|
|
174
|
+
"content": raw_output,
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def _build_step_prompt(self) -> str | None:
|
|
179
|
+
"""Build prompt for first message only."""
|
|
180
|
+
if len(self.message_history) == 0:
|
|
181
|
+
return build_prompt(self.task_description)
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
def _log_step_completion(self, step: Step, prefix: str = "") -> None:
|
|
185
|
+
"""Log step completion status."""
|
|
186
|
+
if step.stop:
|
|
187
|
+
logger.info(f"{prefix}Task completed.")
|
|
188
|
+
else:
|
|
189
|
+
logger.debug(f"{prefix}Step completed with {len(step.actions)} actions")
|
|
190
|
+
|
|
191
|
+
def _log_step_execution(self, prefix: str = ""):
|
|
192
|
+
logger.debug(f"Executing {prefix}step for task: '{self.task_description}'")
|
|
193
|
+
|
|
194
|
+
def _handle_step_error(self, error: Exception, prefix: str = ""):
|
|
195
|
+
logger.error(f"Error during {prefix}step execution: {error}")
|
|
196
|
+
raise
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class BaseAutoMode:
|
|
200
|
+
"""Base class with shared auto_mode logic for ShortTask implementations."""
|
|
201
|
+
|
|
202
|
+
def _log_auto_mode_start(self, task_desc: str, max_steps: int, prefix: str = ""):
|
|
203
|
+
logger.info(
|
|
204
|
+
f"Starting {prefix}auto mode for task: '{task_desc}' (max_steps: {max_steps})"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def _log_auto_mode_step(self, step_num: int, max_steps: int, prefix: str = ""):
|
|
208
|
+
logger.debug(f"{prefix.capitalize()}auto mode step {step_num}/{max_steps}")
|
|
209
|
+
|
|
210
|
+
def _log_auto_mode_actions(self, action_count: int, prefix: str = ""):
|
|
211
|
+
verb = "asynchronously" if "async" in prefix else ""
|
|
212
|
+
logger.debug(f"Executing {action_count} actions {verb}".strip())
|
|
213
|
+
|
|
214
|
+
def _log_auto_mode_completion(self, steps: int, prefix: str = ""):
|
|
215
|
+
logger.info(
|
|
216
|
+
f"{prefix.capitalize()}auto mode completed successfully after {steps} steps"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def _log_auto_mode_max_steps(self, max_steps: int, prefix: str = ""):
|
|
220
|
+
logger.warning(
|
|
221
|
+
f"{prefix.capitalize()}auto mode reached max steps ({max_steps}) without completion"
|
|
222
|
+
)
|
oagi/{task → actor}/short.py
RENAMED
oagi/{task → actor}/sync.py
RENAMED
|
@@ -10,9 +10,12 @@ import warnings
|
|
|
10
10
|
|
|
11
11
|
from ..client import SyncClient
|
|
12
12
|
from ..constants import DEFAULT_MAX_STEPS, MODEL_ACTOR
|
|
13
|
+
from ..logging import get_logger
|
|
13
14
|
from ..types import URL, Image, Step
|
|
14
15
|
from .base import BaseActor
|
|
15
16
|
|
|
17
|
+
logger = get_logger("actor.sync")
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class Actor(BaseActor):
|
|
18
21
|
"""Base class for task automation with the OAGI API."""
|
|
@@ -51,18 +54,31 @@ class Actor(BaseActor):
|
|
|
51
54
|
"""Send screenshot to the server and get the next actions.
|
|
52
55
|
|
|
53
56
|
Args:
|
|
54
|
-
screenshot: Screenshot as Image object or raw bytes
|
|
55
|
-
instruction: Optional additional instruction for this step
|
|
57
|
+
screenshot: Screenshot as Image object, URL string, or raw bytes
|
|
58
|
+
instruction: Optional additional instruction for this step (currently unused)
|
|
56
59
|
temperature: Sampling temperature for this step (overrides task default if provided)
|
|
57
60
|
|
|
58
61
|
Returns:
|
|
59
62
|
Step: The actions and reasoning for this step
|
|
60
63
|
"""
|
|
61
|
-
|
|
64
|
+
self._validate_and_increment_step()
|
|
65
|
+
self._log_step_execution()
|
|
62
66
|
|
|
63
67
|
try:
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
screenshot_url = self._ensure_screenshot_url_sync(screenshot, self.client)
|
|
69
|
+
self._add_user_message_to_history(screenshot_url, self._build_step_prompt())
|
|
70
|
+
|
|
71
|
+
step, raw_output, usage = self.client.chat_completion(
|
|
72
|
+
model=self.model,
|
|
73
|
+
messages=self.message_history,
|
|
74
|
+
temperature=self._get_temperature(temperature),
|
|
75
|
+
task_id=self.task_id,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self._add_assistant_message_to_history(raw_output)
|
|
79
|
+
self._log_step_completion(step)
|
|
80
|
+
return step
|
|
81
|
+
|
|
66
82
|
except Exception as e:
|
|
67
83
|
self._handle_step_error(e)
|
|
68
84
|
|
oagi/agent/default.py
CHANGED
|
@@ -16,6 +16,7 @@ from ..constants import (
|
|
|
16
16
|
DEFAULT_TEMPERATURE,
|
|
17
17
|
MODEL_ACTOR,
|
|
18
18
|
)
|
|
19
|
+
from ..handler import reset_handler
|
|
19
20
|
from ..types import (
|
|
20
21
|
ActionEvent,
|
|
21
22
|
AsyncActionHandler,
|
|
@@ -68,6 +69,9 @@ class AsyncDefaultAgent:
|
|
|
68
69
|
logger.info(f"Starting async task execution: {instruction}")
|
|
69
70
|
await self.actor.init_task(instruction, max_steps=self.max_steps)
|
|
70
71
|
|
|
72
|
+
# Reset handler state at automation start
|
|
73
|
+
reset_handler(action_handler)
|
|
74
|
+
|
|
71
75
|
for i in range(self.max_steps):
|
|
72
76
|
step_num = i + 1
|
|
73
77
|
logger.debug(f"Executing step {step_num}/{self.max_steps}")
|
|
@@ -89,6 +93,7 @@ class AsyncDefaultAgent:
|
|
|
89
93
|
step_num=step_num,
|
|
90
94
|
image=_serialize_image(image),
|
|
91
95
|
step=step,
|
|
96
|
+
task_id=self.actor.task_id,
|
|
92
97
|
)
|
|
93
98
|
)
|
|
94
99
|
|
oagi/agent/factories.py
CHANGED
|
@@ -65,8 +65,8 @@ def create_thinker_agent(
|
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
@async_agent_register(mode="tasker")
|
|
69
|
-
def
|
|
68
|
+
@async_agent_register(mode="tasker:cvs_appointment")
|
|
69
|
+
def create_cvs_appointment_agent(
|
|
70
70
|
api_key: str | None = None,
|
|
71
71
|
base_url: str | None = None,
|
|
72
72
|
model: str = MODEL_ACTOR,
|
|
@@ -75,6 +75,12 @@ def create_planner_agent(
|
|
|
75
75
|
reflection_interval: int = DEFAULT_REFLECTION_INTERVAL_TASKER,
|
|
76
76
|
step_observer: AsyncStepObserver | None = None,
|
|
77
77
|
step_delay: float = DEFAULT_STEP_DELAY,
|
|
78
|
+
# CVS-specific parameters
|
|
79
|
+
first_name: str = "First",
|
|
80
|
+
last_name: str = "Last",
|
|
81
|
+
email: str = "user@example.com",
|
|
82
|
+
birthday: str = "01-01-1990", # MM-DD-YYYY
|
|
83
|
+
zip_code: str = "00000",
|
|
78
84
|
) -> AsyncAgent:
|
|
79
85
|
tasker = TaskerAgent(
|
|
80
86
|
api_key=api_key,
|
|
@@ -86,5 +92,71 @@ def create_planner_agent(
|
|
|
86
92
|
step_observer=step_observer,
|
|
87
93
|
step_delay=step_delay,
|
|
88
94
|
)
|
|
89
|
-
|
|
95
|
+
|
|
96
|
+
month, day, year = birthday.split("-")
|
|
97
|
+
instruction = (
|
|
98
|
+
f"Schedule an appointment at CVS for {first_name} {last_name} "
|
|
99
|
+
f"with email {email} and birthday {birthday}"
|
|
100
|
+
)
|
|
101
|
+
todos = [
|
|
102
|
+
"Open a new tab, go to www.cvs.com, type 'flu shot' in the search bar and press enter, "
|
|
103
|
+
"wait for the page to load, then click on the button of Schedule vaccinations on the "
|
|
104
|
+
"top of the page",
|
|
105
|
+
f"Enter the first name '{first_name}', last name '{last_name}', and email '{email}' "
|
|
106
|
+
"in the form. Do not use any suggested autofills. Make sure the mobile phone number "
|
|
107
|
+
"is empty.",
|
|
108
|
+
f"Slightly scroll down to see the date of birth, enter Month '{month}', Day '{day}', "
|
|
109
|
+
f"and Year '{year}' in the form",
|
|
110
|
+
"Click on 'Continue as guest' button, wait for the page to load with wait, "
|
|
111
|
+
"click on 'Add vaccines' button, select 'Flu' and click on 'Add vaccines'",
|
|
112
|
+
f"Click on 'next' to enter the page with recommendation vaccines, then click on "
|
|
113
|
+
f"'next' again, until on the page of entering zip code, enter '{zip_code}', select "
|
|
114
|
+
"the first option from the dropdown menu, and click on 'Search'",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
tasker.set_task(instruction, todos)
|
|
118
|
+
return tasker
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@async_agent_register(mode="tasker:software_qa")
|
|
122
|
+
def create_software_qa_agent(
|
|
123
|
+
api_key: str | None = None,
|
|
124
|
+
base_url: str | None = None,
|
|
125
|
+
model: str = MODEL_ACTOR,
|
|
126
|
+
max_steps: int = DEFAULT_MAX_STEPS_TASKER,
|
|
127
|
+
temperature: float = DEFAULT_TEMPERATURE_LOW,
|
|
128
|
+
reflection_interval: int = DEFAULT_REFLECTION_INTERVAL_TASKER,
|
|
129
|
+
step_observer: AsyncStepObserver | None = None,
|
|
130
|
+
step_delay: float = DEFAULT_STEP_DELAY,
|
|
131
|
+
) -> AsyncAgent:
|
|
132
|
+
tasker = TaskerAgent(
|
|
133
|
+
api_key=api_key,
|
|
134
|
+
base_url=base_url,
|
|
135
|
+
model=model,
|
|
136
|
+
max_steps=max_steps,
|
|
137
|
+
temperature=temperature,
|
|
138
|
+
reflection_interval=reflection_interval,
|
|
139
|
+
step_observer=step_observer,
|
|
140
|
+
step_delay=step_delay,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
instruction = "QA: click through every sidebar button in the Nuclear Player UI"
|
|
144
|
+
todos = [
|
|
145
|
+
"Click on 'Dashboard' in the left sidebar",
|
|
146
|
+
"Click on 'Downloads' in the left sidebar",
|
|
147
|
+
"Click on 'Lyrics' in the left sidebar",
|
|
148
|
+
"Click on 'Plugins' in the left sidebar",
|
|
149
|
+
"Click on 'Search Results' in the left sidebar",
|
|
150
|
+
"Click on 'Settings' in the left sidebar",
|
|
151
|
+
"Click on 'Equalizer' in the left sidebar",
|
|
152
|
+
"Click on 'Visualizer' in the left sidebar",
|
|
153
|
+
"Click on 'Listening History' in the left sidebar",
|
|
154
|
+
"Click on 'Favorite Albums' in the left sidebar",
|
|
155
|
+
"Click on 'Favorite Tracks' in the left sidebar",
|
|
156
|
+
"Click on 'Favorite Artists' in the left sidebar",
|
|
157
|
+
"Click on 'Local Library' in the left sidebar",
|
|
158
|
+
"Click on 'Playlists' in the left sidebar",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
tasker.set_task(instruction, todos)
|
|
90
162
|
return tasker
|
oagi/agent/observer/exporters.py
CHANGED
|
@@ -98,6 +98,8 @@ def export_to_markdown(
|
|
|
98
98
|
case StepEvent():
|
|
99
99
|
lines.append(f"\n## Step {event.step_num}\n")
|
|
100
100
|
lines.append(f"**Time:** {timestamp}\n")
|
|
101
|
+
if event.task_id:
|
|
102
|
+
lines.append(f"**Task ID:** `{event.task_id}`\n")
|
|
101
103
|
|
|
102
104
|
if isinstance(event.image, bytes):
|
|
103
105
|
if images_dir:
|
|
@@ -159,6 +161,8 @@ def export_to_markdown(
|
|
|
159
161
|
}
|
|
160
162
|
phase_title = phase_titles.get(event.phase, event.phase.capitalize())
|
|
161
163
|
lines.append(f"\n### {phase_title} ({timestamp})\n")
|
|
164
|
+
if event.request_id:
|
|
165
|
+
lines.append(f"**Request ID:** `{event.request_id}`\n")
|
|
162
166
|
|
|
163
167
|
if event.image:
|
|
164
168
|
if isinstance(event.image, bytes):
|
|
@@ -227,6 +231,7 @@ def _convert_events_for_html(events: list[ObserverEvent]) -> list[dict]:
|
|
|
227
231
|
"reason": event.step.reason,
|
|
228
232
|
"actions": actions_list,
|
|
229
233
|
"stop": event.step.stop,
|
|
234
|
+
"task_id": event.task_id,
|
|
230
235
|
}
|
|
231
236
|
)
|
|
232
237
|
|
|
@@ -275,6 +280,7 @@ def _convert_events_for_html(events: list[ObserverEvent]) -> list[dict]:
|
|
|
275
280
|
"image": image_data,
|
|
276
281
|
"reasoning": event.reasoning,
|
|
277
282
|
"result": event.result,
|
|
283
|
+
"request_id": event.request_id,
|
|
278
284
|
}
|
|
279
285
|
)
|
|
280
286
|
|
|
@@ -46,6 +46,19 @@
|
|
|
46
46
|
font-size: 0.9em;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
.task-id, .request-id {
|
|
50
|
+
color: #666;
|
|
51
|
+
font-size: 0.9em;
|
|
52
|
+
margin-left: 10px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.task-id code, .request-id code {
|
|
56
|
+
background: #e9ecef;
|
|
57
|
+
padding: 2px 6px;
|
|
58
|
+
border-radius: 3px;
|
|
59
|
+
font-family: monospace;
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
.screenshot-container {
|
|
50
63
|
position: relative;
|
|
51
64
|
display: inline-block;
|
|
@@ -339,6 +352,9 @@
|
|
|
339
352
|
html += '<div class="step">';
|
|
340
353
|
html += `<h2>Step ${event.step_num}</h2>`;
|
|
341
354
|
html += `<span class="timestamp">${timestamp}</span>`;
|
|
355
|
+
if (event.task_id) {
|
|
356
|
+
html += ` <span class="task-id">Task ID: <code>${event.task_id}</code></span>`;
|
|
357
|
+
}
|
|
342
358
|
|
|
343
359
|
if (event.image) {
|
|
344
360
|
const actionsJson = JSON.stringify(event.action_coords || []).replace(/"/g, '"');
|
|
@@ -409,6 +425,9 @@
|
|
|
409
425
|
html += '<div class="plan">';
|
|
410
426
|
html += `<h3>${phaseTitle}</h3>`;
|
|
411
427
|
html += `<span class="timestamp">${timestamp}</span>`;
|
|
428
|
+
if (event.request_id) {
|
|
429
|
+
html += ` <span class="request-id">Request ID: <code>${event.request_id}</code></span>`;
|
|
430
|
+
}
|
|
412
431
|
|
|
413
432
|
if (event.image) {
|
|
414
433
|
html += '<div class="screenshot-container">';
|