plato-sdk-v2 2.0.50__py3-none-any.whl → 2.2.4__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.
- plato/__init__.py +7 -6
- plato/_generated/__init__.py +1 -1
- plato/_generated/api/v1/env/evaluate_session.py +3 -3
- plato/_generated/api/v1/env/log_state_mutation.py +4 -4
- plato/_generated/api/v1/sandbox/checkpoint_vm.py +3 -3
- plato/_generated/api/v1/sandbox/save_vm_snapshot.py +3 -3
- plato/_generated/api/v1/sandbox/setup_sandbox.py +8 -8
- plato/_generated/api/v1/session/__init__.py +2 -0
- plato/_generated/api/v1/session/get_sessions_for_archival.py +100 -0
- plato/_generated/api/v1/testcases/__init__.py +6 -2
- plato/_generated/api/v1/testcases/get_mutation_groups_for_testcase.py +98 -0
- plato/_generated/api/v1/testcases/{get_next_output_testcase_for_scoring.py → get_next_testcase_for_scoring.py} +23 -10
- plato/_generated/api/v1/testcases/get_testcase_metadata_for_scoring.py +74 -0
- plato/_generated/api/v2/__init__.py +2 -1
- plato/_generated/api/v2/jobs/__init__.py +4 -0
- plato/_generated/api/v2/jobs/checkpoint.py +3 -3
- plato/_generated/api/v2/jobs/disk_snapshot.py +3 -3
- plato/_generated/api/v2/jobs/log_for_job.py +4 -39
- plato/_generated/api/v2/jobs/make.py +4 -4
- plato/_generated/api/v2/jobs/setup_sandbox.py +97 -0
- plato/_generated/api/v2/jobs/snapshot.py +3 -3
- plato/_generated/api/v2/jobs/snapshot_store.py +91 -0
- plato/_generated/api/v2/sessions/__init__.py +4 -0
- plato/_generated/api/v2/sessions/checkpoint.py +3 -3
- plato/_generated/api/v2/sessions/disk_snapshot.py +3 -3
- plato/_generated/api/v2/sessions/evaluate.py +3 -3
- plato/_generated/api/v2/sessions/log_job_mutation.py +4 -39
- plato/_generated/api/v2/sessions/make.py +4 -4
- plato/_generated/api/v2/sessions/setup_sandbox.py +98 -0
- plato/_generated/api/v2/sessions/snapshot.py +3 -3
- plato/_generated/api/v2/sessions/snapshot_store.py +94 -0
- plato/_generated/api/v2/user/__init__.py +7 -0
- plato/_generated/api/v2/user/get_current_user.py +76 -0
- plato/_generated/models/__init__.py +174 -23
- plato/_sims_generator/__init__.py +19 -4
- plato/_sims_generator/instruction.py +203 -0
- plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
- plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
- plato/agents/__init__.py +107 -517
- plato/agents/base.py +145 -0
- plato/agents/build.py +61 -0
- plato/agents/config.py +160 -0
- plato/agents/logging.py +401 -0
- plato/agents/runner.py +161 -0
- plato/agents/trajectory.py +266 -0
- plato/chronos/__init__.py +37 -0
- plato/chronos/api/__init__.py +3 -0
- plato/chronos/api/agents/__init__.py +13 -0
- plato/chronos/api/agents/create_agent.py +63 -0
- plato/chronos/api/agents/delete_agent.py +61 -0
- plato/chronos/api/agents/get_agent.py +62 -0
- plato/chronos/api/agents/get_agent_schema.py +72 -0
- plato/chronos/api/agents/get_agent_versions.py +62 -0
- plato/chronos/api/agents/list_agents.py +57 -0
- plato/chronos/api/agents/lookup_agent.py +74 -0
- plato/chronos/api/auth/__init__.py +9 -0
- plato/chronos/api/auth/debug_auth_api_auth_debug_get.py +43 -0
- plato/chronos/api/auth/get_auth_status_api_auth_status_get.py +61 -0
- plato/chronos/api/auth/get_current_user_route_api_auth_me_get.py +60 -0
- plato/chronos/api/callback/__init__.py +11 -0
- plato/chronos/api/callback/push_agent_logs.py +61 -0
- plato/chronos/api/callback/update_agent_status.py +57 -0
- plato/chronos/api/callback/upload_artifacts.py +59 -0
- plato/chronos/api/callback/upload_logs_zip.py +57 -0
- plato/chronos/api/callback/upload_trajectory.py +57 -0
- plato/chronos/api/default/__init__.py +7 -0
- plato/chronos/api/default/health.py +43 -0
- plato/chronos/api/jobs/__init__.py +7 -0
- plato/chronos/api/jobs/launch_job.py +63 -0
- plato/chronos/api/registry/__init__.py +19 -0
- plato/chronos/api/registry/get_agent_schema_api_registry_agents__agent_name__schema_get.py +62 -0
- plato/chronos/api/registry/get_agent_versions_api_registry_agents__agent_name__versions_get.py +52 -0
- plato/chronos/api/registry/get_world_schema_api_registry_worlds__package_name__schema_get.py +68 -0
- plato/chronos/api/registry/get_world_versions_api_registry_worlds__package_name__versions_get.py +52 -0
- plato/chronos/api/registry/list_registry_agents_api_registry_agents_get.py +44 -0
- plato/chronos/api/registry/list_registry_worlds_api_registry_worlds_get.py +44 -0
- plato/chronos/api/runtimes/__init__.py +11 -0
- plato/chronos/api/runtimes/create_runtime.py +63 -0
- plato/chronos/api/runtimes/delete_runtime.py +61 -0
- plato/chronos/api/runtimes/get_runtime.py +62 -0
- plato/chronos/api/runtimes/list_runtimes.py +57 -0
- plato/chronos/api/runtimes/test_runtime.py +67 -0
- plato/chronos/api/secrets/__init__.py +11 -0
- plato/chronos/api/secrets/create_secret.py +63 -0
- plato/chronos/api/secrets/delete_secret.py +61 -0
- plato/chronos/api/secrets/get_secret.py +62 -0
- plato/chronos/api/secrets/list_secrets.py +57 -0
- plato/chronos/api/secrets/update_secret.py +68 -0
- plato/chronos/api/sessions/__init__.py +10 -0
- plato/chronos/api/sessions/get_session.py +62 -0
- plato/chronos/api/sessions/get_session_logs.py +72 -0
- plato/chronos/api/sessions/get_session_logs_download.py +62 -0
- plato/chronos/api/sessions/list_sessions.py +57 -0
- plato/chronos/api/status/__init__.py +8 -0
- plato/chronos/api/status/get_status_api_status_get.py +44 -0
- plato/chronos/api/status/get_version_info_api_version_get.py +44 -0
- plato/chronos/api/templates/__init__.py +11 -0
- plato/chronos/api/templates/create_template.py +63 -0
- plato/chronos/api/templates/delete_template.py +61 -0
- plato/chronos/api/templates/get_template.py +62 -0
- plato/chronos/api/templates/list_templates.py +57 -0
- plato/chronos/api/templates/update_template.py +68 -0
- plato/chronos/api/trajectories/__init__.py +8 -0
- plato/chronos/api/trajectories/get_trajectory.py +62 -0
- plato/chronos/api/trajectories/list_trajectories.py +62 -0
- plato/chronos/api/worlds/__init__.py +10 -0
- plato/chronos/api/worlds/create_world.py +63 -0
- plato/chronos/api/worlds/delete_world.py +61 -0
- plato/chronos/api/worlds/get_world.py +62 -0
- plato/chronos/api/worlds/list_worlds.py +57 -0
- plato/chronos/client.py +171 -0
- plato/chronos/errors.py +141 -0
- plato/chronos/models/__init__.py +647 -0
- plato/chronos/py.typed +0 -0
- plato/sims/cli.py +299 -123
- plato/sims/registry.py +77 -4
- plato/v1/cli/agent.py +88 -84
- plato/v1/cli/main.py +2 -0
- plato/v1/cli/pm.py +441 -119
- plato/v1/cli/sandbox.py +747 -191
- plato/v1/cli/sim.py +11 -0
- plato/v1/cli/verify.py +1269 -0
- plato/v1/cli/world.py +3 -0
- plato/v1/flow_executor.py +21 -17
- plato/v1/models/env.py +11 -11
- plato/v1/sdk.py +2 -2
- plato/v1/sync_env.py +11 -11
- plato/v1/sync_flow_executor.py +21 -17
- plato/v1/sync_sdk.py +4 -2
- plato/v2/__init__.py +2 -0
- plato/v2/async_/environment.py +20 -1
- plato/v2/async_/session.py +54 -3
- plato/v2/sync/environment.py +2 -1
- plato/v2/sync/session.py +52 -2
- plato/worlds/README.md +218 -0
- plato/worlds/__init__.py +54 -18
- plato/worlds/base.py +304 -93
- plato/worlds/config.py +239 -73
- plato/worlds/runner.py +391 -80
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/METADATA +1 -3
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/RECORD +143 -68
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/entry_points.txt +1 -0
- plato/_generated/api/v2/interfaces/__init__.py +0 -27
- plato/_generated/api/v2/interfaces/v2_interface_browser_create.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_cdp_url.py +0 -65
- plato/_generated/api/v2/interfaces/v2_interface_click.py +0 -64
- plato/_generated/api/v2/interfaces/v2_interface_close.py +0 -59
- plato/_generated/api/v2/interfaces/v2_interface_computer_create.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_cursor.py +0 -64
- plato/_generated/api/v2/interfaces/v2_interface_key.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_screenshot.py +0 -65
- plato/_generated/api/v2/interfaces/v2_interface_scroll.py +0 -70
- plato/_generated/api/v2/interfaces/v2_interface_type.py +0 -64
- plato/world/__init__.py +0 -44
- plato/world/base.py +0 -267
- plato/world/config.py +0 -139
- plato/world/types.py +0 -47
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/WHEEL +0 -0
plato/worlds/base.py
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
"""Base world class for Plato worlds
|
|
1
|
+
"""Base world class for Plato worlds."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
-
from typing import TYPE_CHECKING, Any, ClassVar
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, get_args, get_origin
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
|
+
from plato.worlds.config import RunConfig
|
|
12
|
+
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
|
-
from plato.
|
|
14
|
+
from plato.v2.async_.environment import Environment
|
|
15
|
+
from plato.v2.async_.session import Session
|
|
16
|
+
|
|
17
|
+
from plato.agents.logging import init_logging as _init_chronos_logging
|
|
18
|
+
from plato.agents.logging import log_event as _log_event
|
|
19
|
+
from plato.agents.logging import reset_logging as _reset_chronos_logging
|
|
20
|
+
from plato.agents.logging import span as _span
|
|
13
21
|
|
|
14
22
|
logger = logging.getLogger(__name__)
|
|
15
23
|
|
|
16
24
|
# Global registry of worlds
|
|
17
25
|
_WORLD_REGISTRY: dict[str, type[BaseWorld]] = {}
|
|
18
26
|
|
|
27
|
+
# Type variable for config
|
|
28
|
+
ConfigT = TypeVar("ConfigT", bound=RunConfig)
|
|
29
|
+
|
|
19
30
|
|
|
20
31
|
def register_world(name: str | None = None):
|
|
21
32
|
"""Decorator to register a world class.
|
|
22
33
|
|
|
23
34
|
Usage:
|
|
24
35
|
@register_world("code")
|
|
25
|
-
class CodeWorld(BaseWorld):
|
|
36
|
+
class CodeWorld(BaseWorld[CodeWorldConfig]):
|
|
26
37
|
...
|
|
27
38
|
"""
|
|
28
39
|
|
|
@@ -45,24 +56,8 @@ def get_world(name: str) -> type[BaseWorld] | None:
|
|
|
45
56
|
return _WORLD_REGISTRY.get(name)
|
|
46
57
|
|
|
47
58
|
|
|
48
|
-
class AgentSlot(BaseModel):
|
|
49
|
-
"""Definition of an agent slot in a world."""
|
|
50
|
-
|
|
51
|
-
name: str
|
|
52
|
-
description: str = ""
|
|
53
|
-
required: bool = True
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class SecretSlot(BaseModel):
|
|
57
|
-
"""Definition of a secret slot in a world."""
|
|
58
|
-
|
|
59
|
-
name: str
|
|
60
|
-
description: str = ""
|
|
61
|
-
required: bool = False
|
|
62
|
-
|
|
63
|
-
|
|
64
59
|
class Observation(BaseModel):
|
|
65
|
-
"""Observation returned from reset/step
|
|
60
|
+
"""Observation returned from reset/step."""
|
|
66
61
|
|
|
67
62
|
data: dict[str, Any] = Field(default_factory=dict)
|
|
68
63
|
|
|
@@ -70,48 +65,60 @@ class Observation(BaseModel):
|
|
|
70
65
|
|
|
71
66
|
|
|
72
67
|
class StepResult(BaseModel):
|
|
73
|
-
"""Result of a step
|
|
68
|
+
"""Result of a step."""
|
|
74
69
|
|
|
75
70
|
observation: Observation
|
|
76
71
|
done: bool = False
|
|
77
72
|
info: dict[str, Any] = Field(default_factory=dict)
|
|
78
73
|
|
|
79
74
|
|
|
80
|
-
class BaseWorld(ABC):
|
|
81
|
-
"""Base class for Plato worlds
|
|
75
|
+
class BaseWorld(ABC, Generic[ConfigT]):
|
|
76
|
+
"""Base class for Plato worlds.
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
- reset(): Setup the world, return initial observation
|
|
85
|
-
- step(): Execute one step, return StepResult
|
|
78
|
+
Subclass with a config type parameter for fully typed config access:
|
|
86
79
|
|
|
87
|
-
|
|
80
|
+
class CodeWorldConfig(RunConfig):
|
|
81
|
+
repository_url: str
|
|
82
|
+
prompt: str
|
|
83
|
+
coder: Annotated[AgentConfig, Agent(description="Coding agent")]
|
|
84
|
+
git_token: Annotated[str | None, Secret(description="GitHub token")] = None
|
|
88
85
|
|
|
89
|
-
Example:
|
|
90
86
|
@register_world("code")
|
|
91
|
-
class CodeWorld(BaseWorld):
|
|
87
|
+
class CodeWorld(BaseWorld[CodeWorldConfig]):
|
|
92
88
|
name = "code"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
async def reset(self, config: RunConfig) -> Observation:
|
|
96
|
-
# Clone repo, setup workspace
|
|
97
|
-
return Observation(data={"workspace": "/workspace"})
|
|
89
|
+
description = "Run coding agents"
|
|
98
90
|
|
|
99
|
-
async def
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
async def reset(self) -> Observation:
|
|
92
|
+
url = self.config.repository_url # typed as str
|
|
93
|
+
agent = self.config.coder # typed as AgentConfig
|
|
94
|
+
token = self.config.git_token # typed as str | None
|
|
102
95
|
"""
|
|
103
96
|
|
|
104
97
|
# Class attributes
|
|
105
98
|
name: ClassVar[str] = "base"
|
|
106
99
|
description: ClassVar[str] = ""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
|
|
101
|
+
# Instance attributes
|
|
102
|
+
config: ConfigT # Typed via generic parameter
|
|
103
|
+
plato_session: Session | None = None # Connected Plato session (if running on managed VM)
|
|
110
104
|
|
|
111
105
|
def __init__(self) -> None:
|
|
112
106
|
self.logger = logging.getLogger(f"plato.worlds.{self.name}")
|
|
113
|
-
self.config: RunConfig | None = None
|
|
114
107
|
self._step_count: int = 0
|
|
108
|
+
self.plato_session = None
|
|
109
|
+
self._current_step_id: str | None = None
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_config_class(cls) -> type[RunConfig]:
|
|
113
|
+
"""Get the config class from the generic parameter."""
|
|
114
|
+
# Walk up the class hierarchy to find Generic base
|
|
115
|
+
for base in getattr(cls, "__orig_bases__", []):
|
|
116
|
+
origin = get_origin(base)
|
|
117
|
+
if origin is BaseWorld:
|
|
118
|
+
args = get_args(base)
|
|
119
|
+
if args and isinstance(args[0], type) and issubclass(args[0], RunConfig):
|
|
120
|
+
return args[0]
|
|
121
|
+
return RunConfig
|
|
115
122
|
|
|
116
123
|
@classmethod
|
|
117
124
|
def get_version(cls) -> str:
|
|
@@ -125,62 +132,27 @@ class BaseWorld(ABC):
|
|
|
125
132
|
continue
|
|
126
133
|
return "0.0.0"
|
|
127
134
|
|
|
128
|
-
@classmethod
|
|
129
|
-
def get_agent_slots(cls) -> list[AgentSlot]:
|
|
130
|
-
"""Get the list of agent slots."""
|
|
131
|
-
return [AgentSlot(name=a) if isinstance(a, str) else a for a in cls.agents]
|
|
132
|
-
|
|
133
|
-
@classmethod
|
|
134
|
-
def get_secret_slots(cls) -> list[SecretSlot]:
|
|
135
|
-
"""Get the list of secret slots."""
|
|
136
|
-
return [SecretSlot(name=s) if isinstance(s, str) else s for s in cls.secrets]
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def get_config_schema(cls) -> dict:
|
|
140
|
-
"""Get JSON schema for world_config.
|
|
141
|
-
|
|
142
|
-
If config_class is set, auto-generates schema from the Pydantic model.
|
|
143
|
-
Override this method to provide a custom schema.
|
|
144
|
-
"""
|
|
145
|
-
if cls.config_class is not None:
|
|
146
|
-
return cls.config_class.get_json_schema()
|
|
147
|
-
return {"type": "object", "properties": {}, "required": []}
|
|
148
|
-
|
|
149
135
|
@classmethod
|
|
150
136
|
def get_schema(cls) -> dict:
|
|
151
|
-
"""Get full schema including
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
secret_slots = cls.get_secret_slots()
|
|
137
|
+
"""Get full schema including world config, agents, and secrets."""
|
|
138
|
+
config_class = cls.get_config_class()
|
|
139
|
+
schema = config_class.get_json_schema()
|
|
155
140
|
|
|
156
141
|
return {
|
|
157
142
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
158
143
|
"type": "object",
|
|
159
|
-
"properties":
|
|
160
|
-
"required":
|
|
161
|
-
"agents": [
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"description": slot.description,
|
|
165
|
-
"required": slot.required,
|
|
166
|
-
}
|
|
167
|
-
for slot in agent_slots
|
|
168
|
-
],
|
|
169
|
-
"secrets": [
|
|
170
|
-
{
|
|
171
|
-
"name": slot.name,
|
|
172
|
-
"description": slot.description,
|
|
173
|
-
"required": slot.required,
|
|
174
|
-
}
|
|
175
|
-
for slot in secret_slots
|
|
176
|
-
],
|
|
144
|
+
"properties": schema.get("properties", {}),
|
|
145
|
+
"required": schema.get("required", []),
|
|
146
|
+
"agents": schema.get("agents", []),
|
|
147
|
+
"secrets": schema.get("secrets", []),
|
|
148
|
+
"envs": schema.get("envs", []),
|
|
177
149
|
}
|
|
178
150
|
|
|
179
151
|
@abstractmethod
|
|
180
|
-
async def reset(self
|
|
152
|
+
async def reset(self) -> Observation:
|
|
181
153
|
"""Setup the world and return initial observation.
|
|
182
154
|
|
|
183
|
-
|
|
155
|
+
Access configuration via self.config (fully typed).
|
|
184
156
|
"""
|
|
185
157
|
pass
|
|
186
158
|
|
|
@@ -196,25 +168,251 @@ class BaseWorld(ABC):
|
|
|
196
168
|
"""Cleanup resources. Called after run completes."""
|
|
197
169
|
pass
|
|
198
170
|
|
|
199
|
-
async def
|
|
171
|
+
async def _connect_plato_session(self) -> None:
|
|
172
|
+
"""Connect to Plato session from config.
|
|
173
|
+
|
|
174
|
+
This is called automatically during run() to restore the session
|
|
175
|
+
and start sending heartbeats while the world runs.
|
|
176
|
+
"""
|
|
177
|
+
if not self.config.plato_session:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
from plato.v2.async_.session import Session
|
|
182
|
+
|
|
183
|
+
self.logger.info("Restoring Plato session from serialized data")
|
|
184
|
+
self.plato_session = await Session.load(self.config.plato_session, start_heartbeat=True)
|
|
185
|
+
self.logger.info(f"Plato session {self.plato_session.session_id} restored, heartbeat started")
|
|
186
|
+
except Exception as e:
|
|
187
|
+
self.logger.warning(f"Failed to restore Plato session: {e}")
|
|
188
|
+
|
|
189
|
+
async def _disconnect_plato_session(self) -> None:
|
|
190
|
+
"""Stop heartbeat for the Plato session (does not close the session)."""
|
|
191
|
+
if self.plato_session:
|
|
192
|
+
try:
|
|
193
|
+
await self.plato_session.stop_heartbeat()
|
|
194
|
+
self.logger.info("Plato session heartbeat stopped")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
self.logger.warning(f"Error stopping Plato heartbeat: {e}")
|
|
197
|
+
|
|
198
|
+
def get_env(self, alias: str) -> Environment | None:
|
|
199
|
+
"""Get an environment by alias.
|
|
200
|
+
|
|
201
|
+
Use this to access environments defined in the world config (e.g., gitea, localstack).
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
alias: The environment alias (e.g., "gitea", "localstack", "runtime")
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The Environment object or None if not found or session not connected.
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
gitea = self.get_env("gitea")
|
|
211
|
+
if gitea:
|
|
212
|
+
result = await gitea.execute("git status")
|
|
213
|
+
"""
|
|
214
|
+
if not self.plato_session:
|
|
215
|
+
self.logger.warning("Cannot get env: Plato session not connected")
|
|
216
|
+
return None
|
|
217
|
+
return self.plato_session.get_env(alias)
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def envs(self) -> list[Environment]:
|
|
221
|
+
"""Get all environments in the Plato session.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of Environment objects. Empty list if session not connected.
|
|
225
|
+
"""
|
|
226
|
+
if not self.plato_session:
|
|
227
|
+
return []
|
|
228
|
+
return self.plato_session.envs
|
|
229
|
+
|
|
230
|
+
def get_sim_env_vars(self) -> dict[str, str]:
|
|
231
|
+
"""Get environment variables from all configured sims.
|
|
232
|
+
|
|
233
|
+
Automatically discovers and loads env vars from sims like localstack, gitea, etc.
|
|
234
|
+
based on the environments configured in the world.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Dict of environment variable name -> value
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
env_vars = self.get_sim_env_vars()
|
|
241
|
+
# Returns: {"AWS_ENDPOINT_URL": "https://...", "GITEA_URL": "https://...", ...}
|
|
242
|
+
"""
|
|
243
|
+
env_vars: dict[str, str] = {}
|
|
244
|
+
|
|
245
|
+
# Known sim packages and their env aliases
|
|
246
|
+
sim_packages = [
|
|
247
|
+
("localstack", "localstack"),
|
|
248
|
+
("gitea", "gitea"),
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
for package_name, env_alias in sim_packages:
|
|
252
|
+
env = self.get_env(env_alias)
|
|
253
|
+
if not env:
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Dynamically import the sim package
|
|
258
|
+
sim_module = __import__(f"plato.sims.{package_name}", fromlist=[package_name])
|
|
259
|
+
|
|
260
|
+
# Get service URLs and env vars
|
|
261
|
+
service_urls = sim_module.get_service_urls(env.job_id)
|
|
262
|
+
sim_vars = sim_module.get_env_vars(service_urls)
|
|
263
|
+
env_vars.update(sim_vars)
|
|
264
|
+
self.logger.info(f"{package_name} env vars: {list(sim_vars.keys())}")
|
|
265
|
+
except ImportError:
|
|
266
|
+
self.logger.debug(f"{package_name} sim package not installed, skipping")
|
|
267
|
+
except Exception as e:
|
|
268
|
+
self.logger.warning(f"Failed to get {package_name} env vars: {e}")
|
|
269
|
+
|
|
270
|
+
return env_vars
|
|
271
|
+
|
|
272
|
+
def get_sim_instructions(self) -> str:
|
|
273
|
+
"""Get usage instructions from all configured sims.
|
|
274
|
+
|
|
275
|
+
Returns markdown-formatted instructions for using LocalStack, Gitea, etc.
|
|
276
|
+
based on the environments configured in the world.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Markdown string with instructions, or empty string if no sims configured.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
instructions = self.get_sim_instructions()
|
|
283
|
+
# Returns markdown with LocalStack/Gitea setup instructions
|
|
284
|
+
"""
|
|
285
|
+
instructions_parts: list[str] = []
|
|
286
|
+
|
|
287
|
+
# Known sim packages and their env aliases
|
|
288
|
+
sim_packages = [
|
|
289
|
+
("localstack", "localstack"),
|
|
290
|
+
("gitea", "gitea"),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
for package_name, env_alias in sim_packages:
|
|
294
|
+
env = self.get_env(env_alias)
|
|
295
|
+
if not env:
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
# Dynamically import the sim package
|
|
300
|
+
sim_module = __import__(f"plato.sims.{package_name}", fromlist=[package_name])
|
|
301
|
+
|
|
302
|
+
# Get instructions using the job_id
|
|
303
|
+
if hasattr(sim_module, "get_instructions_from_job"):
|
|
304
|
+
instructions = sim_module.get_instructions_from_job(env.job_id)
|
|
305
|
+
if instructions:
|
|
306
|
+
instructions_parts.append(instructions)
|
|
307
|
+
self.logger.info(f"Added {package_name} instructions to prompt")
|
|
308
|
+
except ImportError:
|
|
309
|
+
self.logger.debug(f"{package_name} sim package not installed, skipping instructions")
|
|
310
|
+
except Exception as e:
|
|
311
|
+
self.logger.warning(f"Failed to get {package_name} instructions: {e}")
|
|
312
|
+
|
|
313
|
+
if instructions_parts:
|
|
314
|
+
return "\n\n---\n\n".join(instructions_parts)
|
|
315
|
+
return ""
|
|
316
|
+
|
|
317
|
+
def format_instruction_with_sims(self, instruction: str) -> str:
|
|
318
|
+
"""Format an instruction with sim context prepended.
|
|
319
|
+
|
|
320
|
+
Automatically adds available service instructions (LocalStack, Gitea, etc.)
|
|
321
|
+
before the main instruction.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
instruction: The base instruction/task
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Formatted instruction with sim context, or original instruction if no sims.
|
|
328
|
+
|
|
329
|
+
Example:
|
|
330
|
+
formatted = self.format_instruction_with_sims("Fix the bug in main.py")
|
|
331
|
+
# Returns:
|
|
332
|
+
# ## Available Services
|
|
333
|
+
# The following services are available...
|
|
334
|
+
# [LocalStack instructions]
|
|
335
|
+
# ---
|
|
336
|
+
# ## Task
|
|
337
|
+
# Fix the bug in main.py
|
|
338
|
+
"""
|
|
339
|
+
sim_instructions = self.get_sim_instructions()
|
|
340
|
+
|
|
341
|
+
if sim_instructions:
|
|
342
|
+
return f"""## Available Services
|
|
343
|
+
|
|
344
|
+
The following services are available for your use:
|
|
345
|
+
|
|
346
|
+
{sim_instructions}
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Task
|
|
351
|
+
|
|
352
|
+
{instruction}"""
|
|
353
|
+
return instruction
|
|
354
|
+
|
|
355
|
+
async def run(self, config: ConfigT) -> None:
|
|
200
356
|
"""Run the world: reset -> step until done -> close.
|
|
201
357
|
|
|
202
|
-
This is the main entry point.
|
|
358
|
+
This is the main entry point. If plato_session_id is provided in config,
|
|
359
|
+
automatically connects to the Plato session to send heartbeats.
|
|
203
360
|
"""
|
|
204
361
|
self.config = config
|
|
205
362
|
self._step_count = 0
|
|
206
363
|
|
|
207
364
|
self.logger.info(f"Starting world '{self.name}'")
|
|
208
365
|
|
|
366
|
+
# Initialize the logging singleton for agents to use
|
|
367
|
+
if config.callback_url and config.session_id:
|
|
368
|
+
_init_chronos_logging(
|
|
369
|
+
callback_url=config.callback_url,
|
|
370
|
+
session_id=config.session_id,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Connect to Plato session if configured (for heartbeats)
|
|
374
|
+
await self._connect_plato_session()
|
|
375
|
+
|
|
376
|
+
# Log session start
|
|
377
|
+
await _log_event(
|
|
378
|
+
span_type="session_start",
|
|
379
|
+
content=f"World '{self.name}' started",
|
|
380
|
+
source="world",
|
|
381
|
+
extra={"world_name": self.name, "world_version": self.get_version()},
|
|
382
|
+
)
|
|
383
|
+
|
|
209
384
|
try:
|
|
210
|
-
#
|
|
211
|
-
|
|
385
|
+
# Execute reset with automatic span tracking
|
|
386
|
+
async with _span("reset", span_type="reset", source="world") as reset_span:
|
|
387
|
+
reset_span.log(f"Resetting world '{self.name}'")
|
|
388
|
+
obs = await self.reset()
|
|
389
|
+
reset_span.set_extra({"observation": obs.model_dump() if hasattr(obs, "model_dump") else str(obs)})
|
|
212
390
|
self.logger.info(f"World reset complete: {obs}")
|
|
213
391
|
|
|
214
|
-
# Step loop
|
|
215
392
|
while True:
|
|
216
393
|
self._step_count += 1
|
|
217
|
-
|
|
394
|
+
|
|
395
|
+
# Execute step with automatic span tracking
|
|
396
|
+
# The span automatically sets itself as the current parent,
|
|
397
|
+
# so agent trajectories will nest under this step
|
|
398
|
+
async with _span(
|
|
399
|
+
f"step_{self._step_count}",
|
|
400
|
+
span_type="step",
|
|
401
|
+
source="world",
|
|
402
|
+
) as step_span:
|
|
403
|
+
self._current_step_id = step_span.event_id
|
|
404
|
+
step_span.log(f"Step {self._step_count} started")
|
|
405
|
+
result = await self.step()
|
|
406
|
+
step_span.set_extra(
|
|
407
|
+
{
|
|
408
|
+
"done": result.done,
|
|
409
|
+
"observation": result.observation.model_dump()
|
|
410
|
+
if hasattr(result.observation, "model_dump")
|
|
411
|
+
else str(result.observation),
|
|
412
|
+
"info": result.info,
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
|
|
218
416
|
self.logger.info(f"Step {self._step_count}: done={result.done}")
|
|
219
417
|
|
|
220
418
|
if result.done:
|
|
@@ -222,4 +420,17 @@ class BaseWorld(ABC):
|
|
|
222
420
|
|
|
223
421
|
finally:
|
|
224
422
|
await self.close()
|
|
423
|
+
await self._disconnect_plato_session()
|
|
424
|
+
|
|
425
|
+
# Log session end
|
|
426
|
+
await _log_event(
|
|
427
|
+
span_type="session_end",
|
|
428
|
+
content=f"World '{self.name}' completed after {self._step_count} steps",
|
|
429
|
+
source="world",
|
|
430
|
+
extra={"total_steps": self._step_count},
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Reset the logging singleton
|
|
434
|
+
_reset_chronos_logging()
|
|
435
|
+
|
|
225
436
|
self.logger.info(f"World '{self.name}' completed after {self._step_count} steps")
|