Skip to content

Commit 46b0774

Browse files
committed
Polish SDK prompt audit handling
1 parent d6c22e9 commit 46b0774

5 files changed

Lines changed: 210 additions & 16 deletions

File tree

examples/optimization/eval_optimize_loop/README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ python examples/optimization/eval_optimize_loop/run_pipeline.py \
7878
--target-prompt system_prompt=prompts/system.md \
7979
--target-prompt router_prompt=prompts/router.md \
8080
--sdk-call-agent your_package.your_module:call_agent \
81+
--run-id local-sdk-smoke \
8182
--output-dir /tmp/eval-optimize-loop-sdk
8283
```
8384

@@ -92,6 +93,11 @@ follow the SDK `OptimizeConfigFile` schema. Put wrapper-only gate settings in
9293
"max_total_cost": 1.0}}`). If `--gate-config` is omitted, the wrapper uses the
9394
same default aggregate gate values as the fake example.
9495

96+
`--target-prompt name=path` may be repeated. If omitted, SDK mode keeps the old
97+
single-field behavior and registers `system_prompt=--prompt`. A run can optimize
98+
only `router_prompt`, only `skill_prompt`, or any set of named fields as long as
99+
`OptimizeResult.best_prompts` returns every registered field.
100+
95101
Fake mode is the complete per-case closed loop. SDK mode is the real
96102
`AgentOptimizer` / `TargetPrompt` path with an aggregate wrapper gate. When the
97103
SDK optimizer returns a best prompt, this wrapper maps `OptimizeResult`
@@ -105,6 +111,12 @@ delta, and per-case score-drop checks are not claimed in SDK mode unless the SDK
105111
exposes full per-case validation scores; they are listed in
106112
`not_applied_checks`.
107113

114+
Fake mode uses a deterministic run id (`eval_optimize_loop_seed_<seed>`) so the
115+
example outputs are byte-stable. SDK mode is append-only by default: the wrapper
116+
derives `run.run_id` from the SDK result `started_at` when available, otherwise
117+
from the current UTC timestamp. Pass `--run-id` only when a fixed audit path is
118+
useful for tests or local smoke runs.
119+
108120
## Source Prompt Writes
109121

110122
The default is **no source write-back**. The baseline prompt file is not modified
@@ -158,8 +170,11 @@ diffs, and the reproducibility command.
158170
`report.py` also writes audit artifacts under `output_dir/runs/<run_id>/`:
159171

160172
- `config.snapshot.json`;
161-
- `input_hashes.json`;
162-
- `candidate_prompts/<candidate_id>/system_prompt.txt`;
173+
- `input_hashes.json` with train, validation, optimizer, prompt,
174+
`target_prompts.<field>`, and optional `gate_config` hashes;
175+
- fake mode: `candidate_prompts/<candidate_id>/system_prompt.txt`;
176+
- SDK mode: `candidate_prompts/<candidate_id>/<field_name>.txt` for every
177+
returned `best_prompts` field;
163178
- `case_results/<candidate_id>_<split>.json`;
164179
- `prompt_diffs/<candidate_id>.diff`.
165180

