plato-sdk-v2 2.1.11__py3-none-any.whl → 2.3.0__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.
@@ -1,9 +1,10 @@
1
1
  """OpenAPI SDK Generator for Plato.
2
2
 
3
- This module provides tools to generate Python SDKs from OpenAPI specifications.
3
+ This module provides tools to generate Python SDKs from OpenAPI specifications,
4
+ as well as instruction-based SDKs for sims that don't need API clients.
4
5
 
5
- Usage:
6
- from plato.sims.generator import parse_openapi, PythonGenerator, OAuthConfig
6
+ Usage (OpenAPI-based):
7
+ from plato._sims_generator import parse_openapi, PythonGenerator, OAuthConfig
7
8
 
8
9
  api = parse_openapi(spec_dict)
9
10
  generator = PythonGenerator(
@@ -21,10 +22,22 @@ Usage:
21
22
  )
22
23
  generator.generate()
23
24
 
25
+ Usage (Instruction-based):
26
+ from plato._sims_generator import InstructionConfig, InstructionGenerator
27
+
28
+ config = InstructionConfig.from_yaml(Path("specs/instructions.yaml"))
29
+ generator = InstructionGenerator(
30
+ config,
31
+ output_path,
32
+ package_name="localstack",
33
+ )
34
+ generator.generate()
35
+
24
36
  Or via CLI:
25
- uv run python -m plato.sims.generator.cli --path spec.yaml --output ./output --package mypackage
37
+ plato sims publish --config plato-config.yml
26
38
  """
27
39
 
40
+ from .instruction import InstructionConfig, InstructionGenerator
28
41
  from .parser import API, Endpoint, Schema, Type, parse_openapi
29
42
  from .python import AuthConfig, BasicAuthConfig, BearerTokenConfig, OAuthConfig, PythonGenerator, SessionAuthConfig
30
43
 
@@ -40,4 +53,6 @@ __all__ = [
40
53
  "BearerTokenConfig",
41
54
  "BasicAuthConfig",
42
55
  "SessionAuthConfig",
56
+ "InstructionConfig",
57
+ "InstructionGenerator",
43
58
  ]
