mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
research-sdk: structure runs as falsifiable tests — hypothesis, notes, verdict
Add the scientific-method frame to the experiment handle: state a hypothesis + prediction up front, .note() the running log, and .conclude(verdict ∈ proven|refuted|inconclusive, because=...) to seal it. A refutation is recorded as clearly and durably as a proof. finish() defaults a stated hypothesis to inconclusive — a finished run never silently reads as proof. All fields serialize into the record's meta alongside the auto-captured provenance (git sha + lib versions), so a verdict is queryable evidence. Backward-compatible: every new param is optional.
This commit is contained in:
@@ -37,9 +37,12 @@ def client():
|
||||
return _default
|
||||
|
||||
|
||||
def experiment(kind, subject, task, metric="accuracy", n_total=0, note=""):
|
||||
"""Get/create an experiment handle on the default client."""
|
||||
return client().experiment(kind, subject, task, metric=metric, n_total=n_total, note=note)
|
||||
def experiment(kind, subject, task, metric="accuracy", n_total=0, note="",
|
||||
hypothesis="", predict=""):
|
||||
"""Get/create an experiment handle on the default client. Pass `hypothesis` + `predict`
|
||||
to structure it as a falsifiable test that conclude(verdict) can prove or refute."""
|
||||
return client().experiment(kind, subject, task, metric=metric, n_total=n_total, note=note,
|
||||
hypothesis=hypothesis, predict=predict)
|
||||
|
||||
|
||||
def query(project=None, kind=None, canonical=True):
|
||||
|
||||
@@ -5,12 +5,22 @@ ZERO-CONFIG surface so hand-rolling is obviously the worse choice:
|
||||
|
||||
import hanzo_research as research
|
||||
|
||||
# A benchmark run — accumulate attempts, seal with the score.
|
||||
exp = research.experiment("benchmark", "grok-4.5", "gpqa_diamond") # get/create → handle
|
||||
exp.record("q1", "grok-4.5", {"answer": "A", "correct": True}) # an attempt, idempotent
|
||||
exp.snapshot(png_bytes) # a board snapshot artifact
|
||||
exp.report(markdown_text) # a generated report artifact
|
||||
exp.finish(value=94.3) # seal the run (or use `with`)
|
||||
|
||||
# A kernel-perf run structured as a falsifiable test — state the claim, log what you
|
||||
# see, then PROVE or REFUTE it. A refutation is a first-class, durable result.
|
||||
k = research.experiment("kernel-perf", "matvec_q4k_f32_blk", "vulkan/6144x2048",
|
||||
metric="ratio_vs_hand",
|
||||
hypothesis="the DSL f32-direct matvec beats the hand kernel",
|
||||
predict="DSL/hand >= 1.0 cold in-engine at the dominant FFN shape")
|
||||
k.note("cold in-engine A/B, evo gfx1151, quiet window, 3 runs, bit-exact 2.3e-6")
|
||||
k.conclude("proven", because="1.022x at 6144 rows (loses small shapes → gate >=4096)",
|
||||
value=1.022) # git sha + lib versions auto-stamped
|
||||
|
||||
rows = research.query(project="enso-bench", kind="benchmark") # read canonical
|
||||
|
||||
Auto-instrumentation (the caller supplies NO provenance): on experiment()/finish() the
|
||||
@@ -82,11 +92,18 @@ class Research:
|
||||
return self._req("POST", "/v1/research/grants", g)
|
||||
|
||||
# ── ergonomic surface ───────────────────────────────────────────────────────
|
||||
def experiment(self, kind, subject, task, metric="accuracy", n_total=0, note=""):
|
||||
def experiment(self, kind, subject, task, metric="accuracy", n_total=0, note="",
|
||||
hypothesis="", predict=""):
|
||||
"""Get/create the experiment for (kind, subject, task) and return a handle. Auto-
|
||||
captures provenance + the self-documenting narrative (the caller's docstring, the
|
||||
commit messages since this experiment's last run, and an optional note)."""
|
||||
return Experiment(self, kind, subject, task, metric=metric, n_total=n_total, note=note)
|
||||
commit messages since this experiment's last run, and an optional note).
|
||||
|
||||
Structure it as a falsifiable test: `hypothesis` is the claim under test and
|
||||
`predict` is the observation that would confirm it — stated up front so a later
|
||||
conclude(verdict) can PROVE or REFUTE it unambiguously. A refutation is a
|
||||
first-class result, recorded as clearly as a proof."""
|
||||
return Experiment(self, kind, subject, task, metric=metric, n_total=n_total,
|
||||
note=note, hypothesis=hypothesis, predict=predict)
|
||||
|
||||
def query(self, project=None, kind=None, canonical=True):
|
||||
"""Read canonical experiments (the answered latest-run view). canonical=False is
|
||||
@@ -122,13 +139,21 @@ class Experiment:
|
||||
snapshot()/report() file diary artifacts; finish() seals the run with its final
|
||||
number + the auto-captured narrative. Usable as a context manager (finish on exit)."""
|
||||
|
||||
def __init__(self, client, kind, subject, task, metric="accuracy", n_total=0, note=""):
|
||||
def __init__(self, client, kind, subject, task, metric="accuracy", n_total=0, note="",
|
||||
hypothesis="", predict=""):
|
||||
self.c = client
|
||||
self.kind, self.subject, self.task, self.metric = kind, subject, task, metric
|
||||
self.id = f"{kind}:{subject}:{task}"
|
||||
self.n_total = n_total
|
||||
self._ok = 0
|
||||
self._n = 0
|
||||
# The falsifiable frame, stated up front so the verdict is unambiguous: the claim
|
||||
# under test, and the observation that would confirm it.
|
||||
self.hypothesis = hypothesis
|
||||
self.predict = predict
|
||||
self.verdict = "" # proven | refuted | inconclusive — set by conclude()
|
||||
self.because = "" # the reasoning that earns the verdict
|
||||
self._log = [] # the running notes/logs that travel with the run
|
||||
# Zero-config, self-documenting provenance. THREE narrative sources, captured with
|
||||
# nothing asked of the caller: (a) the calling code's docstring — what this
|
||||
# experiment IS; (b) the commit messages since this experiment's last recorded run
|
||||
@@ -143,13 +168,36 @@ class Experiment:
|
||||
# Post the run as in-flight so the ops board sees it immediately.
|
||||
self._post(status="running", value=0.0)
|
||||
|
||||
def note(self, text):
|
||||
"""Append to the running log — the 'what I saw / thought' trail that travels with
|
||||
the run into the record. Chainable."""
|
||||
self._log.append(text)
|
||||
return self
|
||||
|
||||
def conclude(self, verdict, because="", value=None):
|
||||
"""Seal the experiment with its epistemic VERDICT ∈ {proven, refuted, inconclusive}
|
||||
(distinct from execution status) and the reasoning that earns it, then finish the
|
||||
run. A refutation is recorded as clearly and durably as a proof — the whole point of
|
||||
an evidentiary layer. Returns the sealed record."""
|
||||
if verdict not in ("proven", "refuted", "inconclusive"):
|
||||
raise ValueError(f"verdict must be proven|refuted|inconclusive, got {verdict!r}")
|
||||
self.verdict = verdict
|
||||
self.because = because
|
||||
return self.finish(value=value)
|
||||
|
||||
def _post(self, status, value):
|
||||
e = {
|
||||
"id": self.id, "kind": self.kind, "subject": self.subject, "task": self.task,
|
||||
"metric": self.metric, "value": value, "n": self._n, "n_total": self.n_total,
|
||||
"status": status, "git_sha": self.prov["git_sha"], "git_branch": self.prov["git_branch"],
|
||||
"git_dirty": self.prov["git_dirty"], "lib_versions": self.libs,
|
||||
"meta": {"doc": self.doc, "commits": self.commits, "note": self.note, "host": self.host},
|
||||
# meta carries the scientific frame + the self-documenting narrative. hypothesis/
|
||||
# predict/verdict/because make the run a falsifiable claim with a clear result;
|
||||
# verdict is queryable evidence (e.g. every refuted lever) — promoting it to a
|
||||
# first-class column is the follow-up, the shape is fixed here.
|
||||
"meta": {"doc": self.doc, "commits": self.commits, "note": self.note, "host": self.host,
|
||||
"hypothesis": self.hypothesis, "predict": self.predict,
|
||||
"verdict": self.verdict, "because": self.because, "log": self._log},
|
||||
}
|
||||
return self.c.ingest(experiments=[e])
|
||||
|
||||
@@ -193,9 +241,12 @@ class Experiment:
|
||||
def finish(self, value=None):
|
||||
"""Seal the run: post the final number (computed from recorded attempts if not
|
||||
given) + status complete. A new version supersedes the in-flight one; the prior
|
||||
is retained."""
|
||||
is retained. If a hypothesis was stated but no verdict declared, it defaults to
|
||||
`inconclusive` — a finished run never silently reads as a proof."""
|
||||
if value is None:
|
||||
value = round(100.0 * self._ok / self._n, 2) if self._n else 0.0
|
||||
if self.hypothesis and not self.verdict:
|
||||
self.verdict = "inconclusive"
|
||||
return self._post(status="complete", value=value)
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
Reference in New Issue
Block a user