plato-sdk 0.1.0a1__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.
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.2
2
+ Name: plato-sdk
3
+ Version: 0.1.0a1
4
+ Summary: Plato SDK
5
+ Requires-Python: >=3.12.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: aiohttp>=3.11.13
8
+ Requires-Dist: pydantic>=2.10.6
9
+ Requires-Dist: pydantic-settings>=2.8.1
10
+
11
+ # Plato SDK
12
+
13
+
14
+ ## Usage
15
+ ```
16
+ class Task(BaseModel):
17
+ ...
18
+
19
+ async with env := plato.make(ENV_ID):
20
+ cdp_url = env.cdp_url
21
+
22
+ # connect to browser and do modifications
23
+
24
+ # get state
25
+ state_mutations: dict = env.get_state()
26
+
27
+ # evaluate
28
+
29
+ ...
30
+
31
+ ```
@@ -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,11 @@
1
+ [project]
2
+ name = "plato-sdk"
3
+ version = "0.1.0a1"
4
+ description = "Plato SDK"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12.8"
7
+ dependencies = [
8
+ "aiohttp>=3.11.13",
9
+ "pydantic>=2.10.6",
10
+ "pydantic-settings>=2.8.1",
11
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,4 @@
1
+ from .task import PlatoTask
2
+ from .env import PlatoEnvironment
3
+
4
+ __all__ = ["PlatoTask", "PlatoEnvironment"]
@@ -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
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.2
2
+ Name: plato-sdk
3
+ Version: 0.1.0a1
4
+ Summary: Plato SDK
5
+ Requires-Python: >=3.12.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: aiohttp>=3.11.13
8
+ Requires-Dist: pydantic>=2.10.6
9
+ Requires-Dist: pydantic-settings>=2.8.1
10
+
11
+ # Plato SDK
12
+
13
+
14
+ ## Usage
15
+ ```
16
+ class Task(BaseModel):
17
+ ...
18
+
19
+ async with env := plato.make(ENV_ID):
20
+ cdp_url = env.cdp_url
21
+
22
+ # connect to browser and do modifications
23
+
24
+ # get state
25
+ state_mutations: dict = env.get_state()
26
+
27
+ # evaluate
28
+
29
+ ...
30
+
31
+ ```
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/plato/client.py
4
+ src/plato/config.py
5
+ src/plato/models/__init__.py
6
+ src/plato/models/env.py
7
+ src/plato/models/task.py
8
+ src/plato/test/test_env.py
9
+ src/plato/test/test_run.py
10
+ src/plato_sdk.egg-info/PKG-INFO
11
+ src/plato_sdk.egg-info/SOURCES.txt
12
+ src/plato_sdk.egg-info/dependency_links.txt
13
+ src/plato_sdk.egg-info/requires.txt
14
+ src/plato_sdk.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ aiohttp>=3.11.13
2
+ pydantic>=2.10.6
3
+ pydantic-settings>=2.8.1