@@ -0,0 +1,203 @@
1
+ """Instruction-based simulator SDK generator.
2
+
3
+ Generates simple helper packages for sims that don't need OpenAPI clients.
4
+ These sims provide setup instructions and environment variable helpers.
5
+ """
6
+
7
+ import importlib.resources
8
+ import subprocess
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ import yaml
13
+ from jinja2 import Environment, FileSystemLoader
14
+
15
+
16
+ @dataclass
17
+ class ServiceConfig:
18
+ """Configuration for a named service endpoint."""
19
+
20
+ port: int
21
+ description: str = ""
22
+
23
+
24
+ @dataclass
25
+ class EnvVarConfig:
26
+ """Configuration for an environment variable."""
27
+
28
+ description: str = ""
29
+ template: str | None = None # Template with {service:NAME} placeholders
30
+ default: str | None = None # Static default value
31
+ env_key: str | None = None # Read from another env var
32
+
33
+
34
+ @dataclass
35
+ class InstructionConfig:
36
+ """Parsed instruction configuration from instructions.yaml."""
37
+
38
+ env_prefix: str
39
+ services: dict[str, ServiceConfig] = field(default_factory=dict)
40
+ env_vars: dict[str, EnvVarConfig] = field(default_factory=dict)
41
+ instructions: str = ""
42
+ title: str = ""
43
+ description: str = ""
44
+ version: str = "0.1.0"
45
+
46
+ @classmethod
47
+ def from_yaml(cls, path: Path) -> "InstructionConfig":
48
+ """Parse instruction config from YAML file."""
49
+ with open(path) as f:
50
+ data = yaml.safe_load(f)
51
+
52
+ if data.get("type") != "instruction":
53
+ raise ValueError(f"Expected type: instruction, got: {data.get('type')}")
54
+
55
+ env_prefix = data.get("env_prefix", "")
56
+ title = data.get("title", "")
57
+ description = data.get("description", "")
58
+ version = data.get("version", "0.1.0")
59
+
60
+ # Parse services
61
+ services = {}
62
+ for name, svc_data in data.get("services", {}).items():
63
+ services[name] = ServiceConfig(
64
+ port=svc_data["port"],
65
+ description=svc_data.get("description", ""),
66
+ )
67
+
68
+ # Parse env vars
69
+ env_vars = {}
70
+ for name, var_data in data.get("env_vars", {}).items():
71
+ env_vars[name] = EnvVarConfig(
72
+ description=var_data.get("description", ""),
73
+ template=var_data.get("template"),
74
+ default=var_data.get("default"),
75
+ env_key=var_data.get("env_key"),
76
+ )
77
+
78
+ instructions = data.get("instructions", "")
79
+
80
+ return cls(
81
+ env_prefix=env_prefix,
82
+ services=services,
83
+ env_vars=env_vars,
84
+ instructions=instructions,
85
+ title=title,
86
+ description=description,
87
+ version=version,
88
+ )
89
+
90
+
91
+ class InstructionGenerator:
92
+ """Generates instruction-based simulator SDK packages."""
93
+
94
+ def __init__(
95
+ self,
96
+ config: InstructionConfig,
97
+ output_path: Path,
98
+ package_name: str,
99
+ generator_version: str | None = None,
100
+ ):
101
+ self.config = config
102
+ self.output = output_path
103
+ self.package_name = package_name
104
+ self.generator_version = generator_version
105
+
106
+ # Get template directory using importlib.resources
107
+ template_ref = importlib.resources.files("plato._sims_generator") / "templates" / "instruction"
108
+ # For filesystem templates, we need the actual path
109
+ self.template_dir = Path(str(template_ref))
110
+
111
+ self.env = Environment(
112
+ loader=FileSystemLoader(self.template_dir),
113
+ trim_blocks=True,
114
+ lstrip_blocks=True,
115
+ )
116
+
117
+ def generate(self):
118
+ """Generate the instruction-based SDK package."""
119
+ # Create output structure
120
+ self.output.mkdir(parents=True, exist_ok=True)
121
+
122
+ # Generate helpers.py
123
+ self._generate_helpers()
124
+
125
+ # Generate __init__.py
126
+ self._generate_init()
127
+
128
+ # Copy instructions.yaml to output
129
+ self._copy_instructions_yaml()
130
+
131
+ # Generate py.typed marker
132
+ self._generate_py_typed()
133
+
134
+ # Format with ruff
135
+ self._format()
136
+
137
+ def _generate_helpers(self):
138
+ """Generate the helpers module."""
139
+ template = self.env.get_template("helpers.py.jinja")
140
+ content = template.render(
141
+ config=self.config,
142
+ package_name=self.package_name,
143
+ generator_version=self.generator_version,
144
+ )
145
+ (self.output / "helpers.py").write_text(content)
146
+
147
+ def _generate_init(self):
148
+ """Generate package __init__.py."""
149
+ template = self.env.get_template("init.py.jinja")
150
+ content = template.render(
151
+ config=self.config,
152
+ package_name=self.package_name,
153
+ generator_version=self.generator_version,
154
+ )
155
+ (self.output / "__init__.py").write_text(content)
156
+
157
+ def _copy_instructions_yaml(self):
158
+ """Bundle the instructions.yaml config with the package."""
159
+ # We'll generate a cleaned version of the config
160
+ env_vars: dict[str, dict[str, str | int]] = {}
161
+ config_data: dict[str, str | dict] = {
162
+ "type": "instruction",
163
+ "env_prefix": self.config.env_prefix,
164
+ "title": self.config.title,
165
+ "description": self.config.description,
166
+ "version": self.config.version,
167
+ "services": {
168
+ name: {"port": svc.port, "description": svc.description} for name, svc in self.config.services.items()
169
+ },
170
+ "env_vars": env_vars,
171
+ "instructions": self.config.instructions,
172
+ }
173
+
174
+ for name, var in self.config.env_vars.items():
175
+ var_data: dict = {"description": var.description}
176
+ if var.template:
177
+ var_data["template"] = var.template
178
+ if var.default:
179
+ var_data["default"] = var.default
180
+ if var.env_key:
181
+ var_data["env_key"] = var.env_key
182
+ env_vars[name] = var_data
183
+
184
+ with open(self.output / "instructions.yaml", "w") as f:
185
+ yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
186
+
187
+ def _generate_py_typed(self):
188
+ """Generate py.typed marker for PEP 561 compliance."""
189
+ (self.output / "py.typed").write_text("")
190
+
191
+ def _format(self):
192
+ """Format generated code with ruff."""
193
+ try:
194
+ subprocess.run(
195
+ ["ruff", "check", str(self.output), "--fix", "--quiet"],
196
+ capture_output=True,
197
+ )
198
+ subprocess.run(
199
+ ["ruff", "format", str(self.output), "--quiet"],
200
+ capture_output=True,
201
+ )
202
+ except FileNotFoundError:
203
+ pass # ruff not installed, skip formatting
@@ -0,0 +1,161 @@
1
+ """{{ config.title or package_name }} simulator helpers.
2
+
3
+ Auto-generated by plato sims publish. Do not edit.
4
+ {% if config.description %}
5
+
6
+ {{ config.description }}
7
+ {% endif %}
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib.resources
13
+ import os
14
+ from typing import Any
15
+
16
+ import yaml
17
+
18
+ # Load instructions config using importlib.resources
19
+ _config_text = importlib.resources.files(__package__).joinpath("instructions.yaml").read_text()
20
+ _CONFIG: dict[str, Any] = yaml.safe_load(_config_text)
21
+
22
+ ENV_PREFIX: str = _CONFIG.get("env_prefix", "")
23
+ SERVICES: dict[str, dict[str, Any]] = _CONFIG.get("services", {})
24
+ CONNECT_URL_TEMPLATE: str = "https://{job_id}--{port}.connect.plato.so"
25
+
26
+
27
+ def get_service_port(service_name: str) -> int:
28
+ """Get the port for a named service.
29
+
30
+ Args:
31
+ service_name: Name of the service (e.g., {% for name in config.services.keys() %}"{{ name }}"{% if not loop.last %}, {% endif %}{% endfor %})
32
+
33
+ Returns:
34
+ Port number for the service
35
+
36
+ Raises:
37
+ ValueError: If service not found
38
+ """
39
+ if service_name not in SERVICES:
40
+ available = list(SERVICES.keys())
41
+ raise ValueError(f"Service '{service_name}' not found. Available: {available}")
42
+ return int(SERVICES[service_name]["port"])
43
+
44
+
45
+ def get_service_url(job_id: str, service_name: str) -> str:
46
+ """Get the connect URL for a specific service.
47
+
48
+ Args:
49
+ job_id: The job ID from the Plato session
50
+ service_name: Name of the service
51
+
52
+ Returns:
53
+ Connect URL for the service (e.g., https://jobid--4566.connect.plato.so)
54
+ """
55
+ port = get_service_port(service_name)
56
+ return CONNECT_URL_TEMPLATE.format(job_id=job_id, port=port)
57
+
58
+
59
+ def get_service_urls(job_id: str) -> dict[str, str]:
60
+ """Get connect URLs for all services.
61
+
62
+ Args:
63
+ job_id: The job ID from the Plato session
64
+
65
+ Returns:
66
+ Dict mapping service name to connect URL
67
+ """
68
+ return {name: get_service_url(job_id, name) for name in SERVICES}
69
+
70
+
71
+ def get_env_vars(service_urls: dict[str, str]) -> dict[str, str]:
72
+ """Get environment variables with resolved service URLs.
73
+
74
+ Args:
75
+ service_urls: Dict mapping service name to its connect URL
76
+ (e.g., {"main": "https://abc--4566.connect.plato.so"})
77
+
78
+ Returns:
79
+ Dict of env var name -> value
80
+ """
81
+ env_vars: dict[str, str] = {}
82
+ for name, var_config in _CONFIG.get("env_vars", {}).items():
83
+ if "template" in var_config:
84
+ template = var_config["template"]
85
+ # Replace {service:NAME} with actual URL
86
+ for svc_name, svc_url in service_urls.items():
87
+ template = template.replace("{" + f"service:{svc_name}" + "}", svc_url)
88
+ env_vars[name] = template
89
+ elif "default" in var_config:
90
+ env_vars[name] = str(var_config["default"])
91
+ elif "env_key" in var_config:
92
+ env_vars[name] = os.environ.get(var_config["env_key"], "")
93
+ return env_vars
94
+
95
+
96
+ def get_env_vars_from_job(job_id: str) -> dict[str, str]:
97
+ """Get environment variables for a job ID.
98
+
99
+ Args:
100
+ job_id: The job ID from the Plato session
101
+
102
+ Returns:
103
+ Dict of env var name -> value
104
+ """
105
+ service_urls = get_service_urls(job_id)
106
+ return get_env_vars(service_urls)
107
+
108
+
109
+ def setup_env(service_urls: dict[str, str]) -> None:
110
+ """Set up environment variables for using this sim.
111
+
112
+ Note: This sets env vars in the current process. For setting env vars
113
+ in a runtime container, use get_env_vars() and pass them to the runtime.
114
+
115
+ Args:
116
+ service_urls: Dict mapping service name to its connect URL
117
+ """
118
+ for name, value in get_env_vars(service_urls).items():
119
+ os.environ[name] = value
120
+
121
+
122
+ def setup_env_from_job(job_id: str) -> None:
123
+ """Set up environment variables from a job ID.
124
+
125
+ Note: This sets env vars in the current process. For setting env vars
126
+ in a runtime container, use get_env_vars_from_job() and pass them to the runtime.
127
+
128
+ Args:
129
+ job_id: The job ID from the Plato session
130
+ """
131
+ service_urls = get_service_urls(job_id)
132
+ setup_env(service_urls)
133
+
134
+
135
+ def get_instructions(service_urls: dict[str, str]) -> str:
136
+ """Get formatted instructions with resolved service URLs.
137
+
138
+ Args:
139
+ service_urls: Dict mapping service name to its connect URL
140
+
141
+ Returns:
142
+ Markdown-formatted instructions
143
+ """
144
+ template = _CONFIG.get("instructions", "")
145
+ # Replace {service:NAME} with actual URL
146
+ for svc_name, svc_url in service_urls.items():
147
+ template = template.replace("{" + f"service:{svc_name}" + "}", svc_url)
148
+ return template
149
+
150
+
151
+ def get_instructions_from_job(job_id: str) -> str:
152
+ """Get formatted instructions for a job ID.
153
+
154
+ Args:
155
+ job_id: The job ID from the Plato session
156
+
157
+ Returns:
158
+ Markdown-formatted instructions
159
+ """
160
+ service_urls = get_service_urls(job_id)
161
+ return get_instructions(service_urls)
@@ -0,0 +1,43 @@
1
+ """{{ config.title or package_name }} simulator.
2
+ {% if config.description %}
3
+
4
+ {{ config.description }}
5
+ {% endif %}
6
+
7
+ Auto-generated by plato sims publish. Do not edit.
8
+ """
9
+
10
+ from .helpers import (
11
+ CONNECT_URL_TEMPLATE,
12
+ ENV_PREFIX,
13
+ SERVICES,
14
+ get_env_vars,
15
+ get_env_vars_from_job,
16
+ get_instructions,
17
+ get_instructions_from_job,
18
+ get_service_port,
19
+ get_service_url,
20
+ get_service_urls,
21
+ setup_env,
22
+ setup_env_from_job,
23
+ )
24
+
25
+ {% if generator_version %}
26
+ __generator_version__ = "{{ generator_version }}"
27
+ {% endif %}
28
+ __version__ = "{{ config.version }}"
29
+
30
+ __all__ = [
31
+ "CONNECT_URL_TEMPLATE",
32
+ "ENV_PREFIX",
33
+ "SERVICES",
34
+ "get_env_vars",
35
+ "get_env_vars_from_job",
36
+ "get_instructions",
37
+ "get_instructions_from_job",
38
+ "get_service_port",
39
+ "get_service_url",
40
+ "get_service_urls",
41
+ "setup_env",
42
+ "setup_env_from_job",
43
+ ]
plato/agents/__init__.py CHANGED
@@ -69,8 +69,7 @@ __all__ = [
69
69
  "get_agent",
70
70
  "get_registered_agents",
71
71
  # Runner
72
- "AgentRunner",
73
- "AgentRunResult",
72
+ "run_agent",
74
73
  # Trajectory (ATIF)
75
74
  "Trajectory",
76
75
  "Step",
@@ -81,8 +80,12 @@ __all__ = [
81
80
  "Metrics",
82
81
  "FinalMetrics",
83
82
  "SCHEMA_VERSION",
84
- # Callback
85
- "ChronosCallback",
83
+ # Logging
84
+ "init_logging",
85
+ "span",
86
+ "log_event",
87
+ "upload_artifacts",
88
+ "reset_logging",
86
89
  ]
87
90
 
88
91
  from plato.agents.base import (
@@ -93,9 +96,15 @@ from plato.agents.base import (
93
96
  register_agent,
94
97
  )
95
98
  from plato.agents.build import BuildConfig, load_build_config
96
- from plato.agents.callback import ChronosCallback
97
99
  from plato.agents.config import AgentConfig, Secret
98
- from plato.agents.runner import AgentRunner, AgentRunResult
100
+ from plato.agents.logging import (
101
+ init_logging,
102
+ log_event,
103
+ reset_logging,
104
+ span,
105
+ upload_artifacts,
106
+ )
107
+ from plato.agents.runner import run_agent
99
108
  from plato.agents.trajectory import (
100
109
  SCHEMA_VERSION,
101
110
  Agent,