feat(pilot): add full-cohort GPT-assisted diagnostic
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# P0: full-cohort GPT-assisted diagnostic (2026-10-28)
|
||||
|
||||
**All 65 items have a fresh source-role description, a fresh semantic judgment and a
|
||||
completed deterministic alignment. This is a same-assistant diagnostic, not an independent
|
||||
benchmark or a completion of the preregistered Qwen gate. No gate decision is issued.**
|
||||
|
||||
## Scope and provenance
|
||||
|
||||
The owner requested that the current GPT assistant reassess the entire cohort after the
|
||||
Qwen run was interrupted by provider billing failures. Every item was reviewed again;
|
||||
no Qwen detector output was reused, and the incomplete Qwen run was not patched with GPT
|
||||
outputs. The earlier labels and Qwen files remain unchanged in the original worktree.
|
||||
This branch starts at main `5d531c9` and does not depend on the separate billing-recovery PR.
|
||||
|
||||
The source-role structures and four-way judgments were authored directly by the assistant
|
||||
in this chat, frozen before computing the new alignment statistics, and replayed by an
|
||||
offline script. They were not responses obtained through a GPT API endpoint. The exact
|
||||
deployed model identifier, temperature and chat token usage are unavailable; they are not
|
||||
invented. There were **zero new external model API calls** for this diagnostic, and no
|
||||
DashScope credential was loaded.
|
||||
|
||||
The assistant had already seen the earlier labels, Lean code, detector implementation and
|
||||
aggregate compilation statistics. It also viewed passages and Lean together during this
|
||||
review. Thus the source descriptions are **not a blinded source-only extraction** and the
|
||||
new judgments are **not independent of the reference labels**. Reading only a passage when
|
||||
writing a role does not remove prior context exposure. Syntax and proof defects are
|
||||
separated from recoverable semantic meaning; no new Lean compilation or library API audit
|
||||
was performed. An independent human review remains necessary for research claims.
|
||||
|
||||
## Results for all 65 items
|
||||
|
||||
| Fresh semantic judgment | Items |
|
||||
|---|---:|
|
||||
| Faithful | 25 |
|
||||
| Minor drift | 13 |
|
||||
| Wrong | 24 |
|
||||
| Not formalizable from the extracted specification | 3 |
|
||||
| Total | 65 |
|
||||
|
||||
The strict binary assistant judge flags every verdict except faithful. The full four-way
|
||||
judgment, error categories and an individual explanation are retained for every item.
|
||||
Explanations and role descriptions are private because they paraphrase the source passages.
|
||||
|
||||
The unchanged deterministic role matcher flagged **55/65** items. It flagged all **24**
|
||||
items newly judged wrong, but also **16/25** newly judged faithful. These are comparisons
|
||||
between dependent components of the same diagnostic, not an independently measured recall
|
||||
or false-positive rate. They identify an over-sensitive diagnostic behavior worth auditing.
|
||||
|
||||
Common conflict categories across all items include missing objects (41 items), degenerate
|
||||
projections (15), goal mismatch (15), missing hypotheses (14), extra hypotheses (13),
|
||||
quantifier conflicts (7) and type conflicts (5). Categories overlap. In particular, a named
|
||||
source quantity can be computed inline in Lean, a product representation can replace an
|
||||
abstract isomorphic group, and a bundled type can encode conditions without separate
|
||||
binders. The current lexical matcher does not establish equivalence of those representations.
|
||||
These are hypotheses for a later matcher audit, not measured causal explanations for every flag.
|
||||
No matching threshold, concept table or authored assessment was changed after the scores
|
||||
were inspected.
|
||||
|
||||
## Relation to the frozen earlier AI labels
|
||||
|
||||
The fresh four-way judgments agree with **59/65** earlier labels. Six changed:
|
||||
|
||||
| Blind ID | Earlier AI label | Fresh judgment |
|
||||
|---|---|---|
|
||||
| P02 | Faithful | Minor drift |
|
||||
| P18 | Faithful | Minor drift |
|
||||
| P23 | Faithful | Minor drift |
|
||||
| P34 | Faithful | Minor drift |
|
||||
| P51 | Faithful | Minor drift |
|
||||
| P57 | Wrong | Minor drift |
|
||||
|
||||
The six changes concern representation, type/scope and coverage judgments. They demonstrate
|
||||
annotation instability and should be independently adjudicated, not silently overwritten.
|
||||
The 59/65 agreement is **not an accuracy estimate**.
|
||||
|
||||
For debugging continuity, the summary contains confusion counts and Wilson intervals against
|
||||
the frozen earlier AI labels. The primary comparison excludes earlier minor-drift and
|
||||
not-formalizable labels (55 items); the secondary comparison includes minor drift as positive
|
||||
(62 items). These tables are explicitly named `reference_agreement`. They cannot establish
|
||||
that GPT or role alignment outperforms Qwen. The Qwen coverage is incomplete and this pass
|
||||
has different provenance and no independent ground truth. `decision` is null; no GO,
|
||||
PARTIAL or NO-GO is computed.
|
||||
|
||||
## Artifacts and replay
|
||||
|
||||
Tracked, text-free artifacts:
|
||||
|
||||
- `experiments/P0-pilot/results/gpt-diagnostic-summary.json`: provenance, input/assessment/code
|
||||
hashes, distributions, fixed matching thresholds and non-independent agreement statistics.
|
||||
- `experiments/P0-pilot/results/gpt-diagnostic-items.json`: all 65 blind IDs, verdicts, error
|
||||
categories, compile flags, role scores and conflict categories; no passages or explanations.
|
||||
- `experiments/P0-pilot/assistant_diagnostic.py`: validates authored inputs and replays the
|
||||
existing projection, lint, alignment and statistics code offline.
|
||||
- `tests/test_assistant_diagnostic.py`: completeness, identity, schema, offline replay and
|
||||
public-output privacy regressions using synthetic text.
|
||||
|
||||
Private artifacts under the gitignored `experiments/P0-pilot/data/gpt-diagnostic/`:
|
||||
|
||||
- `items.jsonl`: source-inclusive copy of the unchanged cohort.
|
||||
- `assistant-assessments.json`: all newly authored source structures and judgments, frozen
|
||||
before alignment statistics were inspected.
|
||||
- `results.jsonl`: complete replay outputs for the 65 items.
|
||||
- `review.html`: local, self-contained per-item source/Lean/reason/role/alignment review.
|
||||
- `original-hashes.json`: snapshot of the earlier Qwen files and reference labels for
|
||||
verifying that this work did not change them.
|
||||
|
||||
Run from the repository root with the private files present:
|
||||
|
||||
```sh
|
||||
PYTHONPATH=. python3 experiments/P0-pilot/assistant_diagnostic.py \
|
||||
--items experiments/P0-pilot/data/gpt-diagnostic/items.jsonl \
|
||||
--key experiments/P0-pilot/labels/key.json \
|
||||
--assessments experiments/P0-pilot/data/gpt-diagnostic/assistant-assessments.json \
|
||||
--labels experiments/P0-pilot/labels/labels.json \
|
||||
--private-out experiments/P0-pilot/data/gpt-diagnostic \
|
||||
--summary experiments/P0-pilot/results/gpt-diagnostic-summary.json \
|
||||
--public-items experiments/P0-pilot/results/gpt-diagnostic-items.json
|
||||
```
|
||||
|
||||
The replay is deterministic; it does not regenerate GPT judgments, call a model, or compile
|
||||
Lean. Without the private assessment file, public readers can inspect the categorical
|
||||
results but cannot reconstruct the authored descriptions and explanations from hashes.
|
||||
|
||||
152 offline unit tests, repository-wide Ruff and diff checks passed locally without a Lean
|
||||
or Docker build. Remote Python and real Lean verification use the owner-confirmed
|
||||
`ubuntu24.04-x64-lean4.35` tag; the delivered PR records their current status. The existing
|
||||
PR workflow skips the Docker image job. No Phase 1 work has started.
|
||||
@@ -52,3 +52,16 @@ Labeling must be finished before anyone opens `results.jsonl`.
|
||||
by the judge. No threshold, concept table or detector prompt was tuned for these labels.
|
||||
- Raw passages, labeling HTML and model responses remain in the ignored `data/` directory.
|
||||
Only verdicts, abstract annotation notes, hashes and text-free summaries belong in Git.
|
||||
|
||||
## Separate GPT-assisted diagnostic
|
||||
|
||||
The owner requested a complete 65-item reassessment by the current GPT assistant on
|
||||
2026-10-28. This is separate from the incomplete Qwen run: no outputs are mixed and
|
||||
neither the earlier labels nor Qwen files are overwritten. The assistant had prior
|
||||
exposure to the reference labels and Lean; this is not blind or independent validation.
|
||||
No GO/PARTIAL/NO-GO decision is issued.
|
||||
|
||||
See `docs/experiments/P0-gpt-diagnostic.md` for all counts, limitations, artifact locations
|
||||
and the offline replay command. The tracked `results/gpt-diagnostic-*.json` files contain
|
||||
only categorical/numeric outputs and audit metadata. The authored source structures,
|
||||
reasons and full review page stay in the ignored `data/gpt-diagnostic/` directory.
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"""Validate and replay a current-chat assistant review without calling any model.
|
||||
|
||||
Private source schemas and reasons stay in data/. Public summaries contain only
|
||||
categories, counts, numeric scores, hashes and an explicit non-independent provenance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import math
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from leanbench.align import DEFAULT_THRESHOLD, GOAL_THRESHOLD, align
|
||||
from leanbench.evalstats import auc, confusion, wilson
|
||||
from leanbench.lint import lint
|
||||
from leanbench.project import project
|
||||
from leanbench.srcsimplex import parse_source_simplex
|
||||
|
||||
VERDICTS = {"faithful", "minor_drift", "wrong", "not_formalizable"}
|
||||
ERRORS = {"missing_hypothesis", "extra_hypothesis", "wrong_type", "quantifier",
|
||||
"wrong_conclusion", "degenerate", "hallucinated_api", "other"}
|
||||
|
||||
|
||||
def digest(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def finite(value):
|
||||
if isinstance(value, dict):
|
||||
return {k: finite(v) for k, v in value.items()}
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [finite(v) for v in value]
|
||||
if isinstance(value, float) and not math.isfinite(value):
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
def unique(rows, field):
|
||||
indexed = {row[field]: row for row in rows}
|
||||
if len(indexed) != len(rows):
|
||||
raise ValueError(f"Duplicate {field}")
|
||||
return indexed
|
||||
|
||||
|
||||
def evaluate(items, key, assessment):
|
||||
"""Keep model-authored assessments separate from deterministic detector outputs."""
|
||||
by_id = unique(items, "id")
|
||||
records = unique(assessment["records"], "blind_id")
|
||||
if (not key or len(set(key.values())) != len(key) or set(records) != set(key)
|
||||
or set(by_id) != set(key.values())):
|
||||
raise ValueError("Items, key and assessments must cover the same cohort exactly once")
|
||||
output = []
|
||||
for blind, item_id in key.items():
|
||||
item, record = by_id[item_id], records[blind]
|
||||
if record["id"] != item_id or not item.get("source_text", "").strip():
|
||||
raise ValueError(f"Missing source text or mismatched item for {blind}")
|
||||
judge = record["judge"]
|
||||
if (judge.get("verdict") not in VERDICTS or type(judge.get("faithful")) is not bool
|
||||
or judge["faithful"] != (judge["verdict"] == "faithful")
|
||||
or not isinstance(judge.get("reason"), str) or not judge["reason"].strip()
|
||||
or not isinstance(judge.get("errors"), list)
|
||||
or not set(judge["errors"]) <= ERRORS):
|
||||
raise ValueError(f"Invalid authored judgment for {blind}")
|
||||
source = parse_source_simplex(record["source"])
|
||||
projection = project(item["lean_code"])
|
||||
alignment = align(source, projection)
|
||||
output.append({"id": item_id, "blind_id": blind,
|
||||
"source": source.as_dict(), "judge": judge,
|
||||
"projection": projection.as_dict(), "role": alignment.as_dict(),
|
||||
"lint": lint(projection), "compiled": bool(item.get("verified")),
|
||||
"errors": {}})
|
||||
return output
|
||||
|
||||
|
||||
def public_items(rows, labels):
|
||||
if set(labels) != {row["blind_id"] for row in rows}:
|
||||
raise ValueError("Reference labels must cover the entire cohort")
|
||||
out = []
|
||||
for row in rows:
|
||||
reference = labels[row["blind_id"]]["verdict"]
|
||||
if reference not in VERDICTS:
|
||||
raise ValueError("Invalid reference verdict")
|
||||
out.append({"blind_id": row["blind_id"], "reference_verdict": reference,
|
||||
"assistant_verdict": row["judge"]["verdict"],
|
||||
"assistant_error_types": row["judge"]["errors"],
|
||||
"verdict_agrees": reference == row["judge"]["verdict"],
|
||||
"compiled": row["compiled"], "lint_flagged": bool(row["lint"]),
|
||||
"role_flagged": row["role"]["flagged"],
|
||||
"role_score": row["role"]["score"],
|
||||
"role_conflict_types": row["role"]["errors"],
|
||||
"source_role_count": len(row["source"]["roles"]),
|
||||
"lean_role_count": len(row["projection"]["roles"]),
|
||||
"degenerate_projection": row["projection"]["degenerate"]})
|
||||
return out
|
||||
|
||||
|
||||
def agreement(rows, labels, secondary=False):
|
||||
cohort = [r for r in rows if labels[r["blind_id"]]["verdict"] != "not_formalizable"
|
||||
and (secondary or labels[r["blind_id"]]["verdict"] != "minor_drift")]
|
||||
positives = {"wrong", "minor_drift"} if secondary else {"wrong"}
|
||||
target = [labels[r["blind_id"]]["verdict"] in positives for r in cohort]
|
||||
out = {}
|
||||
for name, flags in {
|
||||
"role": [r["role"]["flagged"] for r in cohort],
|
||||
"assistant_judge": [not r["judge"]["faithful"] for r in cohort],
|
||||
"lint": [bool(r["lint"]) for r in cohort],
|
||||
"compile_only": [not r["compiled"] for r in cohort],
|
||||
}.items():
|
||||
counts = confusion(flags, target)
|
||||
out[name] = {**counts.as_dict(), "precision_ci": wilson(counts.tp, counts.tp + counts.fp)}
|
||||
out["role"]["auc"] = auc([-r["role"]["score"] for r in cohort], target)
|
||||
return {"n": len(cohort), "metrics_are_non_independent_reference_agreement": True,
|
||||
"detectors": out}
|
||||
|
||||
|
||||
def write_review(path, rows, items):
|
||||
by_id = {r["id"]: r for r in items}
|
||||
sections = []
|
||||
for row in rows:
|
||||
item = by_id[row["id"]]
|
||||
sections.append(
|
||||
f'<section id="{html.escape(row["blind_id"])}"><h2>{row["blind_id"]}</h2>'
|
||||
f'<h3>Passage</h3><pre>{html.escape(item["source_text"])}</pre>'
|
||||
f'<h3>Lean</h3><pre>{html.escape(item["lean_code"])}</pre>'
|
||||
f'<h3>Fresh assistant judgment: {row["judge"]["verdict"]}</h3>'
|
||||
f'<p>{html.escape(row["judge"]["reason"])}</p>'
|
||||
f'<h3>Source roles</h3><pre>{html.escape(json.dumps(row["source"], indent=2, ensure_ascii=False))}</pre>'
|
||||
f'<h3>Deterministic alignment</h3><pre>{html.escape(json.dumps(row["role"], indent=2, ensure_ascii=False))}</pre></section>'
|
||||
)
|
||||
path.write_text('<!doctype html><html lang="en"><meta charset="utf-8">'
|
||||
'<title>P0 GPT-assisted diagnostic</title><style>'
|
||||
'body{font:16px/1.6 system-ui;max-width:1050px;margin:32px auto;padding:0 20px}'
|
||||
'pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f4f5f6;padding:16px}'
|
||||
'section{border-top:1px solid #ccc;margin:40px 0}</style>'
|
||||
'<h1>P0: 65-item GPT-assisted diagnostic</h1>'
|
||||
'<p>Private review. Same-assistant, non-blind diagnosis; no independent accuracy or gate decision. '
|
||||
'Passages and model-derived descriptions must remain outside Git.</p>'
|
||||
+ ''.join(sections) + '</html>', encoding="utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--items", type=Path, required=True)
|
||||
parser.add_argument("--key", type=Path, required=True)
|
||||
parser.add_argument("--assessments", type=Path, required=True)
|
||||
parser.add_argument("--labels", type=Path, required=True)
|
||||
parser.add_argument("--private-out", type=Path, required=True)
|
||||
parser.add_argument("--summary", type=Path, required=True)
|
||||
parser.add_argument("--public-items", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
if "data" not in args.private_out.parts:
|
||||
parser.error("Private output must be under the gitignored data/ directory")
|
||||
items = [json.loads(x) for x in args.items.read_text().splitlines() if x.strip()]
|
||||
key = json.loads(args.key.read_text())["key"]
|
||||
assessment = json.loads(args.assessments.read_text())
|
||||
provenance = assessment["provenance"]
|
||||
if (provenance["items_sha256"] != digest(args.items)
|
||||
or provenance["key_sha256"] != digest(args.key)):
|
||||
parser.error("Authored review is bound to different inputs or blind key")
|
||||
rows = evaluate(items, key, assessment)
|
||||
labels = json.loads(args.labels.read_text())["labels"]
|
||||
public = public_items(rows, labels)
|
||||
summary = {
|
||||
"experiment": "P0-GPT-diagnostic", "status": "complete", "n": len(rows),
|
||||
"provenance": provenance, "decision": None,
|
||||
"decision_withheld_reason": "Same-assistant, non-blind diagnostic; not the preregistered Qwen pilot or independent ground-truth validation.",
|
||||
"hashes": {"items": digest(args.items), "key": digest(args.key),
|
||||
"assessments": digest(args.assessments), "reference_labels": digest(args.labels),
|
||||
"replay_script": digest(Path(__file__)),
|
||||
**{f"leanbench/{name}.py": digest(Path(__file__).resolve().parents[2] / "leanbench" / f"{name}.py")
|
||||
for name in ("align", "project", "srcsimplex", "lint")}},
|
||||
"fixed_thresholds": {"matching": DEFAULT_THRESHOLD, "goal": GOAL_THRESHOLD},
|
||||
"verdict_counts": dict(Counter(r["judge"]["verdict"] for r in rows)),
|
||||
"role_flagged": sum(r["role"]["flagged"] for r in rows),
|
||||
"lint_flagged": sum(bool(r["lint"]) for r in rows),
|
||||
"compiled": sum(r["compiled"] for r in rows),
|
||||
"degenerate_projection": sum(r["projection"]["degenerate"] for r in rows),
|
||||
"exact_verdict_agreement": sum(r["verdict_agrees"] for r in public),
|
||||
"changed_verdicts": [r["blind_id"] for r in public if not r["verdict_agrees"]],
|
||||
"primary_reference_agreement": agreement(rows, labels),
|
||||
"secondary_reference_agreement": agreement(rows, labels, secondary=True),
|
||||
"role_flagged_by_new_verdict": {v: {"n": sum(r["judge"]["verdict"] == v for r in rows),
|
||||
"flagged": sum(r["judge"]["verdict"] == v and r["role"]["flagged"] for r in rows)}
|
||||
for v in sorted(VERDICTS)},
|
||||
}
|
||||
args.private_out.mkdir(parents=True, exist_ok=True)
|
||||
(args.private_out / "results.jsonl").write_text(''.join(json.dumps(r, ensure_ascii=False) + '\n' for r in rows))
|
||||
write_review(args.private_out / "review.html", rows, items)
|
||||
args.summary.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.public_items.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.summary.write_text(json.dumps(finite(summary), indent=2, allow_nan=False) + '\n')
|
||||
args.public_items.write_text(json.dumps(public, indent=2, allow_nan=False) + '\n')
|
||||
print(json.dumps({k: summary[k] for k in ("n", "status", "verdict_counts", "role_flagged", "exact_verdict_agreement", "decision")}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"experiment": "P0-GPT-diagnostic",
|
||||
"status": "complete",
|
||||
"n": 65,
|
||||
"provenance": {
|
||||
"run_kind": "current-chat assistant-authored diagnostic; not an API benchmark",
|
||||
"evaluator": "Codex GPT assistant; exact deployed model identifier unavailable",
|
||||
"authored_at": "2026-10-28T11:23:06.275167+00:00",
|
||||
"items_sha256": "487ecd22919a5e3b0174a2846ab8fe7c0daf4d3feccccf8ea6571aec04c41bed",
|
||||
"key_sha256": "f827cac92ee4e907554fadcc7c0e006ef2b829350f74e7f8b54ca11945e62836",
|
||||
"reference_labels_seen_in_prior_chat": true,
|
||||
"lean_seen_while_authoring_source_roles": true,
|
||||
"new_external_api_calls": 0,
|
||||
"assistant_token_usage": null,
|
||||
"limitations": [
|
||||
"Same assistant previously authored the reference labels; agreement is not independent accuracy.",
|
||||
"Prior context contains the earlier review and detector code. This is not a blinded source-only extraction or a fresh-model replicate.",
|
||||
"All judgments are manually authored in this chat, not responses from a GPT API endpoint.",
|
||||
"Semantics are assessed conditionally on recoverable notation. No new Lean compilation or library API audit is performed.",
|
||||
"The strict Boolean judge flags all verdicts except faithful, including not_formalizable; four-way verdicts are also retained."
|
||||
]
|
||||
},
|
||||
"decision": null,
|
||||
"decision_withheld_reason": "Same-assistant, non-blind diagnostic; not the preregistered Qwen pilot or independent ground-truth validation.",
|
||||
"hashes": {
|
||||
"items": "487ecd22919a5e3b0174a2846ab8fe7c0daf4d3feccccf8ea6571aec04c41bed",
|
||||
"key": "f827cac92ee4e907554fadcc7c0e006ef2b829350f74e7f8b54ca11945e62836",
|
||||
"assessments": "f12735802ba0d4d94b776a91581a13a508eab0ce90ba8973a6c9c4e4d1390272",
|
||||
"reference_labels": "1552dba85e75426799151384e7e28c9a0b1072ed4c862df0876df89920d2a638",
|
||||
"replay_script": "5c02e3d26761ce0b4e38d4be9e65b95bfd8db048c9c7c73edaac17e654e10c0f",
|
||||
"leanbench/align.py": "730e44f1158e6e441e4745dff7324e565b89b6e5148017c3fd93f4c69c22c9ce",
|
||||
"leanbench/project.py": "8ed76924b463ba418ceb732d0fcb6c572a548d94fe7f23759c67746da24d9430",
|
||||
"leanbench/srcsimplex.py": "d10b785299ceb17cadc4e97737eef74183ed390d04b4571a702cd4fd42b069d0",
|
||||
"leanbench/lint.py": "4396e879312f370cd70beeffcbd7ffeab8a41ee1704cc1f235dbbcd6e843fd68"
|
||||
},
|
||||
"fixed_thresholds": {
|
||||
"matching": 0.3,
|
||||
"goal": 0.2
|
||||
},
|
||||
"verdict_counts": {
|
||||
"faithful": 25,
|
||||
"minor_drift": 13,
|
||||
"wrong": 24,
|
||||
"not_formalizable": 3
|
||||
},
|
||||
"role_flagged": 55,
|
||||
"lint_flagged": 21,
|
||||
"compiled": 10,
|
||||
"degenerate_projection": 15,
|
||||
"exact_verdict_agreement": 59,
|
||||
"changed_verdicts": [
|
||||
"P02",
|
||||
"P18",
|
||||
"P23",
|
||||
"P34",
|
||||
"P51",
|
||||
"P57"
|
||||
],
|
||||
"primary_reference_agreement": {
|
||||
"n": 55,
|
||||
"metrics_are_non_independent_reference_agreement": true,
|
||||
"detectors": {
|
||||
"role": {
|
||||
"tp": 25,
|
||||
"fp": 21,
|
||||
"fn": 0,
|
||||
"tn": 9,
|
||||
"n": 55,
|
||||
"recall": 1.0,
|
||||
"precision": 0.5434782608695652,
|
||||
"false_flag_rate": 0.7,
|
||||
"f1": 0.704225352112676,
|
||||
"recall_ci": [
|
||||
0.8668035060468213,
|
||||
1.0
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.5212387898833981,
|
||||
0.8333543735604105
|
||||
],
|
||||
"precision_ci": [
|
||||
0.4017993496608042,
|
||||
0.67845489578878
|
||||
],
|
||||
"auc": 0.61
|
||||
},
|
||||
"assistant_judge": {
|
||||
"tp": 25,
|
||||
"fp": 5,
|
||||
"fn": 0,
|
||||
"tn": 25,
|
||||
"n": 55,
|
||||
"recall": 1.0,
|
||||
"precision": 0.8333333333333334,
|
||||
"false_flag_rate": 0.16666666666666666,
|
||||
"f1": 0.9090909090909091,
|
||||
"recall_ci": [
|
||||
0.8668035060468213,
|
||||
1.0
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.07336434240351683,
|
||||
0.33564705185680177
|
||||
],
|
||||
"precision_ci": [
|
||||
0.6643529481431982,
|
||||
0.9266356575964831
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
"tp": 12,
|
||||
"fp": 5,
|
||||
"fn": 13,
|
||||
"tn": 25,
|
||||
"n": 55,
|
||||
"recall": 0.48,
|
||||
"precision": 0.7058823529411765,
|
||||
"false_flag_rate": 0.16666666666666666,
|
||||
"f1": 0.5714285714285713,
|
||||
"recall_ci": [
|
||||
0.30031003928960015,
|
||||
0.6650178204685269
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.07336434240351683,
|
||||
0.33564705185680177
|
||||
],
|
||||
"precision_ci": [
|
||||
0.468664666737861,
|
||||
0.8672020613444361
|
||||
]
|
||||
},
|
||||
"compile_only": {
|
||||
"tp": 24,
|
||||
"fp": 24,
|
||||
"fn": 1,
|
||||
"tn": 6,
|
||||
"n": 55,
|
||||
"recall": 0.96,
|
||||
"precision": 0.5,
|
||||
"false_flag_rate": 0.8,
|
||||
"f1": 0.6575342465753424,
|
||||
"recall_ci": [
|
||||
0.8045552759497747,
|
||||
0.9929039496133008
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.6269395261897256,
|
||||
0.9049502189759876
|
||||
],
|
||||
"precision_ci": [
|
||||
0.36389098932037767,
|
||||
0.6361090106796223
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondary_reference_agreement": {
|
||||
"n": 62,
|
||||
"metrics_are_non_independent_reference_agreement": true,
|
||||
"detectors": {
|
||||
"role": {
|
||||
"tp": 32,
|
||||
"fp": 21,
|
||||
"fn": 0,
|
||||
"tn": 9,
|
||||
"n": 62,
|
||||
"recall": 1.0,
|
||||
"precision": 0.6037735849056604,
|
||||
"false_flag_rate": 0.7,
|
||||
"f1": 0.7529411764705882,
|
||||
"recall_ci": [
|
||||
0.8928172849426366,
|
||||
1.0
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.5212387898833981,
|
||||
0.8333543735604105
|
||||
],
|
||||
"precision_ci": [
|
||||
0.4694123578888073,
|
||||
0.7241078808096105
|
||||
],
|
||||
"auc": 0.590625
|
||||
},
|
||||
"assistant_judge": {
|
||||
"tp": 32,
|
||||
"fp": 5,
|
||||
"fn": 0,
|
||||
"tn": 25,
|
||||
"n": 62,
|
||||
"recall": 1.0,
|
||||
"precision": 0.8648648648648649,
|
||||
"false_flag_rate": 0.16666666666666666,
|
||||
"f1": 0.927536231884058,
|
||||
"recall_ci": [
|
||||
0.8928172849426366,
|
||||
1.0
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.07336434240351683,
|
||||
0.33564705185680177
|
||||
],
|
||||
"precision_ci": [
|
||||
0.7202227252929444,
|
||||
0.9408679273749238
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
"tp": 14,
|
||||
"fp": 5,
|
||||
"fn": 18,
|
||||
"tn": 25,
|
||||
"n": 62,
|
||||
"recall": 0.4375,
|
||||
"precision": 0.7368421052631579,
|
||||
"false_flag_rate": 0.16666666666666666,
|
||||
"f1": 0.5490196078431372,
|
||||
"recall_ci": [
|
||||
0.2816508596944317,
|
||||
0.6067469796877387
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.07336434240351683,
|
||||
0.33564705185680177
|
||||
],
|
||||
"precision_ci": [
|
||||
0.5120803155892935,
|
||||
0.8819376166045982
|
||||
]
|
||||
},
|
||||
"compile_only": {
|
||||
"tp": 29,
|
||||
"fp": 24,
|
||||
"fn": 3,
|
||||
"tn": 6,
|
||||
"n": 62,
|
||||
"recall": 0.90625,
|
||||
"precision": 0.5471698113207547,
|
||||
"false_flag_rate": 0.8,
|
||||
"f1": 0.6823529411764705,
|
||||
"recall_ci": [
|
||||
0.7578150066422116,
|
||||
0.9675990373736805
|
||||
],
|
||||
"false_flag_ci": [
|
||||
0.6269395261897256,
|
||||
0.9049502189759876
|
||||
],
|
||||
"precision_ci": [
|
||||
0.41453742646439184,
|
||||
0.6734263183985253
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"role_flagged_by_new_verdict": {
|
||||
"faithful": {
|
||||
"n": 25,
|
||||
"flagged": 16
|
||||
},
|
||||
"minor_drift": {
|
||||
"n": 13,
|
||||
"flagged": 13
|
||||
},
|
||||
"not_formalizable": {
|
||||
"n": 3,
|
||||
"flagged": 2
|
||||
},
|
||||
"wrong": {
|
||||
"n": 24,
|
||||
"flagged": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Current-chat review replay is offline, complete and text-free at its public boundary."""
|
||||
import copy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
PATH = Path(__file__).resolve().parents[1] / "experiments/P0-pilot/assistant_diagnostic.py"
|
||||
SPEC = importlib.util.spec_from_file_location("assistant_diagnostic", PATH)
|
||||
DIAG = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(DIAG)
|
||||
|
||||
ITEM = {"id": "synthetic", "source_text": "Synthetic private prose.",
|
||||
"lean_code": "theorem t (n : Nat) : n = n := rfl", "verified": True}
|
||||
ASSESSMENT = {"records": [{"id": "synthetic", "blind_id": "P01", "source": {
|
||||
"roles": [{"kind": "object", "symbols": ["n"], "description": "Private object wording",
|
||||
"keywords": ["natural"]},
|
||||
{"kind": "goal", "description": "Private goal wording", "keywords": ["equal"]}],
|
||||
"goal_has_exists": False},
|
||||
"judge": {"verdict": "faithful", "faithful": True, "errors": [],
|
||||
"reason": "Private reviewer explanation"}}]}
|
||||
|
||||
|
||||
def test_offline_replay_and_text_free_public_output(monkeypatch):
|
||||
def no_network(*args, **kwargs):
|
||||
raise AssertionError("Replay must not call an API")
|
||||
|
||||
monkeypatch.setattr("urllib.request.urlopen", no_network)
|
||||
rows = DIAG.evaluate([ITEM], {"P01": "synthetic"}, ASSESSMENT)
|
||||
assert len(rows) == 1 and rows[0]["errors"] == {}
|
||||
public = DIAG.public_items(rows, {"P01": {"verdict": "faithful"}})
|
||||
assert public[0]["assistant_verdict"] == "faithful"
|
||||
assert not any(word in str(public) for word in ["Private", "Synthetic", "explanation"])
|
||||
assert all(key not in public[0] for key in ["source", "judge", "reason", "lean_code", "source_text"])
|
||||
|
||||
|
||||
def test_duplicate_or_missing_assessments_are_rejected():
|
||||
assessment = copy.deepcopy(ASSESSMENT)
|
||||
assessment["records"] *= 2
|
||||
with pytest.raises(ValueError, match="Duplicate"):
|
||||
DIAG.evaluate([ITEM], {"P01": "synthetic"}, assessment)
|
||||
with pytest.raises(ValueError, match="same cohort"):
|
||||
DIAG.evaluate([ITEM], {"P01": "synthetic"}, {"records": []})
|
||||
|
||||
|
||||
def test_mismatched_item_and_missing_source_are_rejected():
|
||||
assessment = copy.deepcopy(ASSESSMENT)
|
||||
assessment["records"][0]["id"] = "different"
|
||||
with pytest.raises(ValueError, match="mismatched"):
|
||||
DIAG.evaluate([ITEM], {"P01": "synthetic"}, assessment)
|
||||
with pytest.raises(ValueError, match="Missing source"):
|
||||
DIAG.evaluate([{**ITEM, "source_text": ""}], {"P01": "synthetic"}, ASSESSMENT)
|
||||
|
||||
|
||||
def test_judge_cannot_disagree_with_its_boolean_flag():
|
||||
assessment = copy.deepcopy(ASSESSMENT)
|
||||
assessment["records"][0]["judge"]["faithful"] = False
|
||||
with pytest.raises(ValueError, match="Invalid authored judgment"):
|
||||
DIAG.evaluate([ITEM], {"P01": "synthetic"}, assessment)
|
||||
|
||||
|
||||
def test_invalid_source_schema_is_rejected():
|
||||
assessment = copy.deepcopy(ASSESSMENT)
|
||||
assessment["records"][0]["source"]["roles"] = []
|
||||
with pytest.raises(ValueError, match="goal"):
|
||||
DIAG.evaluate([ITEM], {"P01": "synthetic"}, assessment)
|
||||
|
||||
|
||||
def test_agreement_is_explicitly_not_independent_and_has_no_gate():
|
||||
rows = DIAG.evaluate([ITEM], {"P01": "synthetic"}, ASSESSMENT)
|
||||
result = DIAG.agreement(rows, {"P01": {"verdict": "faithful"}})
|
||||
assert result["metrics_are_non_independent_reference_agreement"] is True
|
||||
assert "decision" not in result
|
||||
assert DIAG.finite(float("nan")) is None
|
||||
Reference in New Issue
Block a user