dkappe commited on
Commit
38ea554
·
verified ·
1 Parent(s): 61b1e12

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +118 -9
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
- tally = {"needle": {"choice": 0, "noul": 0, "n": 0},
341
- "bart": {"choice": 0, "noul": 0, "n": 0}}
342
 
343
  for state, expect in cases:
344
  row = {"state": state, "expect": expect}
345
- for backend in ("needle", "bart"):
346
  try:
347
- r = answer_systemone(state, Q, backend=backend)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, got_nl = ch.get("choice"), int(round(nl.get("noul", 0.0)))
354
- row[backend] = {"choice": got_ch, "choice_correct": got_ch == expect["department"],
 
 
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("Needle 3 vs BART-MNLI on the same questions and the same cases. "
553
- "Both polarities of `noul` are exercised, so a 'yes to everything' "
554
- "bug cannot hide.")
 
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