Spaces:
Running on Zero
Running on Zero
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -308,6 +308,24 @@ def answer_systemone(state: str, questions: dict, backend: str = "needle") -> di
|
|
| 308 |
"usage": {"input_tokens": None, "output_tokens": None}}
|
| 309 |
|
| 310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
@spaces.GPU(duration=240)
|
| 312 |
def compare_backends() -> dict:
|
| 313 |
"""Head-to-head: Needle 3 vs BART-MNLI on the SAME questions.
|
|
@@ -337,21 +355,38 @@ def compare_backends() -> dict:
|
|
| 337 |
"no_text": "the writer is not asking for any money back"}}
|
| 338 |
|
| 339 |
out: dict[str, Any] = {"questions": list(Q), "cases": [], "summary": {}}
|
| 340 |
-
|
| 341 |
-
|
| 342 |
|
| 343 |
for state, expect in cases:
|
| 344 |
row = {"state": state, "expect": expect}
|
| 345 |
-
for backend in
|
| 346 |
try:
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
except Exception as e:
|
| 349 |
row[backend] = {"error": f"{type(e).__name__}: {e}"}
|
| 350 |
continue
|
| 351 |
ch = r["answers"]["department"]
|
| 352 |
nl = r["answers"]["refund_requested"]
|
| 353 |
-
got_ch
|
| 354 |
-
|
|
|
|
|
|
|
| 355 |
"choice_conf": ch.get("confidence"),
|
| 356 |
"noul": round(nl.get("noul", 0.0), 4),
|
| 357 |
"noul_correct": got_nl == expect["refund_requested"],
|
|
@@ -365,9 +400,59 @@ def compare_backends() -> dict:
|
|
| 365 |
n2 = max(tally[b]["n"], 1)
|
| 366 |
tally[b]["accuracy"] = (tally[b]["choice"] + tally[b]["noul"]) / n2
|
| 367 |
out["summary"] = tally
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
return out
|
| 369 |
|
| 370 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
@spaces.GPU(duration=120)
|
| 372 |
def run_method_comparison() -> dict:
|
| 373 |
"""The scoring-method comparison on three unambiguous cases.
|
|
@@ -549,12 +634,27 @@ with gr.Blocks(title="needle3 logits") as demo:
|
|
| 549 |
b3.click(_score, [st, instr, backend_sel], out_json, api_name="score")
|
| 550 |
|
| 551 |
with gr.Tab("Compare backends"):
|
| 552 |
-
gr.Markdown("
|
| 553 |
-
"
|
| 554 |
-
"
|
|
|
|
| 555 |
cmp_btn = gr.Button("run head-to-head", variant="primary")
|
| 556 |
cmp_out = gr.JSON(label="comparison")
|
| 557 |
cmp_btn.click(compare_backends, None, cmp_out, api_name="compare")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
| 559 |
with gr.Tab("Method comparison"):
|
| 560 |
gr.Markdown("Which scoring method actually separates the three clear cases?")
|
|
@@ -600,10 +700,19 @@ if __name__ == "__main__":
|
|
| 600 |
f"({os.path.getsize(CKPT) if os.path.exists(CKPT) else 0} bytes)")
|
| 601 |
except Exception as _e: # noqa: BLE001
|
| 602 |
print(f"[startup] checkpoint prefetch failed: {type(_e).__name__}: {_e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
demo.queue(max_size=8).launch(server_name="0.0.0.0", server_port=7860)
|
| 604 |
else:
|
| 605 |
try:
|
| 606 |
_ensure_ckpt()
|
| 607 |
except Exception as _e: # noqa: BLE001
|
| 608 |
print(f"[startup] checkpoint prefetch failed: {type(_e).__name__}: {_e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
|
|
|
|
| 308 |
"usage": {"input_tokens": None, "output_tokens": None}}
|
| 309 |
|
| 310 |
|
| 311 |
+
# --------------------------------------------------------------- tuned adapter vocab
|
| 312 |
+
# The trigger vocabulary from the TUNED adapter (eval_harness.TRIGGERS), copied here so
|
| 313 |
+
# the Space can drive the tuned path without importing the eval harness. These are the
|
| 314 |
+
# lists that produced 87.5% held out; see the README for why each design choice exists.
|
| 315 |
+
TRIG = {
|
| 316 |
+
"billing": r"\b(charge|charged|charges|invoice|refund|refunded|billing|billed|bill|"
|
| 317 |
+
r"payment|paid|pay|subscription|price|pricing|duplicate|double charged|"
|
| 318 |
+
r"credited|owing|owed|bank|card|cost)\b",
|
| 319 |
+
"technical": r"\b(error|errors|bug|bugs|crash|crashes|broken|fails|failing|failed|"
|
| 320 |
+
r"outage|down|exception|blank|screen|sync|search|upload|webhook|api|"
|
| 321 |
+
r"json|csv|export|import|dashboard|app|version|update|fix|feature|"
|
| 322 |
+
r"documentation)\b",
|
| 323 |
+
"account": r"\b(log ?in|login|log into|sign ?in|sign in|password|reset|two-factor|"
|
| 324 |
+
r"2fa|authentication|locked out|lock|unlock|access|permission|profile|"
|
| 325 |
+
r"email address|account|session expired|erasure|delete my account)\b",
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
|
| 329 |
@spaces.GPU(duration=240)
|
| 330 |
def compare_backends() -> dict:
|
| 331 |
"""Head-to-head: Needle 3 vs BART-MNLI on the SAME questions.
|
|
|
|
| 355 |
"no_text": "the writer is not asking for any money back"}}
|
| 356 |
|
| 357 |
out: dict[str, Any] = {"questions": list(Q), "cases": [], "summary": {}}
|
| 358 |
+
backends = ("tuned", "space", "bart")
|
| 359 |
+
tally = {b: {"choice": 0, "noul": 0, "n": 0} for b in backends}
|
| 360 |
|
| 361 |
for state, expect in cases:
|
| 362 |
row = {"state": state, "expect": expect}
|
| 363 |
+
for backend in backends:
|
| 364 |
try:
|
| 365 |
+
if backend == "tuned":
|
| 366 |
+
# the tuned adapter declares only its own criteria; give it the
|
| 367 |
+
# REQUEST portion for trigger scoping, as its design requires
|
| 368 |
+
tq = {"department": {"type": "choice", "instructions": "Which team?",
|
| 369 |
+
"criteria": {k: {"description": v,
|
| 370 |
+
"triggers": TRIG[k]}
|
| 371 |
+
for k, v in crit.items()}},
|
| 372 |
+
"refund_requested": {"type": "noul",
|
| 373 |
+
"instructions": "Is the writer asking "
|
| 374 |
+
"for a refund?"},
|
| 375 |
+
"_request_text": state}
|
| 376 |
+
r = answer_tuned(state, tq)
|
| 377 |
+
elif backend == "space":
|
| 378 |
+
r = answer_systemone(state, Q, backend="needle")
|
| 379 |
+
else:
|
| 380 |
+
r = answer_systemone(state, Q, backend="bart")
|
| 381 |
except Exception as e:
|
| 382 |
row[backend] = {"error": f"{type(e).__name__}: {e}"}
|
| 383 |
continue
|
| 384 |
ch = r["answers"]["department"]
|
| 385 |
nl = r["answers"]["refund_requested"]
|
| 386 |
+
got_ch = ch.get("choice")
|
| 387 |
+
got_nl = int(round(nl.get("noul", 0.0)))
|
| 388 |
+
row[backend] = {"choice": got_ch,
|
| 389 |
+
"choice_correct": got_ch == expect["department"],
|
| 390 |
"choice_conf": ch.get("confidence"),
|
| 391 |
"noul": round(nl.get("noul", 0.0), 4),
|
| 392 |
"noul_correct": got_nl == expect["refund_requested"],
|
|
|
|
| 400 |
n2 = max(tally[b]["n"], 1)
|
| 401 |
tally[b]["accuracy"] = (tally[b]["choice"] + tally[b]["noul"]) / n2
|
| 402 |
out["summary"] = tally
|
| 403 |
+
out["label_note"] = (
|
| 404 |
+
"tuned = the trigger-based adapter that scored 87.5% held out on 28 tickets. "
|
| 405 |
+
"space = this Space's bare-prompt first-token path (a DIFFERENT, weaker design; "
|
| 406 |
+
"do not read it as 'Needle'). bart = facebook/bart-large-mnli zero-shot.")
|
| 407 |
return out
|
| 408 |
|
| 409 |
|
| 410 |
+
def _ensure_tuned_assets() -> dict:
|
| 411 |
+
"""Fetch what the TUNED adapter path needs, at MODULE scope.
|
| 412 |
+
|
| 413 |
+
The tuned adapter (jev_adapter.py) uses the NATIVE engine -- `needle.Needle(tools=...)`
|
| 414 |
+
with no `weights=` -- which is the configuration that scored 87.5% held out. That is a
|
| 415 |
+
DIFFERENT runtime from the JAX-logits path this Space normally uses: it needs the native
|
| 416 |
+
`libneedle` shared library plus the `needle3.cact` archive, both of which the package
|
| 417 |
+
auto-downloads into ~/.cache/cactus-needle/.
|
| 418 |
+
|
| 419 |
+
Why module scope: network access inside a @spaces.GPU function is restricted on ZeroGPU,
|
| 420 |
+
so anything that fetches must run at import. This is the same reason the safetensors
|
| 421 |
+
checkpoint is prefetched at startup.
|
| 422 |
+
"""
|
| 423 |
+
info: dict[str, Any] = {"ok": False}
|
| 424 |
+
try:
|
| 425 |
+
import needle as _n
|
| 426 |
+
# _load_base fetches/validates the .cact; the engine library is fetched lazily
|
| 427 |
+
# by _lib_path on first real call, so trigger it here too.
|
| 428 |
+
from needle import _base_weights_path, _lib_path
|
| 429 |
+
lib = _lib_path(3)
|
| 430 |
+
w = _base_weights_path(3)
|
| 431 |
+
info.update({"ok": True, "lib": str(lib), "weights": str(w),
|
| 432 |
+
"weights_bytes": os.path.getsize(w) if os.path.exists(w) else None})
|
| 433 |
+
except Exception as e:
|
| 434 |
+
info["error"] = f"{type(e).__name__}: {e}"
|
| 435 |
+
return info
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
@spaces.GPU(duration=600)
|
| 439 |
+
def answer_tuned(state: str, questions: dict) -> dict:
|
| 440 |
+
"""The TUNED adapter path: one tool per option, triggers, scoped to the request.
|
| 441 |
+
|
| 442 |
+
This is the implementation that scored 87.5% held out on the 28-ticket set. It is
|
| 443 |
+
exposed separately from `answer_systemone` because it is a different runtime (native
|
| 444 |
+
engine vs JAX logits) and a different design (tools + triggers vs bare-prompt
|
| 445 |
+
first-token matching). Keeping them distinct stops the comparison from silently
|
| 446 |
+
attributing the naive path's score to the tuned one.
|
| 447 |
+
"""
|
| 448 |
+
from jev_adapter import run_systemone as _tuned
|
| 449 |
+
# The adapter needs the request alone for trigger scoping. The caller may pass a
|
| 450 |
+
# combined state; if it does not declare the request portion we use the whole thing
|
| 451 |
+
# and the adapter warns about it.
|
| 452 |
+
request_text = questions.pop("_request_text", None) if isinstance(questions, dict) else None
|
| 453 |
+
return _tuned(state, questions, request_text=request_text)
|
| 454 |
+
|
| 455 |
+
|
| 456 |
@spaces.GPU(duration=120)
|
| 457 |
def run_method_comparison() -> dict:
|
| 458 |
"""The scoring-method comparison on three unambiguous cases.
|
|
|
|
| 634 |
b3.click(_score, [st, instr, backend_sel], out_json, api_name="score")
|
| 635 |
|
| 636 |
with gr.Tab("Compare backends"):
|
| 637 |
+
gr.Markdown("**tuned** = the trigger-based adapter (87.5% held out) · "
|
| 638 |
+
"**space** = this Space's bare-prompt path (weaker, different design) · "
|
| 639 |
+
"**bart** = facebook/bart-large-mnli zero-shot. Both polarities of "
|
| 640 |
+
"`noul` are exercised, so a 'yes to everything' bug cannot hide.")
|
| 641 |
cmp_btn = gr.Button("run head-to-head", variant="primary")
|
| 642 |
cmp_out = gr.JSON(label="comparison")
|
| 643 |
cmp_btn.click(compare_backends, None, cmp_out, api_name="compare")
|
| 644 |
+
tun_btn = gr.Button("tuned adapter only (single state)")
|
| 645 |
+
tun_out = gr.JSON(label="tuned response")
|
| 646 |
+
tun_btn.click(
|
| 647 |
+
lambda s: answer_tuned(s, {
|
| 648 |
+
"department": {"type": "choice", "instructions": "Which team?",
|
| 649 |
+
"criteria": {k: {"description": v, "triggers": TRIG[k]}
|
| 650 |
+
for k, v in
|
| 651 |
+
{"billing": "Payment, charges, invoices, refunds.",
|
| 652 |
+
"technical": "Bugs, errors, outages.",
|
| 653 |
+
"account": "Login, password, access."}.items()}},
|
| 654 |
+
"refund_requested": {"type": "noul",
|
| 655 |
+
"instructions": "Is the writer asking for a refund?"},
|
| 656 |
+
"_request_text": s}),
|
| 657 |
+
[st], tun_out, api_name="tuned")
|
| 658 |
|
| 659 |
with gr.Tab("Method comparison"):
|
| 660 |
gr.Markdown("Which scoring method actually separates the three clear cases?")
|
|
|
|
| 700 |
f"({os.path.getsize(CKPT) if os.path.exists(CKPT) else 0} bytes)")
|
| 701 |
except Exception as _e: # noqa: BLE001
|
| 702 |
print(f"[startup] checkpoint prefetch failed: {type(_e).__name__}: {_e}")
|
| 703 |
+
# The tuned adapter uses the NATIVE engine (libneedle + needle3.cact), which is a
|
| 704 |
+
# DIFFERENT runtime from the JAX safetensors path above. Prefetch it here too, so the
|
| 705 |
+
# first tuned call does not hit the network inside a GPU-decorated function.
|
| 706 |
+
_tuned_asset_info = _ensure_tuned_assets()
|
| 707 |
+
print(f"[startup] tuned assets: {_tuned_asset_info}")
|
| 708 |
demo.queue(max_size=8).launch(server_name="0.0.0.0", server_port=7860)
|
| 709 |
else:
|
| 710 |
try:
|
| 711 |
_ensure_ckpt()
|
| 712 |
except Exception as _e: # noqa: BLE001
|
| 713 |
print(f"[startup] checkpoint prefetch failed: {type(_e).__name__}: {_e}")
|
| 714 |
+
try:
|
| 715 |
+
print(f"[startup] tuned assets: {_ensure_tuned_assets()}")
|
| 716 |
+
except Exception as _e: # noqa: BLE001
|
| 717 |
+
print(f"[startup] tuned asset prefetch failed: {type(_e).__name__}: {_e}")
|
| 718 |
|