File size: 6,026 Bytes
4bec8eb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | """
exec_checks.py — single source of truth for exec-gold hygiene + match canonicalization.
WHY THIS EXISTS
---------------
The data generator, the cleaner, the gold census, and the runtime verification ladder
must agree on (a) what counts as a malformed exec gold and (b) how two outputs are
compared for equality. When each reimplements its own rules they drift, and you get
the failure we already saw: bash checked as python, structured tag-output flagged
"unstructured". One module, imported everywhere:
build_hyper_dataset.py -> repair() + degeneracy() at author time
remove_junk.py -> repair() + degeneracy() + python_for_compiled()
audit_gold.py -> (recommended) import well-formedness helpers here
runtime verifier -> canonicalize() for match-against-gold
PRECISION vs RECALL (important design split)
--------------------------------------------
audit_gold is RECALL-oriented: flag anything suspicious so a human reads it.
remove_junk is PRECISION-oriented: drop only what is junk under ALL plausible task
intents. The functions here are labeled so each caller picks the right ones:
* degeneracy() -> precision-safe drop (junk under any intent)
* python_for_compiled() -> precision-safe drop (a Python def is never the answer to
a C++/Rust/Go/Java task in anything we author)
* repair() -> lossless-intent normalization (strip wrapping fence /
leading "Sure, here is:" preamble) — safe to always apply
* canonicalize() -> equality normalization for match-against-gold
Dependency-free (stdlib only) so every consumer can import it without pulling torch.
"""
from __future__ import annotations
import json
import re
from collections import Counter
# Compiled / typed languages where Qwen tends to fall back to Python.
COMPILED_LANGS = {"c++", "cpp", "rust", "go", "golang", "java", "c", "c#", "csharp"}
# Leading conversational preamble that occasionally survives the open/exec prompt.
_PREAMBLE_RE = re.compile(
r"^\s*(sure[,!.]?\s*|certainly[,!.]?\s*|of course[,!.]?\s*|"
r"here(?:'s| is| are)\b[^\n:]*:?\s*|below is\b[^\n:]*:?\s*|"
r"the (?:answer|result|output) is[:]?\s*)",
re.I)
_PY_DEF = re.compile(r"(?:^|\n)\s*def\s+\w+\s*\([^)]*\)\s*:", re.M)
_PY_IMPORT = re.compile(r"(?:^|\n)\s*(?:import\s+\w+|from\s+\w+\s+import)\b", re.M)
def strip_fences(s):
"""Strip a SINGLE wrapping ```lang ... ``` fence enclosing the WHOLE string.
Conservative: only when fence-count == 2 and it wraps everything; multi-block,
inline-fenced, and non-fenced strings are left untouched."""
if not isinstance(s, str):
return s
t = s.strip()
if not (t.startswith("```") and t.endswith("```") and len(t) >= 6):
return s
if t.count("```") != 2:
return s
nl = t.find("\n")
if nl == -1:
return s
inner = t[nl + 1:].rstrip()
if inner.endswith("```"):
inner = inner[:-3]
return inner.strip()
def strip_preamble(s):
"""Remove leading conversational clauses ('Sure, here is the code:'). Iterates to
a fixed point (capped) so stacked clauses are handled; leaves the artifact intact."""
if not isinstance(s, str):
return s
for _ in range(3):
new = _PREAMBLE_RE.sub("", s, count=1)
if new == s:
break
s = new
return s
def repair(s):
"""Lossless-intent normalization applied to any OUTPUT (never inputs): drop a
wrapping fence, then a leading preamble. Safe to always apply."""
if not isinstance(s, str):
return s
return strip_preamble(strip_fences(s)).strip()
def degeneracy(s):
"""Return a reason string if the output is catastrophically degenerate (empty or
a repetition loop), else None. Bad under ALL task intents -> precision-safe drop."""
if not isinstance(s, str):
return "non-string"
t = s.strip()
if not t:
return "empty"
lines = [ln for ln in t.splitlines() if ln.strip()]
if len(lines) >= 6:
top = Counter(lines).most_common(1)[0][1]
if top / len(lines) > 0.6:
return "repeated-line loop"
toks = t.split()
if len(toks) >= 20:
top = Counter(toks).most_common(1)[0][1]
if top / len(toks) > 0.7:
return "repeated-token loop"
return None
def python_for_compiled(domain, out):
"""High-precision: OUTPUT is clearly a Python function/script authored for a
COMPILED-language task. Returns a reason or None. Gates on domain so it self-skips
everywhere else."""
if (domain or "").strip().lower() not in COMPILED_LANGS:
return None
if not isinstance(out, str):
return None
head = out[:400]
c_family_syntax = (";" in head) or ("{" in head) or ("#include" in head)
looks_python = bool(_PY_DEF.search(out) or _PY_IMPORT.search(head))
if looks_python and not c_family_syntax:
return "python authored for compiled-language task"
return None
def canonicalize(s):
"""Equality normalization for match-against-gold. The runtime verifier and any
eval-time match MUST use this same function so 'equal' means the same thing
everywhere. JSON is canonicalized (sorted keys, no whitespace); everything else
is whitespace-collapsed and stripped. Extend per-type as the ladder grows
(e.g. numeric rounding tolerance), but keep it HERE so it stays shared."""
if not isinstance(s, str):
return s
t = s.strip()
try:
return json.dumps(json.loads(t), sort_keys=True, separators=(",", ":"))
except Exception:
return re.sub(r"\s+", " ", t)
def matches(pred, gold):
"""Deterministic equality after canonicalization. The strongest exec rung when a
gold exists (eval-time / consensus). NOT available at runtime for a NOVEL request
with no gold — there you fall to parse/compile (floor) or RAG-match (knowledge)."""
return canonicalize(pred) == canonicalize(gold)
|