Skip to content

Commit 41d9e89

Browse files
committed
fix(examples): finalize report timing semantics
1 parent 3360ef7 commit 41d9e89

4 files changed

Lines changed: 139 additions & 9 deletions

File tree

examples/optimization/eval_optimize_loop/eval_loop/backends.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import asyncio
66
import importlib
77
import math
8+
import time
89
from collections.abc import Iterable
910
from dataclasses import dataclass
1011
from dataclasses import replace
@@ -130,13 +131,21 @@ async def optimize_candidates(
130131
baseline_prompts,
131132
context="cannot optimize fake prompt bundle",
132133
)
134+
proposal_started_at = time.perf_counter()
133135
candidates = _normalize_fake_candidates(
134136
self._optimizer.propose(
135137
baseline_prompt,
136138
baseline_train,
137139
failure_summary,
138140
)
139141
)
142+
proposal_duration_seconds = _positive_perf_duration(
143+
proposal_started_at,
144+
time.perf_counter(),
145+
)
146+
round_duration_seconds = (
147+
proposal_duration_seconds / len(candidates) if candidates else 0.0
148+
)
140149
zero_cost = CostSummary(complete=True)
141150
rounds = [
142151
OptimizationRound(
@@ -146,7 +155,7 @@ async def optimize_candidates(
146155
rationale=candidate.rationale,
147156
metrics={},
148157
cost=zero_cost,
149-
duration_seconds=0.0,
158+
duration_seconds=round_duration_seconds,
150159
)
151160
for index, candidate in enumerate(candidates, start=1)
152161
]
@@ -158,6 +167,10 @@ async def optimize_candidates(
158167
"backend": "fake",
159168
"baseline_prompt_id": baseline_train.prompt_id,
160169
"failure_summary": _safe_jsonable(failure_summary),
170+
"proposal_duration_seconds": proposal_duration_seconds,
171+
"round_duration_allocation": (
172+
"equal_share_of_batch_proposal_duration"
173+
),
161174
},
162175
)
163176

@@ -837,6 +850,19 @@ def _finite_number(value: Any, *, context: str) -> float:
837850
return number
838851

839852

853+
def _positive_perf_duration(started_at: float, finished_at: float) -> float:
854+
"""Return one finite positive duration for a measured perf-counter span."""
855+
856+
elapsed = finished_at - started_at
857+
if math.isfinite(elapsed) and elapsed > 0.0:
858+
return elapsed
859+
860+
resolution = float(time.get_clock_info("perf_counter").resolution)
861+
if not math.isfinite(resolution) or resolution <= 0.0:
862+
raise RuntimeError("perf_counter resolution must be finite and positive")
863+
return resolution
864+
865+
840866
def _nonnegative_number(value: Any, *, context: str) -> float:
841867
number = _finite_number(value, context=context)
842868
if number < 0.0:

