Spaces:
Running
Running
merge: in-process ZTC (CPU, 4B) in single Docker space; drop second space
Browse files
app.py
CHANGED
|
@@ -1,13 +1,55 @@
|
|
| 1 |
-
import os, json, urllib.request, urllib.error
|
| 2 |
from fastapi import FastAPI, Request
|
| 3 |
from fastapi.responses import JSONResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
|
| 6 |
-
import time, collections
|
| 7 |
app = FastAPI()
|
| 8 |
KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
| 9 |
MODEL = os.environ.get("KNOU_MODEL", "claude-haiku-4-5-20251001")
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# ๋น์ฉ ์
์ฉ ๋ฐฉ์ง: IP๋น ๋ถ๋น ํธ์ถ ์ ํ (์ ์ ์ฌ์ฉ์ ์ํฅ ์์)
|
| 12 |
_HITS = collections.defaultdict(list)
|
| 13 |
_LIMIT = int(os.environ.get("KNOU_RATE_PER_MIN", "20"))
|
|
@@ -75,36 +117,25 @@ async def ask(req: Request):
|
|
| 75 |
except Exception:
|
| 76 |
return JSONResponse({"answer": "(LLM ํธ์ถ ์คํจ)"})
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
global _ZC
|
| 82 |
-
if _ZC is None:
|
| 83 |
-
from gradio_client import Client
|
| 84 |
-
# gradio_client auto-reads HF_TOKEN env for auth (larger ZeroGPU quota than anonymous)
|
| 85 |
-
_ZC = Client(ZTC_SPACE)
|
| 86 |
-
return _ZC
|
| 87 |
|
| 88 |
@app.post("/verify")
|
| 89 |
async def verify_ep(req: Request):
|
| 90 |
-
|
| 91 |
-
if not _rate_ok("v:" + ip):
|
| 92 |
return JSONResponse({"ok": False, "error": "rate"})
|
| 93 |
try:
|
| 94 |
data = await req.json()
|
| 95 |
except Exception:
|
| 96 |
return JSONResponse({"ok": False})
|
| 97 |
-
q = str(data.get("query") or "")[:600]
|
| 98 |
-
a = str(data.get("answer") or "")[:2000]
|
| 99 |
if not q or not a:
|
| 100 |
return JSONResponse({"ok": False})
|
|
|
|
|
|
|
| 101 |
try:
|
| 102 |
-
|
| 103 |
-
def call():
|
| 104 |
-
return _ztc_client().predict(q, a, "", api_name="/verify")
|
| 105 |
-
with cf.ThreadPoolExecutor(max_workers=1) as ex:
|
| 106 |
-
r = ex.submit(call).result(timeout=120)
|
| 107 |
-
return JSONResponse({"ok": True, "ztc": r})
|
| 108 |
except Exception as e:
|
| 109 |
return JSONResponse({"ok": False, "error": str(e)[:160]})
|
| 110 |
|
|
|
|
| 1 |
+
import os, json, time, collections, os.path as op, threading, urllib.request, urllib.error
|
| 2 |
from fastapi import FastAPI, Request
|
| 3 |
from fastapi.responses import JSONResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
|
|
|
|
| 6 |
app = FastAPI()
|
| 7 |
KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
| 8 |
MODEL = os.environ.get("KNOU_MODEL", "claude-haiku-4-5-20251001")
|
| 9 |
|
| 10 |
+
# ================= ZTC (zero-token answer verification, in-process on CPU) =================
|
| 11 |
+
ZTC_REPO = os.environ.get("ZTC_REPO", "FINAL-Bench/ZTC-Judge-4B")
|
| 12 |
+
_ZT = {"ready": False, "err": None, "model": None, "tok": None, "cfg": None, "lin": None, "cv": None}
|
| 13 |
+
|
| 14 |
+
def _ztc_load():
|
| 15 |
+
try:
|
| 16 |
+
import numpy as np, torch
|
| 17 |
+
from transformers import AutoModel, AutoTokenizer
|
| 18 |
+
from huggingface_hub import snapshot_download
|
| 19 |
+
p = snapshot_download(ZTC_REPO)
|
| 20 |
+
cfg = json.load(open(op.join(p, "ztc_config.json"), encoding="utf-8"))
|
| 21 |
+
lin = dict(np.load(op.join(p, cfg["probe_file"]), allow_pickle=True))
|
| 22 |
+
cvp = op.join(p, "ztc_curve_probe_v2.npz")
|
| 23 |
+
if not op.exists(cvp):
|
| 24 |
+
cvp = op.join(p, cfg.get("curve_probe_file", "ztc_curve_probe.npz"))
|
| 25 |
+
cv = dict(np.load(cvp))
|
| 26 |
+
tok = AutoTokenizer.from_pretrained(p)
|
| 27 |
+
if tok.pad_token is None:
|
| 28 |
+
tok.pad_token = tok.eos_token
|
| 29 |
+
model = AutoModel.from_pretrained(p, dtype=torch.bfloat16, low_cpu_mem_usage=True).eval()
|
| 30 |
+
_ZT.update(model=model, tok=tok, cfg=cfg, lin=lin, cv=cv, ready=True)
|
| 31 |
+
except Exception as e:
|
| 32 |
+
_ZT["err"] = str(e)[:200]
|
| 33 |
+
|
| 34 |
+
threading.Thread(target=_ztc_load, daemon=True).start()
|
| 35 |
+
|
| 36 |
+
def ztc_score(question, answer):
|
| 37 |
+
import numpy as np, torch
|
| 38 |
+
cfg, tok, model, lin, cv = _ZT["cfg"], _ZT["tok"], _ZT["model"], _ZT["lin"], _ZT["cv"]
|
| 39 |
+
text = cfg["template"] % ((question or "").strip(), (answer or "").strip())
|
| 40 |
+
b = tok([text], return_tensors="pt", truncation=True, max_length=cfg["max_length"])
|
| 41 |
+
with torch.no_grad():
|
| 42 |
+
h = model(input_ids=b["input_ids"], attention_mask=b["attention_mask"]).last_hidden_state
|
| 43 |
+
v = h[0, int(b["attention_mask"].sum()) - 1].float().numpy().astype(np.float64)
|
| 44 |
+
s = float(((v - lin["mu"]) / lin["sd"]) @ lin["w"])
|
| 45 |
+
z = (v - cv["mu"]) / cv["sd"]; a = cv["anchors"].astype(np.float64)
|
| 46 |
+
d2 = np.sum(a * a, 1) + float(z @ z) - 2.0 * (a @ z)
|
| 47 |
+
k = np.exp(-np.maximum(d2, 0.0) / (float(cv["med"]) * float(cv["gamma"])))
|
| 48 |
+
c = float(k @ cv["alpha"].astype(np.float64))
|
| 49 |
+
t = cfg["thresholds"]
|
| 50 |
+
verdict = "๊ฒํ ์๋ง" if s <= t["review"] else ("์ด์ ์์" if s >= t["clear"] else "ํ๋จ ๋ณด๋ฅ")
|
| 51 |
+
return {"score": round(s, 3), "verdict": verdict, "curved": round(c, 3), "repo": ZTC_REPO, "generated_tokens": 0}
|
| 52 |
+
|
| 53 |
# ๋น์ฉ ์
์ฉ ๋ฐฉ์ง: IP๋น ๋ถ๋น ํธ์ถ ์ ํ (์ ์ ์ฌ์ฉ์ ์ํฅ ์์)
|
| 54 |
_HITS = collections.defaultdict(list)
|
| 55 |
_LIMIT = int(os.environ.get("KNOU_RATE_PER_MIN", "20"))
|
|
|
|
| 117 |
except Exception:
|
| 118 |
return JSONResponse({"answer": "(LLM ํธ์ถ ์คํจ)"})
|
| 119 |
|
| 120 |
+
@app.get("/ztc_status")
|
| 121 |
+
def ztc_status():
|
| 122 |
+
return {"ready": _ZT["ready"], "err": _ZT["err"], "repo": ZTC_REPO}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
@app.post("/verify")
|
| 125 |
async def verify_ep(req: Request):
|
| 126 |
+
if not _rate_ok("v:" + _ip(req)):
|
|
|
|
| 127 |
return JSONResponse({"ok": False, "error": "rate"})
|
| 128 |
try:
|
| 129 |
data = await req.json()
|
| 130 |
except Exception:
|
| 131 |
return JSONResponse({"ok": False})
|
| 132 |
+
q = str(data.get("query") or "")[:600]; a = str(data.get("answer") or "")[:2000]
|
|
|
|
| 133 |
if not q or not a:
|
| 134 |
return JSONResponse({"ok": False})
|
| 135 |
+
if not _ZT["ready"]:
|
| 136 |
+
return JSONResponse({"ok": False, "error": "ztc_loading", "detail": _ZT["err"]})
|
| 137 |
try:
|
| 138 |
+
return JSONResponse({"ok": True, "ztc": ztc_score(q, a)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
except Exception as e:
|
| 140 |
return JSONResponse({"ok": False, "error": str(e)[:160]})
|
| 141 |
|