Initial commit
This commit is contained in:
@@ -0,0 +1,826 @@
|
||||
"""Deep 编译流水线编排。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
from dataclasses import asdict, replace
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from scripts.dynamic_compile.fast.models import RolloutTrace
|
||||
from scripts.dynamic_compile.fast.storage import (
|
||||
atomic_write_json,
|
||||
atomic_write_jsonl,
|
||||
atomic_write_text,
|
||||
load_json,
|
||||
package_hash,
|
||||
read_jsonl,
|
||||
sha256_file,
|
||||
sha256_text,
|
||||
)
|
||||
from scripts.dynamic_compile.fast.optimization.analyzer import SemanticClient
|
||||
|
||||
from .adapters.benchflow import (
|
||||
BenchFlowInput,
|
||||
SkillsBenchDevelopmentRolloutRunner,
|
||||
load_benchflow_input,
|
||||
)
|
||||
from .adapters.semantic import DeepAnalyzer, valid_evidence
|
||||
from .core.models import (
|
||||
CellScore,
|
||||
Coordinate,
|
||||
DIMENSIONS,
|
||||
LocalEdit,
|
||||
ScoreMatrix,
|
||||
SkillUnit,
|
||||
)
|
||||
from .core.markdown import (
|
||||
parse_paragraphs,
|
||||
parse_sections,
|
||||
preserve_unit_boundary,
|
||||
replace_unit_text,
|
||||
validate_edit,
|
||||
)
|
||||
|
||||
|
||||
ROLLOUTS = 3
|
||||
MAX_SECTION_ITERATIONS = 6
|
||||
MAX_PARAGRAPH_ITERATIONS = 3
|
||||
MAX_EDIT_GENERATION_ATTEMPTS = 3
|
||||
GAP_THRESHOLD = 0.375
|
||||
REJECTION_LIMIT = 2
|
||||
MAX_PARALLEL = 3
|
||||
DEFAULT_MODEL = "ali/deepseek-v4-pro-0813"
|
||||
|
||||
|
||||
def _log(message: str) -> None:
|
||||
print(f"[deep] {message}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def _trace_from_dict(value: dict[str, Any]) -> RolloutTrace:
|
||||
return RolloutTrace(**value)
|
||||
|
||||
|
||||
def _column_to_dict(column: dict[str, CellScore]) -> dict[str, Any]:
|
||||
return {unit_id: asdict(cell) for unit_id, cell in column.items()}
|
||||
|
||||
|
||||
def _column_from_dict(
|
||||
value: dict[str, Any], traces: list[RolloutTrace]
|
||||
) -> dict[str, CellScore]:
|
||||
return {
|
||||
unit_id: CellScore(
|
||||
float(cell["score"]), valid_evidence(cell.get("evidence"), traces), str(cell["reason"])
|
||||
)
|
||||
for unit_id, cell in value.items()
|
||||
}
|
||||
|
||||
|
||||
class DeepLoop:
|
||||
def __init__(
|
||||
self,
|
||||
run_dir: Path,
|
||||
analyzer: DeepAnalyzer | None = None,
|
||||
runner: Any | None = None,
|
||||
):
|
||||
self.run_dir = run_dir.resolve()
|
||||
self.state_path = self.run_dir / "run.json"
|
||||
state = load_json(self.state_path)
|
||||
if not isinstance(state, dict):
|
||||
raise ValueError(f"invalid or missing run state: {self.state_path}")
|
||||
self.state = state
|
||||
self.temp = self.run_dir / ".tmp"
|
||||
self.current = self.temp / "current"
|
||||
self.model = state.get("model", state.get("semantic_model", DEFAULT_MODEL))
|
||||
self.analyzer = analyzer or DeepAnalyzer(
|
||||
SemanticClient(self.model), MAX_PARALLEL,
|
||||
)
|
||||
context = BenchFlowInput(
|
||||
state["task"]["name"],
|
||||
Path(state["task"]["directory"]),
|
||||
state["task"]["agent"],
|
||||
state["task"]["model"],
|
||||
state["task"]["prompt"],
|
||||
[],
|
||||
)
|
||||
self.runner = runner or SkillsBenchDevelopmentRolloutRunner(
|
||||
context,
|
||||
Path(tempfile.gettempdir()) / "skill-compiler-deep" / state["run_id"],
|
||||
MAX_PARALLEL,
|
||||
archive_root=self.run_dir / "rollouts",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
skill: Path,
|
||||
traces: Path,
|
||||
output: Path | None = None,
|
||||
*,
|
||||
model: str = DEFAULT_MODEL,
|
||||
analyzer: DeepAnalyzer | None = None,
|
||||
runner: Any | None = None,
|
||||
) -> "DeepLoop":
|
||||
skill = skill.resolve()
|
||||
if not (skill / "SKILL.md").is_file():
|
||||
raise ValueError("--skill must be a skill package containing SKILL.md")
|
||||
context = load_benchflow_input(traces)
|
||||
target = (output or skill.parent / f"{skill.name}-deep").resolve()
|
||||
if target.exists() and any(target.iterdir()):
|
||||
raise ValueError(f"deep run directory is not empty: {target}")
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(skill, target / "S_fast")
|
||||
(target / ".tmp").mkdir()
|
||||
(target / "levels").mkdir()
|
||||
shutil.copytree(target / "S_fast", target / ".tmp" / "current")
|
||||
atomic_write_jsonl(target / "input-traces.jsonl", [asdict(trace) for trace in context.traces])
|
||||
state = {
|
||||
"run_id": uuid.uuid4().hex[:12],
|
||||
"status": "created",
|
||||
"model": model,
|
||||
"skill_name": skill.name,
|
||||
"task": {
|
||||
"name": context.task_name,
|
||||
"directory": str(context.task_dir),
|
||||
"agent": context.agent,
|
||||
"model": context.model,
|
||||
"prompt": context.prompt,
|
||||
},
|
||||
"current_rollouts": str(traces.resolve()),
|
||||
"current_rollout_skill_sha256": sha256_file(skill / "SKILL.md"),
|
||||
"levels": {},
|
||||
}
|
||||
atomic_write_json(target / "run.json", state)
|
||||
return cls(target, analyzer=analyzer, runner=runner)
|
||||
|
||||
def _save(self) -> None:
|
||||
atomic_write_json(self.state_path, self.state)
|
||||
|
||||
def _prompt(self) -> str:
|
||||
return str(self.state["task"]["prompt"])
|
||||
|
||||
def _current_text(self) -> str:
|
||||
return (self.current / "SKILL.md").read_text(encoding="utf-8")
|
||||
|
||||
def _load_or_rollout(
|
||||
self,
|
||||
package: Path,
|
||||
trace_path: Path,
|
||||
batch_id: str,
|
||||
seed: list[RolloutTrace] | None = None,
|
||||
) -> list[RolloutTrace]:
|
||||
if trace_path.is_file():
|
||||
return [_trace_from_dict(item) for item in read_jsonl(trace_path)]
|
||||
if seed is not None:
|
||||
traces = seed
|
||||
else:
|
||||
_log(f"Starting {ROLLOUTS} rollouts for {batch_id}")
|
||||
traces = self.runner.run_batch(
|
||||
package,
|
||||
self._prompt(),
|
||||
batch_id,
|
||||
str(self.state["task"]["name"]),
|
||||
ROLLOUTS,
|
||||
progress=lambda done, total, trace_id: _log(
|
||||
f"Rollout {done}/{total} complete: {trace_id}"
|
||||
),
|
||||
)
|
||||
atomic_write_jsonl(trace_path, [asdict(trace) for trace in traces])
|
||||
return traces
|
||||
|
||||
def _load_or_matrix(
|
||||
self,
|
||||
path: Path,
|
||||
level: str,
|
||||
units: list[SkillUnit],
|
||||
traces: list[RolloutTrace],
|
||||
) -> ScoreMatrix:
|
||||
value = load_json(path)
|
||||
if isinstance(value, dict):
|
||||
return ScoreMatrix.from_dict(value)
|
||||
columns_dir = path.parent / "matrix-columns"
|
||||
cached_columns: dict[str, dict[str, CellScore]] = {}
|
||||
for dimension in DIMENSIONS:
|
||||
cached = load_json(columns_dir / f"{dimension.lower().replace(' ', '-')}.json")
|
||||
if isinstance(cached, dict):
|
||||
cached_columns[dimension] = _column_from_dict(cached, traces)
|
||||
|
||||
def save_column(dimension: str, column: dict[str, CellScore]) -> None:
|
||||
atomic_write_json(
|
||||
columns_dir / f"{dimension.lower().replace(' ', '-')}.json",
|
||||
_column_to_dict(column),
|
||||
)
|
||||
|
||||
_log(f"Scoring full {level} matrix with {self.model}")
|
||||
matrix = self.analyzer.score_matrix(
|
||||
self._current_text(), self._prompt(), units, traces, level,
|
||||
existing_columns=cached_columns,
|
||||
result_callback=save_column,
|
||||
)
|
||||
atomic_write_json(path, matrix.to_dict())
|
||||
return matrix
|
||||
|
||||
def _refresh_matrix(
|
||||
self,
|
||||
level: str,
|
||||
units: list[SkillUnit],
|
||||
traces: list[RolloutTrace],
|
||||
) -> ScoreMatrix:
|
||||
level_state = self.state["levels"][level]
|
||||
number = int(level_state.get("refreshes", 0)) + 1
|
||||
path = (
|
||||
self.run_dir
|
||||
/ "levels"
|
||||
/ level
|
||||
/ "refreshes"
|
||||
/ f"refresh-{number:02d}"
|
||||
/ "matrix.json"
|
||||
)
|
||||
_log(f"Refreshing full {level} matrix")
|
||||
matrix = self._load_or_matrix(path, level, units, traces)
|
||||
atomic_write_json(self.run_dir / "levels" / level / "matrix.json", matrix.to_dict())
|
||||
level_state["refreshes"] = number
|
||||
level_state.pop("active_dimension", None)
|
||||
self._save()
|
||||
return matrix
|
||||
|
||||
def _decisions(self, level: str | None = None) -> list[dict[str, Any]]:
|
||||
roots = (
|
||||
[self.run_dir / "levels" / level]
|
||||
if level else list((self.run_dir / "levels").glob("*"))
|
||||
)
|
||||
decisions = []
|
||||
for root in roots:
|
||||
for path in sorted((root / "iterations").glob("iteration-*/decision.json")):
|
||||
value = load_json(path)
|
||||
if isinstance(value, dict):
|
||||
decisions.append(value)
|
||||
return decisions
|
||||
|
||||
def _rejected(self, coordinate: Coordinate) -> list[dict[str, Any]]:
|
||||
return [
|
||||
decision["rejected_edit"]
|
||||
for decision in self._decisions()
|
||||
if not decision["accepted"]
|
||||
and decision["rejected_edit"]["unit_id"] == coordinate.unit_id
|
||||
and decision["rejected_edit"]["dimension"] == coordinate.dimension
|
||||
]
|
||||
|
||||
def _exhausted(self, level: str) -> set[tuple[str, str]]:
|
||||
counts: dict[tuple[str, str], int] = {}
|
||||
for decision in self._decisions(level):
|
||||
if decision["accepted"]:
|
||||
continue
|
||||
rejected = decision["rejected_edit"]
|
||||
key = (rejected["unit_id"], rejected["dimension"])
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
return {key for key, count in counts.items() if count >= REJECTION_LIMIT}
|
||||
|
||||
@staticmethod
|
||||
def _block_dimension(level_state: dict[str, Any], dimension: str) -> None:
|
||||
blocked = set(level_state.get("blocked_dimensions", []))
|
||||
blocked.add(dimension)
|
||||
level_state["blocked_dimensions"] = sorted(blocked)
|
||||
|
||||
@staticmethod
|
||||
def _candidate_units(
|
||||
units: list[SkillUnit], target: SkillUnit, new_text: str
|
||||
) -> list[SkillUnit]:
|
||||
delta = len(new_text) - len(target.text)
|
||||
updated = []
|
||||
for unit in units:
|
||||
value = replace(unit)
|
||||
if unit.unit_id == target.unit_id:
|
||||
value.text = new_text
|
||||
value.end = value.start + len(new_text)
|
||||
elif unit.start >= target.end:
|
||||
value.start += delta
|
||||
value.end += delta
|
||||
updated.append(value)
|
||||
return updated
|
||||
|
||||
def _apply_edit(
|
||||
self,
|
||||
unit: SkillUnit,
|
||||
edit: LocalEdit,
|
||||
candidate: Path,
|
||||
section_depth: int | None,
|
||||
) -> None:
|
||||
self._validate_edit_candidate(unit, edit, section_depth)
|
||||
text = self._current_text()
|
||||
changed = replace_unit_text(text, unit, edit.new_text)
|
||||
temporary = candidate.with_name(f".{candidate.name}.{uuid.uuid4().hex}.tmp")
|
||||
shutil.copytree(self.current, temporary)
|
||||
atomic_write_text(temporary / "SKILL.md", changed)
|
||||
if candidate.exists():
|
||||
shutil.rmtree(candidate)
|
||||
candidate.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary.rename(candidate)
|
||||
|
||||
def _validate_edit_candidate(
|
||||
self,
|
||||
unit: SkillUnit,
|
||||
edit: LocalEdit,
|
||||
section_depth: int | None,
|
||||
) -> None:
|
||||
"""Validate a local edit against both unit and whole-document invariants."""
|
||||
|
||||
validate_edit(unit, edit.new_text, section_depth)
|
||||
text = self._current_text()
|
||||
if text[unit.start:unit.end] != unit.text:
|
||||
raise ValueError("target unit no longer matches current SKILL.md")
|
||||
changed = replace_unit_text(text, unit, edit.new_text)
|
||||
before = [(item.heading, item.heading_depth) for item in parse_sections(text)]
|
||||
after = [(item.heading, item.heading_depth) for item in parse_sections(changed)]
|
||||
if before != after:
|
||||
raise ValueError("local edit changed section boundaries")
|
||||
|
||||
@staticmethod
|
||||
def _validation_feedback(
|
||||
attempts: list[dict[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"edit_summary": str(item.get("edit_summary", "invalid generated edit")),
|
||||
"reject_reason": f"structural_validation_failed: {item['error']}",
|
||||
"new_text_hash": str(item.get("new_text_hash", "")),
|
||||
}
|
||||
for item in attempts
|
||||
]
|
||||
|
||||
def _valid_edit_or_rejection(
|
||||
self,
|
||||
*,
|
||||
level: str,
|
||||
number: int,
|
||||
iteration_dir: Path,
|
||||
unit: SkillUnit,
|
||||
coordinate: Coordinate,
|
||||
cell: CellScore,
|
||||
section_depth: int | None,
|
||||
) -> tuple[LocalEdit | None, bool, list[dict[str, Any]]]:
|
||||
"""Load or generate a valid edit, feeding structural failures back to the model."""
|
||||
|
||||
edit_path = iteration_dir / "edit.json"
|
||||
attempts_path = iteration_dir / "edit-attempts.json"
|
||||
attempts_value = load_json(attempts_path, [])
|
||||
attempts = attempts_value if isinstance(attempts_value, list) else []
|
||||
cached_value = load_json(edit_path)
|
||||
|
||||
if isinstance(cached_value, dict):
|
||||
try:
|
||||
cached = LocalEdit.from_dict(cached_value)
|
||||
cached.new_text = preserve_unit_boundary(unit, cached.new_text)
|
||||
self._validate_edit_candidate(unit, cached, section_depth)
|
||||
return cached, False, attempts
|
||||
except ValueError as exc:
|
||||
text = str(cached_value.get("new_text", ""))
|
||||
attempts.append({
|
||||
"source": "cached",
|
||||
"error": str(exc),
|
||||
"edit_summary": str(cached_value.get("edit_summary", "")),
|
||||
"new_text_hash": sha256_text(text) if text else "",
|
||||
})
|
||||
atomic_write_json(attempts_path, attempts)
|
||||
_log(
|
||||
f"{level} iteration {number}: cached local edit is invalid: {exc}; "
|
||||
"regenerating"
|
||||
)
|
||||
|
||||
for attempt in range(1, MAX_EDIT_GENERATION_ATTEMPTS + 1):
|
||||
_log(
|
||||
f"{level} iteration {number}: generating local edit for "
|
||||
f"{coordinate.unit_id}/{coordinate.dimension} "
|
||||
f"(attempt {attempt}/{MAX_EDIT_GENERATION_ATTEMPTS})"
|
||||
)
|
||||
feedback = self._rejected(coordinate) + self._validation_feedback(attempts)
|
||||
edit: LocalEdit | None = None
|
||||
try:
|
||||
edit = self.analyzer.generate_edit(
|
||||
coordinate,
|
||||
unit,
|
||||
cell,
|
||||
feedback,
|
||||
self._prompt(),
|
||||
)
|
||||
edit.new_text = preserve_unit_boundary(unit, edit.new_text)
|
||||
self._validate_edit_candidate(unit, edit, section_depth)
|
||||
except ValueError as exc:
|
||||
text = edit.new_text if edit is not None else ""
|
||||
attempts.append({
|
||||
"source": "generated",
|
||||
"generation_attempt": attempt,
|
||||
"error": str(exc),
|
||||
"edit_summary": edit.edit_summary if edit is not None else "",
|
||||
"new_text_hash": sha256_text(text) if text else "",
|
||||
})
|
||||
atomic_write_json(attempts_path, attempts)
|
||||
_log(
|
||||
f"{level} iteration {number}: local edit validation failed "
|
||||
f"(attempt {attempt}/{MAX_EDIT_GENERATION_ATTEMPTS}): {exc}"
|
||||
)
|
||||
continue
|
||||
|
||||
assert edit is not None
|
||||
atomic_write_json(edit_path, asdict(edit))
|
||||
return edit, True, attempts
|
||||
|
||||
return None, True, attempts
|
||||
|
||||
def _commit_iteration(
|
||||
self,
|
||||
level: str,
|
||||
number: int,
|
||||
iteration_dir: Path,
|
||||
matrix: ScoreMatrix,
|
||||
current_trace_path: Path,
|
||||
) -> ScoreMatrix:
|
||||
decision = load_json(iteration_dir / "decision.json")
|
||||
if not isinstance(decision, dict):
|
||||
raise ValueError("missing iteration decision")
|
||||
if decision["accepted"]:
|
||||
candidate = iteration_dir / "candidate" / self.state["skill_name"]
|
||||
replacement = self.temp / "next-current"
|
||||
shutil.rmtree(replacement, ignore_errors=True)
|
||||
shutil.copytree(candidate, replacement)
|
||||
shutil.rmtree(self.current)
|
||||
replacement.rename(self.current)
|
||||
matrix = ScoreMatrix.from_dict(decision["matrix_after"])
|
||||
atomic_write_json(self.run_dir / "levels" / level / "matrix.json", matrix.to_dict())
|
||||
candidate_traces = read_jsonl(iteration_dir / "candidate-traces.jsonl")
|
||||
atomic_write_jsonl(current_trace_path, candidate_traces)
|
||||
rollout_output = decision.get("rollout_output")
|
||||
rollout_skill_sha256 = decision.get("rollout_skill_sha256")
|
||||
if isinstance(rollout_output, str) and isinstance(rollout_skill_sha256, str):
|
||||
self.state["current_rollouts"] = rollout_output
|
||||
self.state["current_rollout_skill_sha256"] = rollout_skill_sha256
|
||||
else:
|
||||
self.state.pop("current_rollouts", None)
|
||||
self.state.pop("current_rollout_skill_sha256", None)
|
||||
level_state = self.state["levels"][level]
|
||||
if int(level_state.get("iterations", 0)) < number:
|
||||
level_state["iterations"] = number
|
||||
self._save()
|
||||
return matrix
|
||||
|
||||
def _run_iteration(
|
||||
self,
|
||||
level: str,
|
||||
number: int,
|
||||
level_dir: Path,
|
||||
matrix: ScoreMatrix,
|
||||
coordinate: Coordinate,
|
||||
current_trace_path: Path,
|
||||
section_depth: int | None,
|
||||
) -> ScoreMatrix:
|
||||
iteration_dir = level_dir / "iterations" / f"iteration-{number:02d}"
|
||||
iteration_dir.mkdir(parents=True, exist_ok=True)
|
||||
unit = matrix.unit(coordinate.unit_id)
|
||||
edit, regenerated, validation_attempts = self._valid_edit_or_rejection(
|
||||
level=level,
|
||||
number=number,
|
||||
iteration_dir=iteration_dir,
|
||||
unit=unit,
|
||||
coordinate=coordinate,
|
||||
cell=matrix.columns[coordinate.dimension][coordinate.unit_id],
|
||||
section_depth=section_depth,
|
||||
)
|
||||
if edit is None:
|
||||
decision = {
|
||||
"coordinate": asdict(coordinate),
|
||||
"accepted": False,
|
||||
"reason": "edit_validation_exhausted",
|
||||
"target_delta": 0.0,
|
||||
"validation_attempts": validation_attempts,
|
||||
"rejected_edit": {
|
||||
"unit_id": coordinate.unit_id,
|
||||
"dimension": coordinate.dimension,
|
||||
"edit_summary": (
|
||||
"Could not generate a structurally valid local edit after "
|
||||
f"{MAX_EDIT_GENERATION_ATTEMPTS} attempts."
|
||||
),
|
||||
"score_change": 0.0,
|
||||
"reject_reason": "edit_validation_exhausted",
|
||||
"new_text_hash": str(
|
||||
validation_attempts[-1].get("new_text_hash", "")
|
||||
) if validation_attempts else "",
|
||||
},
|
||||
}
|
||||
atomic_write_json(iteration_dir / "decision.json", decision)
|
||||
_log(
|
||||
f"{level} iteration {number}: local edit validation exhausted; "
|
||||
"recording rejection and continuing"
|
||||
)
|
||||
return self._commit_iteration(
|
||||
level, number, iteration_dir, matrix, current_trace_path
|
||||
)
|
||||
edit_hash = sha256_text(edit.new_text)
|
||||
if any(item["new_text_hash"] == edit_hash for item in self._rejected(coordinate)):
|
||||
decision = {
|
||||
"coordinate": asdict(coordinate),
|
||||
"accepted": False,
|
||||
"reason": "exact_duplicate_rejected_edit",
|
||||
"target_delta": 0.0,
|
||||
"rejected_edit": {
|
||||
"unit_id": coordinate.unit_id,
|
||||
"dimension": coordinate.dimension,
|
||||
"edit_summary": edit.edit_summary,
|
||||
"score_change": 0.0,
|
||||
"reject_reason": "exact_duplicate_rejected_edit",
|
||||
"new_text_hash": edit_hash,
|
||||
},
|
||||
}
|
||||
atomic_write_json(iteration_dir / "decision.json", decision)
|
||||
return self._commit_iteration(level, number, iteration_dir, matrix, current_trace_path)
|
||||
candidate = iteration_dir / "candidate" / self.state["skill_name"]
|
||||
if regenerated:
|
||||
shutil.rmtree(candidate, ignore_errors=True)
|
||||
for stale in (
|
||||
iteration_dir / "candidate-traces.jsonl",
|
||||
iteration_dir / "comparison.json",
|
||||
):
|
||||
if stale.exists():
|
||||
stale.unlink()
|
||||
if not (candidate / "SKILL.md").is_file():
|
||||
self._apply_edit(unit, edit, candidate, section_depth)
|
||||
candidate_units = self._candidate_units(matrix.units, unit, edit.new_text)
|
||||
candidate_skill_sha256 = sha256_file(candidate / "SKILL.md")
|
||||
batch_id = (
|
||||
f"{self.state['run_id']}-deep-{level}-i{number:02d}-"
|
||||
f"{candidate_skill_sha256[:12]}"
|
||||
)
|
||||
traces = self._load_or_rollout(
|
||||
candidate,
|
||||
iteration_dir / "candidate-traces.jsonl",
|
||||
batch_id,
|
||||
)
|
||||
comparison_path = iteration_dir / "comparison.json"
|
||||
comparison = load_json(comparison_path)
|
||||
if not isinstance(comparison, dict):
|
||||
_log(
|
||||
f"{level} iteration {number}: comparing "
|
||||
f"{coordinate.unit_id}/{coordinate.dimension}"
|
||||
)
|
||||
incumbent_traces = [
|
||||
_trace_from_dict(item) for item in read_jsonl(current_trace_path)
|
||||
]
|
||||
comparison = self.analyzer.compare_cell(
|
||||
self._prompt(),
|
||||
unit,
|
||||
next(item for item in candidate_units if item.unit_id == unit.unit_id),
|
||||
incumbent_traces,
|
||||
traces,
|
||||
coordinate.dimension,
|
||||
)
|
||||
incumbent_timeout_rate = (
|
||||
sum(trace.timed_out is True for trace in incumbent_traces)
|
||||
/ len(incumbent_traces)
|
||||
)
|
||||
candidate_timeout_rate = (
|
||||
sum(trace.timed_out is True for trace in traces) / len(traces)
|
||||
)
|
||||
if (
|
||||
candidate_timeout_rate > incumbent_timeout_rate
|
||||
and "timeout" not in comparison["runtime_regressions"]
|
||||
):
|
||||
comparison["runtime_regressions"].append("timeout")
|
||||
atomic_write_json(comparison_path, comparison)
|
||||
|
||||
delta = float(comparison["candidate_score"]) - float(
|
||||
comparison["incumbent_score"]
|
||||
)
|
||||
if not comparison["task_relevant"]:
|
||||
accepted, reason = False, "target_unit_not_task_relevant"
|
||||
elif comparison["runtime_regressions"]:
|
||||
accepted = False
|
||||
reason = "runtime_regressed:" + ",".join(comparison["runtime_regressions"])
|
||||
elif delta < 0.5:
|
||||
accepted, reason = False, "target_cell_did_not_improve"
|
||||
else:
|
||||
accepted, reason = True, "target_improved_without_runtime_regression"
|
||||
decision: dict[str, Any] = {
|
||||
"coordinate": asdict(coordinate),
|
||||
"accepted": accepted,
|
||||
"reason": reason,
|
||||
"target_delta": delta,
|
||||
"rollout_skill_sha256": candidate_skill_sha256,
|
||||
}
|
||||
artifacts_dir = getattr(self.runner, "artifacts_dir", None)
|
||||
rollout_output = artifacts_dir(batch_id) if callable(artifacts_dir) else None
|
||||
if isinstance(rollout_output, Path):
|
||||
decision["rollout_output"] = str(rollout_output.resolve())
|
||||
if accepted:
|
||||
updated = ScoreMatrix(matrix.level, candidate_units, dict(matrix.columns))
|
||||
updated.columns[coordinate.dimension] = dict(
|
||||
matrix.columns[coordinate.dimension]
|
||||
)
|
||||
updated.columns[coordinate.dimension][coordinate.unit_id] = CellScore(
|
||||
float(comparison["candidate_score"]),
|
||||
list(comparison["candidate_evidence"]),
|
||||
str(comparison["reason"]),
|
||||
)
|
||||
decision["matrix_after"] = updated.to_dict()
|
||||
else:
|
||||
decision["rejected_edit"] = {
|
||||
"unit_id": coordinate.unit_id,
|
||||
"dimension": coordinate.dimension,
|
||||
"edit_summary": edit.edit_summary,
|
||||
"score_change": delta,
|
||||
"reject_reason": reason,
|
||||
"new_text_hash": edit_hash,
|
||||
}
|
||||
atomic_write_json(iteration_dir / "decision.json", decision)
|
||||
return self._commit_iteration(level, number, iteration_dir, matrix, current_trace_path)
|
||||
|
||||
def _run_level(
|
||||
self,
|
||||
level: str,
|
||||
units: list[SkillUnit],
|
||||
seed_traces: list[RolloutTrace] | None = None,
|
||||
section_depth: int | None = None,
|
||||
) -> tuple[ScoreMatrix, list[RolloutTrace], Coordinate | None]:
|
||||
level_dir = self.run_dir / "levels" / level
|
||||
level_dir.mkdir(parents=True, exist_ok=True)
|
||||
level_state = self.state["levels"].setdefault(level, {
|
||||
"iterations": 0, "completed": False,
|
||||
})
|
||||
current_trace_path = level_dir / "current-traces.jsonl"
|
||||
traces = self._load_or_rollout(
|
||||
self.current,
|
||||
current_trace_path,
|
||||
f"{self.state['run_id']}-deep-{level}-initial",
|
||||
seed=seed_traces,
|
||||
)
|
||||
matrix = self._load_or_matrix(level_dir / "matrix.json", level, units, traces)
|
||||
if level_state.get("completed"):
|
||||
exhausted_value = level_state.get("exhausted_coordinate")
|
||||
exhausted = Coordinate(**exhausted_value) if isinstance(exhausted_value, dict) else None
|
||||
return matrix, traces, exhausted
|
||||
exhausted_coordinate: Coordinate | None = None
|
||||
max_iterations = (
|
||||
MAX_SECTION_ITERATIONS if level == "section" else MAX_PARAGRAPH_ITERATIONS
|
||||
)
|
||||
while int(level_state["iterations"]) < max_iterations:
|
||||
excluded = self._exhausted(level)
|
||||
excluded.update(
|
||||
(unit.unit_id, dimension)
|
||||
for dimension in level_state.get("blocked_dimensions", [])
|
||||
for unit in matrix.units
|
||||
)
|
||||
pending_number = int(level_state["iterations"]) + 1
|
||||
pending_dir = level_dir / "iterations" / f"iteration-{pending_number:02d}"
|
||||
pending_decision = load_json(pending_dir / "decision.json")
|
||||
if isinstance(pending_decision, dict):
|
||||
pending_coordinate = Coordinate(**pending_decision["coordinate"])
|
||||
level_state.setdefault("active_dimension", pending_coordinate.dimension)
|
||||
matrix = self._commit_iteration(
|
||||
level, pending_number, pending_dir, matrix, current_trace_path
|
||||
)
|
||||
traces = [_trace_from_dict(item) for item in read_jsonl(current_trace_path)]
|
||||
if len(self._rejected(pending_coordinate)) >= REJECTION_LIMIT:
|
||||
exhausted_coordinate = pending_coordinate
|
||||
level_state["stop_reason"] = "coordinate_exhausted"
|
||||
level_state["exhausted_coordinate"] = asdict(pending_coordinate)
|
||||
break
|
||||
if matrix.select_coordinate(
|
||||
GAP_THRESHOLD, level_state.get("active_dimension"), excluded
|
||||
) is None:
|
||||
matrix = self._refresh_matrix(level, matrix.units, traces)
|
||||
continue
|
||||
active_dimension = level_state.get("active_dimension")
|
||||
coordinate = matrix.select_coordinate(
|
||||
GAP_THRESHOLD, active_dimension, excluded
|
||||
)
|
||||
if coordinate is None:
|
||||
if active_dimension is None:
|
||||
level_state["stop_reason"] = (
|
||||
"normalized_gap_converged"
|
||||
if max(matrix.normalized_gaps().values(), default=0.0) <= GAP_THRESHOLD
|
||||
else "available_coordinates_exhausted"
|
||||
)
|
||||
break
|
||||
matrix = self._refresh_matrix(level, matrix.units, traces)
|
||||
continue
|
||||
if active_dimension is None:
|
||||
level_state["active_dimension"] = coordinate.dimension
|
||||
self._save()
|
||||
number = int(level_state["iterations"]) + 1
|
||||
matrix = self._run_iteration(
|
||||
level, number, level_dir, matrix, coordinate,
|
||||
current_trace_path, section_depth,
|
||||
)
|
||||
traces = [_trace_from_dict(item) for item in read_jsonl(current_trace_path)]
|
||||
if len(self._rejected(coordinate)) >= REJECTION_LIMIT:
|
||||
exhausted_coordinate = coordinate
|
||||
level_state["stop_reason"] = "coordinate_exhausted"
|
||||
level_state["exhausted_coordinate"] = asdict(coordinate)
|
||||
break
|
||||
else:
|
||||
decisions = self._decisions(level)
|
||||
last = decisions[-1] if decisions else {}
|
||||
if matrix.select_coordinate(GAP_THRESHOLD) is None:
|
||||
level_state["stop_reason"] = "normalized_gap_converged"
|
||||
else:
|
||||
level_state["stop_reason"] = (
|
||||
"max_iterations_after_accept" if last.get("accepted") else "max_iterations"
|
||||
)
|
||||
level_state["completed"] = True
|
||||
self._save()
|
||||
return matrix, traces, exhausted_coordinate
|
||||
|
||||
def drive(self) -> Path:
|
||||
if self.state.get("status") == "complete" and (self.run_dir / "S_final").is_dir():
|
||||
return self.run_dir / "S_final"
|
||||
_log(f"Deep Loop start/resume: {self.run_dir}")
|
||||
input_traces = [
|
||||
_trace_from_dict(item) for item in read_jsonl(self.run_dir / "input-traces.jsonl")
|
||||
]
|
||||
while True:
|
||||
section_units = parse_sections(self._current_text())
|
||||
section_matrix, traces, exhausted = self._run_level(
|
||||
"section", section_units, seed_traces=input_traces
|
||||
)
|
||||
if exhausted is None:
|
||||
break
|
||||
target_score = section_matrix.columns[exhausted.dimension][exhausted.unit_id].score
|
||||
current_sections = parse_sections(self._current_text())
|
||||
section = next((item for item in current_sections if item.unit_id == exhausted.unit_id), None)
|
||||
paragraphs = parse_paragraphs(section) if section is not None else []
|
||||
section_state = self.state["levels"]["section"]
|
||||
if (
|
||||
target_score > 3.5
|
||||
or len(paragraphs) < 2
|
||||
or section_state.get("paragraph_returned")
|
||||
):
|
||||
self._block_dimension(section_state, exhausted.dimension)
|
||||
section_state["completed"] = False
|
||||
for key in ("stop_reason", "exhausted_coordinate", "active_dimension"):
|
||||
section_state.pop(key, None)
|
||||
self._save()
|
||||
continue
|
||||
_log(f"Descending into paragraphs of {exhausted.unit_id}")
|
||||
self.state["levels"].setdefault(
|
||||
"paragraph", {"iterations": 0, "completed": False}
|
||||
)["active_dimension"] = exhausted.dimension
|
||||
self._save()
|
||||
_, paragraph_traces, _ = self._run_level(
|
||||
"paragraph", paragraphs, seed_traces=traces,
|
||||
section_depth=section.heading_depth if section else None,
|
||||
)
|
||||
atomic_write_jsonl(
|
||||
self.run_dir / "levels" / "section" / "current-traces.jsonl",
|
||||
[asdict(trace) for trace in paragraph_traces],
|
||||
)
|
||||
section_state["completed"] = False
|
||||
section_state["paragraph_returned"] = True
|
||||
self._block_dimension(section_state, exhausted.dimension)
|
||||
for key in ("stop_reason", "exhausted_coordinate", "active_dimension"):
|
||||
section_state.pop(key, None)
|
||||
self._refresh_matrix(
|
||||
"section", parse_sections(self._current_text()), paragraph_traces
|
||||
)
|
||||
return self._complete()
|
||||
|
||||
def _complete(self) -> Path:
|
||||
target = self.run_dir / "S_final"
|
||||
if target.exists():
|
||||
shutil.rmtree(target)
|
||||
shutil.copytree(self.current, target)
|
||||
final_skill_hash = sha256_file(target / "SKILL.md")
|
||||
final_rollouts = self.state.get("current_rollouts")
|
||||
final_rollout_hash = self.state.get("current_rollout_skill_sha256")
|
||||
if (
|
||||
not isinstance(final_rollouts, str)
|
||||
or not Path(final_rollouts).is_dir()
|
||||
or final_rollout_hash != final_skill_hash
|
||||
):
|
||||
final_rollouts = None
|
||||
report = {
|
||||
"status": "complete",
|
||||
"input_package_hash": package_hash(self.run_dir / "S_fast"),
|
||||
"final_package_hash": package_hash(target),
|
||||
"final_rollouts": final_rollouts,
|
||||
"final_rollout_skill_sha256": final_skill_hash if final_rollouts else None,
|
||||
"task": self.state["task"]["name"],
|
||||
"agent": self.state["task"]["agent"],
|
||||
"target_model": self.state["task"]["model"],
|
||||
"model": self.model,
|
||||
"rollout_backend": "skillsbench_development",
|
||||
"production_rollout_backend": "blank_container_required",
|
||||
"verifier_signal_used": False,
|
||||
"levels": self.state["levels"],
|
||||
"decisions": {
|
||||
name: self._decisions(name)
|
||||
for name in ("section", "paragraph")
|
||||
if name in self.state["levels"]
|
||||
},
|
||||
}
|
||||
atomic_write_json(self.run_dir / "report.json", report)
|
||||
self.state["status"] = "complete"
|
||||
self._save()
|
||||
shutil.rmtree(self.temp, ignore_errors=True)
|
||||
_log(f"Deep Loop complete: {target}")
|
||||
return target
|
||||
Reference in New Issue
Block a user