examples/optimization/eval_optimize_loop/eval_loop/report.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def build_report(
152152
if isinstance(result, EvalResult):
153153
all_results.append(result)
154154
return OptimizationReport(
155-
schema_version="eval_optimize_loop.v1",
155+
schema_version="eval_optimize_loop.v2",
156156
run=run,
157157
baseline={"train": baseline_train, "validation": baseline_validation},
158158
baseline_train=baseline_train,
@@ -645,7 +645,7 @@ def render_markdown(report: OptimizationReport) -> str:
645645
"",
646646
"## Failure Attribution Summary",
647647
"",
648-
f"Total failed case evaluations: {summary['total_failed_cases']}",
648+
f"Total failed case evaluations: {summary['total_failed_cases']}",
649649
"",
650650
"| category | count |",
651651
"| --- | ---: |",
@@ -691,8 +691,8 @@ def render_markdown(report: OptimizationReport) -> str:
691691
candidate = record["candidate"]
692692
lines.extend([
693693
f"### {candidate.candidate_id}",
694-
"",
695-
"```diff",
694+
"",
695+
"```diff",
696696
candidate.prompt_diff,
697697
"```",
698698
"",
@@ -702,8 +702,8 @@ def render_markdown(report: OptimizationReport) -> str:
702702
"## Reproducibility",
703703
"",
704704
f"```{report.run.get('reproducibility_shell') or 'bash'}",
705-
report.run.get("reproducibility_command")
706-
or report.audit.get("reproducibility_command")
705+
report.run.get("reproducibility_command")
706+
or report.audit.get("reproducibility_command")
707707
or REPRODUCIBILITY_COMMAND,
708708
"```",
709709
"",

examples/optimization/eval_optimize_loop/tests/test_pipeline_fake_mode.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys
77

88
from examples.optimization.eval_optimize_loop.run_pipeline import DEFAULT_OPTIMIZER_CONFIG
9-
from examples.optimization.eval_optimize_loop.run_pipeline import DEFAULT_PROMPT
109
from examples.optimization.eval_optimize_loop.run_pipeline import DEFAULT_TRAIN
1110
from examples.optimization.eval_optimize_loop.run_pipeline import DEFAULT_VAL
1211
from examples.optimization.eval_optimize_loop.run_pipeline import run_pipeline
@@ -15,11 +14,18 @@
1514

1615
def test_fake_mode_pipeline_generates_json_and_markdown_reports(tmp_path: Path):
1716
output_dir = tmp_path / "run"
17+
prompt_path = tmp_path / "baseline_system_prompt.txt"
18+
prompt_path.write_text(
19+
"You are a helpful support assistant.\n\n"
20+
"Answer clearly and include a short explanation when it may help the user.\n"
21+
"If the user asks for structured data, provide the information they need.\n",
22+
encoding="utf-8",
23+
)
1824
report = run_pipeline(
1925
train_path=DEFAULT_TRAIN,
2026
val_path=DEFAULT_VAL,
2127
optimizer_config_path=DEFAULT_OPTIMIZER_CONFIG,
22-
prompt_path=DEFAULT_PROMPT,
28+
prompt_path=prompt_path,
2329
output_dir=output_dir,
2430
fake_model=True,
2531
fake_judge=True,
@@ -31,8 +37,10 @@ def test_fake_mode_pipeline_generates_json_and_markdown_reports(tmp_path: Path):
3137
assert json_path.is_file()
3238
assert md_path.is_file()
3339
assert report.selected_candidate == "candidate_002_safe"
40+
assert report.schema_version == "eval_optimize_loop.v2"
3441

3542
payload = json.loads(json_path.read_text(encoding="utf-8"))
43+
assert payload["schema_version"] == "eval_optimize_loop.v2"
3644
assert set(payload) >= {
3745
"schema_version",
3846
"run",
@@ -213,5 +221,11 @@ def _normalized_payload(payload: dict) -> dict:
213221
normalized["run"].pop("reproducibility_command", None)
214222
normalized["audit"].pop("duration_seconds", None)
215223
normalized["audit"].pop("reproducibility_command", None)
224+
normalized["audit"].get("sdk_result_summary", {}).pop(
225+
"proposal_duration_seconds",
226+
None,
227+
)
216228
normalized["audit"].get("writeback_journal", {}).pop("run_id", None)
229+
for round_record in normalized.get("rounds", []):
230+
round_record.pop("duration_seconds", None)
217231
return normalized

examples/optimization/eval_optimize_loop/tests/test_sdk_backend.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import builtins
44
import inspect
55
import json
6+
import math
67
import os
78
import shlex
89
import sys
@@ -127,6 +128,75 @@ async def test_fake_backend_wraps_candidates_in_complete_optimization_result(tmp
127128
assert all(candidate.bundle() == {"system_prompt": candidate.prompt} for candidate in result.candidates)
128129

129130

131+
@pytest.mark.asyncio
132+
async def test_fake_backend_distributes_measured_batch_proposal_duration(
133+
tmp_path: Path,
134+
monkeypatch,
135+
):
136+
perf_counter_values = iter((10.0, 10.006))
137+
monkeypatch.setattr(
138+
backend_module,
139+
"time",
140+
types.SimpleNamespace(
141+
perf_counter=lambda: next(perf_counter_values),
142+
get_clock_info=lambda name: types.SimpleNamespace(resolution=1e-9),
143+
),
144+
raising=False,
145+
)
146+
147+
result = await backend_module.FakeBackend(seed=91).optimize_candidates(
148+
baseline_prompts={"system_prompt": "baseline prompt\n"},
149+
baseline_train=_failed_fake_train_result(),
150+
failure_summary={"by_category": {"format_violation": 1}},
151+
train_path=tmp_path / "train.evalset.json",
152+
validation_path=tmp_path / "validation.evalset.json",
153+
config_path=tmp_path / "optimizer.json",
154+
artifact_dir=tmp_path / "fake_optimize",
155+
)
156+
157+
durations = [round_record.duration_seconds for round_record in result.rounds]
158+
assert durations == pytest.approx([0.003, 0.003])
159+
assert all(duration > 0 and math.isfinite(duration) for duration in durations)
160+
assert sum(durations) == pytest.approx(0.006)
161+
assert result.raw_summary["proposal_duration_seconds"] == pytest.approx(0.006)
162+
assert result.raw_summary["round_duration_allocation"] == (
163+
"equal_share_of_batch_proposal_duration"
164+
)
165+
166+
167+
@pytest.mark.asyncio
168+
async def test_fake_backend_uses_clock_resolution_when_batch_timer_does_not_advance(
169+
tmp_path: Path,
170+
monkeypatch,
171+
):
172+
perf_counter_values = iter((42.0, 42.0))
173+
monkeypatch.setattr(
174+
backend_module,
175+
"time",
176+
types.SimpleNamespace(
177+
perf_counter=lambda: next(perf_counter_values),
178+
get_clock_info=lambda name: types.SimpleNamespace(resolution=1e-6),
179+
),
180+
raising=False,
181+
)
182+
183+
result = await backend_module.FakeBackend(seed=91).optimize_candidates(
184+
baseline_prompts={"system_prompt": "baseline prompt\n"},
185+
baseline_train=_failed_fake_train_result(),
186+
failure_summary={"by_category": {"format_violation": 1}},
187+
train_path=tmp_path / "train.evalset.json",
188+
validation_path=tmp_path / "validation.evalset.json",
189+
config_path=tmp_path / "optimizer.json",
190+
artifact_dir=tmp_path / "fake_optimize",
191+
)
192+
193+
durations = [round_record.duration_seconds for round_record in result.rounds]
194+
assert durations == pytest.approx([0.5e-6, 0.5e-6])
195+
assert all(duration > 0 and math.isfinite(duration) for duration in durations)
196+
assert sum(durations) == pytest.approx(1e-6)
197+
assert result.raw_summary["proposal_duration_seconds"] == pytest.approx(1e-6)
198+
199+
130200
@pytest.mark.asyncio
131201
async def test_fake_backend_returns_no_candidates_without_failure_categories(tmp_path: Path):
132202
baseline_prompt = DEFAULT_PROMPT.read_text(encoding="utf-8")
@@ -1962,6 +2032,26 @@ def _empty_eval_result(prompt_id: str, split: str) -> EvalResult:
19622032
)
19632033

19642034

2035+
def _failed_fake_train_result() -> EvalResult:
2036+
return EvalResult(
2037+
prompt_id="baseline",
2038+
split="train",
2039+
score=0.0,
2040+
passed=False,
2041+
cost=0.0,
2042+
cases=[
2043+
CaseResult(
2044+
case_id="observed_training_failure",
2045+
split="train",
2046+
score=0.0,
2047+
passed=False,
2048+
output="not-json",
2049+
failure_category="format_violation",
2050+
)
2051+
],
2052+
)
2053+
2054+
19652055
def _sdk_round(
19662056
round_id: int,
19672057
candidate_prompts: dict[str, str],

0 commit comments

Comments
 (0)