Spaces:
Runtime error
Runtime error
| """Build a supervised fine-tune dataset by DISTILLING the node's own judgment. | |
| The live node decides with retrieval + a learned policy + a deterministic Spine. | |
| This script turns that behaviour into (prompt -> Advice JSON) pairs over a grid of | |
| (material, geometry, room) conditions, so a small Gemma can be fine-tuned to carry | |
| the same judgment in its weights. That is the "Well-Tuned" frontier the writeup | |
| names: visible memory (retrieval) stays the live path, and this bakes a copy of the | |
| judgment into the weights. | |
| Honesty: targets here are the node's *structured* output (the same Advice JSON the | |
| live call returns). Offline, that is the deterministic advisor over real retrieved | |
| precedent, so it is a faithful distillation of the system, not invented data. For a | |
| higher-fidelity teacher, run with a live model up (Ollama) so `advise()` returns the | |
| real Gemma output instead of the fallback; the pair format is identical either way. | |
| Run: uv run python -m learn.finetune.prep_dataset | |
| Out: data/finetune/sft.train.jsonl + data/finetune/sft.eval.jsonl (chat format) | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from core import seed_lessons | |
| from core.chief_engineer import advise | |
| from core.ledger import LedgerManager | |
| from core.models import Environment, GEOMETRY_TYPES, Job, MATERIALS | |
| from core.prompts import build_system_prompt | |
| from learn.policy import LearnedPolicy | |
| try: | |
| from ingest.distill import reference_block | |
| except Exception: # pragma: no cover | |
| def reference_block(_m): # type: ignore | |
| return [] | |
| OUT = Path(__file__).resolve().parents[2] / "data" / "finetune" | |
| USER_TURN = "Give your recommendation for THIS job now." | |
| # Grid: hold out two room points for eval so we measure generalization, not recall. | |
| TEMPS_TRAIN = [16, 20, 24, 30, 34] | |
| HUMS_TRAIN = [30, 45, 60, 75] | |
| TEMPS_EVAL = [22, 28] | |
| HUMS_EVAL = [38, 68] | |
| def _pair(ledger: LedgerManager, policy: LearnedPolicy, material: str, geo: str, | |
| temp: float, hum: float) -> dict: | |
| job = Job(geometry_type=geo, material=material) | |
| env = Environment(temp=float(temp), humidity=float(hum)) | |
| retrieved = ledger.retrieve(material, geo, env.temp, env.humidity) | |
| refs = reference_block(material) | |
| note = policy.policy_note(material, geo, env) | |
| system = build_system_prompt(job, env, retrieved, refs, note) | |
| advice = advise(job, env, retrieved, refs, note).advice # offline -> deterministic distillation | |
| advice.reasoning = advice.reasoning.replace("[fallback] ", "").strip() # drop the offline marker | |
| # Gemma has no system role: fold the system prompt into the first user turn, | |
| # exactly as the live inference path does (core/llm_zerogpu._build_prompt). | |
| return {"messages": [ | |
| {"role": "user", "content": f"{system}\n\n{USER_TURN}"}, | |
| {"role": "assistant", "content": advice.model_dump_json()}, | |
| ]} | |
| def build(temps: list[float], hums: list[float]) -> list[dict]: | |
| ledger = LedgerManager() | |
| seed_lessons.ensure_seeded(ledger) | |
| policy = LearnedPolicy() | |
| rows = [] | |
| for m in MATERIALS: | |
| for g in GEOMETRY_TYPES: | |
| for t in temps: | |
| for h in hums: | |
| rows.append(_pair(ledger, policy, m, g, t, h)) | |
| return rows | |
| def main() -> None: | |
| OUT.mkdir(parents=True, exist_ok=True) | |
| train = build(TEMPS_TRAIN, HUMS_TRAIN) | |
| ev = build(TEMPS_EVAL, HUMS_EVAL) | |
| (OUT / "sft.train.jsonl").write_text("\n".join(json.dumps(r) for r in train) + "\n") | |
| (OUT / "sft.eval.jsonl").write_text("\n".join(json.dumps(r) for r in ev) + "\n") | |
| print(f"train={len(train)} eval={len(ev)} → {OUT}/sft.train.jsonl + sft.eval.jsonl") | |
| print(f" ({len(MATERIALS)} materials × {len(GEOMETRY_TYPES)} geometries × grid)") | |
| print(" targets = the node's structured Advice (offline = deterministic distillation;") | |
| print(" start `ollama serve` first to distill the live Gemma instead).") | |
| if __name__ == "__main__": | |
| main() | |