linvest21's picture
download
raw
10.3 kB
from __future__ import annotations
import hashlib
from collections import Counter
from pathlib import Path
from typing import Any
from data_pipeline.learning_pdf_to_jsonl import write_jsonl
from data_pipeline.pairwise_preference_memory import (
DIAGNOSTIC_BUCKETS,
candidate_critical_failed,
candidate_lost,
clean_model_response,
corrected_chosen_answer,
defects_for_prediction,
failure_bucket_for_prediction,
judge_rationale_for_prediction,
read_json,
read_jsonl,
repair_acceptance_checks,
repair_admitted_to_training,
score_number,
utc_now,
winner_for_prediction,
)
from n21.config import write_json
from n21.settings import SHFT_WORKSPACE_ROOT
DIAGNOSTIC_SCHEMA_VERSION = "shft_paired_eval_diagnostics_v1"
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def diagnostic_row(*, prediction: dict[str, Any], run_id: str, asset_class: str, role: str, record_index: int) -> dict[str, Any]:
defects = defects_for_prediction(prediction)
prompt = str(prediction.get("prompt") or "").strip()
candidate = clean_model_response(str(prediction.get("candidate_answer") or prediction.get("candidate_response") or ""))
baseline = clean_model_response(str(prediction.get("baseline_answer") or prediction.get("baseline_response") or ""))
repair_answer_raw, repair_source = corrected_chosen_answer(
prediction=prediction, asset_class=asset_class, role=role, defects=defects
)
repair_answer = clean_model_response(repair_answer_raw)
acceptance = repair_acceptance_checks(prompt=prompt, chosen=repair_answer, rejected=candidate, task=prediction.get("task"))
is_loss = candidate_lost(prediction)
is_critical = candidate_critical_failed(prediction)
return {
"schema_version": DIAGNOSTIC_SCHEMA_VERSION,
"source_run_id": run_id,
"record_index": record_index,
"prompt_id": prediction.get("id"),
"task": prediction.get("task"),
"prompt": prompt,
"baseline_answer": baseline,
"candidate_answer": candidate,
"winner": winner_for_prediction(prediction),
"failure_bucket": failure_bucket_for_prediction(prediction, defects),
"defect_types": defects,
"critical_failure": is_critical,
"pairwise_loss": is_loss,
"baseline_score": score_number(prediction.get("baseline_score")),
"candidate_score": score_number(prediction.get("candidate_score")),
"delta": prediction.get("delta"),
"judge_rationale": judge_rationale_for_prediction(prediction, defects),
"repair_target": {
"answer": repair_answer,
"source": repair_source,
"acceptance_checks": acceptance,
"admitted_to_training": bool((is_loss or is_critical) and repair_admitted_to_training(acceptance)),
},
"created_at": utc_now(),
}
def validate_diagnostic_row(row: dict[str, Any]) -> list[str]:
errors: list[str] = []
for field in ("prompt", "baseline_answer", "candidate_answer", "winner", "failure_bucket"):
if not isinstance(row.get(field), str) or not row[field].strip():
errors.append(f"{field} must be non-empty text")
if row.get("failure_bucket") not in DIAGNOSTIC_BUCKETS:
errors.append(f"failure_bucket must be one of {', '.join(DIAGNOSTIC_BUCKETS)}")
if not isinstance(row.get("judge_rationale"), list) or not row["judge_rationale"]:
errors.append("judge_rationale must be a non-empty list")
repair = row.get("repair_target")
if not isinstance(repair, dict):
errors.append("repair_target must be an object")
else:
answer = repair.get("answer")
checks = repair.get("acceptance_checks")
if not isinstance(answer, str) or not answer.strip():
errors.append("repair_target.answer must be non-empty")
if not isinstance(checks, dict) or not checks:
errors.append("repair_target.acceptance_checks must be non-empty")
if "<think" in str(answer).lower() or "</think" in str(answer).lower():
errors.append("repair_target.answer must not contain think tags")
return errors
def write_markdown_summary(path: Path, result: dict[str, Any]) -> None:
summary = result["summary"]
lines = [
"# SHFT Paired Eval Diagnostics",
"",
f"- Run: `{result['run_id']}`",
f"- Asset/role: `{result['asset_class']}/{result['role']}`",
f"- Predictions: `{summary['prediction_count']}`",
f"- Losses: `{summary['pairwise_loss_count']}`",
f"- Critical failures: `{summary['critical_failure_count']}`",
f"- Accepted repair targets: `{summary['accepted_repair_target_count']}`",
f"- Diagnostics JSONL: `{result['diagnostics_jsonl_path']}`",
f"- Repair targets JSONL: `{result['repair_targets_jsonl_path']}`",
"",
"## Failure Buckets",
"",
]
for bucket, count in sorted(summary["failure_bucket_counts"].items(), key=lambda item: (-item[1], item[0])):
lines.append(f"- `{bucket}`: `{count}`")
lines.append("")
path.write_text("\n".join(lines), encoding="utf-8")
def build_paired_eval_diagnostics(
*,
run_id: str,
asset_class: str,
role: str,
predictions_path: Path | None = None,
output_dir: Path | None = None,
max_records: int | None = None,
) -> dict[str, Any]:
run_path = SHFT_WORKSPACE_ROOT / "runs" / run_id
predictions = predictions_path or run_path / "eval" / "paired_predictions.jsonl"
if not predictions.exists():
raise FileNotFoundError(f"paired predictions not found: {predictions}")
report_path = predictions.parent / "paired_eval_report.json"
report = read_json(report_path) if report_path.exists() else {}
out_dir = output_dir or run_path / "diagnostics"
diagnostics_path = out_dir / "paired_eval_diagnostics.jsonl"
repair_targets_path = out_dir / "repair_targets.jsonl"
manifest_path = out_dir / "paired_eval_diagnostics_manifest.json"
markdown_path = out_dir / "paired_eval_diagnostics_summary.md"
rows: list[dict[str, Any]] = []
repair_targets: list[dict[str, Any]] = []
schema_errors: list[str] = []
bucket_counts: Counter[str] = Counter()
loss_bucket_counts: Counter[str] = Counter()
defect_counts: Counter[str] = Counter()
source_loss_ids: set[str] = set()
diagnosed_loss_ids: set[str] = set()
for idx, prediction in enumerate(read_jsonl(predictions), start=1):
if max_records is not None and len(rows) >= max_records:
break
if candidate_lost(prediction):
source_loss_ids.add(str(prediction.get("id") or idx))
row = diagnostic_row(prediction=prediction, run_id=run_id, asset_class=asset_class, role=role, record_index=idx)
errors = validate_diagnostic_row(row)
if errors:
schema_errors.extend(f"{row.get('prompt_id')}: {error}" for error in errors)
continue
rows.append(row)
bucket_counts[row["failure_bucket"]] += 1
if row["pairwise_loss"]:
loss_bucket_counts[row["failure_bucket"]] += 1
diagnosed_loss_ids.add(str(row.get("prompt_id") or idx))
defect_counts.update(row["defect_types"])
if row["repair_target"]["admitted_to_training"]:
repair_targets.append(
{
"schema_version": "shft_repair_target_v1",
"source_run_id": run_id,
"source_prediction_id": row["prompt_id"],
"failure_bucket": row["failure_bucket"],
"defect_types": row["defect_types"],
"source_task": row["task"],
"source_delta": row["delta"],
"prompt": row["prompt"],
"repair_answer": row["repair_target"]["answer"],
"acceptance_checks": row["repair_target"]["acceptance_checks"],
"created_at": row["created_at"],
}
)
out_dir.mkdir(parents=True, exist_ok=True)
write_jsonl(diagnostics_path, rows)
write_jsonl(repair_targets_path, repair_targets)
summary = {
"prediction_count": len(rows),
"source_pairwise_loss_count": len(source_loss_ids),
"pairwise_loss_count": sum(1 for row in rows if row["pairwise_loss"]),
"diagnosed_pairwise_loss_count": len(diagnosed_loss_ids),
"latest_loss_coverage_ratio": round(len(diagnosed_loss_ids) / max(1, len(source_loss_ids)), 6),
"latest_loss_coverage_ok": len(source_loss_ids) == len(diagnosed_loss_ids),
"critical_failure_count": sum(1 for row in rows if row["critical_failure"]),
"accepted_repair_target_count": len(repair_targets),
"failure_bucket_counts": {bucket: int(bucket_counts.get(bucket, 0)) for bucket in DIAGNOSTIC_BUCKETS},
"loss_failure_bucket_counts": {bucket: int(loss_bucket_counts.get(bucket, 0)) for bucket in DIAGNOSTIC_BUCKETS},
"defect_counts": dict(defect_counts),
"schema_error_count": len(schema_errors),
}
result = {
"ok": bool(rows) and not schema_errors and summary["latest_loss_coverage_ok"],
"schema_version": DIAGNOSTIC_SCHEMA_VERSION,
"run_id": run_id,
"asset_class": asset_class,
"role": role,
"predictions_path": str(predictions),
"paired_eval_report_path": str(report_path) if report_path.exists() else None,
"paired_eval_improvement": report.get("improvement", {}),
"diagnostics_jsonl_path": str(diagnostics_path),
"diagnostics_jsonl_sha256": sha256_file(diagnostics_path),
"repair_targets_jsonl_path": str(repair_targets_path),
"repair_targets_jsonl_sha256": sha256_file(repair_targets_path),
"manifest_path": str(manifest_path),
"markdown_path": str(markdown_path),
"summary": summary,
"schema_errors": schema_errors,
"created_at": utc_now(),
}
write_json(manifest_path, result)
write_markdown_summary(markdown_path, result)
return result

Xet Storage Details

Size:
10.3 kB
·
Xet hash:
c99a42cd533eee7cd4e85469fcf5ca4821ed3496d6f80d8150d5180be1d73c1a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.