examples/optimization/eval_optimize_loop/eval_loop/backends.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,23 @@ async def optimize_async(
119119
update_source=self.update_source,
120120
verbose=0,
121121
)
122-
best_prompt = getattr(result, "best_prompts", {}).get("system_prompt")
123-
if not best_prompt:
124-
raise ValueError("sdk mode completed but OptimizeResult.best_prompts['system_prompt'] was missing")
122+
best_prompts = dict(getattr(result, "best_prompts", {}) or {})
123+
if not best_prompts:
124+
raise ValueError("sdk mode completed but OptimizeResult.best_prompts was empty")
125+
missing_fields = [
126+
name
127+
for name in target_prompt_paths
128+
if not best_prompts.get(name)
129+
]
130+
if missing_fields:
131+
missing = ", ".join(sorted(missing_fields))
132+
raise ValueError(
133+
"sdk mode completed but OptimizeResult.best_prompts is missing registered target fields: "
134+
f"{missing}"
135+
)
125136
self.last_result = result
126137
self.last_result_summary = _summarize_sdk_result(result)
127138
self.last_artifact_dir = str(output_dir)
128-
best_prompts = dict(getattr(result, "best_prompts", {}) or {})
129139
baseline_prompts = _read_prompt_bundle(target_prompt_paths)
130140
return [
131141
CandidatePrompt(
@@ -176,6 +186,7 @@ def _summarize_sdk_result(result: Any) -> dict[str, Any]:
176186
"total_llm_cost": _safe_jsonable(getattr(result, "total_llm_cost", 0.0)),
177187
"total_token_usage": _safe_jsonable(getattr(result, "total_token_usage", {})),
178188
"duration_seconds": _safe_jsonable(getattr(result, "duration_seconds", 0.0)),
189+
"started_at": _safe_jsonable(getattr(result, "started_at", None)),
179190
"total_rounds": _safe_jsonable(getattr(result, "total_rounds", 0)),
180191
"baseline_prompts": _safe_jsonable(getattr(result, "baseline_prompts", {})),
181192
"best_prompts": _safe_jsonable(getattr(result, "best_prompts", {})),

examples/optimization/eval_optimize_loop/eval_loop/report.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,12 @@ def write_audit_artifacts(report: OptimizationReport, output_path: Path) -> None
277277
candidate: CandidatePrompt = record["candidate"]
278278
candidate_dir = prompt_dir / candidate.candidate_id
279279
candidate_dir.mkdir(exist_ok=True)
280-
(candidate_dir / "system_prompt.txt").write_text(candidate.prompt, encoding="utf-8")
280+
best_prompts = report.audit.get("sdk_result_summary", {}).get("best_prompts", {})
281+
if report.run.get("mode") == "sdk" and isinstance(best_prompts, dict) and best_prompts:
282+
for field_name, prompt_text in best_prompts.items():
283+
(candidate_dir / f"{field_name}.txt").write_text(str(prompt_text), encoding="utf-8")
284+
else:
285+
(candidate_dir / "system_prompt.txt").write_text(candidate.prompt, encoding="utf-8")
281286
(diffs_dir / f"{candidate.candidate_id}.diff").write_text(candidate.prompt_diff, encoding="utf-8")
282287
for split_name in ("train_result", "validation_result"):
283288
split_result = record[split_name]

examples/optimization/eval_optimize_loop/run_pipeline.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import json
88
import sys
99
import tempfile
10+
from datetime import datetime
11+
from datetime import timezone
1012
from pathlib import Path
1113
from typing import Any
1214

@@ -55,6 +57,7 @@ def run_pipeline(
5557
update_source: bool = False,
5658
gate_config_path: str | Path | None = None,
5759
target_prompts: list[str] | None = None,
60+
run_id: str | None = None,
5861
) -> OptimizationReport:
5962
"""Run baseline eval, fake optimization, validation gate, and reports."""
6063

@@ -101,6 +104,8 @@ def run_pipeline(
101104
gate_config=wrapper_gate_config,
102105
gate_config_path=gate_config_path,
103106
target_prompt_paths=target_prompt_paths,
107+
sdk_call_agent=sdk_call_agent,
108+
run_id=run_id,
104109
)
105110
write_reports(report, output_dir)
106111
return report
@@ -334,6 +339,8 @@ def _build_sdk_report(
334339
gate_config: dict[str, float],
335340
gate_config_path: str | Path | None,
336341
target_prompt_paths: dict[str, str | Path],
342+
sdk_call_agent: str | None,
343+
run_id: str | None,
337344
) -> OptimizationReport:
338345
input_hashes = _input_hashes(
339346
train_path=train_path,
@@ -351,6 +358,14 @@ def _build_sdk_report(
351358
)
352359
total_llm_cost = _summary_float(sdk_summary, "total_llm_cost", 0.0)
353360
duration_seconds = _summary_float(sdk_summary, "duration_seconds", 0.0)
361+
effective_run_id = run_id or _default_sdk_run_id(sdk_summary)
362+
target_prompt_hashes = {
363+
name: sha256_file(path)
364+
for name, path in target_prompt_paths.items()
365+
}
366+
input_hashes["target_prompts"] = target_prompt_hashes
367+
if gate_config_path:
368+
input_hashes["gate_config"] = sha256_file(gate_config_path)
354369
availability = {
355370
"aggregate_validation_result": True,
356371
"full_train_eval_result": False,
@@ -401,10 +416,6 @@ def _build_sdk_report(
401416
for candidate in candidates
402417
}
403418
field_prompt_hashes = _candidate_prompt_hashes_by_field(candidates, sdk_summary)
404-
target_prompt_hashes = {
405-
name: sha256_file(path)
406-
for name, path in target_prompt_paths.items()
407-
}
408419
audit = {
409420
"seed": None,
410421
"duration_seconds": duration_seconds,
@@ -450,10 +461,12 @@ def _build_sdk_report(
450461
update_source=update_source,
451462
gate_config_path=gate_config_path,
452463
target_prompt_paths=target_prompt_paths,
464+
sdk_call_agent=sdk_call_agent,
465+
run_id=effective_run_id,
453466
),
454467
}
455468
run = {
456-
"run_id": "eval_optimize_loop_sdk",
469+
"run_id": effective_run_id,
457470
"mode": "sdk",
458471
"fake_model": False,
459472
"fake_judge": False,
@@ -507,18 +520,21 @@ def _sdk_reproducibility_command(
507520
update_source: bool,
508521
gate_config_path: str | Path | None,
509522
target_prompt_paths: dict[str, str | Path],
523+
sdk_call_agent: str | None,
524+
run_id: str,
510525
) -> str:
511526
command = (
512527
"python examples/optimization/eval_optimize_loop/run_pipeline.py "
513528
f"--mode sdk --train {train_path} --val {val_path} "
514529
f"--optimizer-config {optimizer_config_path} --prompt {prompt_path} "
515-
f"--output-dir {output_dir} --sdk-call-agent module:function"
530+
f"--output-dir {output_dir} --sdk-call-agent {sdk_call_agent or ''}"
516531
)
517532
for name, path in target_prompt_paths.items():
518533
if not (name == "system_prompt" and Path(path) == Path(prompt_path) and len(target_prompt_paths) == 1):
519534
command += f" --target-prompt {name}={path}"
520535
if gate_config_path:
521536
command += f" --gate-config {gate_config_path}"
537+
command += f" --run-id {run_id}"
522538
if update_source:
523539
command += " --update-source"
524540
return command
@@ -624,6 +640,21 @@ def _summary_float(summary: dict[str, Any], key: str, default: float) -> float:
624640
return default
625641

626642

643+
def _default_sdk_run_id(sdk_summary: dict[str, Any]) -> str:
644+
started_at = sdk_summary.get("started_at")
645+
if isinstance(started_at, str) and started_at.strip():
646+
source = started_at.strip()
647+
else:
648+
source = datetime.now(timezone.utc).isoformat(timespec="seconds")
649+
safe = []
650+
for char in source:
651+
if char.isalnum() or char in {"-", "_"}:
652+
safe.append(char)
653+
else:
654+
safe.append("-")
655+
return "eval_optimize_loop_sdk_" + "".join(safe).strip("-")
656+
657+
627658
def _parse_target_prompt_paths(
628659
target_prompts: list[str] | None,
629660
*,
@@ -681,6 +712,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
681712
action="append",
682713
help="SDK target prompt path in name=path format. May be repeated. Defaults to system_prompt=--prompt.",
683714
)
715+
parser.add_argument("--run-id", help="Optional report/audit run id. Fake mode keeps its deterministic default.")
684716
return parser.parse_args(argv)
685717

686718

@@ -734,6 +766,7 @@ def main(argv: list[str] | None = None) -> OptimizationReport:
734766
update_source=args.update_source,
735767
gate_config_path=args.gate_config,
736768
target_prompts=args.target_prompt,
769+
run_id=args.run_id,
737770
)
738771
output_dir = Path(args.output_dir)
739772
print(f"Wrote {output_dir / 'optimization_report.json'}")

0 commit comments

Comments
 (0)