@@ -67,6 +67,7 @@ def run_pipeline(
6767 if mode == "sdk" :
6868 optimizer_config_dict = _read_json_object_for_audit (optimizer_config_path )
6969 baseline_prompt = load_prompt (prompt_path )
70+ sdk_artifact_dir = Path (output_dir ) / "sdk_optimizer"
7071 sdk_backend = SDKBackend (
7172 prompt_path = prompt_path ,
7273 call_agent_path = sdk_call_agent ,
@@ -77,7 +78,7 @@ def run_pipeline(
7778 train_path = train_path ,
7879 val_path = val_path ,
7980 optimizer_config_path = optimizer_config_path ,
80- output_dir = output_dir ,
81+ output_dir = sdk_artifact_dir ,
8182 )
8283 report = _build_sdk_report (
8384 candidates = candidates ,
@@ -329,12 +330,24 @@ def _build_sdk_report(
329330 optimizer_config_path = optimizer_config_path ,
330331 prompt_path = prompt_path ,
331332 )
333+ sdk_summary = sdk_backend .last_result_summary or {}
334+ baseline_pass_rate = _summary_float (sdk_summary , "baseline_pass_rate" , 0.0 )
335+ best_pass_rate = _summary_float (sdk_summary , "best_pass_rate" , baseline_pass_rate )
336+ pass_rate_improvement = _summary_float (
337+ sdk_summary ,
338+ "pass_rate_improvement" ,
339+ best_pass_rate - baseline_pass_rate ,
340+ )
341+ total_llm_cost = _summary_float (sdk_summary , "total_llm_cost" , 0.0 )
342+ duration_seconds = _summary_float (sdk_summary , "duration_seconds" , 0.0 )
343+ gate_config = _sdk_gate_config (optimizer_config_dict )
344+
332345 baseline_train = EvalResult (prompt_id = "baseline" , split = "train" , score = 0.0 , passed = False , cost = 0.0 , cases = [])
333346 baseline_validation = EvalResult (
334347 prompt_id = "baseline" ,
335348 split = "validation" ,
336- score = 0.0 ,
337- passed = False ,
349+ score = baseline_pass_rate ,
350+ passed = baseline_pass_rate >= 1.0 ,
338351 cost = 0.0 ,
339352 cases = [],
340353 )
@@ -352,14 +365,14 @@ def _build_sdk_report(
352365 "validation_result" : EvalResult (
353366 prompt_id = candidate .candidate_id ,
354367 split = "validation" ,
355- score = 0.0 ,
356- passed = False ,
357- cost = 0.0 ,
368+ score = best_pass_rate ,
369+ passed = best_pass_rate >= baseline_pass_rate ,
370+ cost = total_llm_cost ,
358371 cases = [],
359372 ),
360- "gate_status" : "not_applicable " ,
361- "gate_not_applied_reason" : "SDK optimizer result does not expose per-case validation results " ,
362- "sdk_result_summary" : sdk_backend . last_result_summary or {} ,
373+ "gate_status" : "partial_applied " ,
374+ "gate_not_applied_reason" : "SDK OptimizeResult exposes aggregate scores but not full per-case deltas " ,
375+ "sdk_result_summary" : sdk_summary ,
363376 }
364377 for candidate in candidates
365378 ]
@@ -369,7 +382,7 @@ def _build_sdk_report(
369382 }
370383 audit = {
371384 "seed" : None ,
372- "duration_seconds" : 0.0 ,
385+ "duration_seconds" : duration_seconds ,
373386 "config_hash" : stable_config_hash (optimizer_config_dict ),
374387 "input_hashes" : input_hashes ,
375388 "input_paths" : {
@@ -380,8 +393,12 @@ def _build_sdk_report(
380393 },
381394 "prompt_hash" : input_hashes ["prompt" ],
382395 "candidate_prompt_hashes" : prompt_hashes ,
383- "total_run_cost" : 0.0 ,
384- "cost" : {"baseline" : 0.0 , "candidates" : {candidate .candidate_id : 0.0 for candidate in candidates }, "total" : 0.0 },
396+ "total_run_cost" : total_llm_cost ,
397+ "cost" : {
398+ "baseline" : 0.0 ,
399+ "candidates" : {candidate .candidate_id : total_llm_cost for candidate in candidates },
400+ "total" : total_llm_cost ,
401+ },
385402 "candidate_prompts" : {
386403 candidate .candidate_id : {
387404 "rationale" : candidate .rationale ,
@@ -391,8 +408,8 @@ def _build_sdk_report(
391408 for candidate in candidates
392409 },
393410 "prompt_diffs" : {candidate .candidate_id : candidate .prompt_diff for candidate in candidates },
394- "sdk_artifact_dir" : str (output_dir ),
395- "sdk_result_summary" : sdk_backend . last_result_summary or {} ,
411+ "sdk_artifact_dir" : sdk_backend . last_artifact_dir or str (Path ( output_dir ) / "sdk_optimizer" ),
412+ "sdk_result_summary" : sdk_summary ,
396413 "reproducibility_command" : _sdk_reproducibility_command (
397414 train_path = train_path ,
398415 val_path = val_path ,
@@ -411,7 +428,7 @@ def _build_sdk_report(
411428 "train_cases" : train_case_count ,
412429 "validation_cases" : validation_case_count ,
413430 "update_source" : update_source ,
414- "sdk_artifact_dir" : str ( output_dir ) ,
431+ "sdk_artifact_dir" : audit [ "sdk_artifact_dir" ] ,
415432 "reproducibility_command" : audit ["reproducibility_command" ],
416433 "paths" : {
417434 "train" : str (train_path ),
@@ -422,34 +439,24 @@ def _build_sdk_report(
422439 "prompt_source" : str (prompt_path ),
423440 }
424441 gate_decisions = [
425- GateDecision (
442+ _sdk_gate_decision (
426443 candidate_id = candidate .candidate_id ,
427- accepted = True ,
428- reasons = ["gate not applied: SDK optimizer result does not expose per-case validation results" ],
429- train_score_delta = 0.0 ,
430- validation_score_delta = 0.0 ,
431- new_hard_failures = [],
432- protected_regressions = [],
433- validation_new_failures = [],
434- excessive_score_drops = [],
435- overfit_detected = False ,
436- candidate_cost = 0.0 ,
437- cumulative_cost = 0.0 ,
438- total_run_cost = 0.0 ,
439- cost = 0.0 ,
440- gate_status = "not_applicable" ,
441- gate_not_applied_reason = "SDK optimizer result does not expose per-case validation results" ,
444+ sdk_summary = sdk_summary ,
445+ gate_config = gate_config ,
442446 )
443447 for candidate in candidates
444448 ]
449+ selected_candidate = None
450+ if candidates and gate_decisions and gate_decisions [0 ].accepted :
451+ selected_candidate = candidates [0 ].candidate_id
445452 return build_report (
446453 run = run ,
447454 baseline_train = baseline_train ,
448455 baseline_validation = baseline_validation ,
449456 candidate_records = candidate_records ,
450457 per_case_deltas = [],
451458 gate_decisions = gate_decisions ,
452- selected_candidate = candidates [ 0 ]. candidate_id if candidates else None ,
459+ selected_candidate = selected_candidate ,
453460 audit = audit ,
454461 )
455462
@@ -474,6 +481,100 @@ def _sdk_reproducibility_command(
474481 return command
475482
476483
484+ def _sdk_gate_decision (
485+ * ,
486+ candidate_id : str ,
487+ sdk_summary : dict [str , Any ],
488+ gate_config : dict [str , float ],
489+ ) -> GateDecision :
490+ status = str (sdk_summary .get ("status" ) or "UNKNOWN" )
491+ improvement = _summary_float (sdk_summary , "pass_rate_improvement" , 0.0 )
492+ total_cost = _summary_float (sdk_summary , "total_llm_cost" , 0.0 )
493+ min_improvement = gate_config ["min_val_score_improvement" ]
494+ max_cost = gate_config ["max_total_cost" ]
495+
496+ reasons : list [str ] = []
497+ accepted = True
498+ if status != "SUCCEEDED" :
499+ accepted = False
500+ reasons .append (f"reject: SDK optimizer status { status } is not SUCCEEDED" )
501+ else :
502+ reasons .append ("accept: SDK optimizer status is SUCCEEDED" )
503+
504+ if improvement < min_improvement :
505+ accepted = False
506+ reasons .append (
507+ f"reject: validation improvement { improvement :.3f} is below threshold { min_improvement :.3f} "
508+ )
509+ else :
510+ reasons .append (
511+ f"accept: validation improvement { improvement :.3f} meets threshold { min_improvement :.3f} "
512+ )
513+
514+ if total_cost > max_cost :
515+ accepted = False
516+ reasons .append (f"reject: total SDK cost { total_cost :.3f} exceeds budget { max_cost :.3f} " )
517+ else :
518+ reasons .append (f"accept: total SDK cost { total_cost :.3f} is within budget { max_cost :.3f} " )
519+
520+ if accepted :
521+ reasons .append ("accept: SDK aggregate gate passed" )
522+
523+ return GateDecision (
524+ candidate_id = candidate_id ,
525+ accepted = accepted ,
526+ reasons = reasons ,
527+ train_score_delta = 0.0 ,
528+ validation_score_delta = improvement ,
529+ new_hard_failures = [],
530+ protected_regressions = [],
531+ validation_new_failures = [],
532+ excessive_score_drops = [],
533+ overfit_detected = False ,
534+ candidate_cost = total_cost ,
535+ cumulative_cost = 0.0 ,
536+ total_run_cost = total_cost ,
537+ cost = total_cost ,
538+ gate_status = "partial_applied" ,
539+ gate_not_applied_reason = "SDK OptimizeResult exposes aggregate scores but not full per-case deltas" ,
540+ not_applied_checks = [
541+ "per_case_delta" ,
542+ "protected_regression" ,
543+ "new_hard_failure" ,
544+ "max_score_drop_per_case" ,
545+ ],
546+ )
547+
548+
549+ def _sdk_gate_config (optimizer_config_dict : dict [str , Any ]) -> dict [str , float ]:
550+ gate_payload = optimizer_config_dict .get ("gate" )
551+ if gate_payload is None :
552+ gate_payload = {}
553+ if not isinstance (gate_payload , dict ):
554+ raise ValueError ("optimizer config field 'gate' must be an object when present" )
555+
556+ min_improvement = gate_payload .get ("min_val_score_improvement" , 0.01 )
557+ max_cost = gate_payload .get ("max_total_cost" , 1.0 )
558+ if not isinstance (min_improvement , (int , float )) or min_improvement < 0 :
559+ raise ValueError ("optimizer config field 'gate.min_val_score_improvement' must be a non-negative number" )
560+ if not isinstance (max_cost , (int , float )) or max_cost < 0 :
561+ raise ValueError ("optimizer config field 'gate.max_total_cost' must be a non-negative number" )
562+ return {
563+ "min_val_score_improvement" : float (min_improvement ),
564+ "max_total_cost" : float (max_cost ),
565+ }
566+
567+
568+ def _summary_float (summary : dict [str , Any ], key : str , default : float ) -> float :
569+ value = summary .get (key , default )
570+ if value is None :
571+ return default
572+ try :
573+ return float (value )
574+ except (TypeError , ValueError ):
575+ return default
576+
577+
477578def parse_args (argv : list [str ] | None = None ) -> argparse .Namespace :
478579 parser = argparse .ArgumentParser (description = __doc__ )
479580 parser .add_argument ("--train" , default = str (DEFAULT_TRAIN ), help = "Path to train.evalset.json" )
0 commit comments