plato-sdk 1.0.0__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.
- plato_sdk-1.0.0/.gitignore +10 -0
- plato_sdk-1.0.0/.python-version +1 -0
- plato_sdk-1.0.0/PKG-INFO +35 -0
- plato_sdk-1.0.0/README.md +21 -0
- plato_sdk-1.0.0/pyproject.toml +32 -0
- plato_sdk-1.0.0/requirements-dev.lock +115 -0
- plato_sdk-1.0.0/requirements.lock +115 -0
- plato_sdk-1.0.0/src/plato/client.py +203 -0
- plato_sdk-1.0.0/src/plato/config.py +22 -0
- plato_sdk-1.0.0/src/plato/models/__init__.py +4 -0
- plato_sdk-1.0.0/src/plato/models/env.py +158 -0
- plato_sdk-1.0.0/src/plato/models/task.py +53 -0
- plato_sdk-1.0.0/src/plato/test/test_env.py +42 -0
- plato_sdk-1.0.0/src/plato/test/test_run.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.8
|
plato_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plato-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Plato SDK
|
|
5
|
+
Author-email: Pranav Putta <pranav@plato.so>, Rob Farlow <rob@plato.so>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: aiohttp>=3.11.13
|
|
8
|
+
Requires-Dist: build>=1.2.2.post1
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.8.1
|
|
11
|
+
Requires-Dist: pydantic>=2.10.6
|
|
12
|
+
Requires-Dist: twine>=6.1.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Plato SDK
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
```
|
|
20
|
+
class Task(BaseModel):
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
async with env := plato.make(ENV_ID):
|
|
24
|
+
cdp_url = env.cdp_url
|
|
25
|
+
|
|
26
|
+
# connect to browser and do modifications
|
|
27
|
+
|
|
28
|
+
# get state
|
|
29
|
+
state_mutations: dict = env.get_state()
|
|
30
|
+
|
|
31
|
+
# evaluate
|
|
32
|
+
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Plato SDK
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
```
|
|
6
|
+
class Task(BaseModel):
|
|
7
|
+
...
|
|
8
|
+
|
|
9
|
+
async with env := plato.make(ENV_ID):
|
|
10
|
+
cdp_url = env.cdp_url
|
|
11
|
+
|
|
12
|
+
# connect to browser and do modifications
|
|
13
|
+
|
|
14
|
+
# get state
|
|
15
|
+
state_mutations: dict = env.get_state()
|
|
16
|
+
|
|
17
|
+
# evaluate
|
|
18
|
+
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "plato-sdk"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Plato SDK"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Pranav Putta", email = "pranav@plato.so" },
|
|
7
|
+
{name = "Rob Farlow", email="rob@plato.so"}
|
|
8
|
+
]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"httpx>=0.28.1",
|
|
11
|
+
"pydantic>=2.10.6",
|
|
12
|
+
"pydantic-settings>=2.8.1",
|
|
13
|
+
"aiohttp>=3.11.13",
|
|
14
|
+
"build>=1.2.2.post1",
|
|
15
|
+
"twine>=6.1.0",
|
|
16
|
+
]
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
requires-python = ">= 3.8"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.rye]
|
|
25
|
+
managed = true
|
|
26
|
+
dev-dependencies = []
|
|
27
|
+
|
|
28
|
+
[tool.hatch.metadata]
|
|
29
|
+
allow-direct-references = true
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/plato"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# generated by rye
|
|
2
|
+
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
+
#
|
|
4
|
+
# last locked with the following flags:
|
|
5
|
+
# pre: false
|
|
6
|
+
# features: []
|
|
7
|
+
# all-features: false
|
|
8
|
+
# with-sources: false
|
|
9
|
+
# generate-hashes: false
|
|
10
|
+
# universal: false
|
|
11
|
+
|
|
12
|
+
-e file:.
|
|
13
|
+
aiohappyeyeballs==2.6.1
|
|
14
|
+
# via aiohttp
|
|
15
|
+
aiohttp==3.11.13
|
|
16
|
+
# via plato
|
|
17
|
+
aiosignal==1.3.2
|
|
18
|
+
# via aiohttp
|
|
19
|
+
annotated-types==0.7.0
|
|
20
|
+
# via pydantic
|
|
21
|
+
anyio==4.8.0
|
|
22
|
+
# via httpx
|
|
23
|
+
attrs==25.2.0
|
|
24
|
+
# via aiohttp
|
|
25
|
+
build==1.2.2.post1
|
|
26
|
+
# via plato
|
|
27
|
+
certifi==2025.1.31
|
|
28
|
+
# via httpcore
|
|
29
|
+
# via httpx
|
|
30
|
+
# via requests
|
|
31
|
+
charset-normalizer==3.4.1
|
|
32
|
+
# via requests
|
|
33
|
+
docutils==0.21.2
|
|
34
|
+
# via readme-renderer
|
|
35
|
+
frozenlist==1.5.0
|
|
36
|
+
# via aiohttp
|
|
37
|
+
# via aiosignal
|
|
38
|
+
h11==0.14.0
|
|
39
|
+
# via httpcore
|
|
40
|
+
httpcore==1.0.7
|
|
41
|
+
# via httpx
|
|
42
|
+
httpx==0.28.1
|
|
43
|
+
# via plato
|
|
44
|
+
id==1.5.0
|
|
45
|
+
# via twine
|
|
46
|
+
idna==3.10
|
|
47
|
+
# via anyio
|
|
48
|
+
# via httpx
|
|
49
|
+
# via requests
|
|
50
|
+
# via yarl
|
|
51
|
+
jaraco-classes==3.4.0
|
|
52
|
+
# via keyring
|
|
53
|
+
jaraco-context==6.0.1
|
|
54
|
+
# via keyring
|
|
55
|
+
jaraco-functools==4.1.0
|
|
56
|
+
# via keyring
|
|
57
|
+
keyring==25.6.0
|
|
58
|
+
# via twine
|
|
59
|
+
markdown-it-py==3.0.0
|
|
60
|
+
# via rich
|
|
61
|
+
mdurl==0.1.2
|
|
62
|
+
# via markdown-it-py
|
|
63
|
+
more-itertools==10.6.0
|
|
64
|
+
# via jaraco-classes
|
|
65
|
+
# via jaraco-functools
|
|
66
|
+
multidict==6.1.0
|
|
67
|
+
# via aiohttp
|
|
68
|
+
# via yarl
|
|
69
|
+
nh3==0.2.21
|
|
70
|
+
# via readme-renderer
|
|
71
|
+
packaging==24.2
|
|
72
|
+
# via build
|
|
73
|
+
# via twine
|
|
74
|
+
propcache==0.3.0
|
|
75
|
+
# via aiohttp
|
|
76
|
+
# via yarl
|
|
77
|
+
pydantic==2.10.6
|
|
78
|
+
# via plato
|
|
79
|
+
# via pydantic-settings
|
|
80
|
+
pydantic-core==2.27.2
|
|
81
|
+
# via pydantic
|
|
82
|
+
pydantic-settings==2.8.1
|
|
83
|
+
# via plato
|
|
84
|
+
pygments==2.19.1
|
|
85
|
+
# via readme-renderer
|
|
86
|
+
# via rich
|
|
87
|
+
pyproject-hooks==1.2.0
|
|
88
|
+
# via build
|
|
89
|
+
python-dotenv==1.0.1
|
|
90
|
+
# via pydantic-settings
|
|
91
|
+
readme-renderer==44.0
|
|
92
|
+
# via twine
|
|
93
|
+
requests==2.32.3
|
|
94
|
+
# via id
|
|
95
|
+
# via requests-toolbelt
|
|
96
|
+
# via twine
|
|
97
|
+
requests-toolbelt==1.0.0
|
|
98
|
+
# via twine
|
|
99
|
+
rfc3986==2.0.0
|
|
100
|
+
# via twine
|
|
101
|
+
rich==13.9.4
|
|
102
|
+
# via twine
|
|
103
|
+
sniffio==1.3.1
|
|
104
|
+
# via anyio
|
|
105
|
+
twine==6.1.0
|
|
106
|
+
# via plato
|
|
107
|
+
typing-extensions==4.12.2
|
|
108
|
+
# via anyio
|
|
109
|
+
# via pydantic
|
|
110
|
+
# via pydantic-core
|
|
111
|
+
urllib3==2.3.0
|
|
112
|
+
# via requests
|
|
113
|
+
# via twine
|
|
114
|
+
yarl==1.18.3
|
|
115
|
+
# via aiohttp
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# generated by rye
|
|
2
|
+
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
+
#
|
|
4
|
+
# last locked with the following flags:
|
|
5
|
+
# pre: false
|
|
6
|
+
# features: []
|
|
7
|
+
# all-features: false
|
|
8
|
+
# with-sources: false
|
|
9
|
+
# generate-hashes: false
|
|
10
|
+
# universal: false
|
|
11
|
+
|
|
12
|
+
-e file:.
|
|
13
|
+
aiohappyeyeballs==2.6.1
|
|
14
|
+
# via aiohttp
|
|
15
|
+
aiohttp==3.11.13
|
|
16
|
+
# via plato
|
|
17
|
+
aiosignal==1.3.2
|
|
18
|
+
# via aiohttp
|
|
19
|
+
annotated-types==0.7.0
|
|
20
|
+
# via pydantic
|
|
21
|
+
anyio==4.8.0
|
|
22
|
+
# via httpx
|
|
23
|
+
attrs==25.2.0
|
|
24
|
+
# via aiohttp
|
|
25
|
+
build==1.2.2.post1
|
|
26
|
+
# via plato
|
|
27
|
+
certifi==2025.1.31
|
|
28
|
+
# via httpcore
|
|
29
|
+
# via httpx
|
|
30
|
+
# via requests
|
|
31
|
+
charset-normalizer==3.4.1
|
|
32
|
+
# via requests
|
|
33
|
+
docutils==0.21.2
|
|
34
|
+
# via readme-renderer
|
|
35
|
+
frozenlist==1.5.0
|
|
36
|
+
# via aiohttp
|
|
37
|
+
# via aiosignal
|
|
38
|
+
h11==0.14.0
|
|
39
|
+
# via httpcore
|
|
40
|
+
httpcore==1.0.7
|
|
41
|
+
# via httpx
|
|
42
|
+
httpx==0.28.1
|
|
43
|
+
# via plato
|
|
44
|
+
id==1.5.0
|
|
45
|
+
# via twine
|
|
46
|
+
idna==3.10
|
|
47
|
+
# via anyio
|
|
48
|
+
# via httpx
|
|
49
|
+
# via requests
|
|
50
|
+
# via yarl
|
|
51
|
+
jaraco-classes==3.4.0
|
|
52
|
+
# via keyring
|
|
53
|
+
jaraco-context==6.0.1
|
|
54
|
+
# via keyring
|
|
55
|
+
jaraco-functools==4.1.0
|
|
56
|
+
# via keyring
|
|
57
|
+
keyring==25.6.0
|
|
58
|
+
# via twine
|
|
59
|
+
markdown-it-py==3.0.0
|
|
60
|
+
# via rich
|
|
61
|
+
mdurl==0.1.2
|
|
62
|
+
# via markdown-it-py
|
|
63
|
+
more-itertools==10.6.0
|
|
64
|
+
# via jaraco-classes
|
|
65
|
+
# via jaraco-functools
|
|
66
|
+
multidict==6.1.0
|
|
67
|
+
# via aiohttp
|
|
68
|
+
# via yarl
|
|
69
|
+
nh3==0.2.21
|
|
70
|
+
# via readme-renderer
|
|
71
|
+
packaging==24.2
|
|
72
|
+
# via build
|
|
73
|
+
# via twine
|
|
74
|
+
propcache==0.3.0
|
|
75
|
+
# via aiohttp
|
|
76
|
+
# via yarl
|
|
77
|
+
pydantic==2.10.6
|
|
78
|
+
# via plato
|
|
79
|
+
# via pydantic-settings
|
|
80
|
+
pydantic-core==2.27.2
|
|
81
|
+
# via pydantic
|
|
82
|
+
pydantic-settings==2.8.1
|
|
83
|
+
# via plato
|
|
84
|
+
pygments==2.19.1
|
|
85
|
+
# via readme-renderer
|
|
86
|
+
# via rich
|
|
87
|
+
pyproject-hooks==1.2.0
|
|
88
|
+
# via build
|
|
89
|
+
python-dotenv==1.0.1
|
|
90
|
+
# via pydantic-settings
|
|
91
|
+
readme-renderer==44.0
|
|
92
|
+
# via twine
|
|
93
|
+
requests==2.32.3
|
|
94
|
+
# via id
|
|
95
|
+
# via requests-toolbelt
|
|
96
|
+
# via twine
|
|
97
|
+
requests-toolbelt==1.0.0
|
|
98
|
+
# via twine
|
|
99
|
+
rfc3986==2.0.0
|
|
100
|
+
# via twine
|
|
101
|
+
rich==13.9.4
|
|
102
|
+
# via twine
|
|
103
|
+
sniffio==1.3.1
|
|
104
|
+
# via anyio
|
|
105
|
+
twine==6.1.0
|
|
106
|
+
# via plato
|
|
107
|
+
typing-extensions==4.12.2
|
|
108
|
+
# via anyio
|
|
109
|
+
# via pydantic
|
|
110
|
+
# via pydantic-core
|
|
111
|
+
urllib3==2.3.0
|
|
112
|
+
# via requests
|
|
113
|
+
# via twine
|
|
114
|
+
yarl==1.18.3
|
|
115
|
+
# via aiohttp
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
import aiohttp
|
|
3
|
+
from plato.config import get_config
|
|
4
|
+
from plato.models import PlatoTask, PlatoEnvironment
|
|
5
|
+
|
|
6
|
+
config = get_config()
|
|
7
|
+
|
|
8
|
+
class PlatoClient:
|
|
9
|
+
"""Client for interacting with the Plato API.
|
|
10
|
+
|
|
11
|
+
This class provides methods to create and manage Plato environments, handle API authentication,
|
|
12
|
+
and manage HTTP sessions.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
api_key (str): The API key used for authentication with Plato API.
|
|
16
|
+
base_url (str): The base URL of the Plato API.
|
|
17
|
+
http_session (Optional[aiohttp.ClientSession]): The aiohttp session for making HTTP requests.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
21
|
+
"""Initialize a new PlatoClient.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
api_key (Optional[str]): The API key for authentication. If not provided,
|
|
25
|
+
falls back to the key from config.
|
|
26
|
+
"""
|
|
27
|
+
self.api_key = api_key or config.api_key
|
|
28
|
+
self.base_url = config.base_url
|
|
29
|
+
self._http_session: Optional[aiohttp.ClientSession] = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def http_session(self) -> aiohttp.ClientSession:
|
|
33
|
+
"""Get or create an aiohttp client session.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
aiohttp.ClientSession: The active HTTP client session.
|
|
37
|
+
"""
|
|
38
|
+
if self._http_session is None or self._http_session.closed:
|
|
39
|
+
self._http_session = aiohttp.ClientSession()
|
|
40
|
+
return self._http_session
|
|
41
|
+
|
|
42
|
+
async def close(self):
|
|
43
|
+
"""Close the aiohttp client session if it exists."""
|
|
44
|
+
if self._http_session is not None and not self._http_session.closed:
|
|
45
|
+
await self._http_session.close()
|
|
46
|
+
self._http_session = None
|
|
47
|
+
|
|
48
|
+
async def make_environment(self, env_id: str) -> PlatoEnvironment:
|
|
49
|
+
"""Create a new Plato environment for the given task.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
task (PlatoTask): The task to create an environment for.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
PlatoEnvironment: The created environment instance.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
aiohttp.ClientError: If the API request fails.
|
|
59
|
+
"""
|
|
60
|
+
headers = {"X-API-Key": self.api_key}
|
|
61
|
+
async with self.http_session.post(
|
|
62
|
+
f"{self.base_url}/env/make",
|
|
63
|
+
json={"config": {
|
|
64
|
+
"type": "browser_sim",
|
|
65
|
+
"env_id": env_id,
|
|
66
|
+
"sim_connection_type": "fastapi"
|
|
67
|
+
}},
|
|
68
|
+
headers=headers
|
|
69
|
+
) as response:
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
data = await response.json()
|
|
72
|
+
return PlatoEnvironment(
|
|
73
|
+
client=self,
|
|
74
|
+
id=data["job_id"]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async def get_job_status(self, job_id: str) -> Dict[str, Any]:
|
|
78
|
+
"""Get the status of a job.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
job_id (str): The ID of the job to check.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dict[str, Any]: The job status information.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
aiohttp.ClientError: If the API request fails.
|
|
88
|
+
"""
|
|
89
|
+
headers = {"X-API-Key": self.api_key}
|
|
90
|
+
async with self.http_session.get(
|
|
91
|
+
f"{self.base_url}/env/{job_id}/status",
|
|
92
|
+
headers=headers
|
|
93
|
+
) as response:
|
|
94
|
+
response.raise_for_status()
|
|
95
|
+
return await response.json()
|
|
96
|
+
|
|
97
|
+
async def get_cdp_url(self, job_id: str) -> str:
|
|
98
|
+
"""Get the Chrome DevTools Protocol URL for a job.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
job_id (str): The ID of the job to get the CDP URL for.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: The CDP URL for the job.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
aiohttp.ClientError: If the API request fails.
|
|
108
|
+
"""
|
|
109
|
+
headers = {"X-API-Key": self.api_key}
|
|
110
|
+
async with self.http_session.get(
|
|
111
|
+
f"{self.base_url}/env/{job_id}/cdp_url",
|
|
112
|
+
headers=headers
|
|
113
|
+
) as response:
|
|
114
|
+
response.raise_for_status()
|
|
115
|
+
data = await response.json()
|
|
116
|
+
return data["cdp_url"]
|
|
117
|
+
|
|
118
|
+
async def close_environment(self, job_id: str) -> Dict[str, Any]:
|
|
119
|
+
"""Close an environment.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
job_id (str): The ID of the job to close.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Dict[str, Any]: The response from the server.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
aiohttp.ClientError: If the API request fails.
|
|
129
|
+
"""
|
|
130
|
+
headers = {"X-API-Key": self.api_key}
|
|
131
|
+
async with self.http_session.post(
|
|
132
|
+
f"{self.base_url}/env/{job_id}/close",
|
|
133
|
+
headers=headers
|
|
134
|
+
) as response:
|
|
135
|
+
response.raise_for_status()
|
|
136
|
+
return await response.json()
|
|
137
|
+
|
|
138
|
+
async def reset_environment(self, job_id: str, task: Optional[PlatoTask] = None) -> Dict[str, Any]:
|
|
139
|
+
"""Reset an environment with an optional new task.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
job_id (str): The ID of the job to reset.
|
|
143
|
+
task (Optional[PlatoTask]): Optional new task for the environment.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Dict[str, Any]: The response from the server.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
aiohttp.ClientError: If the API request fails.
|
|
150
|
+
"""
|
|
151
|
+
headers = {"X-API-Key": self.api_key}
|
|
152
|
+
params = {"task": task.dict() if task else None}
|
|
153
|
+
async with self.http_session.post(
|
|
154
|
+
f"{self.base_url}/env/{job_id}/reset",
|
|
155
|
+
headers=headers,
|
|
156
|
+
) as response:
|
|
157
|
+
response.raise_for_status()
|
|
158
|
+
return await response.json()
|
|
159
|
+
|
|
160
|
+
async def get_environment_state(self, job_id: str) -> Dict[str, Any]:
|
|
161
|
+
"""Get the current state of an environment.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
job_id (str): The ID of the job to get state for.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dict[str, Any]: The current state of the environment.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
aiohttp.ClientError: If the API request fails.
|
|
171
|
+
"""
|
|
172
|
+
headers = {"X-API-Key": self.api_key}
|
|
173
|
+
async with self.http_session.get(
|
|
174
|
+
f"{self.base_url}/env/{job_id}/state",
|
|
175
|
+
headers=headers
|
|
176
|
+
) as response:
|
|
177
|
+
response.raise_for_status()
|
|
178
|
+
return await response.json()
|
|
179
|
+
|
|
180
|
+
async def get_worker_ready(self, job_id: str) -> Dict[str, Any]:
|
|
181
|
+
"""Check if the worker for this job is ready and healthy.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
job_id (str): The ID of the job to check.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict[str, Any]: The worker ready status information including:
|
|
188
|
+
- ready (bool): Whether the worker is ready
|
|
189
|
+
- worker_ip (Optional[str]): The worker's IP if ready
|
|
190
|
+
- worker_port (Optional[int]): The worker's port if ready
|
|
191
|
+
- health_status (Optional[Dict]): The worker's health status if ready
|
|
192
|
+
- error (Optional[str]): Error message if not ready
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
aiohttp.ClientError: If the API request fails.
|
|
196
|
+
"""
|
|
197
|
+
headers = {"X-API-Key": self.api_key}
|
|
198
|
+
async with self.http_session.get(
|
|
199
|
+
f"{self.base_url}/env/{job_id}/worker_ready",
|
|
200
|
+
headers=headers
|
|
201
|
+
) as response:
|
|
202
|
+
response.raise_for_status()
|
|
203
|
+
return await response.json()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Configuration settings for the Plato client."""
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
|
|
7
|
+
class Config(BaseSettings):
|
|
8
|
+
model_config = SettingsConfigDict(env_file=".env", env_prefix="PLATO_")
|
|
9
|
+
base_url: str = Field(
|
|
10
|
+
default="http://localhost:25565/api",
|
|
11
|
+
description="Base URL for the Plato API",
|
|
12
|
+
validation_alias="PLATO_BASE_URL ",
|
|
13
|
+
)
|
|
14
|
+
api_key: str = Field(
|
|
15
|
+
default="",
|
|
16
|
+
description="API key for the Plato API",
|
|
17
|
+
validation_alias="PLATO_API_KEY",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
@lru_cache
|
|
21
|
+
def get_config() -> Config:
|
|
22
|
+
return Config()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from plato.models import PlatoTask
|
|
3
|
+
from typing import List, Optional, Type, Dict, Any
|
|
4
|
+
import time
|
|
5
|
+
import asyncio
|
|
6
|
+
from aiohttp import ClientError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PlatoEnvironment:
|
|
10
|
+
"""A base environment class for Plato that handles task execution and state management.
|
|
11
|
+
|
|
12
|
+
This class provides the core interface for creating, managing, and interacting with
|
|
13
|
+
task environments. It implements an async context manager pattern and defines the
|
|
14
|
+
basic contract that all Plato environments must fulfill.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
_client (PlatoClient): The client instance for interacting with the environment
|
|
18
|
+
_current_task (Optional[PlatoTask]): The task currently being executed
|
|
19
|
+
id (str): Unique identifier for this environment instance (job ID)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
_current_task: Optional[PlatoTask] = Field(
|
|
23
|
+
description="The current task for the environment", default=None
|
|
24
|
+
)
|
|
25
|
+
_client: "PlatoClient" = Field(description="The client for the environment")
|
|
26
|
+
id: str = Field(description="The ID for the environment (job ID)")
|
|
27
|
+
|
|
28
|
+
def __init__(self, client: "PlatoClient", id: str):
|
|
29
|
+
self._client = client
|
|
30
|
+
self.id = id
|
|
31
|
+
|
|
32
|
+
async def wait_for_ready(self, timeout: Optional[float] = None) -> None:
|
|
33
|
+
"""Wait for the environment to be ready.
|
|
34
|
+
|
|
35
|
+
This method checks both the job status and worker health until everything is ready.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
timeout (Optional[float]): Maximum time to wait in seconds before raising an error.
|
|
39
|
+
If None, will wait indefinitely.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
RuntimeError: If the environment fails to start within the timeout period.
|
|
43
|
+
"""
|
|
44
|
+
start_time = time.time()
|
|
45
|
+
|
|
46
|
+
# wait for the job to be running
|
|
47
|
+
while True:
|
|
48
|
+
status = await self._client.get_job_status(self.id)
|
|
49
|
+
if status["status"].lower() == "running":
|
|
50
|
+
break
|
|
51
|
+
await asyncio.sleep(0.1)
|
|
52
|
+
if timeout and time.time() - start_time > timeout:
|
|
53
|
+
raise RuntimeError(
|
|
54
|
+
"Environment failed to start - job never entered running state"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# wait for the worker to be ready and healthy
|
|
58
|
+
while True:
|
|
59
|
+
worker_status = await self._client.get_worker_ready(self.id)
|
|
60
|
+
if worker_status["ready"]:
|
|
61
|
+
break
|
|
62
|
+
await asyncio.sleep(0.1)
|
|
63
|
+
if timeout and time.time() - start_time > timeout:
|
|
64
|
+
error_msg = worker_status.get("error", "Unknown error")
|
|
65
|
+
raise RuntimeError(
|
|
66
|
+
f"Environment failed to start - worker not ready: {error_msg}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# wait for the cdp url to be ready
|
|
70
|
+
while True:
|
|
71
|
+
try:
|
|
72
|
+
cdp_url = await self._client.get_cdp_url(self.id)
|
|
73
|
+
if cdp_url:
|
|
74
|
+
break
|
|
75
|
+
except ClientError as e:
|
|
76
|
+
await asyncio.sleep(0.1)
|
|
77
|
+
if timeout and time.time() - start_time > timeout:
|
|
78
|
+
raise RuntimeError("Environment failed to start - cdp url not ready")
|
|
79
|
+
|
|
80
|
+
async def __aenter__(self):
|
|
81
|
+
"""Enter the async context manager.
|
|
82
|
+
|
|
83
|
+
Calls make() to initialize the environment and returns self.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
PlatoEnvironment: The initialized environment instance.
|
|
87
|
+
"""
|
|
88
|
+
await self.wait_for_ready()
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
async def __aexit__(
|
|
92
|
+
self,
|
|
93
|
+
exc_type: Optional[Type[BaseException]],
|
|
94
|
+
exc_val: Optional[BaseException],
|
|
95
|
+
exc_tb: Optional[Type[BaseException]],
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Exit the async context manager.
|
|
98
|
+
|
|
99
|
+
Performs cleanup by calling close() when the context manager exits.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
exc_type: The type of the exception that was raised, if any
|
|
103
|
+
exc_val: The instance of the exception that was raised, if any
|
|
104
|
+
exc_tb: The traceback of the exception that was raised, if any
|
|
105
|
+
"""
|
|
106
|
+
await self.close()
|
|
107
|
+
|
|
108
|
+
async def get_cdp_url(self) -> str:
|
|
109
|
+
"""Get the Chrome DevTools Protocol URL for this environment's session.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
str: The CDP URL for connecting to this environment's browser session.
|
|
113
|
+
"""
|
|
114
|
+
return await self._client.get_cdp_url(self.id)
|
|
115
|
+
|
|
116
|
+
async def reset(self, task: Optional[PlatoTask] = None) -> None:
|
|
117
|
+
"""Reset the environment with an optional new task.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
task (Optional[PlatoTask]): The new task to set up the environment for.
|
|
121
|
+
"""
|
|
122
|
+
await self._client.reset_environment(self.id, task)
|
|
123
|
+
if task:
|
|
124
|
+
self._current_task = task
|
|
125
|
+
|
|
126
|
+
async def get_state(self) -> Dict[str, Any]:
|
|
127
|
+
"""Get the current state of the environment.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dict[str, Any]: A dictionary representing the current state of the environment.
|
|
131
|
+
"""
|
|
132
|
+
return await self._client.get_environment_state(self.id)
|
|
133
|
+
|
|
134
|
+
async def get_state_mutations(self) -> List[Dict[str, Any]]:
|
|
135
|
+
"""Get a list of state mutations that have occurred in the environment.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List[Dict[str, Any]]: A list of dictionaries representing state changes.
|
|
139
|
+
"""
|
|
140
|
+
state = await self.get_state()
|
|
141
|
+
return state.get("mutations", [])
|
|
142
|
+
|
|
143
|
+
async def evaluate(self) -> bool:
|
|
144
|
+
"""Evaluate whether the current task has been completed successfully.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True if the task is completed successfully, False otherwise.
|
|
148
|
+
"""
|
|
149
|
+
state = await self.get_state()
|
|
150
|
+
return state.get("completed", False)
|
|
151
|
+
|
|
152
|
+
async def close(self) -> None:
|
|
153
|
+
"""Clean up and close the environment.
|
|
154
|
+
|
|
155
|
+
This method handles cleanup of resources by closing the environment
|
|
156
|
+
through the API client.
|
|
157
|
+
"""
|
|
158
|
+
await self._client.close_environment(self.id)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import Optional, Literal, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BasePlatoEvalConfig(BaseModel):
|
|
6
|
+
"""Base configuration class for Plato evaluation settings.
|
|
7
|
+
|
|
8
|
+
This class serves as the base configuration for different types of evaluation
|
|
9
|
+
methods in the Plato system.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
type (Literal["base", "state_mutation_match"]): The type of evaluation configuration.
|
|
13
|
+
Can be either "base" or "state_mutation_match".
|
|
14
|
+
"""
|
|
15
|
+
type: Literal["base", "state_mutation_match"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StateMutationMatchEvalConfig(BasePlatoEvalConfig):
|
|
19
|
+
"""Configuration for state mutation matching evaluation.
|
|
20
|
+
|
|
21
|
+
This class defines the configuration for evaluating tasks based on matching
|
|
22
|
+
state mutations. It inherits from BasePlatoEvalConfig and specifies
|
|
23
|
+
state mutations that should be matched during evaluation.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
type (Literal["state_mutation_match"]): The type of evaluation, fixed as
|
|
27
|
+
"state_mutation_match" for this configuration.
|
|
28
|
+
state_mutations (List[dict]): A list of state mutation specifications that
|
|
29
|
+
define the expected changes in state during task execution.
|
|
30
|
+
"""
|
|
31
|
+
type: Literal["state_mutation_match"] = "state_mutation_match"
|
|
32
|
+
state_mutations: List[dict]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PlatoTask(BaseModel):
|
|
36
|
+
"""Represents a task in the Plato system.
|
|
37
|
+
|
|
38
|
+
This class defines the structure of a task, including its name, metadata,
|
|
39
|
+
initial state, and evaluation configuration.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
name (Optional[str]): The name of the task. Defaults to None.
|
|
43
|
+
metadata (Optional[dict]): Additional metadata associated with the task.
|
|
44
|
+
Defaults to None.
|
|
45
|
+
initial_state (Optional[dict]): The initial state of the task before
|
|
46
|
+
execution. Defaults to None.
|
|
47
|
+
eval_config (Optional[BasePlatoEvalConfig]): The evaluation configuration
|
|
48
|
+
for the task. Defaults to None.
|
|
49
|
+
"""
|
|
50
|
+
name: Optional[str] = None
|
|
51
|
+
metadata: Optional[dict] = None
|
|
52
|
+
initial_state: Optional[dict] = None
|
|
53
|
+
eval_config: Optional[BasePlatoEvalConfig] = None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from plato.client import PlatoClient
|
|
3
|
+
from plato.models import PlatoEnvironment, PlatoTask
|
|
4
|
+
|
|
5
|
+
async def test_environment_lifecycle():
|
|
6
|
+
"""Test the lifecycle of a Plato environment including creation, reset, and closure."""
|
|
7
|
+
# Initialize the client
|
|
8
|
+
client = PlatoClient()
|
|
9
|
+
|
|
10
|
+
# Create a sample task
|
|
11
|
+
task = PlatoTask(
|
|
12
|
+
name="example_task",
|
|
13
|
+
metadata={"type": "test"},
|
|
14
|
+
initial_state={"url": "https://example.com"}
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Create and initialize the environment
|
|
18
|
+
env = await client.make_environment("doordash")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
# Wait for the environment to be ready
|
|
22
|
+
await env.wait_for_ready(timeout=30.0)
|
|
23
|
+
|
|
24
|
+
# Get the CDP URL for browser connection
|
|
25
|
+
cdp_url = await env.get_cdp_url()
|
|
26
|
+
print(f"Environment ready with CDP URL: {cdp_url}")
|
|
27
|
+
|
|
28
|
+
# Reset the environment with a new task
|
|
29
|
+
await env.reset()
|
|
30
|
+
print("Environment reset with new task")
|
|
31
|
+
breakpoint()
|
|
32
|
+
|
|
33
|
+
finally:
|
|
34
|
+
# Always ensure we close the environment
|
|
35
|
+
await env.close()
|
|
36
|
+
print("Environment closed")
|
|
37
|
+
# Close the client to cleanup aiohttp session
|
|
38
|
+
await client.close()
|
|
39
|
+
print("Client closed")
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
asyncio.run(test_environment_lifecycle())
|
|
File without changes
|