Spaces:
Running
Running
File size: 6,659 Bytes
01f5654 | 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 | """Which generator writes versions the detector reads as human? 30 AI-written sentences (2 per subject), 3 versions
from each generator with the fast engine's prompt (keep every detail), every version scored by the live detector
on its own. Per generator: mean of each sentence's best (lowest) score, share of sentences with a version below 0.5,
seconds.
python devtools/bench_generators.py [--only groq:openai/gpt-oss-120b,parrot]"""
import argparse
import json
import os
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(Path(__file__).resolve().parent))
import env # noqa: E402
env.load()
os.environ.setdefault("LT_MODE", "off")
sys.stdout.reconfigure(encoding="utf-8")
import requests # noqa: E402
import detector as D # noqa: E402
import groqai as G # noqa: E402
from samples_domains import DOMAINS # noqa: E402
ENDPOINTS = {
"groq": ("https://api.groq.com/openai/v1/chat/completions", os.getenv("GROQ_API_KEY", "")),
"gemini": ("https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", os.getenv("GEMINI_API_KEY", "")),
"anthropic": ("https://api.anthropic.com/v1/chat/completions", os.getenv("ANTHROPIC_API_KEY", "")),
}
GENERATORS = ["groq:openai/gpt-oss-120b", "groq:openai/gpt-oss-20b", "groq:qwen/qwen3.6-27b", "groq:qwen/qwen3.8-27b",
"gemini:gemini-3.5-flash", "gemini:gemini-3.5-flash-lite", "anthropic:claude-haiku-5-5", "anthropic:claude-sonnet-5-5", "parrot"]
def sentences():
out = []
for v in DOMAINS.values():
out += [s for s in D.split_sentences(v) if len(s.split()) >= 8][:2]
return out[:30]
LEAN = """Each numbered item is one sentence from an academic paper that reads as machine-written. Rewrite it {n}
different ways so a careful human researcher could have typed it.
KEEP EXACTLY: every fact, number, unit, statistic, citation (same form), technical term, comparison, condition,
qualifier and the strength of each claim. One sentence in, one sentence out. Correct grammar. Same spelling (UK/US).
HOW HUMANS WRITE IT: plain, concrete words a scientist would actually use; start from a different element (the
result, the method, a condition, the object); active voice where it is natural; an occasional less predictable but
correct word or phrase; no filler openers (Furthermore, Moreover, Additionally, Overall, Notably, It is important to
note); never use delve, crucial, pivotal, comprehensive, robust, leverage, foster, underscore, highlight, insights,
valuable, landscape, realm, intricate, seamless, paramount, multifaceted, showcase, "plays a role", "sheds light";
no em dashes; do not shorten by dropping content.
Answer only with JSON: {"items": [{"id": 1, "versions": ["...", "..."]}, ...]} - one per id."""
PROMPT = os.getenv("BENCH_PROMPT", "full")
def llm(gen, sents, n=3):
prov, model = gen.split(":", 1)
url, key = ENDPOINTS[prov]
tok = G.FAITHFUL.set(True)
try:
system = LEAN.replace("{n}", str(n)) if PROMPT == "lean" else G._variants_system(n)
finally:
G.FAITHFUL.reset(tok)
def one(batch):
user = "PROTECTED TERMS: (none)\n\n" + "\n".join(f"{i}. SENTENCE: {t}" for i, t in enumerate(batch, 1))
body = {"model": model, "temperature": 0.7, "max_tokens": 6000,
"messages": [{"role": "system", "content": system}, {"role": "user", "content": user}]}
if "gpt-oss" in model:
body["reasoning_effort"] = "low"
if "qwen" in model:
body["reasoning_effort"] = "none"
if prov == "anthropic":
body.pop("temperature")
r = requests.post(url, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
json=body, timeout=120)
if not r.ok:
raise RuntimeError(f"{r.status_code} {r.text[:200]}")
data = G._load_json(r.json()["choices"][0]["message"]["content"])
res = [[] for _ in batch]
for k, it in enumerate(data.get("items") or []):
try:
i = int(it.get("id", k + 1)) - 1
res[i] = [v for v in it.get("versions") or [] if isinstance(v, str)][:n]
except Exception:
pass
return res
batches = [sents[i:i + 10] for i in range(0, len(sents), 10)]
with ThreadPoolExecutor(len(batches)) as pool:
return [v for b in pool.map(one, batches) for v in b]
def parrot(sents, n=3):
import humanizer as H
out = H._generate(sents, 0.9, "cpu", 8, m=H._get_cpu_model(), k=n)
return [out[i * n:(i + 1) * n] for i in range(len(sents))]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--only", default="")
a = ap.parse_args()
gens = [g for g in GENERATORS if not a.only or g in a.only.split(",")]
sents = sentences()
base = D.score_texts(sents)
print(f"{len(sents)} sentences, original mean score {sum(base) / len(base):.2f}", flush=True)
results = {}
for g in gens:
t = time.time()
try:
vs = parrot(sents) if g == "parrot" else llm(g, sents)
except Exception as err:
print(f"{g:32s} FAILED: {str(err)[:160]}", flush=True)
continue
secs = time.time() - t
flat = [(i, v) for i, vv in enumerate(vs) for v in vv if v and v.strip()]
sc = D.score_texts([v for _, v in flat]) if flat else []
best = {}
for (i, v), s in zip(flat, sc):
if i not in best or s < best[i][0]:
best[i] = (s, v)
got = [best[i][0] if i in best else 1.0 for i in range(len(sents))]
results[g] = {"best": got, "examples": [best.get(i, (1.0, ""))[1] for i in range(len(sents))]}
print(f"{g:32s} best-version mean {sum(got) / len(got):.2f} | below 0.5: {sum(x < 0.5 for x in got)}/{len(got)}"
f" | all versions mean {sum(sc) / max(1, len(sc)):.2f} | {secs:.1f}s", flush=True)
if len(results) > 1: # one pool of versions from all generators
pool = [min(r["best"][i] for r in results.values()) for i in range(len(sents))]
print(f"{'ALL TOGETHER':32s} best-version mean {sum(pool) / len(pool):.2f} | below 0.5: "
f"{sum(x < 0.5 for x in pool)}/{len(pool)}", flush=True)
json.dump({"sentences": sents, "results": results}, open(ROOT / "devtools" / "logs" / "bench_generators.json", "w",
encoding="utf-8"), indent=1, ensure_ascii=False)
if __name__ == "__main__":
main()
|