orizu 0.3.1 → 0.3.3
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.
- package/README.md +11 -0
- package/dist/index.js +171 -11
- package/package.json +12 -2
- package/scripts/vendor-gepa-python.mjs +54 -0
- package/src/index.ts +203 -4
- package/vendor/orizu-gepa-python/manifest.json +9 -0
- package/vendor/orizu-gepa-python/pyproject.toml +16 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/__init__.py +19 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +206 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +360 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +123 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +1276 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +177 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.parse
|
|
8
|
+
import urllib.request
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .optimizer import DatasetRow, PromptContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class OrizuClient:
|
|
17
|
+
api_url: str
|
|
18
|
+
token: str
|
|
19
|
+
project: str | None = None
|
|
20
|
+
sequence: int = 0
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_env(cls) -> "OrizuClient":
|
|
24
|
+
api_url = (os.environ.get("ORIZU_API_URL") or "").rstrip("/")
|
|
25
|
+
token = os.environ.get("ORIZU_TOKEN") or ""
|
|
26
|
+
if not api_url or not token:
|
|
27
|
+
raise RuntimeError("ORIZU_API_URL and ORIZU_TOKEN are required. Run: eval \"$(orizu env --project <team>/<project>)\"")
|
|
28
|
+
return cls(api_url=api_url, token=token, project=os.environ.get("ORIZU_PROJECT"))
|
|
29
|
+
|
|
30
|
+
def _request(
|
|
31
|
+
self,
|
|
32
|
+
method: str,
|
|
33
|
+
path: str,
|
|
34
|
+
body: dict[str, Any] | None = None,
|
|
35
|
+
) -> Any:
|
|
36
|
+
data = None if body is None else json.dumps(body).encode("utf-8")
|
|
37
|
+
request = urllib.request.Request(
|
|
38
|
+
f"{self.api_url}{path}",
|
|
39
|
+
data=data,
|
|
40
|
+
method=method,
|
|
41
|
+
headers={
|
|
42
|
+
"Authorization": f"Bearer {self.token}",
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
with urllib.request.urlopen(request, timeout=120) as response:
|
|
48
|
+
raw = response.read().decode("utf-8")
|
|
49
|
+
return json.loads(raw) if raw else {}
|
|
50
|
+
except urllib.error.HTTPError as error:
|
|
51
|
+
detail = error.read().decode("utf-8", errors="replace")
|
|
52
|
+
raise RuntimeError(f"{method} {path} failed: {error.code} {detail}") from error
|
|
53
|
+
|
|
54
|
+
def _request_bytes(self, path: str) -> bytes:
|
|
55
|
+
request = urllib.request.Request(
|
|
56
|
+
f"{self.api_url}{path}",
|
|
57
|
+
method="GET",
|
|
58
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
with urllib.request.urlopen(request, timeout=120) as response:
|
|
62
|
+
return response.read()
|
|
63
|
+
except urllib.error.HTTPError as error:
|
|
64
|
+
detail = error.read().decode("utf-8", errors="replace")
|
|
65
|
+
raise RuntimeError(f"GET {path} failed: {error.code} {detail}") from error
|
|
66
|
+
|
|
67
|
+
def start_run(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
project: str,
|
|
71
|
+
optimizer_version_id: str,
|
|
72
|
+
prompt_version_id: str,
|
|
73
|
+
scorer_version_id: str,
|
|
74
|
+
dataset_version_id: str,
|
|
75
|
+
split_set_id: str,
|
|
76
|
+
train_split: str,
|
|
77
|
+
validation_split: str,
|
|
78
|
+
metadata: dict[str, Any] | None = None,
|
|
79
|
+
) -> str:
|
|
80
|
+
query = urllib.parse.urlencode({"project": project})
|
|
81
|
+
data = self._request("POST", f"/api/cli/optimization-runs?{query}", {
|
|
82
|
+
"optimizerVersionId": optimizer_version_id,
|
|
83
|
+
"promptVersionIds": [prompt_version_id],
|
|
84
|
+
"scorers": [
|
|
85
|
+
{
|
|
86
|
+
"scorerVersionId": scorer_version_id,
|
|
87
|
+
"role": "selection",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"scorerVersionId": scorer_version_id,
|
|
91
|
+
"role": "reflection",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
"datasetVersionId": dataset_version_id,
|
|
95
|
+
"splitSetId": split_set_id,
|
|
96
|
+
"trainSplitName": train_split,
|
|
97
|
+
"validationSplitName": validation_split,
|
|
98
|
+
"metadata": metadata or {},
|
|
99
|
+
})
|
|
100
|
+
return data["optimization_run_id"]
|
|
101
|
+
|
|
102
|
+
def fetch_exec_context(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
prompt_version_id: str,
|
|
106
|
+
runner_version_id: str,
|
|
107
|
+
dataset_version_id: str,
|
|
108
|
+
split_set_id: str,
|
|
109
|
+
split: str,
|
|
110
|
+
) -> tuple[PromptContext, list[DatasetRow]]:
|
|
111
|
+
query = urllib.parse.urlencode({
|
|
112
|
+
"promptVersion": prompt_version_id,
|
|
113
|
+
"runnerVersion": runner_version_id,
|
|
114
|
+
"datasetVersion": dataset_version_id,
|
|
115
|
+
"splitSet": split_set_id,
|
|
116
|
+
"split": split,
|
|
117
|
+
})
|
|
118
|
+
data = self._request("GET", f"/api/cli/runners/exec-context?{query}")
|
|
119
|
+
prompt = data["prompt"]
|
|
120
|
+
rows = [
|
|
121
|
+
DatasetRow(id=item["id"], row=item["row"])
|
|
122
|
+
for item in data.get("rows", [])
|
|
123
|
+
]
|
|
124
|
+
return PromptContext(
|
|
125
|
+
body=prompt.get("body"),
|
|
126
|
+
body_kind=prompt.get("bodyKind") or "text",
|
|
127
|
+
provider_settings=prompt.get("providerSettings") or {},
|
|
128
|
+
prompt_version_id=prompt["promptVersionId"],
|
|
129
|
+
runner_version_id=prompt["runnerVersionId"],
|
|
130
|
+
prompt_id=prompt.get("promptId"),
|
|
131
|
+
), rows
|
|
132
|
+
|
|
133
|
+
def fetch_scorer_exec_context(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
scorer_version_id: str,
|
|
137
|
+
runner_version_id: str | None,
|
|
138
|
+
dataset_version_id: str,
|
|
139
|
+
split_set_id: str,
|
|
140
|
+
split: str,
|
|
141
|
+
) -> tuple[PromptContext, list[DatasetRow]]:
|
|
142
|
+
params = {
|
|
143
|
+
"scorerVersion": scorer_version_id,
|
|
144
|
+
"datasetVersion": dataset_version_id,
|
|
145
|
+
"splitSet": split_set_id,
|
|
146
|
+
"split": split,
|
|
147
|
+
}
|
|
148
|
+
if runner_version_id:
|
|
149
|
+
params["runnerVersion"] = runner_version_id
|
|
150
|
+
query = urllib.parse.urlencode(params)
|
|
151
|
+
data = self._request("GET", f"/api/cli/runners/exec-context?{query}")
|
|
152
|
+
prompt = data["prompt"]
|
|
153
|
+
scorer = data.get("scorer") or {}
|
|
154
|
+
higher_is_better = scorer.get("higherIsBetter")
|
|
155
|
+
if not isinstance(higher_is_better, bool):
|
|
156
|
+
higher_is_better = scorer.get("higher_is_better")
|
|
157
|
+
if not isinstance(higher_is_better, bool):
|
|
158
|
+
higher_is_better = True
|
|
159
|
+
rows = [
|
|
160
|
+
DatasetRow(id=item["id"], row=item["row"])
|
|
161
|
+
for item in data.get("rows", [])
|
|
162
|
+
]
|
|
163
|
+
return PromptContext(
|
|
164
|
+
body=prompt.get("body"),
|
|
165
|
+
body_kind=prompt.get("bodyKind") or "text",
|
|
166
|
+
provider_settings=prompt.get("providerSettings") or {},
|
|
167
|
+
prompt_version_id=prompt["promptVersionId"],
|
|
168
|
+
runner_version_id=prompt["runnerVersionId"],
|
|
169
|
+
prompt_id=prompt.get("promptId"),
|
|
170
|
+
scorer_version_id=scorer_version_id,
|
|
171
|
+
metric_key=scorer.get("metricKey") or scorer.get("metric_key"),
|
|
172
|
+
higher_is_better=higher_is_better,
|
|
173
|
+
), rows
|
|
174
|
+
|
|
175
|
+
def log_event(
|
|
176
|
+
self,
|
|
177
|
+
run_id: str,
|
|
178
|
+
*,
|
|
179
|
+
sequence: int,
|
|
180
|
+
event_type: str,
|
|
181
|
+
payload: dict[str, Any] | None = None,
|
|
182
|
+
event_layer: str = "core",
|
|
183
|
+
optimizer_family: str = "gepa",
|
|
184
|
+
iteration: int | None = None,
|
|
185
|
+
candidate_id: str | None = None,
|
|
186
|
+
parent_candidate_id: str | None = None,
|
|
187
|
+
child_candidate_id: str | None = None,
|
|
188
|
+
) -> None:
|
|
189
|
+
event_id = f"orizu-gepa-{run_id}-{sequence}"
|
|
190
|
+
self._request("POST", f"/api/cli/optimization-runs/{urllib.parse.quote(run_id)}/events", {
|
|
191
|
+
"eventId": event_id,
|
|
192
|
+
"sequence": sequence,
|
|
193
|
+
"eventType": event_type,
|
|
194
|
+
"eventLayer": event_layer,
|
|
195
|
+
"optimizerFamily": optimizer_family,
|
|
196
|
+
"iteration": iteration,
|
|
197
|
+
"candidateId": candidate_id,
|
|
198
|
+
"parentCandidateId": parent_candidate_id,
|
|
199
|
+
"childCandidateId": child_candidate_id,
|
|
200
|
+
"payload": payload or {},
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
def promote_candidate(
|
|
204
|
+
self,
|
|
205
|
+
run_id: str,
|
|
206
|
+
*,
|
|
207
|
+
candidate_id: str,
|
|
208
|
+
prompt_id: str,
|
|
209
|
+
parent_prompt_version_id: str,
|
|
210
|
+
body: str,
|
|
211
|
+
body_kind: str,
|
|
212
|
+
provider_settings: dict[str, Any],
|
|
213
|
+
runner_version_id: str,
|
|
214
|
+
label: str | None,
|
|
215
|
+
) -> str:
|
|
216
|
+
data = self._request("POST", f"/api/cli/optimization-runs/{urllib.parse.quote(run_id)}/promote", {
|
|
217
|
+
"candidateId": candidate_id,
|
|
218
|
+
"promptId": prompt_id,
|
|
219
|
+
"parentPromptVersionId": parent_prompt_version_id,
|
|
220
|
+
"body": body,
|
|
221
|
+
"bodyKind": body_kind,
|
|
222
|
+
"providerSettings": provider_settings,
|
|
223
|
+
"runnerVersionId": runner_version_id,
|
|
224
|
+
"label": label,
|
|
225
|
+
})
|
|
226
|
+
return data["promptVersionId"]
|
|
227
|
+
|
|
228
|
+
def update_run(
|
|
229
|
+
self,
|
|
230
|
+
run_id: str,
|
|
231
|
+
*,
|
|
232
|
+
status: str,
|
|
233
|
+
best_score: float | None = None,
|
|
234
|
+
best_candidate_id: str | None = None,
|
|
235
|
+
result_prompt_version_id: str | None = None,
|
|
236
|
+
metadata: dict[str, Any] | None = None,
|
|
237
|
+
) -> None:
|
|
238
|
+
body: dict[str, Any] = {"status": status}
|
|
239
|
+
if best_score is not None:
|
|
240
|
+
body["bestScore"] = best_score
|
|
241
|
+
if best_candidate_id:
|
|
242
|
+
body["bestCandidateId"] = best_candidate_id
|
|
243
|
+
if result_prompt_version_id:
|
|
244
|
+
body["resultPromptVersionId"] = result_prompt_version_id
|
|
245
|
+
if metadata:
|
|
246
|
+
body["metadata"] = metadata
|
|
247
|
+
self._request("PATCH", f"/api/cli/optimization-runs/{urllib.parse.quote(run_id)}", body)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class OrizuEventSink:
|
|
251
|
+
def __init__(
|
|
252
|
+
self,
|
|
253
|
+
client: OrizuClient,
|
|
254
|
+
run_id: str,
|
|
255
|
+
*,
|
|
256
|
+
fail_on_log_error: bool = True,
|
|
257
|
+
max_log_retries: int = 2,
|
|
258
|
+
local_logger: Any | None = None,
|
|
259
|
+
):
|
|
260
|
+
self.client = client
|
|
261
|
+
self.run_id = run_id
|
|
262
|
+
self.sequence = 0
|
|
263
|
+
self.fail_on_log_error = fail_on_log_error
|
|
264
|
+
self.max_log_retries = max(0, max_log_retries)
|
|
265
|
+
self.local_logger = local_logger
|
|
266
|
+
|
|
267
|
+
def log_event(
|
|
268
|
+
self,
|
|
269
|
+
event_type: str,
|
|
270
|
+
payload: dict[str, Any] | None = None,
|
|
271
|
+
*,
|
|
272
|
+
event_layer: str = "core",
|
|
273
|
+
optimizer_family: str = "gepa",
|
|
274
|
+
iteration: int | None = None,
|
|
275
|
+
candidate_id: str | None = None,
|
|
276
|
+
parent_candidate_id: str | None = None,
|
|
277
|
+
child_candidate_id: str | None = None,
|
|
278
|
+
) -> None:
|
|
279
|
+
self.sequence += 1
|
|
280
|
+
local_event = {
|
|
281
|
+
"run_id": self.run_id,
|
|
282
|
+
"sequence": self.sequence,
|
|
283
|
+
"event_type": event_type,
|
|
284
|
+
"event_layer": event_layer,
|
|
285
|
+
"optimizer_family": optimizer_family,
|
|
286
|
+
"iteration": iteration,
|
|
287
|
+
"candidate_id": candidate_id,
|
|
288
|
+
"parent_candidate_id": parent_candidate_id,
|
|
289
|
+
"child_candidate_id": child_candidate_id,
|
|
290
|
+
"payload": payload or {},
|
|
291
|
+
}
|
|
292
|
+
if self.local_logger is not None:
|
|
293
|
+
self.local_logger.append_event(local_event)
|
|
294
|
+
last_error: Exception | None = None
|
|
295
|
+
for attempt in range(self.max_log_retries + 1):
|
|
296
|
+
try:
|
|
297
|
+
self.client.log_event(
|
|
298
|
+
self.run_id,
|
|
299
|
+
sequence=self.sequence,
|
|
300
|
+
event_type=event_type,
|
|
301
|
+
payload=payload,
|
|
302
|
+
event_layer=event_layer,
|
|
303
|
+
optimizer_family=optimizer_family,
|
|
304
|
+
iteration=iteration,
|
|
305
|
+
candidate_id=candidate_id,
|
|
306
|
+
parent_candidate_id=parent_candidate_id,
|
|
307
|
+
child_candidate_id=child_candidate_id,
|
|
308
|
+
)
|
|
309
|
+
print(f"[orizu-gepa] {self.sequence:03d} {event_type}", flush=True)
|
|
310
|
+
return
|
|
311
|
+
except Exception as exc:
|
|
312
|
+
last_error = exc
|
|
313
|
+
if attempt < self.max_log_retries:
|
|
314
|
+
time.sleep(0.25 * (2 ** attempt))
|
|
315
|
+
|
|
316
|
+
if self.fail_on_log_error and last_error is not None:
|
|
317
|
+
raise last_error
|
|
318
|
+
print(
|
|
319
|
+
f"[orizu-gepa] {self.sequence:03d} {event_type} log failed: {last_error}",
|
|
320
|
+
flush=True,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def promote_candidate(self, **kwargs: Any) -> str:
|
|
324
|
+
prompt_version_id = self.client.promote_candidate(self.run_id, **kwargs)
|
|
325
|
+
# Promotion writes a system event at max(sequence)+1 in Postgres.
|
|
326
|
+
self.sequence += 1
|
|
327
|
+
if self.local_logger is not None:
|
|
328
|
+
self.local_logger.append_event({
|
|
329
|
+
"run_id": self.run_id,
|
|
330
|
+
"sequence": self.sequence,
|
|
331
|
+
"event_type": "candidate_promoted",
|
|
332
|
+
"event_layer": "system",
|
|
333
|
+
"optimizer_family": "gepa",
|
|
334
|
+
"iteration": None,
|
|
335
|
+
"candidate_id": kwargs.get("candidate_id"),
|
|
336
|
+
"parent_candidate_id": None,
|
|
337
|
+
"child_candidate_id": None,
|
|
338
|
+
"payload": {
|
|
339
|
+
**kwargs,
|
|
340
|
+
"prompt_version_id": prompt_version_id,
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
return prompt_version_id
|
|
344
|
+
|
|
345
|
+
def finish_run(self, **kwargs: Any) -> None:
|
|
346
|
+
if self.local_logger is not None:
|
|
347
|
+
self.sequence += 1
|
|
348
|
+
self.local_logger.append_event({
|
|
349
|
+
"run_id": self.run_id,
|
|
350
|
+
"sequence": self.sequence,
|
|
351
|
+
"event_type": "run_status_updated",
|
|
352
|
+
"event_layer": "system",
|
|
353
|
+
"optimizer_family": "gepa",
|
|
354
|
+
"iteration": None,
|
|
355
|
+
"candidate_id": kwargs.get("best_candidate_id"),
|
|
356
|
+
"parent_candidate_id": None,
|
|
357
|
+
"child_candidate_id": None,
|
|
358
|
+
"payload": kwargs,
|
|
359
|
+
})
|
|
360
|
+
self.client.update_run(self.run_id, **kwargs)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .optimizer import DatasetRow, PromptContext, RowEvaluation, TextGepaResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _json_default(value: Any) -> Any:
|
|
12
|
+
if dataclasses.is_dataclass(value):
|
|
13
|
+
return dataclasses.asdict(value)
|
|
14
|
+
return str(value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
18
|
+
path.write_text(
|
|
19
|
+
json.dumps(payload, indent=2, ensure_ascii=False, default=_json_default) + "\n",
|
|
20
|
+
encoding="utf-8",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _append_jsonl(path: Path, payload: dict[str, Any]) -> None:
|
|
25
|
+
with path.open("a", encoding="utf-8") as handle:
|
|
26
|
+
handle.write(json.dumps(payload, ensure_ascii=False, default=_json_default) + "\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LocalOptimizationLogger:
|
|
30
|
+
def __init__(self, directory: Path):
|
|
31
|
+
self.directory = directory
|
|
32
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def create(cls, root: str | Path, run_id: str) -> "LocalOptimizationLogger":
|
|
36
|
+
return cls(Path(root) / run_id)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def path(self) -> str:
|
|
40
|
+
return str(self.directory)
|
|
41
|
+
|
|
42
|
+
def write_context(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
project: str,
|
|
46
|
+
run_id: str,
|
|
47
|
+
args: dict[str, Any],
|
|
48
|
+
prompt_context: PromptContext,
|
|
49
|
+
scorer_context: PromptContext,
|
|
50
|
+
trainset: list[DatasetRow],
|
|
51
|
+
valset: list[DatasetRow],
|
|
52
|
+
metadata: dict[str, Any],
|
|
53
|
+
) -> None:
|
|
54
|
+
_write_json(self.directory / "run.json", {
|
|
55
|
+
"schema_version": "orizu.optimization-local-log.v1",
|
|
56
|
+
"optimization_run_id": run_id,
|
|
57
|
+
"project": project,
|
|
58
|
+
"args": args,
|
|
59
|
+
"metadata": metadata,
|
|
60
|
+
})
|
|
61
|
+
_write_json(self.directory / "prompt_context.json", dataclasses.asdict(prompt_context))
|
|
62
|
+
_write_json(self.directory / "scorer_context.json", dataclasses.asdict(scorer_context))
|
|
63
|
+
_write_json(self.directory / "trainset.json", {
|
|
64
|
+
"rows": [dataclasses.asdict(row) for row in trainset],
|
|
65
|
+
})
|
|
66
|
+
_write_json(self.directory / "valset.json", {
|
|
67
|
+
"rows": [dataclasses.asdict(row) for row in valset],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
def append_event(self, event: dict[str, Any]) -> None:
|
|
71
|
+
_append_jsonl(self.directory / "events.jsonl", event)
|
|
72
|
+
|
|
73
|
+
def append_evaluations(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
stage: str,
|
|
77
|
+
split: str,
|
|
78
|
+
iteration: int | None,
|
|
79
|
+
candidate_id: str,
|
|
80
|
+
parent_candidate_id: str | None = None,
|
|
81
|
+
results: list[RowEvaluation],
|
|
82
|
+
) -> None:
|
|
83
|
+
for result in results:
|
|
84
|
+
_append_jsonl(self.directory / "evaluations.jsonl", {
|
|
85
|
+
"stage": stage,
|
|
86
|
+
"split": split,
|
|
87
|
+
"iteration": iteration,
|
|
88
|
+
"candidate_id": candidate_id,
|
|
89
|
+
"parent_candidate_id": parent_candidate_id,
|
|
90
|
+
**result.to_payload(),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
def append_reflection(
|
|
94
|
+
self,
|
|
95
|
+
*,
|
|
96
|
+
iteration: int,
|
|
97
|
+
parent_candidate_id: str,
|
|
98
|
+
child_candidate_id: str,
|
|
99
|
+
row_ids: list[str],
|
|
100
|
+
prompt: str,
|
|
101
|
+
response: str,
|
|
102
|
+
candidate_text: str,
|
|
103
|
+
) -> None:
|
|
104
|
+
_append_jsonl(self.directory / "reflections.jsonl", {
|
|
105
|
+
"iteration": iteration,
|
|
106
|
+
"parent_candidate_id": parent_candidate_id,
|
|
107
|
+
"child_candidate_id": child_candidate_id,
|
|
108
|
+
"row_ids": row_ids,
|
|
109
|
+
"prompt": prompt,
|
|
110
|
+
"response": response,
|
|
111
|
+
"candidate_text": candidate_text,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
def write_result(self, result: TextGepaResult) -> None:
|
|
115
|
+
_write_json(self.directory / "result.json", {
|
|
116
|
+
"optimization_run_id": result.run_id,
|
|
117
|
+
"best_candidate_id": result.best_candidate_id,
|
|
118
|
+
"best_candidate_text": result.best_candidate_text,
|
|
119
|
+
"best_score": result.best_score,
|
|
120
|
+
"seed_score": result.seed_score,
|
|
121
|
+
"promoted_prompt_version_id": result.promoted_prompt_version_id,
|
|
122
|
+
"budget": result.budget.to_payload(),
|
|
123
|
+
})
|