oagi-core 0.9.1__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oagi/__init__.py +76 -33
- oagi/agent/__init__.py +2 -0
- oagi/agent/default.py +45 -12
- oagi/agent/factories.py +22 -3
- oagi/agent/observer/__init__.py +38 -0
- oagi/agent/observer/agent_observer.py +99 -0
- oagi/agent/observer/events.py +28 -0
- oagi/agent/observer/exporters.py +445 -0
- oagi/agent/observer/protocol.py +12 -0
- oagi/agent/registry.py +2 -2
- oagi/agent/tasker/models.py +1 -0
- oagi/agent/tasker/planner.py +41 -9
- oagi/agent/tasker/taskee_agent.py +178 -86
- oagi/agent/tasker/tasker_agent.py +25 -14
- oagi/cli/agent.py +50 -9
- oagi/cli/tracking.py +27 -17
- oagi/cli/utils.py +11 -4
- oagi/client/base.py +3 -7
- oagi/handler/_macos.py +55 -0
- oagi/handler/pyautogui_action_handler.py +19 -2
- oagi/server/agent_wrappers.py +5 -5
- oagi/server/config.py +3 -3
- oagi/server/models.py +2 -2
- oagi/server/session_store.py +2 -2
- oagi/server/socketio_server.py +1 -1
- oagi/task/async_.py +13 -34
- oagi/task/async_short.py +2 -2
- oagi/task/base.py +41 -7
- oagi/task/short.py +2 -2
- oagi/task/sync.py +11 -34
- oagi/types/__init__.py +24 -4
- oagi/types/async_image_provider.py +3 -2
- oagi/types/image_provider.py +3 -2
- oagi/types/step_observer.py +75 -16
- oagi/types/url.py +3 -0
- {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/METADATA +38 -25
- oagi_core-0.10.0.dist-info/RECORD +68 -0
- oagi/types/url_image.py +0 -47
- oagi_core-0.9.1.dist-info/RECORD +0 -62
- {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/WHEEL +0 -0
- {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/entry_points.txt +0 -0
- {oagi_core-0.9.1.dist-info → oagi_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,7 +11,16 @@ from datetime import datetime
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from oagi import AsyncActor
|
|
14
|
-
from oagi.types import
|
|
14
|
+
from oagi.types import (
|
|
15
|
+
URL,
|
|
16
|
+
ActionEvent,
|
|
17
|
+
AsyncActionHandler,
|
|
18
|
+
AsyncImageProvider,
|
|
19
|
+
AsyncObserver,
|
|
20
|
+
Image,
|
|
21
|
+
PlanEvent,
|
|
22
|
+
StepEvent,
|
|
23
|
+
)
|
|
15
24
|
|
|
16
25
|
from ..protocol import AsyncAgent
|
|
17
26
|
from .memory import PlannerMemory
|
|
@@ -21,6 +30,13 @@ from .planner import Planner
|
|
|
21
30
|
logger = logging.getLogger(__name__)
|
|
22
31
|
|
|
23
32
|
|
|
33
|
+
def _serialize_image(image: Image | str) -> bytes | str:
|
|
34
|
+
"""Convert an image to bytes or keep URL as string."""
|
|
35
|
+
if isinstance(image, str):
|
|
36
|
+
return image
|
|
37
|
+
return image.read()
|
|
38
|
+
|
|
39
|
+
|
|
24
40
|
class TaskeeAgent(AsyncAgent):
|
|
25
41
|
"""Executes a single todo with planning and reflection capabilities.
|
|
26
42
|
|
|
@@ -35,14 +51,14 @@ class TaskeeAgent(AsyncAgent):
|
|
|
35
51
|
self,
|
|
36
52
|
api_key: str | None = None,
|
|
37
53
|
base_url: str | None = None,
|
|
38
|
-
model: str = "lux-
|
|
39
|
-
|
|
40
|
-
reflection_interval: int =
|
|
41
|
-
temperature: float = 0.
|
|
54
|
+
model: str = "lux-actor-1",
|
|
55
|
+
max_steps: int = 20,
|
|
56
|
+
reflection_interval: int = 4,
|
|
57
|
+
temperature: float = 0.5,
|
|
42
58
|
planner: Planner | None = None,
|
|
43
59
|
external_memory: PlannerMemory | None = None,
|
|
44
60
|
todo_index: int | None = None,
|
|
45
|
-
step_observer:
|
|
61
|
+
step_observer: AsyncObserver | None = None,
|
|
46
62
|
):
|
|
47
63
|
"""Initialize the taskee agent.
|
|
48
64
|
|
|
@@ -50,7 +66,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
50
66
|
api_key: OAGI API key
|
|
51
67
|
base_url: OAGI API base URL
|
|
52
68
|
model: Model to use for vision tasks
|
|
53
|
-
|
|
69
|
+
max_steps: Maximum steps before reinitializing task
|
|
54
70
|
reflection_interval: Number of actions before triggering reflection
|
|
55
71
|
temperature: Sampling temperature
|
|
56
72
|
planner: Planner for planning and reflection
|
|
@@ -61,10 +77,10 @@ class TaskeeAgent(AsyncAgent):
|
|
|
61
77
|
self.api_key = api_key
|
|
62
78
|
self.base_url = base_url
|
|
63
79
|
self.model = model
|
|
64
|
-
self.
|
|
80
|
+
self.max_steps = max_steps
|
|
65
81
|
self.reflection_interval = reflection_interval
|
|
66
82
|
self.temperature = temperature
|
|
67
|
-
self.planner = planner or Planner()
|
|
83
|
+
self.planner = planner or Planner(api_key=api_key, base_url=base_url)
|
|
68
84
|
self.external_memory = external_memory
|
|
69
85
|
self.todo_index = todo_index
|
|
70
86
|
self.step_observer = step_observer
|
|
@@ -101,17 +117,27 @@ class TaskeeAgent(AsyncAgent):
|
|
|
101
117
|
self.success = False
|
|
102
118
|
|
|
103
119
|
try:
|
|
120
|
+
self.actor = AsyncActor(
|
|
121
|
+
api_key=self.api_key,
|
|
122
|
+
base_url=self.base_url,
|
|
123
|
+
model=self.model,
|
|
124
|
+
temperature=self.temperature,
|
|
125
|
+
)
|
|
104
126
|
# Initial planning
|
|
105
127
|
await self._initial_plan(image_provider)
|
|
106
128
|
|
|
129
|
+
# Initialize the actor with the task
|
|
130
|
+
await self.actor.init_task(
|
|
131
|
+
self.current_instruction, max_steps=self.max_steps
|
|
132
|
+
)
|
|
133
|
+
|
|
107
134
|
# Main execution loop with reinitializations
|
|
108
|
-
|
|
109
|
-
remaining_steps = max_total_steps
|
|
135
|
+
remaining_steps = self.max_steps
|
|
110
136
|
|
|
111
137
|
while remaining_steps > 0 and not self.success:
|
|
112
138
|
# Execute subtask
|
|
113
139
|
steps_taken = await self._execute_subtask(
|
|
114
|
-
min(self.
|
|
140
|
+
min(self.max_steps, remaining_steps),
|
|
115
141
|
action_handler,
|
|
116
142
|
image_provider,
|
|
117
143
|
)
|
|
@@ -174,6 +200,17 @@ class TaskeeAgent(AsyncAgent):
|
|
|
174
200
|
result=plan_output.instruction,
|
|
175
201
|
)
|
|
176
202
|
|
|
203
|
+
# Emit plan event
|
|
204
|
+
if self.step_observer:
|
|
205
|
+
await self.step_observer.on_event(
|
|
206
|
+
PlanEvent(
|
|
207
|
+
phase="initial",
|
|
208
|
+
image=_serialize_image(screenshot),
|
|
209
|
+
reasoning=plan_output.reasoning,
|
|
210
|
+
result=plan_output.instruction,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
177
214
|
# Set current instruction
|
|
178
215
|
self.current_instruction = plan_output.instruction
|
|
179
216
|
logger.info(f"Initial instruction: {self.current_instruction}")
|
|
@@ -201,89 +238,108 @@ class TaskeeAgent(AsyncAgent):
|
|
|
201
238
|
"""
|
|
202
239
|
logger.info(f"Executing subtask with max {max_steps} steps")
|
|
203
240
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
241
|
+
steps_taken = 0
|
|
242
|
+
client = self.planner._ensure_client()
|
|
243
|
+
|
|
244
|
+
for step_num in range(max_steps):
|
|
245
|
+
# Capture screenshot
|
|
246
|
+
screenshot = await image_provider()
|
|
247
|
+
|
|
248
|
+
# Upload screenshot first to get UUID (avoids re-upload in actor.step)
|
|
249
|
+
try:
|
|
250
|
+
upload_response = await client.put_s3_presigned_url(screenshot)
|
|
251
|
+
screenshot_uuid = upload_response.uuid
|
|
252
|
+
screenshot_url = upload_response.download_url
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error(f"Error uploading screenshot: {e}")
|
|
255
|
+
self._record_action(
|
|
256
|
+
action_type="error",
|
|
257
|
+
target="screenshot_upload",
|
|
258
|
+
reasoning=str(e),
|
|
259
|
+
)
|
|
260
|
+
break
|
|
261
|
+
|
|
262
|
+
# Get next step from OAGI using URL (avoids re-upload)
|
|
263
|
+
try:
|
|
264
|
+
step = await self.actor.step(URL(screenshot_url), instruction=None)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Error getting step from OAGI: {e}")
|
|
267
|
+
self._record_action(
|
|
268
|
+
action_type="error",
|
|
269
|
+
target="oagi_step",
|
|
270
|
+
reasoning=str(e),
|
|
271
|
+
screenshot_uuid=screenshot_uuid,
|
|
272
|
+
)
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
# Log reasoning
|
|
276
|
+
if step.reason:
|
|
277
|
+
logger.info(f"Step {self.total_actions + 1}: {step.reason}")
|
|
278
|
+
|
|
279
|
+
# Emit step event
|
|
280
|
+
if self.step_observer:
|
|
281
|
+
await self.step_observer.on_event(
|
|
282
|
+
StepEvent(
|
|
283
|
+
step_num=self.total_actions + 1,
|
|
284
|
+
image=_serialize_image(screenshot),
|
|
285
|
+
step=step,
|
|
231
286
|
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# Log reasoning
|
|
235
|
-
if step.reason:
|
|
236
|
-
logger.info(f"Step {self.total_actions + 1}: {step.reason}")
|
|
237
|
-
|
|
238
|
-
# Record OAGI actions
|
|
239
|
-
if step.actions:
|
|
240
|
-
# Log actions with details
|
|
241
|
-
logger.info(f"Actions ({len(step.actions)}):")
|
|
242
|
-
for action in step.actions:
|
|
243
|
-
count_suffix = (
|
|
244
|
-
f" x{action.count}"
|
|
245
|
-
if action.count and action.count > 1
|
|
246
|
-
else ""
|
|
247
|
-
)
|
|
248
|
-
logger.info(
|
|
249
|
-
f" [{action.type.value}] {action.argument}{count_suffix}"
|
|
250
|
-
)
|
|
287
|
+
)
|
|
251
288
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
289
|
+
# Record OAGI actions
|
|
290
|
+
if step.actions:
|
|
291
|
+
# Log actions with details
|
|
292
|
+
logger.info(f"Actions ({len(step.actions)}):")
|
|
293
|
+
for action in step.actions:
|
|
294
|
+
count_suffix = (
|
|
295
|
+
f" x{action.count}" if action.count and action.count > 1 else ""
|
|
296
|
+
)
|
|
297
|
+
logger.info(
|
|
298
|
+
f" [{action.type.value}] {action.argument}{count_suffix}"
|
|
299
|
+
)
|
|
258
300
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
301
|
+
for action in step.actions:
|
|
302
|
+
self._record_action(
|
|
303
|
+
action_type=action.type.lower(),
|
|
304
|
+
target=action.argument,
|
|
305
|
+
reasoning=step.reason,
|
|
306
|
+
screenshot_uuid=screenshot_uuid,
|
|
307
|
+
)
|
|
264
308
|
|
|
265
|
-
|
|
309
|
+
# Execute actions
|
|
310
|
+
error = None
|
|
311
|
+
try:
|
|
266
312
|
await action_handler(step.actions)
|
|
267
|
-
|
|
268
|
-
|
|
313
|
+
except Exception as e:
|
|
314
|
+
error = str(e)
|
|
315
|
+
raise
|
|
316
|
+
|
|
317
|
+
# Emit action event
|
|
318
|
+
if self.step_observer:
|
|
319
|
+
await self.step_observer.on_event(
|
|
320
|
+
ActionEvent(
|
|
321
|
+
step_num=self.total_actions + 1,
|
|
322
|
+
actions=step.actions,
|
|
323
|
+
error=error,
|
|
324
|
+
)
|
|
325
|
+
)
|
|
269
326
|
|
|
270
|
-
|
|
327
|
+
self.total_actions += len(step.actions)
|
|
328
|
+
self.since_reflection += len(step.actions)
|
|
271
329
|
|
|
272
|
-
|
|
273
|
-
if step.stop:
|
|
274
|
-
logger.info("OAGI signaled task completion")
|
|
275
|
-
self.success = True
|
|
276
|
-
break
|
|
330
|
+
steps_taken += 1
|
|
277
331
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
332
|
+
# Check if task is complete
|
|
333
|
+
if step.stop:
|
|
334
|
+
logger.info("OAGI signaled task completion")
|
|
335
|
+
break
|
|
282
336
|
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
337
|
+
# Check if reflection is needed
|
|
338
|
+
if self.since_reflection >= self.reflection_interval:
|
|
339
|
+
logger.info("Reflection interval reached")
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
return steps_taken
|
|
287
343
|
|
|
288
344
|
async def _reflect_and_decide(self, image_provider: AsyncImageProvider) -> bool:
|
|
289
345
|
"""Reflect on progress and decide whether to continue.
|
|
@@ -314,6 +370,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
314
370
|
memory=self.external_memory,
|
|
315
371
|
todo_index=self.todo_index,
|
|
316
372
|
current_instruction=self.current_instruction,
|
|
373
|
+
reflection_interval=self.reflection_interval,
|
|
317
374
|
)
|
|
318
375
|
|
|
319
376
|
# Record reflection
|
|
@@ -324,6 +381,22 @@ class TaskeeAgent(AsyncAgent):
|
|
|
324
381
|
result=("continue" if reflection.continue_current else "pivot"),
|
|
325
382
|
)
|
|
326
383
|
|
|
384
|
+
# Emit plan event for reflection
|
|
385
|
+
if self.step_observer:
|
|
386
|
+
decision = (
|
|
387
|
+
"success"
|
|
388
|
+
if reflection.success_assessment
|
|
389
|
+
else ("continue" if reflection.continue_current else "pivot")
|
|
390
|
+
)
|
|
391
|
+
await self.step_observer.on_event(
|
|
392
|
+
PlanEvent(
|
|
393
|
+
phase="reflection",
|
|
394
|
+
image=_serialize_image(screenshot),
|
|
395
|
+
reasoning=reflection.reasoning,
|
|
396
|
+
result=decision,
|
|
397
|
+
)
|
|
398
|
+
)
|
|
399
|
+
|
|
327
400
|
# Update success assessment
|
|
328
401
|
if reflection.success_assessment:
|
|
329
402
|
self.success = True
|
|
@@ -337,6 +410,11 @@ class TaskeeAgent(AsyncAgent):
|
|
|
337
410
|
if not reflection.continue_current and reflection.new_instruction:
|
|
338
411
|
logger.info(f"Pivoting to new instruction: {reflection.new_instruction}")
|
|
339
412
|
self.current_instruction = reflection.new_instruction
|
|
413
|
+
|
|
414
|
+
# the following line create a new actor
|
|
415
|
+
await self.actor.init_task(
|
|
416
|
+
self.current_instruction, max_steps=self.max_steps
|
|
417
|
+
)
|
|
340
418
|
return True
|
|
341
419
|
|
|
342
420
|
return reflection.continue_current
|
|
@@ -362,6 +440,17 @@ class TaskeeAgent(AsyncAgent):
|
|
|
362
440
|
reasoning=summary,
|
|
363
441
|
)
|
|
364
442
|
|
|
443
|
+
# Emit plan event for summary
|
|
444
|
+
if self.step_observer:
|
|
445
|
+
await self.step_observer.on_event(
|
|
446
|
+
PlanEvent(
|
|
447
|
+
phase="summary",
|
|
448
|
+
image=None,
|
|
449
|
+
reasoning=summary,
|
|
450
|
+
result=None,
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
|
|
365
454
|
logger.info(f"Execution summary: {summary}")
|
|
366
455
|
|
|
367
456
|
def _record_action(
|
|
@@ -370,6 +459,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
370
459
|
target: str | None,
|
|
371
460
|
reasoning: str | None = None,
|
|
372
461
|
result: str | None = None,
|
|
462
|
+
screenshot_uuid: str | None = None,
|
|
373
463
|
) -> None:
|
|
374
464
|
"""Record an action to the history.
|
|
375
465
|
|
|
@@ -378,6 +468,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
378
468
|
target: Target of the action
|
|
379
469
|
reasoning: Reasoning for the action
|
|
380
470
|
result: Result of the action
|
|
471
|
+
screenshot_uuid: UUID of uploaded screenshot for this action
|
|
381
472
|
"""
|
|
382
473
|
action = Action(
|
|
383
474
|
timestamp=datetime.now().isoformat(),
|
|
@@ -386,6 +477,7 @@ class TaskeeAgent(AsyncAgent):
|
|
|
386
477
|
reasoning=reasoning,
|
|
387
478
|
result=result,
|
|
388
479
|
details={},
|
|
480
|
+
screenshot_uuid=screenshot_uuid,
|
|
389
481
|
)
|
|
390
482
|
self.actions.append(action)
|
|
391
483
|
|
|
@@ -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, AsyncObserver, SplitEvent
|
|
13
13
|
|
|
14
14
|
from ..protocol import AsyncAgent
|
|
15
15
|
from .memory import PlannerMemory
|
|
@@ -34,12 +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:
|
|
42
|
+
step_observer: AsyncObserver | None = None,
|
|
43
43
|
):
|
|
44
44
|
"""Initialize the tasker agent.
|
|
45
45
|
|
|
@@ -59,7 +59,7 @@ class TaskerAgent(AsyncAgent):
|
|
|
59
59
|
self.max_steps = max_steps
|
|
60
60
|
self.temperature = temperature
|
|
61
61
|
self.reflection_interval = reflection_interval
|
|
62
|
-
self.planner = planner or Planner()
|
|
62
|
+
self.planner = planner or Planner(api_key=api_key, base_url=base_url)
|
|
63
63
|
self.step_observer = step_observer
|
|
64
64
|
|
|
65
65
|
# Memory for tracking workflow
|
|
@@ -100,18 +100,13 @@ class TaskerAgent(AsyncAgent):
|
|
|
100
100
|
or a failure occurs.
|
|
101
101
|
|
|
102
102
|
Args:
|
|
103
|
-
instruction:
|
|
103
|
+
instruction: Not used in TaskerAgent
|
|
104
104
|
action_handler: Handler for executing actions
|
|
105
105
|
image_provider: Provider for capturing screenshots
|
|
106
106
|
|
|
107
107
|
Returns:
|
|
108
108
|
True if all todos completed successfully, False otherwise
|
|
109
109
|
"""
|
|
110
|
-
# If task not set, use instruction as task description
|
|
111
|
-
if not self.memory.task_description:
|
|
112
|
-
logger.warning("Task not set, using instruction as task description")
|
|
113
|
-
self.memory.task_description = instruction
|
|
114
|
-
|
|
115
110
|
overall_success = True
|
|
116
111
|
|
|
117
112
|
# Execute todos until none remain
|
|
@@ -127,6 +122,14 @@ class TaskerAgent(AsyncAgent):
|
|
|
127
122
|
todo, todo_index = todo_info
|
|
128
123
|
logger.info(f"Executing todo {todo_index}: {todo.description}")
|
|
129
124
|
|
|
125
|
+
# Emit split event at the start of todo
|
|
126
|
+
if self.step_observer:
|
|
127
|
+
await self.step_observer.on_event(
|
|
128
|
+
SplitEvent(
|
|
129
|
+
label=f"Start of todo {todo_index + 1}: {todo.description}"
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
130
133
|
# Execute the todo
|
|
131
134
|
success = await self._execute_todo(
|
|
132
135
|
todo_index,
|
|
@@ -134,6 +137,14 @@ class TaskerAgent(AsyncAgent):
|
|
|
134
137
|
image_provider,
|
|
135
138
|
)
|
|
136
139
|
|
|
140
|
+
# Emit split event after each todo
|
|
141
|
+
if self.step_observer:
|
|
142
|
+
await self.step_observer.on_event(
|
|
143
|
+
SplitEvent(
|
|
144
|
+
label=f"End of todo {todo_index + 1}: {todo.description}"
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
137
148
|
if not success:
|
|
138
149
|
logger.warning(f"Todo {todo_index} failed")
|
|
139
150
|
overall_success = False
|
|
@@ -171,7 +182,7 @@ class TaskerAgent(AsyncAgent):
|
|
|
171
182
|
api_key=self.api_key,
|
|
172
183
|
base_url=self.base_url,
|
|
173
184
|
model=self.model,
|
|
174
|
-
|
|
185
|
+
max_steps=self.max_steps, # Smaller steps per subtask
|
|
175
186
|
reflection_interval=self.reflection_interval,
|
|
176
187
|
temperature=self.temperature,
|
|
177
188
|
planner=self.planner,
|
oagi/cli/agent.py
CHANGED
|
@@ -13,6 +13,7 @@ import sys
|
|
|
13
13
|
import time
|
|
14
14
|
import traceback
|
|
15
15
|
|
|
16
|
+
from oagi.agent.observer import AsyncAgentObserver
|
|
16
17
|
from oagi.exceptions import check_optional_dependency
|
|
17
18
|
|
|
18
19
|
from .display import display_step_table
|
|
@@ -30,12 +31,14 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
30
31
|
run_parser.add_argument(
|
|
31
32
|
"instruction", type=str, help="Task instruction for the agent to execute"
|
|
32
33
|
)
|
|
33
|
-
run_parser.add_argument("--model", type=str, help="Model to use (default: lux-v1)")
|
|
34
34
|
run_parser.add_argument(
|
|
35
|
-
"--
|
|
35
|
+
"--model", type=str, help="Model to use (default: lux-actor-1)"
|
|
36
36
|
)
|
|
37
37
|
run_parser.add_argument(
|
|
38
|
-
"--
|
|
38
|
+
"--max-steps", type=int, help="Maximum number of steps (default: 20)"
|
|
39
|
+
)
|
|
40
|
+
run_parser.add_argument(
|
|
41
|
+
"--temperature", type=float, help="Sampling temperature (default: 0.5)"
|
|
39
42
|
)
|
|
40
43
|
run_parser.add_argument(
|
|
41
44
|
"--mode",
|
|
@@ -51,6 +54,17 @@ def add_agent_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
51
54
|
type=str,
|
|
52
55
|
help="OAGI base URL (default: https://api.agiopen.org, or OAGI_BASE_URL env var)",
|
|
53
56
|
)
|
|
57
|
+
run_parser.add_argument(
|
|
58
|
+
"--export",
|
|
59
|
+
type=str,
|
|
60
|
+
choices=["markdown", "html", "json"],
|
|
61
|
+
help="Export execution history to file (markdown, html, or json)",
|
|
62
|
+
)
|
|
63
|
+
run_parser.add_argument(
|
|
64
|
+
"--export-file",
|
|
65
|
+
type=str,
|
|
66
|
+
help="Output file path for export (default: execution_report.[md|html|json])",
|
|
67
|
+
)
|
|
54
68
|
|
|
55
69
|
|
|
56
70
|
def handle_agent_command(args: argparse.Namespace) -> None:
|
|
@@ -79,15 +93,27 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
79
93
|
base_url = args.oagi_base_url or os.getenv(
|
|
80
94
|
"OAGI_BASE_URL", "https://api.agiopen.org"
|
|
81
95
|
)
|
|
82
|
-
model = args.model or "lux-
|
|
83
|
-
max_steps = args.max_steps or
|
|
84
|
-
temperature = args.temperature if args.temperature is not None else 0.
|
|
96
|
+
model = args.model or "lux-actor-1"
|
|
97
|
+
max_steps = args.max_steps or 20
|
|
98
|
+
temperature = args.temperature if args.temperature is not None else 0.5
|
|
85
99
|
mode = args.mode or "actor"
|
|
100
|
+
export_format = args.export
|
|
101
|
+
export_file = args.export_file
|
|
86
102
|
|
|
87
|
-
# Create
|
|
103
|
+
# Create observers
|
|
88
104
|
step_tracker = StepTracker()
|
|
105
|
+
agent_observer = AsyncAgentObserver() if export_format else None
|
|
106
|
+
|
|
107
|
+
# Use a combined observer that forwards to both
|
|
108
|
+
class CombinedObserver:
|
|
109
|
+
async def on_event(self, event):
|
|
110
|
+
await step_tracker.on_event(event)
|
|
111
|
+
if agent_observer:
|
|
112
|
+
await agent_observer.on_event(event)
|
|
89
113
|
|
|
90
|
-
|
|
114
|
+
observer = CombinedObserver()
|
|
115
|
+
|
|
116
|
+
# Create agent with observer
|
|
91
117
|
agent = create_agent(
|
|
92
118
|
mode=mode,
|
|
93
119
|
api_key=api_key,
|
|
@@ -95,7 +121,7 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
95
121
|
model=model,
|
|
96
122
|
max_steps=max_steps,
|
|
97
123
|
temperature=temperature,
|
|
98
|
-
step_observer=
|
|
124
|
+
step_observer=observer,
|
|
99
125
|
)
|
|
100
126
|
|
|
101
127
|
# Create handlers
|
|
@@ -135,6 +161,21 @@ def run_agent(args: argparse.Namespace) -> None:
|
|
|
135
161
|
else:
|
|
136
162
|
print("\nNo steps were executed.")
|
|
137
163
|
|
|
164
|
+
# Export if requested
|
|
165
|
+
if export_format and agent_observer:
|
|
166
|
+
# Determine output file path
|
|
167
|
+
if export_file:
|
|
168
|
+
output_path = export_file
|
|
169
|
+
else:
|
|
170
|
+
ext_map = {"markdown": "md", "html": "html", "json": "json"}
|
|
171
|
+
output_path = f"execution_report.{ext_map[export_format]}"
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
agent_observer.export(export_format, output_path)
|
|
175
|
+
print(f"\nExecution history exported to: {output_path}")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
print(f"\nError exporting execution history: {e}", file=sys.stderr)
|
|
178
|
+
|
|
138
179
|
if interrupted:
|
|
139
180
|
sys.exit(130)
|
|
140
181
|
elif not success:
|
oagi/cli/tracking.py
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
|
|
12
|
-
from oagi.types import Action
|
|
12
|
+
from oagi.types import Action, ActionEvent, ObserverEvent, StepEvent
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclass
|
|
@@ -23,23 +23,33 @@ class StepData:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class StepTracker:
|
|
26
|
-
"""Tracks agent step execution by implementing
|
|
26
|
+
"""Tracks agent step execution by implementing AsyncObserver protocol."""
|
|
27
27
|
|
|
28
28
|
def __init__(self):
|
|
29
29
|
self.steps: list[StepData] = []
|
|
30
30
|
|
|
31
|
-
async def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
async def on_event(self, event: ObserverEvent) -> None:
|
|
32
|
+
"""Handle observer events.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
event: The observer event to handle.
|
|
36
|
+
"""
|
|
37
|
+
match event:
|
|
38
|
+
case StepEvent():
|
|
39
|
+
step_data = StepData(
|
|
40
|
+
step_num=event.step_num,
|
|
41
|
+
timestamp=event.timestamp,
|
|
42
|
+
reasoning=event.step.reason,
|
|
43
|
+
actions=event.step.actions,
|
|
44
|
+
action_count=len(event.step.actions),
|
|
45
|
+
status="running",
|
|
46
|
+
)
|
|
47
|
+
self.steps.append(step_data)
|
|
48
|
+
case ActionEvent():
|
|
49
|
+
# Update status of corresponding step
|
|
50
|
+
for step in self.steps:
|
|
51
|
+
if step.step_num == event.step_num:
|
|
52
|
+
step.status = "error" if event.error else "completed"
|
|
53
|
+
break
|
|
54
|
+
case _:
|
|
55
|
+
pass
|
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"),
|