synth-optimizers 0.1.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.
- gepa/__init__.py +5 -0
- prompt_opt/__init__.py +16 -0
- prompt_opt/adapters/__init__.py +18 -0
- prompt_opt/adapters/synth_container.py +170 -0
- prompt_opt/adapters/synth_offline.py +81 -0
- prompt_opt/dspy/__init__.py +5 -0
- prompt_opt/dspy/gepa.py +5 -0
- prompt_opt/dspy/miprov2.py +281 -0
- prompt_opt/gepa_ai_compat.py +225 -0
- prompt_opt/mipro.py +114 -0
- prompt_opt/sdk/__init__.py +2 -0
- prompt_opt/sdk/optimization/__init__.py +14 -0
- prompt_opt/sdk/optimization/internal/__init__.py +2 -0
- prompt_opt/sdk/optimization/internal/configs/__init__.py +16 -0
- prompt_opt/sdk/optimization/internal/configs/prompt_learning.py +230 -0
- prompt_opt/sdk/optimization/internal/configs/shared.py +12 -0
- prompt_opt/sdk/optimization/internal/prompt_learning.py +302 -0
- prompt_opt/sdk/optimization/internal/runtime.py +1465 -0
- prompt_opt/sdk/optimization/internal/utils.py +29 -0
- prompt_opt/sdk/optimization/models.py +71 -0
- prompt_opt/sdk/optimization/policy/__init__.py +6 -0
- prompt_opt/sdk/optimization/policy/v1.py +470 -0
- synth_optimizers-0.1.0.dist-info/METADATA +243 -0
- synth_optimizers-0.1.0.dist-info/RECORD +27 -0
- synth_optimizers-0.1.0.dist-info/WHEEL +5 -0
- synth_optimizers-0.1.0.dist-info/licenses/LICENSE +176 -0
- synth_optimizers-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""High-level local prompt-learning job wrapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..models import PolicyCandidate, PolicyCandidatePage, PromptLearningResult
|
|
10
|
+
from .configs.prompt_learning import PromptLearningConfig
|
|
11
|
+
from .runtime import RUNTIME, get_candidate_typed, list_candidates_typed
|
|
12
|
+
from .utils import load_toml, run_sync
|
|
13
|
+
from ..policy.v1 import PolicyOptimizationOfflineJob
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PromptLearningJobConfig:
|
|
18
|
+
"""Configuration for a local prompt-learning job."""
|
|
19
|
+
|
|
20
|
+
backend_url: str
|
|
21
|
+
api_key: str
|
|
22
|
+
config_path: Path | None = None
|
|
23
|
+
config_dict: dict[str, Any] | None = None
|
|
24
|
+
container_api_key: str | None = field(default=None, repr=False)
|
|
25
|
+
container_key: str | None = field(default=None, repr=False)
|
|
26
|
+
container_worker_token: str | None = field(default=None, repr=False)
|
|
27
|
+
allow_experimental: bool | None = None
|
|
28
|
+
overrides: dict[str, Any] | None = None
|
|
29
|
+
|
|
30
|
+
def __post_init__(self) -> None:
|
|
31
|
+
has_path = self.config_path is not None
|
|
32
|
+
has_dict = self.config_dict is not None
|
|
33
|
+
if has_path == has_dict:
|
|
34
|
+
raise ValueError("Provide exactly one of config_path or config_dict")
|
|
35
|
+
if self.config_path is not None and not self.config_path.exists():
|
|
36
|
+
raise FileNotFoundError(f"Config file not found: {self.config_path}")
|
|
37
|
+
if not self.backend_url:
|
|
38
|
+
raise ValueError("backend_url is required")
|
|
39
|
+
if not self.api_key:
|
|
40
|
+
raise ValueError("api_key is required")
|
|
41
|
+
if self.container_api_key is None and isinstance(self.container_key, str):
|
|
42
|
+
stripped = self.container_key.strip()
|
|
43
|
+
self.container_api_key = stripped or None
|
|
44
|
+
|
|
45
|
+
def materialize(self) -> dict[str, Any]:
|
|
46
|
+
payload = load_toml(self.config_path) if self.config_path is not None else dict(self.config_dict or {})
|
|
47
|
+
if self.overrides:
|
|
48
|
+
payload = _merge_overrides(payload, dict(self.overrides))
|
|
49
|
+
return payload
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _merge_overrides(payload: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]:
|
|
53
|
+
merged = dict(payload)
|
|
54
|
+
for dotted_key, value in overrides.items():
|
|
55
|
+
current = merged
|
|
56
|
+
parts = str(dotted_key).split(".")
|
|
57
|
+
for part in parts[:-1]:
|
|
58
|
+
existing = current.get(part)
|
|
59
|
+
if not isinstance(existing, dict):
|
|
60
|
+
existing = {}
|
|
61
|
+
current[part] = existing
|
|
62
|
+
current = existing
|
|
63
|
+
current[parts[-1]] = value
|
|
64
|
+
return merged
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PromptLearningJobPoller:
|
|
68
|
+
"""Small local poller."""
|
|
69
|
+
|
|
70
|
+
def poll_job(self, job_id: str) -> dict[str, Any]:
|
|
71
|
+
return RUNTIME.get_job(job_id).status_payload()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PromptLearningJob:
|
|
75
|
+
"""High-level local prompt-learning job."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
config: PromptLearningJobConfig,
|
|
80
|
+
job_id: str | None = None,
|
|
81
|
+
skip_health_check: bool = False,
|
|
82
|
+
) -> None:
|
|
83
|
+
self.config = config
|
|
84
|
+
self._job_id = job_id
|
|
85
|
+
self._skip_health_check = skip_health_check
|
|
86
|
+
self._offline_job: PolicyOptimizationOfflineJob | None = None
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_config(
|
|
90
|
+
cls,
|
|
91
|
+
config_path: str | Path,
|
|
92
|
+
backend_url: str | None = None,
|
|
93
|
+
api_key: str | None = None,
|
|
94
|
+
container_worker_token: str | None = None,
|
|
95
|
+
allow_experimental: bool | None = None,
|
|
96
|
+
overrides: dict[str, Any] | None = None,
|
|
97
|
+
) -> "PromptLearningJob":
|
|
98
|
+
return cls(
|
|
99
|
+
PromptLearningJobConfig(
|
|
100
|
+
config_path=Path(config_path),
|
|
101
|
+
backend_url=str(backend_url or "local://prompt-opt"),
|
|
102
|
+
api_key=str(api_key or "local"),
|
|
103
|
+
container_worker_token=container_worker_token,
|
|
104
|
+
allow_experimental=allow_experimental,
|
|
105
|
+
overrides=overrides,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def from_dict(
|
|
111
|
+
cls,
|
|
112
|
+
config_dict: dict[str, Any],
|
|
113
|
+
backend_url: str | None = None,
|
|
114
|
+
api_key: str | None = None,
|
|
115
|
+
container_worker_token: str | None = None,
|
|
116
|
+
allow_experimental: bool | None = None,
|
|
117
|
+
overrides: dict[str, Any] | None = None,
|
|
118
|
+
skip_health_check: bool = False,
|
|
119
|
+
) -> "PromptLearningJob":
|
|
120
|
+
return cls(
|
|
121
|
+
PromptLearningJobConfig(
|
|
122
|
+
config_dict=dict(config_dict),
|
|
123
|
+
backend_url=str(backend_url or "local://prompt-opt"),
|
|
124
|
+
api_key=str(api_key or "local"),
|
|
125
|
+
container_worker_token=container_worker_token,
|
|
126
|
+
allow_experimental=allow_experimental,
|
|
127
|
+
overrides=overrides,
|
|
128
|
+
),
|
|
129
|
+
skip_health_check=skip_health_check,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_job_id(
|
|
134
|
+
cls,
|
|
135
|
+
job_id: str,
|
|
136
|
+
backend_url: str | None = None,
|
|
137
|
+
api_key: str | None = None,
|
|
138
|
+
) -> "PromptLearningJob":
|
|
139
|
+
config = PromptLearningJobConfig(
|
|
140
|
+
config_dict={"prompt_learning": {"algorithm": "gepa"}},
|
|
141
|
+
backend_url=str(backend_url or "local://prompt-opt"),
|
|
142
|
+
api_key=str(api_key or "local"),
|
|
143
|
+
)
|
|
144
|
+
return cls(config=config, job_id=job_id)
|
|
145
|
+
|
|
146
|
+
def _kind_from_payload(self, payload: dict[str, Any]) -> str:
|
|
147
|
+
parsed = PromptLearningConfig.from_mapping(payload)
|
|
148
|
+
return "mipro_offline" if parsed.algorithm == "mipro" else "gepa_offline"
|
|
149
|
+
|
|
150
|
+
def submit(self) -> str:
|
|
151
|
+
if self._job_id is not None:
|
|
152
|
+
raise RuntimeError(f"Job already submitted: {self._job_id}")
|
|
153
|
+
payload = self.config.materialize()
|
|
154
|
+
system_name = _resolve_system_name(payload)
|
|
155
|
+
offline_job = PolicyOptimizationOfflineJob.create(
|
|
156
|
+
kind=self._kind_from_payload(payload),
|
|
157
|
+
technique="discrete_optimization",
|
|
158
|
+
system_name=system_name,
|
|
159
|
+
config=payload,
|
|
160
|
+
metadata={"skip_health_check": self._skip_health_check},
|
|
161
|
+
backend_url=self.config.backend_url,
|
|
162
|
+
api_key=self.config.api_key,
|
|
163
|
+
)
|
|
164
|
+
self._offline_job = offline_job
|
|
165
|
+
self._job_id = offline_job.job_id
|
|
166
|
+
return offline_job.job_id
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def job_id(self) -> str | None:
|
|
170
|
+
return self._job_id
|
|
171
|
+
|
|
172
|
+
def _ensure_offline_job(self) -> PolicyOptimizationOfflineJob:
|
|
173
|
+
if self._job_id is None:
|
|
174
|
+
raise RuntimeError("Job not yet submitted. Call submit() first.")
|
|
175
|
+
if self._offline_job is None:
|
|
176
|
+
self._offline_job = PolicyOptimizationOfflineJob.get(
|
|
177
|
+
self._job_id,
|
|
178
|
+
backend_url=self.config.backend_url,
|
|
179
|
+
api_key=self.config.api_key,
|
|
180
|
+
)
|
|
181
|
+
return self._offline_job
|
|
182
|
+
|
|
183
|
+
async def get_status_async(self) -> dict[str, Any]:
|
|
184
|
+
return await self._ensure_offline_job().status_async()
|
|
185
|
+
|
|
186
|
+
def get_status(self) -> dict[str, Any]:
|
|
187
|
+
return run_sync(self.get_status_async())
|
|
188
|
+
|
|
189
|
+
async def stream_until_complete_async(
|
|
190
|
+
self,
|
|
191
|
+
*,
|
|
192
|
+
timeout: float = 3600.0,
|
|
193
|
+
interval: float = 15.0,
|
|
194
|
+
handlers: list[Any] | None = None,
|
|
195
|
+
on_event: Any | None = None,
|
|
196
|
+
) -> PromptLearningResult:
|
|
197
|
+
payload = await self._ensure_offline_job().stream_until_complete_async(
|
|
198
|
+
timeout=timeout,
|
|
199
|
+
interval=interval,
|
|
200
|
+
handlers=handlers,
|
|
201
|
+
on_event=on_event,
|
|
202
|
+
)
|
|
203
|
+
return PromptLearningResult.from_response(self._job_id or "", payload)
|
|
204
|
+
|
|
205
|
+
def stream_until_complete(
|
|
206
|
+
self,
|
|
207
|
+
*,
|
|
208
|
+
timeout: float = 3600.0,
|
|
209
|
+
interval: float = 15.0,
|
|
210
|
+
handlers: list[Any] | None = None,
|
|
211
|
+
on_event: Any | None = None,
|
|
212
|
+
) -> PromptLearningResult:
|
|
213
|
+
return run_sync(
|
|
214
|
+
self.stream_until_complete_async(
|
|
215
|
+
timeout=timeout,
|
|
216
|
+
interval=interval,
|
|
217
|
+
handlers=handlers,
|
|
218
|
+
on_event=on_event,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
async def get_results_async(self) -> dict[str, Any]:
|
|
223
|
+
return dict(RUNTIME.get_job(self._job_id or "").result_payload)
|
|
224
|
+
|
|
225
|
+
def get_results(self) -> dict[str, Any]:
|
|
226
|
+
return run_sync(self.get_results_async())
|
|
227
|
+
|
|
228
|
+
async def list_candidates_async(
|
|
229
|
+
self,
|
|
230
|
+
*,
|
|
231
|
+
algorithm: str | None = None,
|
|
232
|
+
mode: str | None = None,
|
|
233
|
+
status: str | None = None,
|
|
234
|
+
limit: int = 100,
|
|
235
|
+
cursor: str | None = None,
|
|
236
|
+
sort: str | None = None,
|
|
237
|
+
include: str | None = None,
|
|
238
|
+
) -> dict[str, Any]:
|
|
239
|
+
del algorithm, mode, cursor, sort, include
|
|
240
|
+
return RUNTIME.list_candidates(self._job_id or "", limit=limit, status=status)
|
|
241
|
+
|
|
242
|
+
async def list_candidates_typed_async(
|
|
243
|
+
self,
|
|
244
|
+
*,
|
|
245
|
+
algorithm: str | None = None,
|
|
246
|
+
mode: str | None = None,
|
|
247
|
+
status: str | None = None,
|
|
248
|
+
limit: int = 100,
|
|
249
|
+
cursor: str | None = None,
|
|
250
|
+
sort: str | None = None,
|
|
251
|
+
include: str | None = None,
|
|
252
|
+
) -> PolicyCandidatePage:
|
|
253
|
+
del algorithm, mode, cursor, sort, include
|
|
254
|
+
return list_candidates_typed(self._job_id or "", limit=limit, status=status)
|
|
255
|
+
|
|
256
|
+
def list_candidates(self, **kwargs: Any) -> dict[str, Any]:
|
|
257
|
+
return run_sync(self.list_candidates_async(**kwargs))
|
|
258
|
+
|
|
259
|
+
def list_candidates_typed(self, **kwargs: Any) -> PolicyCandidatePage:
|
|
260
|
+
return run_sync(self.list_candidates_typed_async(**kwargs))
|
|
261
|
+
|
|
262
|
+
async def get_candidate_async(self, candidate_id: str) -> dict[str, Any]:
|
|
263
|
+
return RUNTIME.get_candidate(self._job_id or "", candidate_id)
|
|
264
|
+
|
|
265
|
+
async def get_candidate_typed_async(self, candidate_id: str) -> PolicyCandidate:
|
|
266
|
+
return get_candidate_typed(self._job_id or "", candidate_id)
|
|
267
|
+
|
|
268
|
+
def get_candidate(self, candidate_id: str) -> dict[str, Any]:
|
|
269
|
+
return run_sync(self.get_candidate_async(candidate_id))
|
|
270
|
+
|
|
271
|
+
def get_candidate_typed(self, candidate_id: str) -> PolicyCandidate:
|
|
272
|
+
return run_sync(self.get_candidate_typed_async(candidate_id))
|
|
273
|
+
|
|
274
|
+
async def submit_candidates_async(self, **kwargs: Any) -> dict[str, Any]:
|
|
275
|
+
return await self._ensure_offline_job().submit_candidates_async(**kwargs)
|
|
276
|
+
|
|
277
|
+
def submit_candidates(self, **kwargs: Any) -> dict[str, Any]:
|
|
278
|
+
return run_sync(self.submit_candidates_async(**kwargs))
|
|
279
|
+
|
|
280
|
+
async def get_state_baseline_info_async(self) -> dict[str, Any]:
|
|
281
|
+
return await self._ensure_offline_job().get_state_baseline_info_async()
|
|
282
|
+
|
|
283
|
+
def get_state_baseline_info(self) -> dict[str, Any]:
|
|
284
|
+
return run_sync(self.get_state_baseline_info_async())
|
|
285
|
+
|
|
286
|
+
async def get_state_envelope_async(self) -> dict[str, Any]:
|
|
287
|
+
return await self._ensure_offline_job().get_state_envelope_async()
|
|
288
|
+
|
|
289
|
+
def get_state_envelope(self) -> dict[str, Any]:
|
|
290
|
+
return run_sync(self.get_state_envelope_async())
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _resolve_system_name(payload: dict[str, Any]) -> str:
|
|
294
|
+
top_level = payload.get("prompt_learning", payload)
|
|
295
|
+
if isinstance(top_level, dict):
|
|
296
|
+
system_name = top_level.get("system_name") or top_level.get("env_name")
|
|
297
|
+
if isinstance(system_name, str) and system_name.strip():
|
|
298
|
+
return system_name.strip()
|
|
299
|
+
algorithm = top_level.get("algorithm")
|
|
300
|
+
if isinstance(algorithm, str) and algorithm.strip():
|
|
301
|
+
return f"{algorithm.strip()}-local-job"
|
|
302
|
+
return "prompt-learning-local-job"
|