oagi-core 0.11.0__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 +1 -0
- oagi/agent/observer/exporters.py +6 -0
- oagi/agent/observer/report_template.html +19 -0
- oagi/agent/tasker/planner.py +14 -12
- oagi/agent/tasker/taskee_agent.py +7 -3
- oagi/client/async_.py +54 -96
- oagi/client/base.py +81 -133
- oagi/client/sync.py +52 -99
- oagi/constants.py +7 -2
- oagi/task/__init__.py +22 -8
- 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/utils/__init__.py +12 -0
- oagi/utils/output_parser.py +166 -0
- oagi/utils/prompt_builder.py +44 -0
- {oagi_core-0.11.0.dist-info → oagi_core-0.12.0.dist-info}/METADATA +57 -10
- {oagi_core-0.11.0.dist-info → oagi_core-0.12.0.dist-info}/RECORD +29 -25
- oagi/task/base.py +0 -158
- {oagi_core-0.11.0.dist-info → oagi_core-0.12.0.dist-info}/WHEEL +0 -0
- {oagi_core-0.11.0.dist-info → oagi_core-0.12.0.dist-info}/entry_points.txt +0 -0
- {oagi_core-0.11.0.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
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">';
|
oagi/agent/tasker/planner.py
CHANGED
|
@@ -122,7 +122,7 @@ class Planner:
|
|
|
122
122
|
screenshot: Image | URL | None = None,
|
|
123
123
|
memory: PlannerMemory | None = None,
|
|
124
124
|
todo_index: int | None = None,
|
|
125
|
-
) -> PlannerOutput:
|
|
125
|
+
) -> tuple[PlannerOutput, str | None]:
|
|
126
126
|
"""Generate initial plan for a todo.
|
|
127
127
|
|
|
128
128
|
Args:
|
|
@@ -133,7 +133,7 @@ class Planner:
|
|
|
133
133
|
todo_index: Optional todo index for formatting internal context
|
|
134
134
|
|
|
135
135
|
Returns:
|
|
136
|
-
|
|
136
|
+
Tuple of (PlannerOutput, request_id) where request_id is from API response
|
|
137
137
|
"""
|
|
138
138
|
# Ensure we have a client
|
|
139
139
|
client = self._ensure_client()
|
|
@@ -170,8 +170,8 @@ class Planner:
|
|
|
170
170
|
current_screenshot=screenshot_uuid,
|
|
171
171
|
)
|
|
172
172
|
|
|
173
|
-
# Parse response
|
|
174
|
-
return self._parse_planner_output(response.response)
|
|
173
|
+
# Parse response and return with request_id
|
|
174
|
+
return self._parse_planner_output(response.response), response.request_id
|
|
175
175
|
|
|
176
176
|
async def reflect(
|
|
177
177
|
self,
|
|
@@ -182,7 +182,7 @@ class Planner:
|
|
|
182
182
|
todo_index: int | None = None,
|
|
183
183
|
current_instruction: str | None = None,
|
|
184
184
|
reflection_interval: int = DEFAULT_REFLECTION_INTERVAL,
|
|
185
|
-
) -> ReflectionOutput:
|
|
185
|
+
) -> tuple[ReflectionOutput, str | None]:
|
|
186
186
|
"""Reflect on recent actions and progress.
|
|
187
187
|
|
|
188
188
|
Args:
|
|
@@ -195,7 +195,7 @@ class Planner:
|
|
|
195
195
|
reflection_interval: Window size for recent actions/screenshots
|
|
196
196
|
|
|
197
197
|
Returns:
|
|
198
|
-
ReflectionOutput
|
|
198
|
+
Tuple of (ReflectionOutput, request_id) where request_id is from API response
|
|
199
199
|
"""
|
|
200
200
|
# Ensure we have a client
|
|
201
201
|
client = self._ensure_client()
|
|
@@ -260,8 +260,8 @@ class Planner:
|
|
|
260
260
|
prior_notes=prior_notes,
|
|
261
261
|
)
|
|
262
262
|
|
|
263
|
-
# Parse response
|
|
264
|
-
return self._parse_reflection_output(response.response)
|
|
263
|
+
# Parse response and return with request_id
|
|
264
|
+
return self._parse_reflection_output(response.response), response.request_id
|
|
265
265
|
|
|
266
266
|
async def summarize(
|
|
267
267
|
self,
|
|
@@ -269,7 +269,7 @@ class Planner:
|
|
|
269
269
|
context: dict[str, Any],
|
|
270
270
|
memory: PlannerMemory | None = None,
|
|
271
271
|
todo_index: int | None = None,
|
|
272
|
-
) -> str:
|
|
272
|
+
) -> tuple[str, str | None]:
|
|
273
273
|
"""Generate execution summary.
|
|
274
274
|
|
|
275
275
|
Args:
|
|
@@ -279,7 +279,7 @@ class Planner:
|
|
|
279
279
|
todo_index: Optional todo index for formatting internal context
|
|
280
280
|
|
|
281
281
|
Returns:
|
|
282
|
-
|
|
282
|
+
Tuple of (summary string, request_id) where request_id is from API response
|
|
283
283
|
"""
|
|
284
284
|
# Ensure we have a client
|
|
285
285
|
client = self._ensure_client()
|
|
@@ -314,9 +314,11 @@ class Planner:
|
|
|
314
314
|
# Parse response and extract summary
|
|
315
315
|
try:
|
|
316
316
|
result = json.loads(response.response)
|
|
317
|
-
|
|
317
|
+
summary = result.get("task_summary", response.response)
|
|
318
318
|
except json.JSONDecodeError:
|
|
319
|
-
|
|
319
|
+
summary = response.response
|
|
320
|
+
|
|
321
|
+
return summary, response.request_id
|
|
320
322
|
|
|
321
323
|
def _format_execution_notes(self, context: dict[str, Any]) -> str:
|
|
322
324
|
"""Format execution history notes.
|
|
@@ -200,7 +200,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
200
200
|
context = self._get_context()
|
|
201
201
|
|
|
202
202
|
# Generate plan using LLM planner
|
|
203
|
-
plan_output = await self.planner.initial_plan(
|
|
203
|
+
plan_output, request_id = await self.planner.initial_plan(
|
|
204
204
|
self.current_todo,
|
|
205
205
|
context,
|
|
206
206
|
screenshot,
|
|
@@ -224,6 +224,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
224
224
|
image=_serialize_image(screenshot),
|
|
225
225
|
reasoning=plan_output.reasoning,
|
|
226
226
|
result=plan_output.instruction,
|
|
227
|
+
request_id=request_id,
|
|
227
228
|
)
|
|
228
229
|
)
|
|
229
230
|
|
|
@@ -309,6 +310,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
309
310
|
step_num=self.total_actions + 1,
|
|
310
311
|
image=_serialize_image(screenshot),
|
|
311
312
|
step=step,
|
|
313
|
+
task_id=self.actor.task_id,
|
|
312
314
|
)
|
|
313
315
|
)
|
|
314
316
|
|
|
@@ -393,7 +395,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
393
395
|
recent_actions = self.actions[-self.since_reflection :]
|
|
394
396
|
|
|
395
397
|
# Reflect using planner
|
|
396
|
-
reflection = await self.planner.reflect(
|
|
398
|
+
reflection, request_id = await self.planner.reflect(
|
|
397
399
|
recent_actions,
|
|
398
400
|
context,
|
|
399
401
|
screenshot,
|
|
@@ -424,6 +426,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
424
426
|
image=_serialize_image(screenshot),
|
|
425
427
|
reasoning=reflection.reasoning,
|
|
426
428
|
result=decision,
|
|
429
|
+
request_id=request_id,
|
|
427
430
|
)
|
|
428
431
|
)
|
|
429
432
|
|
|
@@ -456,7 +459,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
456
459
|
context = self._get_context()
|
|
457
460
|
context["current_todo"] = self.current_todo
|
|
458
461
|
|
|
459
|
-
summary = await self.planner.summarize(
|
|
462
|
+
summary, request_id = await self.planner.summarize(
|
|
460
463
|
self.actions,
|
|
461
464
|
context,
|
|
462
465
|
memory=self.external_memory,
|
|
@@ -478,6 +481,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
478
481
|
image=None,
|
|
479
482
|
reasoning=summary,
|
|
480
483
|
result=None,
|
|
484
|
+
request_id=request_id,
|
|
481
485
|
)
|
|
482
486
|
)
|
|
483
487
|
|