oagi 0.1.0__tar.gz → 0.2.1__tar.gz
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.
Potentially problematic release.
This version of oagi might be problematic. Click here for more details.
- oagi-0.2.1/.github/workflows/release.yml +41 -0
- {oagi-0.1.0 → oagi-0.2.1}/PKG-INFO +21 -1
- oagi-0.2.1/README.md +21 -0
- {oagi-0.1.0 → oagi-0.2.1}/examples/execute_task_manual.py +11 -5
- oagi-0.2.1/examples/single_step.py +19 -0
- {oagi-0.1.0 → oagi-0.2.1}/pyproject.toml +2 -2
- oagi-0.2.1/src/oagi/__init__.py +53 -0
- oagi-0.2.1/src/oagi/exceptions.py +75 -0
- oagi-0.2.1/src/oagi/short_task.py +44 -0
- oagi-0.2.1/src/oagi/single_step.py +82 -0
- oagi-0.2.1/src/oagi/sync_client.py +289 -0
- oagi-0.1.0/src/oagi/short_task.py → oagi-0.2.1/src/oagi/task.py +20 -33
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/image.py +2 -1
- {oagi-0.1.0 → oagi-0.2.1}/tests/conftest.py +52 -3
- oagi-0.2.1/tests/test_logging.py +334 -0
- oagi-0.2.1/tests/test_short_task.py +148 -0
- oagi-0.2.1/tests/test_single_step.py +193 -0
- oagi-0.2.1/tests/test_sync_client.py +410 -0
- oagi-0.2.1/tests/test_task.py +275 -0
- {oagi-0.1.0 → oagi-0.2.1}/uv.lock +1 -1
- oagi-0.1.0/.claude/settings.local.json +0 -19
- oagi-0.1.0/CLAUDE.md +0 -105
- oagi-0.1.0/README.md +0 -1
- oagi-0.1.0/examples/screenshot.png +0 -0
- oagi-0.1.0/examples/test.py +0 -20
- oagi-0.1.0/examples/test_screenshot.py +0 -41
- oagi-0.1.0/src/oagi/__init__.py +0 -13
- oagi-0.1.0/src/oagi/sync_client.py +0 -183
- oagi-0.1.0/tests/test_logging.py +0 -321
- oagi-0.1.0/tests/test_sync_client.py +0 -331
- {oagi-0.1.0 → oagi-0.2.1}/.github/workflows/ci.yml +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/.gitignore +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/.python-version +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/CONTRIBUTING.md +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/LICENSE +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/Makefile +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/examples/execute_task_auto.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/examples/google_weather.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/examples/hotel_booking.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/logging.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/pyautogui_action_handler.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/screenshot_maker.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/__init__.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/action_handler.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/image_provider.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/models/__init__.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/models/action.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/src/oagi/types/models/step.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/tests/__init__.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/tests/test_pyautogui_action_handler.py +0 -0
- {oagi-0.1.0 → oagi-0.2.1}/tests/test_screenshot_maker.py +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # For trusted publishing
|
|
13
|
+
contents: write # For GitHub release
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v3
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: true
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
run: uv python install 3.12
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
31
|
+
with:
|
|
32
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
33
|
+
# Another option: Use Trusted Publishing (recommended, no token needed)
|
|
34
|
+
# Configure at: https://pypi.org/manage/project/oagi/settings/publishing/
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
- name: Create GitHub Release
|
|
38
|
+
uses: softprops/action-gh-release@v1
|
|
39
|
+
with:
|
|
40
|
+
files: dist/*
|
|
41
|
+
generate_release_notes: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: oagi
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Official API of OpenAGI Foundation
|
|
5
5
|
Project-URL: Homepage, https://github.com/agiopen-org/oagi
|
|
6
6
|
Author-email: OpenAGI Foundation <contact@agiopen.org>
|
|
@@ -33,3 +33,23 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
35
35
|
# OAGI Python SDK
|
|
36
|
+
|
|
37
|
+
## Basic Usage
|
|
38
|
+
```bash
|
|
39
|
+
pip install oagi # python >= 3.10
|
|
40
|
+
```
|
|
41
|
+
```bash
|
|
42
|
+
export OAGI_BASE_URL=""
|
|
43
|
+
export OAGI_API_KEY="sk-xxxx"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from oagi import PyautoguiActionHandler, ScreenshotMaker, ShortTask
|
|
48
|
+
short_task = ShortTask()
|
|
49
|
+
is_completed = short_task.auto_mode(
|
|
50
|
+
"Search weather with Google",
|
|
51
|
+
max_steps=5,
|
|
52
|
+
executor=PyautoguiActionHandler(),
|
|
53
|
+
image_provider=(sm := ScreenshotMaker()),
|
|
54
|
+
)
|
|
55
|
+
```
|
oagi-0.2.1/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# OAGI Python SDK
|
|
2
|
+
|
|
3
|
+
## Basic Usage
|
|
4
|
+
```bash
|
|
5
|
+
pip install oagi # python >= 3.10
|
|
6
|
+
```
|
|
7
|
+
```bash
|
|
8
|
+
export OAGI_BASE_URL=""
|
|
9
|
+
export OAGI_API_KEY="sk-xxxx"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from oagi import PyautoguiActionHandler, ScreenshotMaker, ShortTask
|
|
14
|
+
short_task = ShortTask()
|
|
15
|
+
is_completed = short_task.auto_mode(
|
|
16
|
+
"Search weather with Google",
|
|
17
|
+
max_steps=5,
|
|
18
|
+
executor=PyautoguiActionHandler(),
|
|
19
|
+
image_provider=(sm := ScreenshotMaker()),
|
|
20
|
+
)
|
|
21
|
+
```
|
|
@@ -6,23 +6,29 @@
|
|
|
6
6
|
# Licensed under the MIT License.
|
|
7
7
|
# -----------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
from oagi import PyautoguiActionHandler, ScreenshotMaker,
|
|
9
|
+
from oagi import PyautoguiActionHandler, ScreenshotMaker, Task
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def execute_task_manual(task_desc, max_steps=5):
|
|
13
13
|
# set OAGI_API_KEY and OAGI_BASE_URL
|
|
14
14
|
# or ShortTask(api_key="your_api_key", base_url="your_base_url")
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
task = Task()
|
|
16
|
+
task.init_task(task_desc, max_steps=max_steps)
|
|
17
17
|
executor = (
|
|
18
18
|
PyautoguiActionHandler()
|
|
19
19
|
) # executor = lambda actions: print(actions) for debugging
|
|
20
20
|
image_provider = ScreenshotMaker()
|
|
21
21
|
|
|
22
22
|
for i in range(max_steps):
|
|
23
|
+
# image can also be bytes
|
|
24
|
+
# with open("test_screenshot.png", "rb") as f:
|
|
25
|
+
# image = f.read()
|
|
23
26
|
image = image_provider()
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
# For additional instructions
|
|
29
|
+
# step = task.step(image, instruction="some instruction")
|
|
30
|
+
step = task.step(image)
|
|
31
|
+
|
|
26
32
|
# do something with step, maybe print to debug
|
|
27
33
|
print(f"Step {i}: {step.reason=}")
|
|
28
34
|
|
|
@@ -0,0 +1,19 @@
|
|
|
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 oagi import single_step
|
|
10
|
+
|
|
11
|
+
step = single_step(
|
|
12
|
+
task_description="Search weather with Google",
|
|
13
|
+
screenshot="some/path/to/local/image", # bytes or Path object or Image object
|
|
14
|
+
instruction="The operating system is macos", # optional instruction
|
|
15
|
+
# api_key="your-api-key", if not set with OAGI_API_KEY env var
|
|
16
|
+
# base_url="https://api.example.com" if not set with OAGI_BASE_URL env var
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
print(step)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oagi"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Official API of OpenAGI Foundation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -32,4 +32,4 @@ dev = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[tool.ruff.lint]
|
|
35
|
-
extend-select = ["I"]
|
|
35
|
+
extend-select = ["I", "PLC0415"]
|
|
@@ -0,0 +1,53 @@
|
|
|
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 oagi.exceptions import (
|
|
10
|
+
APIError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
ConfigurationError,
|
|
13
|
+
NetworkError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
OAGIError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
RequestTimeoutError,
|
|
18
|
+
ServerError,
|
|
19
|
+
ValidationError,
|
|
20
|
+
)
|
|
21
|
+
from oagi.pyautogui_action_handler import PyautoguiActionHandler
|
|
22
|
+
from oagi.screenshot_maker import ScreenshotMaker
|
|
23
|
+
from oagi.short_task import ShortTask
|
|
24
|
+
from oagi.single_step import single_step
|
|
25
|
+
from oagi.sync_client import ErrorDetail, ErrorResponse, LLMResponse, SyncClient
|
|
26
|
+
from oagi.task import Task
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# Core classes
|
|
30
|
+
"Task",
|
|
31
|
+
"ShortTask",
|
|
32
|
+
"SyncClient",
|
|
33
|
+
# Functions
|
|
34
|
+
"single_step",
|
|
35
|
+
# Handler classes
|
|
36
|
+
"PyautoguiActionHandler",
|
|
37
|
+
"ScreenshotMaker",
|
|
38
|
+
# Response models
|
|
39
|
+
"LLMResponse",
|
|
40
|
+
"ErrorResponse",
|
|
41
|
+
"ErrorDetail",
|
|
42
|
+
# Exceptions
|
|
43
|
+
"OAGIError",
|
|
44
|
+
"APIError",
|
|
45
|
+
"AuthenticationError",
|
|
46
|
+
"ConfigurationError",
|
|
47
|
+
"NetworkError",
|
|
48
|
+
"NotFoundError",
|
|
49
|
+
"RateLimitError",
|
|
50
|
+
"ServerError",
|
|
51
|
+
"RequestTimeoutError",
|
|
52
|
+
"ValidationError",
|
|
53
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) OpenAGI Foundation
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the official API project.
|
|
6
|
+
# Licensed under the MIT License.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OAGIError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class APIError(OAGIError):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
message: str,
|
|
20
|
+
code: str | None = None,
|
|
21
|
+
status_code: int | None = None,
|
|
22
|
+
response: httpx.Response | None = None,
|
|
23
|
+
):
|
|
24
|
+
"""Initialize APIError.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
message: Human-readable error message
|
|
28
|
+
code: API error code for programmatic handling
|
|
29
|
+
status_code: HTTP status code
|
|
30
|
+
response: Original HTTP response object
|
|
31
|
+
"""
|
|
32
|
+
super().__init__(message)
|
|
33
|
+
self.message = message
|
|
34
|
+
self.code = code
|
|
35
|
+
self.status_code = status_code
|
|
36
|
+
self.response = response
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
if self.code:
|
|
40
|
+
return f"API Error [{self.code}]: {self.message}"
|
|
41
|
+
return f"API Error: {self.message}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AuthenticationError(APIError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RateLimitError(APIError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ValidationError(APIError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class NotFoundError(APIError):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ServerError(APIError):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class NetworkError(OAGIError):
|
|
65
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
66
|
+
super().__init__(message)
|
|
67
|
+
self.original_error = original_error
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RequestTimeoutError(NetworkError):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ConfigurationError(OAGIError):
|
|
75
|
+
pass
|
|
@@ -0,0 +1,44 @@
|
|
|
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 .logging import get_logger
|
|
10
|
+
from .task import Task
|
|
11
|
+
from .types import ActionHandler, ImageProvider
|
|
12
|
+
|
|
13
|
+
logger = get_logger("short_task")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ShortTask(Task):
|
|
17
|
+
"""Task implementation with automatic mode for short-duration tasks."""
|
|
18
|
+
|
|
19
|
+
def auto_mode(
|
|
20
|
+
self,
|
|
21
|
+
task_desc: str,
|
|
22
|
+
max_steps: int = 5,
|
|
23
|
+
executor: ActionHandler = None,
|
|
24
|
+
image_provider: ImageProvider = None,
|
|
25
|
+
) -> bool:
|
|
26
|
+
"""Run the task in automatic mode with the provided executor and image provider."""
|
|
27
|
+
logger.info(
|
|
28
|
+
f"Starting auto mode for task: '{task_desc}' (max_steps: {max_steps})"
|
|
29
|
+
)
|
|
30
|
+
self.init_task(task_desc, max_steps=max_steps)
|
|
31
|
+
|
|
32
|
+
for i in range(max_steps):
|
|
33
|
+
logger.debug(f"Auto mode step {i + 1}/{max_steps}")
|
|
34
|
+
image = image_provider()
|
|
35
|
+
step = self.step(image)
|
|
36
|
+
if step.stop:
|
|
37
|
+
logger.info(f"Auto mode completed successfully after {i + 1} steps")
|
|
38
|
+
return True
|
|
39
|
+
if executor:
|
|
40
|
+
logger.debug(f"Executing {len(step.actions)} actions")
|
|
41
|
+
executor(step.actions)
|
|
42
|
+
|
|
43
|
+
logger.warning(f"Auto mode reached max steps ({max_steps}) without completion")
|
|
44
|
+
return False
|
|
@@ -0,0 +1,82 @@
|
|
|
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 pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .task import Task
|
|
12
|
+
from .types import Image, Step
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def single_step(
|
|
16
|
+
task_description: str,
|
|
17
|
+
screenshot: str | bytes | Path | Image,
|
|
18
|
+
instruction: str | None = None,
|
|
19
|
+
api_key: str | None = None,
|
|
20
|
+
base_url: str | None = None,
|
|
21
|
+
) -> Step:
|
|
22
|
+
"""
|
|
23
|
+
Perform a single-step inference without maintaining task state.
|
|
24
|
+
|
|
25
|
+
This is useful for one-off analyses where you don't need to maintain
|
|
26
|
+
a conversation or task context across multiple steps.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
task_description: Description of the task to perform
|
|
30
|
+
screenshot: Screenshot as Image, bytes, or file path
|
|
31
|
+
instruction: Optional additional instruction for the task
|
|
32
|
+
api_key: OAGI API key (uses environment variable if not provided)
|
|
33
|
+
base_url: OAGI base URL (uses environment variable if not provided)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Step: Object containing reasoning, actions, and completion status
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> # Using with bytes
|
|
40
|
+
>>> with open("screenshot.png", "rb") as f:
|
|
41
|
+
... image_bytes = f.read()
|
|
42
|
+
>>> step = single_step(
|
|
43
|
+
... task_description="Click the submit button",
|
|
44
|
+
... screenshot=image_bytes
|
|
45
|
+
... )
|
|
46
|
+
|
|
47
|
+
>>> # Using with file path
|
|
48
|
+
>>> step = single_step(
|
|
49
|
+
... task_description="Fill in the form",
|
|
50
|
+
... screenshot=Path("screenshot.png"),
|
|
51
|
+
... instruction="Use test@example.com for email"
|
|
52
|
+
... )
|
|
53
|
+
|
|
54
|
+
>>> # Using with Image object
|
|
55
|
+
>>> from oagi.types import Image
|
|
56
|
+
>>> image = Image(...)
|
|
57
|
+
>>> step = single_step(
|
|
58
|
+
... task_description="Navigate to settings",
|
|
59
|
+
... screenshot=image
|
|
60
|
+
... )
|
|
61
|
+
"""
|
|
62
|
+
# Convert file paths to bytes
|
|
63
|
+
if isinstance(screenshot, (str, Path)):
|
|
64
|
+
path = Path(screenshot) if isinstance(screenshot, str) else screenshot
|
|
65
|
+
if path.exists():
|
|
66
|
+
with open(path, "rb") as f:
|
|
67
|
+
screenshot_bytes = f.read()
|
|
68
|
+
else:
|
|
69
|
+
raise FileNotFoundError(f"Screenshot file not found: {path}")
|
|
70
|
+
elif isinstance(screenshot, bytes):
|
|
71
|
+
screenshot_bytes = screenshot
|
|
72
|
+
elif isinstance(screenshot, Image):
|
|
73
|
+
screenshot_bytes = screenshot.read()
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"screenshot must be Image, bytes, str, or Path, got {type(screenshot)}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Use Task to perform single step
|
|
80
|
+
with Task(api_key=api_key, base_url=base_url) as task:
|
|
81
|
+
task.init_task(task_description)
|
|
82
|
+
return task.step(screenshot_bytes, instruction=instruction)
|