linvest21's picture
download
raw
17.9 kB
from __future__ import annotations
from pathlib import Path
from typing import Any
from data_pipeline.learning_pdf_to_jsonl import convert_learning_tree
from data_pipeline.live_source_discovery import discover_public_sources
from data_pipeline.reasoning_data_generation import generate_grounded_reasoning_examples_from_intake_records
from data_pipeline.source_intake import intake_public_sources, load_policy
from data_pipeline.training_data_validation import validate_training_data
from n21.config import write_json
from n21.settings import CONFIG_ROOT, SHFT_WORKSPACE_ROOT
from observability.audit_log import utc_now
TRAINABLE_SUFFIXES = {".pdf", ".jsonl", ".hf_finetune.jsonl", ".normalized.json"}
def _run_dir(run_id: str) -> Path:
return SHFT_WORKSPACE_ROOT / "runs" / run_id
def _trainable_paths(records: list[dict[str, Any]]) -> list[str]:
paths: list[str] = []
for record in records:
path = str(record.get("training_path") or "")
if not path:
continue
lower = path.lower()
if lower.endswith(".hf_finetune.jsonl") or lower.endswith(".normalized.json") or Path(path).suffix.lower() in TRAINABLE_SUFFIXES:
paths.append(path)
return paths
def _urls_from_records(records: list[dict[str, Any]]) -> set[str]:
urls: set[str] = set()
for record in records:
url = str(record.get("url") or record.get("source_url") or "").strip().lower()
if url:
urls.add(url)
return urls
def _quality_errors(run_path: Path) -> list[str]:
path = run_path / "eval" / "model_quality_gate.json"
if not path.exists():
return []
try:
import json
report = json.loads(path.read_text(encoding="utf-8-sig"))
except Exception:
return []
return [str(item) for item in report.get("errors", [])]
def run_stall_breakout(
*,
run_id: str,
asset_class: str,
role: str,
train_source: Path,
quality_gate_exit_code: int,
catalog_path: Path | None = None,
policy_path: Path | None = None,
max_sources: int | None = None,
apply_quarantine: bool = False,
intake_root: Path | None = None,
training_root: Path | None = None,
) -> dict[str, Any]:
"""Run the Step 0 data breakout that follows a failed model-quality gate.
This is intentionally not a certification bypass. It downloads public
sources according to source_policy.yaml, promotes only train-allowed files
into data/learning, validates the training corpus, and records whether the
new material is actually trainable by the current Step 0b converters.
"""
run_path = _run_dir(run_id)
breakout_dir = run_path / "stall_breakout"
validation_dir = breakout_dir / "training_data_validation"
backup_dir = breakout_dir / "quarantine_backup"
breakout_dir.mkdir(parents=True, exist_ok=True)
quality_errors = _quality_errors(run_path)
policy = load_policy(policy_path)
intake_manifest = intake_public_sources(
asset_class=asset_class,
role=role,
catalog_path=catalog_path,
policy_path=policy_path,
max_sources=max_sources,
promote_approved=True,
intake_root=intake_root,
training_root=training_root,
quality_errors=quality_errors,
)
records = list(intake_manifest.get("records", []))
trainable_paths = _trainable_paths(records)
live_discovery_result: dict[str, Any] | None = None
live_intake_manifest: dict[str, Any] | None = None
live_attempts: list[dict[str, Any]] = []
reasoning_generation_report: dict[str, Any] | None = None
discovery_policy = policy.get("live_discovery", {})
max_new_sources = int(max_sources or policy.get("stall_breakout", {}).get("max_new_sources_per_round", 10))
if len(trainable_paths) < max_new_sources:
if bool(discovery_policy.get("enabled", True)):
max_attempts = int(
policy.get("stall_breakout", {}).get(
"max_source_discovery_attempts",
discovery_policy.get("max_source_discovery_attempts", 4),
)
)
excluded_urls = _urls_from_records(records)
for attempt in range(1, max_attempts + 1):
if len(trainable_paths) >= max_new_sources:
break
live_dir_name = "live_discovery" if attempt == 1 else f"live_discovery_attempt_{attempt:02d}"
live_dir = breakout_dir / live_dir_name
live_discovery_result = discover_public_sources(
asset_class=asset_class,
role=role,
policy=policy,
output_dir=live_dir,
exclude_urls=excluded_urls,
quality_errors=quality_errors,
discovery_attempt=attempt,
)
discovered_catalog = live_dir / "live_discovered_public_source_catalog.json"
remaining = max(1, max_new_sources - len(trainable_paths))
live_intake_manifest = intake_public_sources(
asset_class=asset_class,
role=role,
catalog_path=discovered_catalog,
policy_path=policy_path,
max_sources=remaining,
promote_approved=True,
intake_root=(live_dir / "intake"),
training_root=training_root,
quality_errors=quality_errors,
)
new_records = list(live_intake_manifest.get("records", []))
records.extend(new_records)
excluded_urls.update(_urls_from_records(new_records))
trainable_paths = _trainable_paths(records)
live_attempts.append(
{
"attempt": attempt,
"discovery_dir": str(live_dir),
"candidate_count": (live_discovery_result or {}).get("manifest", {}).get("candidate_count", 0),
"catalog_path": (live_discovery_result or {}).get("manifest", {}).get("catalog_path"),
"intake_source_count": live_intake_manifest.get("source_count", 0),
"intake_downloaded_count": live_intake_manifest.get("downloaded_count", 0),
"intake_training_eligible_count": live_intake_manifest.get("training_eligible_count", 0),
"intake_verification_eligible_count": live_intake_manifest.get("validation_eligible_count", 0),
"ai_training_certified_count": live_intake_manifest.get("ai_training_certified_count", 0),
"ai_verification_certified_count": live_intake_manifest.get("ai_verification_certified_count", 0),
"ai_rejected_count": live_intake_manifest.get("ai_rejected_count", 0),
"content_ai_training_certified_count": live_intake_manifest.get("content_ai_training_certified_count", 0),
"content_ai_rejected_count": live_intake_manifest.get("content_ai_rejected_count", 0),
"download_failed_count": sum(1 for record in new_records if record.get("decision") == "download_failed"),
"already_in_training_count": sum(1 for record in new_records if record.get("already_in_training")),
"review_required_count": live_intake_manifest.get("review_required_count", 0),
"total_trainable_new_source_count": len(trainable_paths),
"errors": (live_discovery_result or {}).get("manifest", {}).get("errors", []),
}
)
if not new_records and not (live_discovery_result or {}).get("manifest", {}).get("candidate_count", 0):
# The next attempt would use broader retry terms, but if the
# provider returns no candidates at all after query expansion
# is already active, stop and surface that blocker.
if attempt > 1:
break
if not trainable_paths:
reasoning_max_records = int(policy.get("stall_breakout", {}).get("max_breakout_reasoning_records", 300))
reasoning_output = train_source / f"synthetic_{asset_class}_{role}_critical_reasoning.hf_finetune.jsonl"
reasoning_generation_report = generate_grounded_reasoning_examples_from_intake_records(
asset_class=asset_class,
role=role,
records=records,
output_path=reasoning_output,
max_records=reasoning_max_records,
policy_path=policy_path,
)
if reasoning_generation_report.get("ok"):
trainable_paths.append(str(reasoning_output))
conversion_report: dict[str, Any] | None = None
conversion_error: str | None = None
if any(not str(path).lower().endswith(".jsonl") for path in trainable_paths):
try:
conversion_report = convert_learning_tree(
asset_class=asset_class,
role=role,
source_type="pdf",
skip_existing=False,
)
except Exception as exc: # pragma: no cover - reported as artifact, not hidden
conversion_error = str(exc)
validation_report = validate_training_data(
source=train_source,
output_dir=validation_dir,
backup_dir=backup_dir,
apply_quarantine=apply_quarantine,
)
non_trainable_approved = [
str(record.get("training_path"))
for record in records
if record.get("train_allowed") and record.get("training_path") and str(record.get("training_path")) not in trainable_paths
]
approved_count = sum(1 for record in records if record.get("train_allowed"))
verification_count = sum(1 for record in records if record.get("verification_allowed"))
ai_training_certified_count = sum(1 for record in records if (record.get("ai_certification") or {}).get("training_eligible"))
ai_verification_certified_count = sum(
1 for record in records if (record.get("ai_certification") or {}).get("verification_eligible")
)
ai_rejected_count = sum(1 for record in records if (record.get("ai_certification") or {}).get("intended_use") == "reject")
content_ai_training_certified_count = sum(
1 for record in records if (record.get("content_ai_certification") or {}).get("training_eligible")
)
content_ai_verification_certified_count = sum(
1 for record in records if (record.get("content_ai_certification") or {}).get("verification_eligible")
)
content_ai_rejected_count = sum(
1 for record in records if (record.get("content_ai_certification") or {}).get("intended_use") == "reject"
)
generated_reasoning_count = int((reasoning_generation_report or {}).get("generated_count") or 0)
downloaded_count = sum(1 for record in records if record.get("downloaded"))
review_required_count = sum(1 for record in records if record.get("requires_human_review"))
blockers: list[str] = []
if approved_count == 0 and generated_reasoning_count == 0:
blockers.append("no AI-certified, policy-approved training sources were found for this asset/role")
if not trainable_paths and generated_reasoning_count == 0:
blockers.append("no newly AI-certified training sources are trainable by current Step 0b converters")
if verification_count and not approved_count and generated_reasoning_count == 0:
blockers.append("only verification material was found; verification material measures quality but does not improve the model")
if non_trainable_approved:
blockers.append("some approved downloads need a converter before they can become training records")
if not validation_report.get("ok"):
blockers.append("training data validation failed")
if conversion_error:
blockers.append("Step 0b conversion failed after breakout intake")
next_actions = [
"Review stall_breakout/source_intake_manifest.json and license_manifest.json.",
"Use only records with train_allowed=true and ai_certification.training_eligible=true for training.",
"Use records with verification_allowed=true only for evaluation/model-judge/human spot checks.",
"If no trainable sources were added, add high-signal PDF/HTML/JSONL sources with explicit case-study, checklist, red-flag, or pass/fail reasoning.",
"Start a fresh run/version after corpus validation passes; do not reuse the failed RUN_ID unless explicitly allowed.",
]
plan = {
"schema_version": "stall_breakout_plan_v1",
"run_id": run_id,
"asset_class": asset_class,
"role": role,
"train_source": str(train_source),
"quality_gate_exit_code": quality_gate_exit_code,
"policy_path": str(policy_path or (CONFIG_ROOT / "data" / "source_policy.yaml")),
"catalog_path": str(catalog_path or (CONFIG_ROOT / "data" / "public_source_catalog.json")),
"policy_summary": {
"auto_download_public_sources": bool(policy.get("auto_download_public_sources", True)),
"promote_approved_sources_to_training": bool(policy.get("promote_approved_sources_to_training", True)),
"approved_license_statuses": policy.get("approved_license_statuses", []),
"review_license_statuses": policy.get("review_license_statuses", []),
"blocked_license_statuses": policy.get("blocked_license_statuses", []),
"source_quality_certification": policy.get("source_quality_certification", {}),
},
"intake": {
"source_count": len(records),
"catalog_source_count": intake_manifest.get("source_count", 0),
"downloaded_count": downloaded_count,
"approved_for_training_count": approved_count,
"approved_for_verification_count": verification_count,
"ai_training_certified_count": ai_training_certified_count,
"ai_verification_certified_count": ai_verification_certified_count,
"ai_rejected_count": ai_rejected_count,
"content_ai_training_certified_count": content_ai_training_certified_count,
"content_ai_verification_certified_count": content_ai_verification_certified_count,
"content_ai_rejected_count": content_ai_rejected_count,
"review_required_count": review_required_count,
"trainable_new_source_count": len(trainable_paths),
"trainable_new_sources": trainable_paths,
"non_trainable_approved_sources": non_trainable_approved,
"generated_reasoning_count": generated_reasoning_count,
},
"live_discovery": {
"attempted": bool(live_attempts),
"attempt_count": len(live_attempts),
"attempts": live_attempts,
"candidate_count": (live_discovery_result or {}).get("manifest", {}).get("candidate_count", 0),
"catalog_path": (live_discovery_result or {}).get("manifest", {}).get("catalog_path"),
"intake_source_count": (live_intake_manifest or {}).get("source_count", 0),
"intake_downloaded_count": (live_intake_manifest or {}).get("downloaded_count", 0),
"intake_training_eligible_count": (live_intake_manifest or {}).get("training_eligible_count", 0),
"intake_verification_eligible_count": (live_intake_manifest or {}).get("validation_eligible_count", 0),
"ai_training_certified_count": (live_intake_manifest or {}).get("ai_training_certified_count", 0),
"ai_verification_certified_count": (live_intake_manifest or {}).get("ai_verification_certified_count", 0),
"ai_rejected_count": (live_intake_manifest or {}).get("ai_rejected_count", 0),
"content_ai_training_certified_count": (live_intake_manifest or {}).get("content_ai_training_certified_count", 0),
"content_ai_verification_certified_count": (live_intake_manifest or {}).get("content_ai_verification_certified_count", 0),
"content_ai_rejected_count": (live_intake_manifest or {}).get("content_ai_rejected_count", 0),
"errors": (live_discovery_result or {}).get("manifest", {}).get("errors", []),
},
"conversion": {
"attempted": any(not str(path).lower().endswith(".jsonl") for path in trainable_paths),
"ok": conversion_error is None,
"error": conversion_error,
"report": conversion_report,
},
"reasoning_generation": reasoning_generation_report
or {
"attempted": False,
"ok": False,
"generated_count": 0,
"status": "not_needed",
},
"validation": {
"ok": bool(validation_report.get("ok")),
"record_count": validation_report.get("record_count", 0),
"schema_error_count": validation_report.get("schema_error_count", 0),
"conflict_count": validation_report.get("conflict_count", 0),
"report_path": str(validation_dir / "training_data_validation_report.json"),
},
"status": "ready_for_next_training_run" if not blockers else "blocked_after_breakout",
"blockers": blockers,
"next_actions": next_actions,
"created_at": utc_now(),
}
write_json(breakout_dir / "stall_breakout_plan.json", plan)
write_json(breakout_dir / "source_intake_manifest.json", intake_manifest)
if live_intake_manifest is not None:
write_json(breakout_dir / "live_source_intake_manifest.json", live_intake_manifest)
write_json(
breakout_dir / "license_manifest.json",
{"records": records, "created_at": utc_now()},
)
return plan

Xet Storage Details

Size:
17.9 kB
·
Xet hash:
a0682a058c2e0fa1ff3f86cc8bf55efdaba9132a8a783575dcbea0c040e47e0b

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