Pranav2748's picture
Add src
cbc33fe verified
Raw
History Blame Contribute Delete
5.79 kB
"""
Programmatic quality gates. Free, deterministic, zero judge variance.
Contract: a story that FAILS any hard gate gets total reward 0, regardless of
what the judge said. Gates run before the judge so we can skip paying for
obviously-broken generations.
This module exists because of a specific autopsy finding. The prior run's
completeness check was:
enders = ".!?\\"'"
text[-1] in enders
which uses straight quotes only. The policy emits curly U+201D constantly, so
properly-ended stories were scored 0.3 instead of 1.0. Worse, 100% of prior-run
eval samples were truncated mid-word by a max_new_tokens wall, and the judge
rubric capped quality at 4 for truncation -- collapsing within-group quality
variance to ~0, which in GRPO means zero advantage. The quality signal was
silently dead for the entire run. Everything here is built to make that
failure loud instead of silent.
"""
from __future__ import annotations
import math
import re
import unicodedata
from dataclasses import dataclass, asdict, field
# Sentence terminators, including the curly/CJK variants the prior run missed.
TERMINALS = set('.!?…' + '"”’\'' + '。!?' + ')]}*_')
# Characters allowed to trail a real terminator (markdown emphasis, quotes).
_TRAILING_DECOR = set('*_`"”’\'» )]}')
MIN_WORDS = 150
MAX_WORDS = 600
MAX_NGRAM_REPEAT_FRAC = 0.18 # frac of 4-grams that are repeats
MIN_CHAR_ENTROPY = 3.2 # bits/char; English prose sits ~4.0-4.4
MAX_NONASCII_FRAC = 0.08 # em-dashes/curly quotes are fine; CJK dumps are not
MAX_LINE_REPEAT_FRAC = 0.25
@dataclass
class GateResult:
passed: bool
completeness: float # 1.0 ended cleanly, 0.0 truncated
n_words: int
reasons: list[str] = field(default_factory=list)
# diagnostics, logged not rewarded
repeat_4gram_frac: float = 0.0
char_entropy: float = 0.0
nonascii_frac: float = 0.0
hit_token_cap: bool = False
def as_dict(self) -> dict:
return asdict(self)
def _words(text: str) -> list[str]:
return text.split()
def _norm_tokens(text: str) -> list[str]:
return re.sub(r"[^\w\s]", " ", text.lower()).split()
def ends_cleanly(text: str) -> bool:
"""True if the story ends on a sentence terminator.
Strips trailing markdown/quote decoration first, so `...over.*` and
`...done."` and `...gone.”` all count. Also rejects the specific
mid-word cutoff signature: last token is a bare word with no terminator.
"""
t = text.rstrip()
if not t:
return False
while t and t[-1] in _TRAILING_DECOR:
t = t[:-1].rstrip()
if t and t[-1] in '.!?…。!?':
return True
return bool(t) and t[-1] in '.!?…。!?'
def char_entropy(text: str) -> float:
"""Shannon entropy over characters, bits/char."""
if not text:
return 0.0
counts: dict[str, int] = {}
for ch in text:
counts[ch] = counts.get(ch, 0) + 1
n = len(text)
return -sum((c / n) * math.log2(c / n) for c in counts.values())
def repeat_ngram_frac(text: str, n: int = 4) -> float:
"""Fraction of n-grams that are non-first occurrences. Catches loops."""
toks = _norm_tokens(text)
if len(toks) < n + 1:
return 0.0
grams = [tuple(toks[i:i + n]) for i in range(len(toks) - n + 1)]
return 1.0 - (len(set(grams)) / len(grams))
def repeat_line_frac(text: str) -> float:
"""Fraction of non-trivial lines that are duplicates. Catches list loops."""
lines = [l.strip() for l in text.splitlines() if len(l.strip()) > 12]
if len(lines) < 4:
return 0.0
return 1.0 - (len(set(lines)) / len(lines))
def nonascii_frac(text: str) -> float:
"""Fraction of chars outside Latin/punctuation. Typographic marks exempt."""
if not text:
return 0.0
bad = 0
for ch in text:
if ord(ch) < 128:
continue
if unicodedata.category(ch) in ("Pd", "Pi", "Pf", "Po", "Zs", "Sm"):
continue # em dash, curly quotes, ellipsis, nbsp
bad += 1
return bad / len(text)
def check(
text: str,
*,
finish_reason: str | None = None,
min_words: int = MIN_WORDS,
max_words: int = MAX_WORDS,
) -> GateResult:
"""Run all gates. `finish_reason` from vLLM/OpenAI ('length' == hit cap).
Passing `finish_reason` is strongly preferred: it detects the token-cap
truncation that destroyed the prior run directly, rather than inferring it
from punctuation.
"""
text = (text or "").strip()
reasons: list[str] = []
n_words = len(_words(text))
hit_cap = finish_reason == "length"
complete = ends_cleanly(text) and not hit_cap
rep4 = repeat_ngram_frac(text, 4)
repline = repeat_line_frac(text)
ent = char_entropy(text)
na = nonascii_frac(text)
if not text:
reasons.append("empty")
if hit_cap:
reasons.append("hit_token_cap")
if not ends_cleanly(text):
reasons.append("no_terminal_punctuation")
if n_words < min_words:
reasons.append(f"too_short({n_words}<{min_words})")
if n_words > max_words:
reasons.append(f"too_long({n_words}>{max_words})")
if rep4 > MAX_NGRAM_REPEAT_FRAC:
reasons.append(f"ngram_loop({rep4:.2f})")
if repline > MAX_LINE_REPEAT_FRAC:
reasons.append(f"line_loop({repline:.2f})")
if text and ent < MIN_CHAR_ENTROPY:
reasons.append(f"low_entropy({ent:.2f})")
if na > MAX_NONASCII_FRAC:
reasons.append(f"nonascii({na:.2f})")
return GateResult(
passed=not reasons,
completeness=1.0 if complete else 0.0,
n_words=n_words,
reasons=reasons,
repeat_4gram_frac=rep4,
char_entropy=ent,
nonascii_frac=na,
hit_token_cap=hit_cap,
)