plato-sdk-v2 2.8.8__py3-none-any.whl → 2.9.1__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/_generated/__init__.py +1 -1
- plato/_generated/api/v1/testcases/get_testcases.py +21 -0
- plato/_generated/models/__init__.py +11 -3
- plato/agents/base.py +3 -9
- plato/agents/config.py +7 -64
- plato/agents/markers.py +18 -0
- plato/agents/otel.py +22 -60
- plato/agents/schema.py +96 -0
- plato/chronos/models/__init__.py +27 -27
- plato/cli/chronos.py +2 -100
- plato/worlds/__init__.py +2 -6
- plato/worlds/base.py +44 -130
- plato/worlds/config.py +5 -166
- plato/worlds/markers.py +63 -0
- plato/worlds/models.py +23 -0
- plato/worlds/schema.py +154 -0
- {plato_sdk_v2-2.8.8.dist-info → plato_sdk_v2-2.9.1.dist-info}/METADATA +1 -1
- {plato_sdk_v2-2.8.8.dist-info → plato_sdk_v2-2.9.1.dist-info}/RECORD +20 -15
- {plato_sdk_v2-2.8.8.dist-info → plato_sdk_v2-2.9.1.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.8.8.dist-info → plato_sdk_v2-2.9.1.dist-info}/entry_points.txt +0 -0
plato/cli/chronos.py
CHANGED
|
@@ -6,11 +6,13 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
import platform as plat
|
|
9
10
|
import subprocess
|
|
10
11
|
import tempfile
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Annotated
|
|
13
14
|
|
|
15
|
+
import httpx
|
|
14
16
|
import typer
|
|
15
17
|
|
|
16
18
|
from plato.cli.utils import console
|
|
@@ -64,8 +66,6 @@ def launch(
|
|
|
64
66
|
The config file should contain world.package (required) and optionally world.config,
|
|
65
67
|
runtime.artifact_id, and tags.
|
|
66
68
|
"""
|
|
67
|
-
import httpx
|
|
68
|
-
|
|
69
69
|
# Set defaults
|
|
70
70
|
if not chronos_url:
|
|
71
71
|
chronos_url = "https://chronos.plato.so"
|
|
@@ -126,80 +126,6 @@ def launch(
|
|
|
126
126
|
raise typer.Exit(1)
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
@chronos_app.command()
|
|
130
|
-
def example(
|
|
131
|
-
world: str = typer.Argument(
|
|
132
|
-
"structured-execution",
|
|
133
|
-
help="World to generate example config for",
|
|
134
|
-
),
|
|
135
|
-
output: Path = typer.Option(
|
|
136
|
-
None,
|
|
137
|
-
"--output",
|
|
138
|
-
"-o",
|
|
139
|
-
help="Output file path (prints to stdout if not specified)",
|
|
140
|
-
),
|
|
141
|
-
):
|
|
142
|
-
"""Generate an example job config file.
|
|
143
|
-
|
|
144
|
-
Creates a sample JSON configuration for launching Chronos jobs, which can be
|
|
145
|
-
customized for your use case.
|
|
146
|
-
|
|
147
|
-
Arguments:
|
|
148
|
-
world: World type to generate example for (default: "structured-execution")
|
|
149
|
-
|
|
150
|
-
Options:
|
|
151
|
-
-o, --output: Output file path. If not specified, prints to stdout.
|
|
152
|
-
|
|
153
|
-
Available worlds: structured-execution, code-world
|
|
154
|
-
"""
|
|
155
|
-
examples = {
|
|
156
|
-
"structured-execution": {
|
|
157
|
-
"world_package": "plato-world-structured-execution",
|
|
158
|
-
"world_version": "latest",
|
|
159
|
-
"world_config": {
|
|
160
|
-
"sim_name": "my-sim",
|
|
161
|
-
"github_url": "https://github.com/example/repo",
|
|
162
|
-
"max_attempts": 3,
|
|
163
|
-
"use_backtrack": True,
|
|
164
|
-
"skill_runner": {
|
|
165
|
-
"image": "claude-code:2.1.5",
|
|
166
|
-
"config": {"model_name": "anthropic/claude-sonnet-4-20250514", "max_turns": 100},
|
|
167
|
-
},
|
|
168
|
-
"plato_api_key": "pk_xxx",
|
|
169
|
-
"anthropic_api_key": "sk-ant-xxx",
|
|
170
|
-
},
|
|
171
|
-
"_comment": "Agents and secrets are embedded directly in world_config",
|
|
172
|
-
},
|
|
173
|
-
"code-world": {
|
|
174
|
-
"world_package": "plato-world-code",
|
|
175
|
-
"world_config": {
|
|
176
|
-
"task": "Fix the bug in src/main.py",
|
|
177
|
-
"repo_url": "https://github.com/example/repo",
|
|
178
|
-
"coder": {
|
|
179
|
-
"image": "claude-code:latest",
|
|
180
|
-
"config": {"model_name": "anthropic/claude-sonnet-4-20250514"},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
"_comment": "world_version is optional - uses latest if not specified",
|
|
184
|
-
},
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if world not in examples:
|
|
188
|
-
console.print(f"[red]❌ Unknown world: {world}[/red]")
|
|
189
|
-
console.print(f"Available examples: {list(examples.keys())}")
|
|
190
|
-
raise typer.Exit(1)
|
|
191
|
-
|
|
192
|
-
example_config = examples[world]
|
|
193
|
-
json_output = json.dumps(example_config, indent=2)
|
|
194
|
-
|
|
195
|
-
if output:
|
|
196
|
-
with open(output, "w") as f:
|
|
197
|
-
f.write(json_output)
|
|
198
|
-
console.print(f"[green]✅ Example config written to {output}[/green]")
|
|
199
|
-
else:
|
|
200
|
-
console.print(json_output)
|
|
201
|
-
|
|
202
|
-
|
|
203
129
|
def _get_world_runner_dockerfile() -> Path:
|
|
204
130
|
"""Get the path to the world runner Dockerfile template."""
|
|
205
131
|
return Path(__file__).parent.parent / "v1" / "cli" / "templates" / "world-runner.Dockerfile"
|
|
@@ -253,8 +179,6 @@ def _get_docker_platform(override: str | None = None) -> str:
|
|
|
253
179
|
if override:
|
|
254
180
|
return override
|
|
255
181
|
|
|
256
|
-
import platform as plat
|
|
257
|
-
|
|
258
182
|
system = plat.system()
|
|
259
183
|
machine = plat.machine().lower()
|
|
260
184
|
|
|
@@ -424,8 +348,6 @@ async def _create_chronos_session(
|
|
|
424
348
|
tags: list[str] | None = None,
|
|
425
349
|
) -> dict:
|
|
426
350
|
"""Create a session in Chronos."""
|
|
427
|
-
import httpx
|
|
428
|
-
|
|
429
351
|
url = f"{chronos_url.rstrip('/')}/api/sessions"
|
|
430
352
|
|
|
431
353
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
@@ -443,25 +365,6 @@ async def _create_chronos_session(
|
|
|
443
365
|
return response.json()
|
|
444
366
|
|
|
445
367
|
|
|
446
|
-
async def _close_chronos_session(
|
|
447
|
-
chronos_url: str,
|
|
448
|
-
api_key: str,
|
|
449
|
-
session_id: str,
|
|
450
|
-
) -> None:
|
|
451
|
-
"""Close a Chronos session."""
|
|
452
|
-
import httpx
|
|
453
|
-
|
|
454
|
-
url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/close"
|
|
455
|
-
|
|
456
|
-
try:
|
|
457
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
458
|
-
response = await client.post(url, headers={"x-api-key": api_key})
|
|
459
|
-
response.raise_for_status()
|
|
460
|
-
logger.info(f"Closed Chronos session: {session_id}")
|
|
461
|
-
except Exception as e:
|
|
462
|
-
logger.warning(f"Failed to close Chronos session: {e}")
|
|
463
|
-
|
|
464
|
-
|
|
465
368
|
async def _complete_chronos_session(
|
|
466
369
|
chronos_url: str,
|
|
467
370
|
api_key: str,
|
|
@@ -471,7 +374,6 @@ async def _complete_chronos_session(
|
|
|
471
374
|
error_message: str | None = None,
|
|
472
375
|
) -> None:
|
|
473
376
|
"""Complete a Chronos session with final status."""
|
|
474
|
-
import httpx
|
|
475
377
|
|
|
476
378
|
url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/complete"
|
|
477
379
|
|
plato/worlds/__init__.py
CHANGED
|
@@ -46,23 +46,19 @@ from plato._generated.models import (
|
|
|
46
46
|
from plato.worlds.base import (
|
|
47
47
|
BaseWorld,
|
|
48
48
|
ConfigT,
|
|
49
|
-
Observation,
|
|
50
|
-
StepResult,
|
|
51
49
|
get_registered_worlds,
|
|
52
50
|
get_world,
|
|
53
51
|
register_world,
|
|
54
52
|
)
|
|
55
53
|
from plato.worlds.config import (
|
|
56
|
-
Agent,
|
|
57
54
|
AgentConfig,
|
|
58
55
|
CheckpointConfig,
|
|
59
|
-
Env,
|
|
60
56
|
EnvConfig,
|
|
61
|
-
EnvList,
|
|
62
57
|
RunConfig,
|
|
63
|
-
Secret,
|
|
64
58
|
StateConfig,
|
|
65
59
|
)
|
|
60
|
+
from plato.worlds.markers import Agent, Env, EnvList, Secret
|
|
61
|
+
from plato.worlds.models import Observation, StepResult
|
|
66
62
|
from plato.worlds.runner import run_world
|
|
67
63
|
|
|
68
64
|
__all__ = [
|
plato/worlds/base.py
CHANGED
|
@@ -2,51 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib.metadata
|
|
5
7
|
import logging
|
|
6
8
|
import os
|
|
7
9
|
import subprocess
|
|
8
10
|
from abc import ABC, abstractmethod
|
|
9
11
|
from pathlib import Path
|
|
10
|
-
from typing import TYPE_CHECKING,
|
|
11
|
-
|
|
12
|
-
from pydantic import BaseModel, Field
|
|
12
|
+
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar, get_args, get_origin
|
|
13
13
|
|
|
14
|
+
from plato.agents.artifacts import upload_artifact as _upload_artifact_raw
|
|
15
|
+
from plato.agents.otel import get_tracer, init_tracing, shutdown_tracing
|
|
16
|
+
from plato.agents.runner import run_agent as _run_agent_raw
|
|
17
|
+
from plato.v2.async_.session import Session
|
|
14
18
|
from plato.worlds.config import RunConfig
|
|
19
|
+
from plato.worlds.models import Observation, StepResult
|
|
20
|
+
from plato.worlds.schema import get_world_schema
|
|
15
21
|
|
|
16
22
|
if TYPE_CHECKING:
|
|
17
23
|
from plato.v2.async_.environment import Environment
|
|
18
|
-
from plato.v2.async_.session import Session
|
|
19
|
-
|
|
20
|
-
from plato.agents.artifacts import (
|
|
21
|
-
upload_artifact as _upload_artifact_raw,
|
|
22
|
-
)
|
|
23
|
-
from plato.agents.otel import (
|
|
24
|
-
get_tracer,
|
|
25
|
-
init_tracing,
|
|
26
|
-
shutdown_tracing,
|
|
27
|
-
)
|
|
28
|
-
from plato.agents.runner import run_agent as _run_agent_raw
|
|
29
24
|
|
|
30
25
|
logger = logging.getLogger(__name__)
|
|
31
26
|
|
|
27
|
+
# Global registry of worlds
|
|
28
|
+
_WORLD_REGISTRY: dict[str, type[BaseWorld]] = {}
|
|
29
|
+
|
|
30
|
+
# Type variable for config
|
|
31
|
+
ConfigT = TypeVar("ConfigT", bound=RunConfig)
|
|
32
|
+
|
|
32
33
|
|
|
33
34
|
def _get_plato_version() -> str:
|
|
34
35
|
"""Get the installed plato SDK version."""
|
|
35
36
|
try:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return version("plato")
|
|
37
|
+
return importlib.metadata.version("plato")
|
|
39
38
|
except Exception:
|
|
40
39
|
return "unknown"
|
|
41
40
|
|
|
42
41
|
|
|
43
|
-
# Global registry of worlds
|
|
44
|
-
_WORLD_REGISTRY: dict[str, type[BaseWorld]] = {}
|
|
45
|
-
|
|
46
|
-
# Type variable for config
|
|
47
|
-
ConfigT = TypeVar("ConfigT", bound=RunConfig)
|
|
48
|
-
|
|
49
|
-
|
|
50
42
|
def register_world(name: str | None = None):
|
|
51
43
|
"""Decorator to register a world class.
|
|
52
44
|
|
|
@@ -75,22 +67,6 @@ def get_world(name: str) -> type[BaseWorld] | None:
|
|
|
75
67
|
return _WORLD_REGISTRY.get(name)
|
|
76
68
|
|
|
77
69
|
|
|
78
|
-
class Observation(BaseModel):
|
|
79
|
-
"""Observation returned from reset/step."""
|
|
80
|
-
|
|
81
|
-
data: dict[str, Any] = Field(default_factory=dict)
|
|
82
|
-
|
|
83
|
-
model_config = {"extra": "allow"}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class StepResult(BaseModel):
|
|
87
|
-
"""Result of a step."""
|
|
88
|
-
|
|
89
|
-
observation: Observation
|
|
90
|
-
done: bool = False
|
|
91
|
-
info: dict[str, Any] = Field(default_factory=dict)
|
|
92
|
-
|
|
93
|
-
|
|
94
70
|
class BaseWorld(ABC, Generic[ConfigT]):
|
|
95
71
|
"""Base class for Plato worlds.
|
|
96
72
|
|
|
@@ -144,8 +120,6 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
144
120
|
@classmethod
|
|
145
121
|
def get_version(cls) -> str:
|
|
146
122
|
"""Get version from package metadata."""
|
|
147
|
-
import importlib.metadata
|
|
148
|
-
|
|
149
123
|
for pkg_name in [cls.__module__.split(".")[0], f"plato-world-{cls.name}"]:
|
|
150
124
|
try:
|
|
151
125
|
return importlib.metadata.version(pkg_name)
|
|
@@ -156,28 +130,7 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
156
130
|
@classmethod
|
|
157
131
|
def get_schema(cls) -> dict:
|
|
158
132
|
"""Get full schema including world config, agents, secrets, and envs."""
|
|
159
|
-
|
|
160
|
-
schema = config_class.get_json_schema()
|
|
161
|
-
|
|
162
|
-
result = {
|
|
163
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
164
|
-
"type": "object",
|
|
165
|
-
"properties": schema.get("properties", {}),
|
|
166
|
-
"required": schema.get("required", []),
|
|
167
|
-
"agents": schema.get("agents", []),
|
|
168
|
-
"secrets": schema.get("secrets", []),
|
|
169
|
-
"envs": schema.get("envs", []),
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
# Include env_list if present (for worlds with arbitrary environment lists)
|
|
173
|
-
if "env_list" in schema:
|
|
174
|
-
result["env_list"] = schema["env_list"]
|
|
175
|
-
|
|
176
|
-
# Include $defs if present (for nested type references)
|
|
177
|
-
if "$defs" in schema:
|
|
178
|
-
result["$defs"] = schema["$defs"]
|
|
179
|
-
|
|
180
|
-
return result
|
|
133
|
+
return get_world_schema(cls)
|
|
181
134
|
|
|
182
135
|
@abstractmethod
|
|
183
136
|
async def reset(self) -> Observation:
|
|
@@ -201,8 +154,6 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
201
154
|
|
|
202
155
|
async def _cleanup_agent_containers(self) -> None:
|
|
203
156
|
"""Stop any agent containers spawned by this world."""
|
|
204
|
-
import asyncio
|
|
205
|
-
|
|
206
157
|
if not self._agent_containers:
|
|
207
158
|
return
|
|
208
159
|
|
|
@@ -272,8 +223,6 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
272
223
|
return
|
|
273
224
|
|
|
274
225
|
try:
|
|
275
|
-
from plato.v2.async_.session import Session
|
|
276
|
-
|
|
277
226
|
self.logger.info("Restoring Plato session from serialized data")
|
|
278
227
|
self.plato_session = await Session.load(self.config.plato_session, start_heartbeat=True)
|
|
279
228
|
self.logger.info(f"Plato session {self.plato_session.session_id} restored, heartbeat started")
|
|
@@ -544,18 +493,18 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
544
493
|
def get_env(self, alias: str) -> Environment | None:
|
|
545
494
|
"""Get an environment by alias.
|
|
546
495
|
|
|
547
|
-
Use this to access environments defined in the world config
|
|
496
|
+
Use this to access environments defined in the world config.
|
|
548
497
|
|
|
549
498
|
Args:
|
|
550
|
-
alias: The environment alias (e.g., "
|
|
499
|
+
alias: The environment alias (e.g., "runtime", "database")
|
|
551
500
|
|
|
552
501
|
Returns:
|
|
553
502
|
The Environment object or None if not found or session not connected.
|
|
554
503
|
|
|
555
504
|
Example:
|
|
556
|
-
|
|
557
|
-
if
|
|
558
|
-
result = await
|
|
505
|
+
env = self.get_env("runtime")
|
|
506
|
+
if env:
|
|
507
|
+
result = await env.execute("ls -la")
|
|
559
508
|
"""
|
|
560
509
|
if not self.plato_session:
|
|
561
510
|
self.logger.warning("Cannot get env: Plato session not connected")
|
|
@@ -576,8 +525,8 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
576
525
|
def get_sim_env_vars(self) -> dict[str, str]:
|
|
577
526
|
"""Get environment variables from all configured sims.
|
|
578
527
|
|
|
579
|
-
Automatically discovers and loads env vars from sims
|
|
580
|
-
|
|
528
|
+
Automatically discovers and loads env vars from sims based on the
|
|
529
|
+
environments configured in the world.
|
|
581
530
|
|
|
582
531
|
Returns:
|
|
583
532
|
Dict of environment variable name -> value
|
|
@@ -591,88 +540,54 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
591
540
|
"""
|
|
592
541
|
env_vars: dict[str, str] = {}
|
|
593
542
|
|
|
594
|
-
|
|
595
|
-
sim_packages = [
|
|
596
|
-
("localstack", "localstack"),
|
|
597
|
-
("gitea", "gitea"),
|
|
598
|
-
]
|
|
599
|
-
|
|
600
|
-
for package_name, env_alias in sim_packages:
|
|
601
|
-
env = self.get_env(env_alias)
|
|
602
|
-
if not env:
|
|
603
|
-
continue
|
|
604
|
-
|
|
543
|
+
for env in self.envs:
|
|
605
544
|
try:
|
|
606
|
-
|
|
607
|
-
|
|
545
|
+
sim_module = __import__(f"plato.sims.{env.alias}", fromlist=[env.alias])
|
|
546
|
+
|
|
547
|
+
if not hasattr(sim_module, "get_service_urls") or not hasattr(sim_module, "get_env_vars"):
|
|
548
|
+
continue
|
|
608
549
|
|
|
609
|
-
# Get service URLs and env vars
|
|
610
550
|
service_urls = sim_module.get_service_urls(env.job_id)
|
|
611
551
|
sim_vars = sim_module.get_env_vars(service_urls)
|
|
612
552
|
env_vars.update(sim_vars)
|
|
613
|
-
self.logger.info(f"{
|
|
553
|
+
self.logger.info(f"{env.alias} env vars: {list(sim_vars.keys())}")
|
|
614
554
|
except ImportError:
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
f"package is not installed.\n\n"
|
|
618
|
-
f"Install sims packages:\n"
|
|
619
|
-
f' export INDEX_URL="https://__token__:${{PLATO_API_KEY}}@plato.so/api/v2/pypi/sims/simple/"\n'
|
|
620
|
-
f" uv pip install '.[sims]' --extra-index-url $INDEX_URL"
|
|
621
|
-
) from None
|
|
555
|
+
# Sim package not installed for this env - skip silently
|
|
556
|
+
continue
|
|
622
557
|
except Exception as e:
|
|
623
|
-
self.logger.warning(f"Failed to get {
|
|
558
|
+
self.logger.warning(f"Failed to get {env.alias} env vars: {e}")
|
|
624
559
|
|
|
625
560
|
return env_vars
|
|
626
561
|
|
|
627
562
|
def get_sim_instructions(self) -> str:
|
|
628
563
|
"""Get usage instructions from all configured sims.
|
|
629
564
|
|
|
630
|
-
Returns markdown-formatted instructions for using
|
|
631
|
-
based on the environments
|
|
565
|
+
Returns markdown-formatted instructions for using configured sims
|
|
566
|
+
based on the environments in the world.
|
|
632
567
|
|
|
633
568
|
Returns:
|
|
634
569
|
Markdown string with instructions, or empty string if no sims configured.
|
|
635
570
|
|
|
636
|
-
Raises:
|
|
637
|
-
ImportError: If a sim environment is configured but package is not installed.
|
|
638
|
-
|
|
639
571
|
Example:
|
|
640
572
|
instructions = self.get_sim_instructions()
|
|
641
|
-
# Returns markdown with
|
|
573
|
+
# Returns markdown with sim setup instructions
|
|
642
574
|
"""
|
|
643
575
|
instructions_parts: list[str] = []
|
|
644
576
|
|
|
645
|
-
|
|
646
|
-
sim_packages = [
|
|
647
|
-
("localstack", "localstack"),
|
|
648
|
-
("gitea", "gitea"),
|
|
649
|
-
]
|
|
650
|
-
|
|
651
|
-
for package_name, env_alias in sim_packages:
|
|
652
|
-
env = self.get_env(env_alias)
|
|
653
|
-
if not env:
|
|
654
|
-
continue
|
|
655
|
-
|
|
577
|
+
for env in self.envs:
|
|
656
578
|
try:
|
|
657
|
-
|
|
658
|
-
sim_module = __import__(f"plato.sims.{package_name}", fromlist=[package_name])
|
|
579
|
+
sim_module = __import__(f"plato.sims.{env.alias}", fromlist=[env.alias])
|
|
659
580
|
|
|
660
|
-
# Get instructions using the job_id
|
|
661
581
|
if hasattr(sim_module, "get_instructions_from_job"):
|
|
662
582
|
instructions = sim_module.get_instructions_from_job(env.job_id)
|
|
663
583
|
if instructions:
|
|
664
584
|
instructions_parts.append(instructions)
|
|
665
|
-
self.logger.info(f"Added {
|
|
585
|
+
self.logger.info(f"Added {env.alias} instructions to prompt")
|
|
666
586
|
except ImportError:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
f"package is not installed.\n\n"
|
|
670
|
-
f"Install sims packages:\n"
|
|
671
|
-
f' export INDEX_URL="https://__token__:${{PLATO_API_KEY}}@plato.so/api/v2/pypi/sims/simple/"\n'
|
|
672
|
-
f" uv pip install '.[sims]' --extra-index-url $INDEX_URL"
|
|
673
|
-
) from None
|
|
587
|
+
# Sim package not installed for this env - skip silently
|
|
588
|
+
continue
|
|
674
589
|
except Exception as e:
|
|
675
|
-
self.logger.warning(f"Failed to get {
|
|
590
|
+
self.logger.warning(f"Failed to get {env.alias} instructions: {e}")
|
|
676
591
|
|
|
677
592
|
if instructions_parts:
|
|
678
593
|
return "\n\n---\n\n".join(instructions_parts)
|
|
@@ -681,8 +596,7 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
681
596
|
def format_instruction_with_sims(self, instruction: str) -> str:
|
|
682
597
|
"""Format an instruction with sim context prepended.
|
|
683
598
|
|
|
684
|
-
Automatically adds available service instructions
|
|
685
|
-
before the main instruction.
|
|
599
|
+
Automatically adds available service instructions before the main instruction.
|
|
686
600
|
|
|
687
601
|
Args:
|
|
688
602
|
instruction: The base instruction/task
|
|
@@ -695,7 +609,7 @@ class BaseWorld(ABC, Generic[ConfigT]):
|
|
|
695
609
|
# Returns:
|
|
696
610
|
# ## Available Services
|
|
697
611
|
# The following services are available...
|
|
698
|
-
# [
|
|
612
|
+
# [sim instructions]
|
|
699
613
|
# ---
|
|
700
614
|
# ## Task
|
|
701
615
|
# Fix the bug in main.py
|
plato/worlds/config.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -13,6 +14,8 @@ from plato._generated.models import (
|
|
|
13
14
|
EnvFromSimulator,
|
|
14
15
|
)
|
|
15
16
|
from plato.v2.async_.session import SerializedSession
|
|
17
|
+
from plato.worlds.markers import Agent, Env, EnvList, Secret
|
|
18
|
+
from plato.worlds.schema import get_field_annotations, get_world_config_schema
|
|
16
19
|
|
|
17
20
|
# Union type for environment configurations
|
|
18
21
|
EnvConfig = EnvFromArtifact | EnvFromSimulator | EnvFromResource
|
|
@@ -30,66 +33,6 @@ class AgentConfig(BaseModel):
|
|
|
30
33
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
31
34
|
|
|
32
35
|
|
|
33
|
-
class Agent:
|
|
34
|
-
"""Annotation marker for agent fields.
|
|
35
|
-
|
|
36
|
-
Usage:
|
|
37
|
-
coder: Annotated[AgentConfig, Agent(description="Coding agent")]
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
def __init__(self, description: str = "", required: bool = True):
|
|
41
|
-
self.description = description
|
|
42
|
-
self.required = required
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Secret:
|
|
46
|
-
"""Annotation marker for secret fields.
|
|
47
|
-
|
|
48
|
-
Usage:
|
|
49
|
-
api_key: Annotated[str | None, Secret(description="API key")] = None
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, description: str = "", required: bool = False):
|
|
53
|
-
self.description = description
|
|
54
|
-
self.required = required
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class Env:
|
|
58
|
-
"""Annotation marker for single environment fields.
|
|
59
|
-
|
|
60
|
-
Environments are VMs that run alongside the world's runtime.
|
|
61
|
-
They can be specified by artifact ID, simulator name, or resource config.
|
|
62
|
-
|
|
63
|
-
Usage:
|
|
64
|
-
gitea: Annotated[EnvConfig, Env(description="Git server")] = EnvFromArtifact(
|
|
65
|
-
artifact_id="abc123",
|
|
66
|
-
alias="gitea",
|
|
67
|
-
)
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
def __init__(self, description: str = "", required: bool = True):
|
|
71
|
-
self.description = description
|
|
72
|
-
self.required = required
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class EnvList:
|
|
76
|
-
"""Annotation marker for a list of arbitrary environments.
|
|
77
|
-
|
|
78
|
-
Use this when the world accepts a dynamic list of environments
|
|
79
|
-
rather than named, fixed environment fields.
|
|
80
|
-
|
|
81
|
-
Usage:
|
|
82
|
-
envs: Annotated[
|
|
83
|
-
list[EnvFromSimulator | EnvFromArtifact | EnvFromResource],
|
|
84
|
-
EnvList(description="Environments to create for this task"),
|
|
85
|
-
Field(discriminator="type"),
|
|
86
|
-
] = []
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
def __init__(self, description: str = ""):
|
|
90
|
-
self.description = description
|
|
91
|
-
|
|
92
|
-
|
|
93
36
|
class StateConfig(BaseModel):
|
|
94
37
|
"""Configuration for world state persistence.
|
|
95
38
|
|
|
@@ -169,114 +112,12 @@ class RunConfig(BaseModel):
|
|
|
169
112
|
@classmethod
|
|
170
113
|
def get_field_annotations(cls) -> dict[str, Agent | Secret | Env | EnvList | None]:
|
|
171
114
|
"""Get Agent/Secret/Env/EnvList annotations for each field."""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for field_name, field_info in cls.model_fields.items():
|
|
175
|
-
marker = None
|
|
176
|
-
|
|
177
|
-
# Pydantic stores Annotated metadata in field_info.metadata
|
|
178
|
-
for meta in field_info.metadata:
|
|
179
|
-
if isinstance(meta, (Agent, Secret, Env, EnvList)):
|
|
180
|
-
marker = meta
|
|
181
|
-
break
|
|
182
|
-
|
|
183
|
-
result[field_name] = marker
|
|
184
|
-
|
|
185
|
-
return result
|
|
115
|
+
return get_field_annotations(cls)
|
|
186
116
|
|
|
187
117
|
@classmethod
|
|
188
118
|
def get_json_schema(cls) -> dict:
|
|
189
119
|
"""Get JSON schema with agents, secrets, and envs separated."""
|
|
190
|
-
|
|
191
|
-
full_schema = cls.model_json_schema()
|
|
192
|
-
full_schema.pop("title", None)
|
|
193
|
-
|
|
194
|
-
# Separate fields by annotation type
|
|
195
|
-
annotations = cls.get_field_annotations()
|
|
196
|
-
properties = full_schema.get("properties", {})
|
|
197
|
-
|
|
198
|
-
world_properties = {}
|
|
199
|
-
agents = []
|
|
200
|
-
secrets = []
|
|
201
|
-
envs = []
|
|
202
|
-
env_list_field: dict | None = None # For EnvList marker (arbitrary envs)
|
|
203
|
-
|
|
204
|
-
# Skip runtime fields
|
|
205
|
-
runtime_fields = {"session_id", "otel_url", "upload_url", "plato_session", "checkpoint", "state"}
|
|
206
|
-
|
|
207
|
-
for field_name, prop_schema in properties.items():
|
|
208
|
-
if field_name in runtime_fields:
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
marker = annotations.get(field_name)
|
|
212
|
-
|
|
213
|
-
if isinstance(marker, Agent):
|
|
214
|
-
agents.append(
|
|
215
|
-
{
|
|
216
|
-
"name": field_name,
|
|
217
|
-
"description": marker.description,
|
|
218
|
-
"required": marker.required,
|
|
219
|
-
}
|
|
220
|
-
)
|
|
221
|
-
elif isinstance(marker, Secret):
|
|
222
|
-
secrets.append(
|
|
223
|
-
{
|
|
224
|
-
"name": field_name,
|
|
225
|
-
"description": marker.description,
|
|
226
|
-
"required": marker.required,
|
|
227
|
-
}
|
|
228
|
-
)
|
|
229
|
-
elif isinstance(marker, Env):
|
|
230
|
-
# Get default value for this env field
|
|
231
|
-
field_info = cls.model_fields.get(field_name)
|
|
232
|
-
default_value = None
|
|
233
|
-
if field_info and field_info.default is not None:
|
|
234
|
-
# Serialize the default EnvConfig to dict
|
|
235
|
-
default_env = field_info.default
|
|
236
|
-
if hasattr(default_env, "model_dump"):
|
|
237
|
-
default_value = default_env.model_dump()
|
|
238
|
-
elif isinstance(default_env, dict):
|
|
239
|
-
default_value = default_env
|
|
240
|
-
|
|
241
|
-
envs.append(
|
|
242
|
-
{
|
|
243
|
-
"name": field_name,
|
|
244
|
-
"description": marker.description,
|
|
245
|
-
"required": marker.required,
|
|
246
|
-
"default": default_value,
|
|
247
|
-
}
|
|
248
|
-
)
|
|
249
|
-
elif isinstance(marker, EnvList):
|
|
250
|
-
# Field marked as a list of arbitrary environments
|
|
251
|
-
env_list_field = {
|
|
252
|
-
"name": field_name,
|
|
253
|
-
"description": marker.description,
|
|
254
|
-
}
|
|
255
|
-
else:
|
|
256
|
-
world_properties[field_name] = prop_schema
|
|
257
|
-
|
|
258
|
-
# Compute required fields (excluding runtime and annotated fields)
|
|
259
|
-
required = [
|
|
260
|
-
r for r in full_schema.get("required", []) if r not in runtime_fields and annotations.get(r) is None
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
result = {
|
|
264
|
-
"properties": world_properties,
|
|
265
|
-
"required": required,
|
|
266
|
-
"agents": agents,
|
|
267
|
-
"secrets": secrets,
|
|
268
|
-
"envs": envs,
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
# Include $defs if present (for nested type references)
|
|
272
|
-
if "$defs" in full_schema:
|
|
273
|
-
result["$defs"] = full_schema["$defs"]
|
|
274
|
-
|
|
275
|
-
# Add env_list if present (for worlds with arbitrary environment lists)
|
|
276
|
-
if env_list_field:
|
|
277
|
-
result["env_list"] = env_list_field
|
|
278
|
-
|
|
279
|
-
return result
|
|
120
|
+
return get_world_config_schema(cls)
|
|
280
121
|
|
|
281
122
|
def get_envs(self) -> list[EnvConfig]:
|
|
282
123
|
"""Get all environment configurations from this config.
|
|
@@ -298,8 +139,6 @@ class RunConfig(BaseModel):
|
|
|
298
139
|
@classmethod
|
|
299
140
|
def from_file(cls, path: str | Path) -> RunConfig:
|
|
300
141
|
"""Load config from a JSON file."""
|
|
301
|
-
import json
|
|
302
|
-
|
|
303
142
|
path = Path(path)
|
|
304
143
|
with open(path) as f:
|
|
305
144
|
data = json.load(f)
|