oagi-core 0.9.0__py3-none-any.whl → 0.9.2__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 +13 -16
- oagi/agent/default.py +26 -10
- oagi/agent/factories.py +5 -0
- oagi/agent/tasker/planner.py +11 -2
- oagi/agent/tasker/taskee_agent.py +45 -20
- oagi/agent/tasker/tasker_agent.py +11 -7
- oagi/cli/agent.py +39 -21
- oagi/cli/display.py +56 -0
- oagi/cli/tracking.py +45 -0
- oagi/cli/utils.py +11 -4
- oagi/client/base.py +3 -7
- oagi/handler/__init__.py +24 -0
- oagi/handler/_macos.py +55 -0
- oagi/{async_pyautogui_action_handler.py → handler/async_pyautogui_action_handler.py} +1 -1
- oagi/{async_screenshot_maker.py → handler/async_screenshot_maker.py} +1 -1
- oagi/{pil_image.py → handler/pil_image.py} +2 -2
- oagi/{pyautogui_action_handler.py → handler/pyautogui_action_handler.py} +14 -4
- oagi/{screenshot_maker.py → handler/screenshot_maker.py} +2 -2
- oagi/logging.py +8 -0
- oagi/server/config.py +3 -3
- oagi/server/models.py +1 -1
- oagi/server/socketio_server.py +1 -1
- oagi/task/__init__.py +10 -3
- oagi/task/async_.py +27 -2
- oagi/task/async_short.py +16 -4
- oagi/task/base.py +2 -0
- oagi/task/short.py +16 -4
- oagi/task/sync.py +27 -2
- oagi/types/__init__.py +2 -0
- oagi/types/step_observer.py +34 -0
- {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/METADATA +4 -29
- oagi_core-0.9.2.dist-info/RECORD +63 -0
- oagi/async_single_step.py +0 -85
- oagi/single_step.py +0 -87
- oagi_core-0.9.0.dist-info/RECORD +0 -60
- {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/WHEEL +0 -0
- {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/entry_points.txt +0 -0
- {oagi_core-0.9.0.dist-info → oagi_core-0.9.2.dist-info}/licenses/LICENSE +0 -0
oagi/__init__.py
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
# -----------------------------------------------------------------------------
|
|
8
8
|
import importlib
|
|
9
9
|
|
|
10
|
-
from oagi.async_single_step import async_single_step
|
|
11
10
|
from oagi.client import AsyncClient, SyncClient
|
|
12
11
|
from oagi.exceptions import (
|
|
13
12
|
APIError,
|
|
@@ -21,8 +20,7 @@ from oagi.exceptions import (
|
|
|
21
20
|
ServerError,
|
|
22
21
|
ValidationError,
|
|
23
22
|
)
|
|
24
|
-
from oagi.
|
|
25
|
-
from oagi.task import AsyncShortTask, AsyncTask, ShortTask, Task
|
|
23
|
+
from oagi.task import Actor, AsyncActor, AsyncShortTask, AsyncTask, ShortTask, Task
|
|
26
24
|
from oagi.types import (
|
|
27
25
|
AsyncActionHandler,
|
|
28
26
|
AsyncImageProvider,
|
|
@@ -33,12 +31,12 @@ from oagi.types.models import ErrorDetail, ErrorResponse, LLMResponse
|
|
|
33
31
|
# Lazy imports for pyautogui-dependent modules
|
|
34
32
|
# These will only be imported when actually accessed
|
|
35
33
|
_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",
|
|
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",
|
|
42
40
|
# Agent modules (to avoid circular imports)
|
|
43
41
|
"TaskerAgent": "oagi.agent.tasker",
|
|
44
42
|
# Server modules (optional - requires server dependencies)
|
|
@@ -59,18 +57,17 @@ def __getattr__(name: str):
|
|
|
59
57
|
|
|
60
58
|
__all__ = [
|
|
61
59
|
# Core sync classes
|
|
62
|
-
"
|
|
63
|
-
"
|
|
60
|
+
"Actor",
|
|
61
|
+
"AsyncActor",
|
|
62
|
+
"Task", # Deprecated: Use Actor instead
|
|
63
|
+
"ShortTask", # Deprecated
|
|
64
64
|
"SyncClient",
|
|
65
65
|
# Core async classes
|
|
66
|
-
"AsyncTask",
|
|
67
|
-
"AsyncShortTask",
|
|
66
|
+
"AsyncTask", # Deprecated: Use AsyncActor instead
|
|
67
|
+
"AsyncShortTask", # Deprecated
|
|
68
68
|
"AsyncClient",
|
|
69
69
|
# Agent classes
|
|
70
70
|
"TaskerAgent",
|
|
71
|
-
# Functions
|
|
72
|
-
"single_step",
|
|
73
|
-
"async_single_step",
|
|
74
71
|
# Async protocols
|
|
75
72
|
"AsyncActionHandler",
|
|
76
73
|
"AsyncImageProvider",
|
oagi/agent/default.py
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
from .. import
|
|
11
|
+
from .. import AsyncActor
|
|
12
12
|
from ..types import (
|
|
13
13
|
AsyncActionHandler,
|
|
14
14
|
AsyncImageProvider,
|
|
15
|
+
AsyncStepObserver,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
@@ -24,15 +25,17 @@ class AsyncDefaultAgent:
|
|
|
24
25
|
self,
|
|
25
26
|
api_key: str | None = None,
|
|
26
27
|
base_url: str | None = None,
|
|
27
|
-
model: str = "lux-
|
|
28
|
-
max_steps: int =
|
|
29
|
-
temperature: float | None =
|
|
28
|
+
model: str = "lux-actor-1",
|
|
29
|
+
max_steps: int = 20,
|
|
30
|
+
temperature: float | None = 0.5,
|
|
31
|
+
step_observer: AsyncStepObserver | None = None,
|
|
30
32
|
):
|
|
31
33
|
self.api_key = api_key
|
|
32
34
|
self.base_url = base_url
|
|
33
35
|
self.model = model
|
|
34
36
|
self.max_steps = max_steps
|
|
35
37
|
self.temperature = temperature
|
|
38
|
+
self.step_observer = step_observer
|
|
36
39
|
|
|
37
40
|
async def execute(
|
|
38
41
|
self,
|
|
@@ -40,11 +43,11 @@ class AsyncDefaultAgent:
|
|
|
40
43
|
action_handler: AsyncActionHandler,
|
|
41
44
|
image_provider: AsyncImageProvider,
|
|
42
45
|
) -> bool:
|
|
43
|
-
async with
|
|
46
|
+
async with AsyncActor(
|
|
44
47
|
api_key=self.api_key, base_url=self.base_url, model=self.model
|
|
45
|
-
) as self.
|
|
48
|
+
) as self.actor:
|
|
46
49
|
logger.info(f"Starting async task execution: {instruction}")
|
|
47
|
-
await self.
|
|
50
|
+
await self.actor.init_task(instruction, max_steps=self.max_steps)
|
|
48
51
|
|
|
49
52
|
for i in range(self.max_steps):
|
|
50
53
|
logger.debug(f"Executing step {i + 1}/{self.max_steps}")
|
|
@@ -53,15 +56,28 @@ class AsyncDefaultAgent:
|
|
|
53
56
|
image = await image_provider()
|
|
54
57
|
|
|
55
58
|
# Get next step from OAGI
|
|
56
|
-
step = await self.
|
|
59
|
+
step = await self.actor.step(image, temperature=self.temperature)
|
|
57
60
|
|
|
58
61
|
# Log reasoning
|
|
59
62
|
if step.reason:
|
|
60
|
-
logger.
|
|
63
|
+
logger.info(f"Step {i + 1}: {step.reason}")
|
|
64
|
+
|
|
65
|
+
# Notify observer if present
|
|
66
|
+
if self.step_observer:
|
|
67
|
+
await self.step_observer.on_step(i + 1, step.reason, step.actions)
|
|
61
68
|
|
|
62
69
|
# Execute actions if any
|
|
63
70
|
if step.actions:
|
|
64
|
-
logger.
|
|
71
|
+
logger.info(f"Actions ({len(step.actions)}):")
|
|
72
|
+
for action in step.actions:
|
|
73
|
+
count_suffix = (
|
|
74
|
+
f" x{action.count}"
|
|
75
|
+
if action.count and action.count > 1
|
|
76
|
+
else ""
|
|
77
|
+
)
|
|
78
|
+
logger.info(
|
|
79
|
+
f" [{action.type.value}] {action.argument}{count_suffix}"
|
|
80
|
+
)
|
|
65
81
|
await action_handler(step.actions)
|
|
66
82
|
|
|
67
83
|
# Check if task is complete
|
oagi/agent/factories.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# Licensed under the MIT License.
|
|
7
7
|
# -----------------------------------------------------------------------------
|
|
8
8
|
from oagi.agent.tasker import TaskerAgent
|
|
9
|
+
from oagi.types import AsyncStepObserver
|
|
9
10
|
|
|
10
11
|
from .default import AsyncDefaultAgent
|
|
11
12
|
from .protocol import AsyncAgent
|
|
@@ -19,6 +20,7 @@ def create_default_agent(
|
|
|
19
20
|
model: str = "lux-v1",
|
|
20
21
|
max_steps: int = 20,
|
|
21
22
|
temperature: float = 0.1,
|
|
23
|
+
step_observer: AsyncStepObserver | None = None,
|
|
22
24
|
) -> AsyncAgent:
|
|
23
25
|
return AsyncDefaultAgent(
|
|
24
26
|
api_key=api_key,
|
|
@@ -26,6 +28,7 @@ def create_default_agent(
|
|
|
26
28
|
model=model,
|
|
27
29
|
max_steps=max_steps,
|
|
28
30
|
temperature=temperature,
|
|
31
|
+
step_observer=step_observer,
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
|
|
@@ -37,6 +40,7 @@ def create_planner_agent(
|
|
|
37
40
|
max_steps: int = 30,
|
|
38
41
|
temperature: float = 0.0,
|
|
39
42
|
reflection_interval: int = 20,
|
|
43
|
+
step_observer: AsyncStepObserver | None = None,
|
|
40
44
|
) -> AsyncAgent:
|
|
41
45
|
tasker = TaskerAgent(
|
|
42
46
|
api_key=api_key,
|
|
@@ -45,6 +49,7 @@ def create_planner_agent(
|
|
|
45
49
|
max_steps=max_steps,
|
|
46
50
|
temperature=temperature,
|
|
47
51
|
reflection_interval=reflection_interval,
|
|
52
|
+
step_observer=step_observer,
|
|
48
53
|
)
|
|
49
54
|
# tasker.set_task()
|
|
50
55
|
return tasker
|
oagi/agent/tasker/planner.py
CHANGED
|
@@ -20,19 +20,28 @@ class Planner:
|
|
|
20
20
|
This class provides planning and reflection capabilities using OAGI workers.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
client: AsyncClient | None = None,
|
|
26
|
+
api_key: str | None = None,
|
|
27
|
+
base_url: str | None = None,
|
|
28
|
+
):
|
|
24
29
|
"""Initialize the planner.
|
|
25
30
|
|
|
26
31
|
Args:
|
|
27
32
|
client: AsyncClient for OAGI API calls. If None, one will be created when needed.
|
|
33
|
+
api_key: API key for creating internal client
|
|
34
|
+
base_url: Base URL for creating internal client
|
|
28
35
|
"""
|
|
29
36
|
self.client = client
|
|
37
|
+
self.api_key = api_key
|
|
38
|
+
self.base_url = base_url
|
|
30
39
|
self._owns_client = False # Track if we created the client
|
|
31
40
|
|
|
32
41
|
def _ensure_client(self) -> AsyncClient:
|
|
33
42
|
"""Ensure we have a client, creating one if needed."""
|
|
34
43
|
if not self.client:
|
|
35
|
-
self.client = AsyncClient()
|
|
44
|
+
self.client = AsyncClient(api_key=self.api_key, base_url=self.base_url)
|
|
36
45
|
self._owns_client = True
|
|
37
46
|
return self.client
|
|
38
47
|
|
|
@@ -10,8 +10,8 @@ import logging
|
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from oagi import
|
|
14
|
-
from oagi.types import AsyncActionHandler, AsyncImageProvider
|
|
13
|
+
from oagi import AsyncActor
|
|
14
|
+
from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
|
|
15
15
|
|
|
16
16
|
from ..protocol import AsyncAgent
|
|
17
17
|
from .memory import PlannerMemory
|
|
@@ -35,13 +35,14 @@ class TaskeeAgent(AsyncAgent):
|
|
|
35
35
|
self,
|
|
36
36
|
api_key: str | None = None,
|
|
37
37
|
base_url: str | None = None,
|
|
38
|
-
model: str = "lux-
|
|
39
|
-
max_steps_per_subtask: int =
|
|
40
|
-
reflection_interval: int =
|
|
41
|
-
temperature: float = 0.
|
|
38
|
+
model: str = "lux-actor-1",
|
|
39
|
+
max_steps_per_subtask: int = 20,
|
|
40
|
+
reflection_interval: int = 4,
|
|
41
|
+
temperature: float = 0.5,
|
|
42
42
|
planner: Planner | None = None,
|
|
43
43
|
external_memory: PlannerMemory | None = None,
|
|
44
44
|
todo_index: int | None = None,
|
|
45
|
+
step_observer: AsyncStepObserver | None = None,
|
|
45
46
|
):
|
|
46
47
|
"""Initialize the taskee agent.
|
|
47
48
|
|
|
@@ -55,6 +56,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
55
56
|
planner: Planner for planning and reflection
|
|
56
57
|
external_memory: External memory from parent agent
|
|
57
58
|
todo_index: Index of the todo being executed
|
|
59
|
+
step_observer: Optional observer for step tracking
|
|
58
60
|
"""
|
|
59
61
|
self.api_key = api_key
|
|
60
62
|
self.base_url = base_url
|
|
@@ -62,12 +64,13 @@ class TaskeeAgent(AsyncAgent):
|
|
|
62
64
|
self.max_steps_per_subtask = max_steps_per_subtask
|
|
63
65
|
self.reflection_interval = reflection_interval
|
|
64
66
|
self.temperature = temperature
|
|
65
|
-
self.planner = planner or Planner()
|
|
67
|
+
self.planner = planner or Planner(api_key=api_key, base_url=base_url)
|
|
66
68
|
self.external_memory = external_memory
|
|
67
69
|
self.todo_index = todo_index
|
|
70
|
+
self.step_observer = step_observer
|
|
68
71
|
|
|
69
72
|
# Internal state
|
|
70
|
-
self.
|
|
73
|
+
self.actor: AsyncActor | None = None
|
|
71
74
|
self.current_todo: str = ""
|
|
72
75
|
self.current_instruction: str = ""
|
|
73
76
|
self.actions: list[Action] = []
|
|
@@ -135,10 +138,10 @@ class TaskeeAgent(AsyncAgent):
|
|
|
135
138
|
)
|
|
136
139
|
return False
|
|
137
140
|
finally:
|
|
138
|
-
# Clean up
|
|
139
|
-
if self.
|
|
140
|
-
await self.
|
|
141
|
-
self.
|
|
141
|
+
# Clean up actor
|
|
142
|
+
if self.actor:
|
|
143
|
+
await self.actor.close()
|
|
144
|
+
self.actor = None
|
|
142
145
|
|
|
143
146
|
async def _initial_plan(self, image_provider: AsyncImageProvider) -> None:
|
|
144
147
|
"""Generate initial plan for the todo.
|
|
@@ -199,17 +202,17 @@ class TaskeeAgent(AsyncAgent):
|
|
|
199
202
|
logger.info(f"Executing subtask with max {max_steps} steps")
|
|
200
203
|
|
|
201
204
|
# Use async with for automatic resource management
|
|
202
|
-
async with
|
|
205
|
+
async with AsyncActor(
|
|
203
206
|
api_key=self.api_key,
|
|
204
207
|
base_url=self.base_url,
|
|
205
208
|
model=self.model,
|
|
206
209
|
temperature=self.temperature,
|
|
207
|
-
) as
|
|
210
|
+
) as actor:
|
|
208
211
|
# Store reference for potential cleanup in execute's finally block
|
|
209
|
-
self.
|
|
212
|
+
self.actor = actor
|
|
210
213
|
|
|
211
|
-
# Initialize
|
|
212
|
-
await
|
|
214
|
+
# Initialize actor with current instruction
|
|
215
|
+
await actor.init_task(self.current_instruction)
|
|
213
216
|
|
|
214
217
|
steps_taken = 0
|
|
215
218
|
for step_num in range(max_steps):
|
|
@@ -218,7 +221,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
218
221
|
|
|
219
222
|
# Get next step from OAGI
|
|
220
223
|
try:
|
|
221
|
-
step = await
|
|
224
|
+
step = await actor.step(screenshot, instruction=None)
|
|
222
225
|
except Exception as e:
|
|
223
226
|
logger.error(f"Error getting step from OAGI: {e}")
|
|
224
227
|
self._record_action(
|
|
@@ -228,8 +231,30 @@ class TaskeeAgent(AsyncAgent):
|
|
|
228
231
|
)
|
|
229
232
|
break
|
|
230
233
|
|
|
234
|
+
# Log reasoning
|
|
235
|
+
if step.reason:
|
|
236
|
+
logger.info(f"Step {self.total_actions + 1}: {step.reason}")
|
|
237
|
+
|
|
238
|
+
# Notify observer if present
|
|
239
|
+
if self.step_observer:
|
|
240
|
+
await self.step_observer.on_step(
|
|
241
|
+
self.total_actions + 1, step.reason, step.actions
|
|
242
|
+
)
|
|
243
|
+
|
|
231
244
|
# Record OAGI actions
|
|
232
245
|
if step.actions:
|
|
246
|
+
# Log actions with details
|
|
247
|
+
logger.info(f"Actions ({len(step.actions)}):")
|
|
248
|
+
for action in step.actions:
|
|
249
|
+
count_suffix = (
|
|
250
|
+
f" x{action.count}"
|
|
251
|
+
if action.count and action.count > 1
|
|
252
|
+
else ""
|
|
253
|
+
)
|
|
254
|
+
logger.info(
|
|
255
|
+
f" [{action.type.value}] {action.argument}{count_suffix}"
|
|
256
|
+
)
|
|
257
|
+
|
|
233
258
|
for action in step.actions:
|
|
234
259
|
self._record_action(
|
|
235
260
|
action_type=action.type.lower(),
|
|
@@ -255,9 +280,9 @@ class TaskeeAgent(AsyncAgent):
|
|
|
255
280
|
logger.info("Reflection interval reached")
|
|
256
281
|
break
|
|
257
282
|
|
|
258
|
-
#
|
|
283
|
+
# Actor will be automatically closed by async with context manager
|
|
259
284
|
# Clear reference after context manager closes it
|
|
260
|
-
self.
|
|
285
|
+
self.actor = None
|
|
261
286
|
return steps_taken
|
|
262
287
|
|
|
263
288
|
async def _reflect_and_decide(self, image_provider: AsyncImageProvider) -> bool:
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from oagi.types import AsyncActionHandler, AsyncImageProvider
|
|
12
|
+
from oagi.types import AsyncActionHandler, AsyncImageProvider, AsyncStepObserver
|
|
13
13
|
|
|
14
14
|
from ..protocol import AsyncAgent
|
|
15
15
|
from .memory import PlannerMemory
|
|
@@ -34,11 +34,12 @@ class TaskerAgent(AsyncAgent):
|
|
|
34
34
|
self,
|
|
35
35
|
api_key: str | None = None,
|
|
36
36
|
base_url: str | None = None,
|
|
37
|
-
model: str = "lux-
|
|
38
|
-
max_steps: int =
|
|
39
|
-
temperature: float = 0.
|
|
40
|
-
reflection_interval: int =
|
|
37
|
+
model: str = "lux-actor-1",
|
|
38
|
+
max_steps: int = 60,
|
|
39
|
+
temperature: float = 0.5,
|
|
40
|
+
reflection_interval: int = 4,
|
|
41
41
|
planner: Planner | None = None,
|
|
42
|
+
step_observer: AsyncStepObserver | None = None,
|
|
42
43
|
):
|
|
43
44
|
"""Initialize the tasker agent.
|
|
44
45
|
|
|
@@ -50,6 +51,7 @@ class TaskerAgent(AsyncAgent):
|
|
|
50
51
|
temperature: Sampling temperature
|
|
51
52
|
reflection_interval: Actions before reflection
|
|
52
53
|
planner: Planner for planning and reflection
|
|
54
|
+
step_observer: Optional observer for step tracking
|
|
53
55
|
"""
|
|
54
56
|
self.api_key = api_key
|
|
55
57
|
self.base_url = base_url
|
|
@@ -57,7 +59,8 @@ class TaskerAgent(AsyncAgent):
|
|
|
57
59
|
self.max_steps = max_steps
|
|
58
60
|
self.temperature = temperature
|
|
59
61
|
self.reflection_interval = reflection_interval
|
|
60
|
-
self.planner = planner or Planner()
|
|
62
|
+
self.planner = planner or Planner(api_key=api_key, base_url=base_url)
|
|
63
|
+
self.step_observer = step_observer
|
|
61
64
|
|
|
62
65
|
# Memory for tracking workflow
|
|
63
66
|
self.memory = PlannerMemory()
|
|
@@ -168,12 +171,13 @@ class TaskerAgent(AsyncAgent):
|
|
|
168
171
|
api_key=self.api_key,
|
|
169
172
|
base_url=self.base_url,
|
|
170
173
|
model=self.model,
|
|
171
|
-
max_steps_per_subtask=
|
|
174
|
+
max_steps_per_subtask=20, # Smaller steps per subtask
|
|
172
175
|
reflection_interval=self.reflection_interval,
|
|
173
176
|
temperature=self.temperature,
|
|
174
177
|
planner=self.planner,
|
|
175
178
|
external_memory=self.memory, # Share memory with child
|
|
176
179
|
todo_index=todo_index, # Pass the todo index
|
|
180
|
+
step_observer=self.step_observer, # Pass step observer
|
|
177
181
|
)
|
|
178
182
|
|
|
179
183
|
self.current_todo_index = todo_index
|
oagi/cli/agent.py
CHANGED
|
@@ -10,9 +10,14 @@ import argparse
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import os
|
|
12
12
|
import sys
|
|
13
|
+
import time
|
|
14
|
+
import traceback
|
|
13
15
|
|
|
14
16
|
from oagi.exceptions import check_optional_dependency
|
|
15
17
|
|
|
18
|
+
from .display import display_step_table
|
|
19
|
+
from .tracking import StepTracker
|
|
20
|
+
|
|
16
21
|
|
|
17
22
|
def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
18
23
|
agent_parser = subparsers.add_parser("agent", help="Agent execution commands")
|
|
@@ -25,12 +30,14 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
25
30
|
run_parser.add_argument(
|
|
26
31
|
"instruction", type=str, help="Task instruction for the agent to execute"
|
|
27
32
|
)
|
|
28
|
-
run_parser.add_argument("--model", type=str, help="Model to use (default: lux-v1)")
|
|
29
33
|
run_parser.add_argument(
|
|
30
|
-
"--
|
|
34
|
+
"--model", type=str, help="Model to use (default: lux-actor-1)"
|
|
35
|
+
)
|
|
36
|
+
run_parser.add_argument(
|
|
37
|
+
"--max-steps", type=int, help="Maximum number of steps (default: 20)"
|
|
31
38
|
)
|
|
32
39
|
run_parser.add_argument(
|
|
33
|
-
"--temperature", type=float, help="Sampling temperature (default: 0.
|
|
40
|
+
"--temperature", type=float, help="Sampling temperature (default: 0.5)"
|
|
34
41
|
)
|
|
35
42
|
run_parser.add_argument(
|
|
36
43
|
"--mode",
|
|
@@ -74,12 +81,15 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
74
81
|
base_url = args.oagi_base_url or os.getenv(
|
|
75
82
|
"OAGI_BASE_URL", "https://api.agiopen.org"
|
|
76
83
|
)
|
|
77
|
-
model = args.model or "lux-
|
|
78
|
-
max_steps = args.max_steps or
|
|
79
|
-
temperature = args.temperature if args.temperature is not None else 0.
|
|
84
|
+
model = args.model or "lux-actor-1"
|
|
85
|
+
max_steps = args.max_steps or 20
|
|
86
|
+
temperature = args.temperature if args.temperature is not None else 0.5
|
|
80
87
|
mode = args.mode or "actor"
|
|
81
88
|
|
|
82
|
-
# Create
|
|
89
|
+
# Create step tracker
|
|
90
|
+
step_tracker = StepTracker()
|
|
91
|
+
|
|
92
|
+
# Create agent with step tracker
|
|
83
93
|
agent = create_agent(
|
|
84
94
|
mode=mode,
|
|
85
95
|
api_key=api_key,
|
|
@@ -87,6 +97,7 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
87
97
|
model=model,
|
|
88
98
|
max_steps=max_steps,
|
|
89
99
|
temperature=temperature,
|
|
100
|
+
step_observer=step_tracker,
|
|
90
101
|
)
|
|
91
102
|
|
|
92
103
|
# Create handlers
|
|
@@ -99,7 +110,10 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
99
110
|
)
|
|
100
111
|
print("-" * 60)
|
|
101
112
|
|
|
102
|
-
|
|
113
|
+
start_time = time.time()
|
|
114
|
+
success = False
|
|
115
|
+
interrupted = False
|
|
116
|
+
|
|
103
117
|
try:
|
|
104
118
|
success = asyncio.run(
|
|
105
119
|
agent.execute(
|
|
@@ -108,18 +122,22 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
108
122
|
image_provider=image_provider,
|
|
109
123
|
)
|
|
110
124
|
)
|
|
111
|
-
|
|
112
|
-
print("-" * 60)
|
|
113
|
-
if success:
|
|
114
|
-
print("Task completed successfully!")
|
|
115
|
-
sys.exit(0)
|
|
116
|
-
else:
|
|
117
|
-
print("Task failed or reached max steps without completion.")
|
|
118
|
-
sys.exit(1)
|
|
119
|
-
|
|
120
125
|
except KeyboardInterrupt:
|
|
121
|
-
print("\nAgent execution interrupted
|
|
122
|
-
|
|
126
|
+
print("\nAgent execution interrupted by user (Ctrl+C)")
|
|
127
|
+
interrupted = True
|
|
123
128
|
except Exception as e:
|
|
124
|
-
print(f"
|
|
125
|
-
|
|
129
|
+
print(f"\nError during agent execution: {e}", file=sys.stderr)
|
|
130
|
+
traceback.print_exc()
|
|
131
|
+
finally:
|
|
132
|
+
duration = time.time() - start_time
|
|
133
|
+
|
|
134
|
+
if step_tracker.steps:
|
|
135
|
+
print("\n" + "=" * 60)
|
|
136
|
+
display_step_table(step_tracker.steps, success, duration)
|
|
137
|
+
else:
|
|
138
|
+
print("\nNo steps were executed.")
|
|
139
|
+
|
|
140
|
+
if interrupted:
|
|
141
|
+
sys.exit(130)
|
|
142
|
+
elif not success:
|
|
143
|
+
sys.exit(1)
|
oagi/cli/display.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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 rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from .tracking import StepData
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def display_step_table(
|
|
16
|
+
steps: list[StepData], success: bool, duration: float | None = None
|
|
17
|
+
):
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
table = Table(title="Agent Execution Summary", show_lines=True)
|
|
21
|
+
table.add_column("Step", justify="center", style="cyan", width=6)
|
|
22
|
+
table.add_column("Reasoning", style="white")
|
|
23
|
+
table.add_column("Actions", style="yellow", width=35)
|
|
24
|
+
table.add_column("Status", justify="center", width=8)
|
|
25
|
+
|
|
26
|
+
for step in steps:
|
|
27
|
+
reason = step.reasoning or "N/A"
|
|
28
|
+
|
|
29
|
+
actions_display = []
|
|
30
|
+
for action in step.actions[:3]:
|
|
31
|
+
arg = action.argument[:20] if action.argument else ""
|
|
32
|
+
actions_display.append(f"{action.type.value}({arg})")
|
|
33
|
+
|
|
34
|
+
actions_str = ", ".join(actions_display)
|
|
35
|
+
if len(step.actions) > 3:
|
|
36
|
+
actions_str += f" (+{len(step.actions) - 3} more)"
|
|
37
|
+
|
|
38
|
+
status_display = "✓" if step.status == "complete" else "→"
|
|
39
|
+
|
|
40
|
+
table.add_row(
|
|
41
|
+
str(step.step_num),
|
|
42
|
+
reason,
|
|
43
|
+
actions_str,
|
|
44
|
+
status_display,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
console.print(table)
|
|
48
|
+
|
|
49
|
+
status_text = "Success" if success else "Failed/Interrupted"
|
|
50
|
+
console.print(
|
|
51
|
+
f"\nTotal Steps: {len(steps)} | Status: {status_text}",
|
|
52
|
+
style="bold",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if duration:
|
|
56
|
+
console.print(f"Duration: {duration:.2f}s")
|
oagi/cli/tracking.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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 dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from oagi.types import Action
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class StepData:
|
|
17
|
+
step_num: int
|
|
18
|
+
timestamp: datetime
|
|
19
|
+
reasoning: str | None
|
|
20
|
+
actions: list[Action]
|
|
21
|
+
action_count: int
|
|
22
|
+
status: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StepTracker:
|
|
26
|
+
"""Tracks agent step execution by implementing AsyncStepObserver protocol."""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.steps: list[StepData] = []
|
|
30
|
+
|
|
31
|
+
async def on_step(
|
|
32
|
+
self,
|
|
33
|
+
step_num: int,
|
|
34
|
+
reasoning: str | None,
|
|
35
|
+
actions: list[Action],
|
|
36
|
+
) -> None:
|
|
37
|
+
step_data = StepData(
|
|
38
|
+
step_num=step_num,
|
|
39
|
+
timestamp=datetime.now(),
|
|
40
|
+
reasoning=reasoning,
|
|
41
|
+
actions=actions,
|
|
42
|
+
action_count=len(actions),
|
|
43
|
+
status="running",
|
|
44
|
+
)
|
|
45
|
+
self.steps.append(step_data)
|
oagi/cli/utils.py
CHANGED
|
@@ -15,10 +15,16 @@ from oagi.exceptions import check_optional_dependency
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_sdk_version() -> str:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# Try oagi-core first (development install), then oagi (metapackage install)
|
|
19
|
+
for package_name in ["oagi-core", "oagi"]:
|
|
20
|
+
try:
|
|
21
|
+
version = get_version(package_name)
|
|
22
|
+
# Skip if version is 0.0.0 (placeholder/invalid)
|
|
23
|
+
if version != "0.0.0":
|
|
24
|
+
return version
|
|
25
|
+
except Exception:
|
|
26
|
+
continue
|
|
27
|
+
return "unknown"
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
def display_version() -> None:
|
|
@@ -50,6 +56,7 @@ def display_config() -> None:
|
|
|
50
56
|
config_vars = {
|
|
51
57
|
"OAGI_API_KEY": os.getenv("OAGI_API_KEY", ""),
|
|
52
58
|
"OAGI_BASE_URL": os.getenv("OAGI_BASE_URL", "https://api.agiopen.org"),
|
|
59
|
+
"OAGI_DEFAULT_MODEL": os.getenv("OAGI_DEFAULT_MODEL", "lux-actor-1"),
|
|
53
60
|
"OAGI_LOG_LEVEL": os.getenv("OAGI_LOG_LEVEL", "INFO"),
|
|
54
61
|
"OAGI_SERVER_HOST": os.getenv("OAGI_SERVER_HOST", "0.0.0.0"),
|
|
55
62
|
"OAGI_SERVER_PORT": os.getenv("OAGI_SERVER_PORT", "8000"),
|
oagi/client/base.py
CHANGED
|
@@ -41,16 +41,12 @@ class BaseClient(Generic[HttpClientT]):
|
|
|
41
41
|
|
|
42
42
|
def __init__(self, base_url: str | None = None, api_key: str | None = None):
|
|
43
43
|
# Get from environment if not provided
|
|
44
|
-
self.base_url =
|
|
44
|
+
self.base_url = (
|
|
45
|
+
base_url or os.getenv("OAGI_BASE_URL") or "https://api.agiopen.org"
|
|
46
|
+
)
|
|
45
47
|
self.api_key = api_key or os.getenv("OAGI_API_KEY")
|
|
46
48
|
|
|
47
49
|
# Validate required configuration
|
|
48
|
-
if not self.base_url:
|
|
49
|
-
raise ConfigurationError(
|
|
50
|
-
"OAGI base URL must be provided either as 'base_url' parameter or "
|
|
51
|
-
"OAGI_BASE_URL environment variable"
|
|
52
|
-
)
|
|
53
|
-
|
|
54
50
|
if not self.api_key:
|
|
55
51
|
raise ConfigurationError(
|
|
56
52
|
"OAGI API key must be provided either as 'api_key' parameter or "
|