Spaces:
Running
Running
File size: 7,466 Bytes
f5bc4ee d10910c 0ea7820 d10910c 0ea7820 d10910c f5bc4ee 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c 3cdb5a1 d10910c 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c 0ea7820 d10910c f5bc4ee 0ea7820 d10910c f5bc4ee d10910c f5bc4ee d10910c f5bc4ee d10910c f5bc4ee d10910c | 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 139 140 141 142 143 144 145 146 147 | import os, json, time, collections, os.path as op, threading, urllib.request, urllib.error
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
app = FastAPI()
KEY = os.environ.get("ANTHROPIC_API_KEY", "")
MODEL = os.environ.get("KNOU_MODEL", "claude-haiku-4-5-20251001")
# ================= ZTC (zero-token answer verification, in-process on CPU) =================
ZTC_REPO = os.environ.get("ZTC_REPO", "FINAL-Bench/ZTC-Judge-4B")
_ZT = {"ready": False, "err": None, "model": None, "tok": None, "cfg": None, "lin": None, "cv": None}
def _ztc_load():
try:
import numpy as np, torch
from transformers import AutoModel, AutoTokenizer
from huggingface_hub import snapshot_download
p = snapshot_download(ZTC_REPO)
cfg = json.load(open(op.join(p, "ztc_config.json"), encoding="utf-8"))
lin = dict(np.load(op.join(p, cfg["probe_file"]), allow_pickle=True))
cvp = op.join(p, "ztc_curve_probe_v2.npz")
if not op.exists(cvp):
cvp = op.join(p, cfg.get("curve_probe_file", "ztc_curve_probe.npz"))
cv = dict(np.load(cvp))
tok = AutoTokenizer.from_pretrained(p)
if tok.pad_token is None:
tok.pad_token = tok.eos_token
model = AutoModel.from_pretrained(p, dtype=torch.bfloat16, low_cpu_mem_usage=True).eval()
_ZT.update(model=model, tok=tok, cfg=cfg, lin=lin, cv=cv, ready=True)
except Exception as e:
_ZT["err"] = str(e)[:200]
threading.Thread(target=_ztc_load, daemon=True).start()
def ztc_score(question, answer):
import numpy as np, torch
cfg, tok, model, lin, cv = _ZT["cfg"], _ZT["tok"], _ZT["model"], _ZT["lin"], _ZT["cv"]
text = cfg["template"] % ((question or "").strip(), (answer or "").strip())
b = tok([text], return_tensors="pt", truncation=True, max_length=cfg["max_length"])
with torch.no_grad():
h = model(input_ids=b["input_ids"], attention_mask=b["attention_mask"]).last_hidden_state
v = h[0, int(b["attention_mask"].sum()) - 1].float().numpy().astype(np.float64)
s = float(((v - lin["mu"]) / lin["sd"]) @ lin["w"])
z = (v - cv["mu"]) / cv["sd"]; a = cv["anchors"].astype(np.float64)
d2 = np.sum(a * a, 1) + float(z @ z) - 2.0 * (a @ z)
k = np.exp(-np.maximum(d2, 0.0) / (float(cv["med"]) * float(cv["gamma"])))
c = float(k @ cv["alpha"].astype(np.float64))
t = cfg["thresholds"]
verdict = "๊ฒํ ์๋ง" if s <= t["review"] else ("์ด์ ์์" if s >= t["clear"] else "ํ๋จ ๋ณด๋ฅ")
return {"score": round(s, 3), "verdict": verdict, "curved": round(c, 3), "repo": ZTC_REPO, "generated_tokens": 0}
# ๋น์ฉ ์
์ฉ ๋ฐฉ์ง: IP๋น ๋ถ๋น ํธ์ถ ์ ํ (์ ์ ์ฌ์ฉ์ ์ํฅ ์์)
_HITS = collections.defaultdict(list)
_LIMIT = int(os.environ.get("KNOU_RATE_PER_MIN", "20"))
def _rate_ok(ip):
now = time.time(); q = _HITS[ip]
while q and now - q[0] > 60: q.pop(0)
if len(q) >= _LIMIT: return False
q.append(now); return True
SYS = (
"๋๋ ์ง์๊ทธ๋ํ ๊ธฐ๋ฐ ์ง์์๋ต ์์ง์ด๋ค. ์ ๊ณต๋ '์ฌ์ค(ํธ๋ฆฌํ)'๋ง ๊ทผ๊ฑฐ๋ก ๋ตํ๋ค. "
"ํธ๋ฆฌํ์ 'A โ ๊ด๊ณ โ B' ํ์์ด๋ฉฐ ๋ค์์ฒ๋ผ ์์ฐ์ด๋ก ํด์ํด ๋ต์ ๊ตฌ์ฑํ๋ค: "
"IsA=A๋ B์ ํ ์ข
๋ฅ, UsedFor=A๋ B์ ์ฐ์, AtLocation=A๋ B์ ์์, "
"CapableOf=A๋ B๋ฅผ ํ ์ ์์, MadeOf=A๋ B๋ก ๋ง๋ค์ด์ง, HasA=A๋ B๋ฅผ ๊ฐ์ง, "
"HasProperty=A๋ Bํ ์ฑ์ง, PartOf=A๋ B์ ์ผ๋ถ, Causes=A๋ B๋ฅผ ์ผ์ผํด, HasSubevent=A๋ฅผ ํ๋ฉด B๊ฐ ์ผ์ด๋จ, RelatedTo=A๋ B์ ๊ด๋ จ. "
"์ง๋ฌธ ๋์์ ๊ดํ ์ฌ์ค๋ค์ ๋ฌถ์ด ์์ ๊ฐ ์๊ฒ ์ง์ ๋ ์ธ์ด๋ก 2~4๋ฌธ์ฅ์ผ๋ก ๋ตํ๋ค. "
"๊ด๊ณ ์ด๋ฆ(IsA ๋ฑ)์ ๊ทธ๋๋ก ๋์ดํ์ง ๋ง๊ณ ์์ฐ์ค๋ฌ์ด ๋ฌธ์ฅ์ผ๋ก ํ์ด ์ด๋ค. "
"๋ณด๋ ๊ธฐ์ฌ(๊ธฐ์ฌ ์ ๋ชฉ์ด ์ฌ์ค๋ก ์ฃผ์ด์ง)์ ๊ฒฝ์ฐ ์ ๋ชฉ ๋ด์ฉ์ ๊ทผ๊ฑฐ๋ก ์ฌ์ค๋ง ์์ฝํ๋ค. "
"์ ๋ง๋ก ๊ด๋ จ ์ฌ์ค์ด ํ๋๋ ์์ ๋๋ง ํด๋น ์ธ์ด๋ก '๊ด๋ จ ์ ๋ณด๊ฐ ์์ต๋๋ค'๋ผ๊ณ ๋ตํ๋ค. ์๋ ์ฌ์ค์ ์ง์ด๋ด์ง ์๋๋ค."
)
def _ip(req):
fwd = req.headers.get("x-forwarded-for")
return (fwd.split(",")[0].strip() if fwd else (req.client.host if req.client else "x")) or "x"
@app.get("/health")
def health():
return {"ok": True, "has_key": bool(KEY), "model": MODEL}
@app.post("/ask")
async def ask(req: Request):
try:
data = await req.json()
except Exception:
return JSONResponse({"answer": "์์ฒญ ํ์ ์ค๋ฅ."})
q = (str(data.get("query") or "")).strip()[:600]
facts = data.get("facts") or []
facts = [str(f)[:200] for f in facts][:60]
lang = str(data.get("lang") or "ko")[:2]
langname = {"ko": "ํ๊ตญ์ด(Korean)", "en": "English", "zh": "ไธญๆ(Chinese)"}.get(lang, "ํ๊ตญ์ด(Korean)")
ip = (req.client.host if req.client else "x")
fwd = req.headers.get("x-forwarded-for")
if fwd: ip = fwd.split(",")[0].strip()
if not _rate_ok(ip):
return JSONResponse({"answer": "์์ฒญ์ด ๋ง์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์. / Rate limit, please retry shortly."})
if not q:
return JSONResponse({"answer": "..."})
if not KEY:
return JSONResponse({"answer": "(์๋ฒ์ LLM ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. Space Secrets์ ANTHROPIC_API_KEY๋ฅผ ์ถ๊ฐํ์ธ์.)"})
factstr = "\n".join("- " + f for f in facts) if facts else "(๊ด๋ จ ์ฌ์ค ์์)"
prompt = ("Facts retrieved from the knowledge graph:\n" + factstr + "\n\nQuestion: " + q +
"\n\nAnswer ONLY from the facts above, in " + langname +
", in 2-4 sentences. If the facts are insufficient, say so briefly in " + langname + ". Do not invent facts.")
body = json.dumps({
"model": MODEL, "max_tokens": 400, "system": SYS,
"messages": [{"role": "user", "content": prompt}],
}).encode()
r = urllib.request.Request(
"https://api.anthropic.com/v1/messages", data=body,
headers={"x-api-key": KEY, "anthropic-version": "2023-06-01", "content-type": "application/json"},
)
try:
resp = urllib.request.urlopen(r, timeout=45)
d = json.load(resp)
text = "".join(b.get("text", "") for b in d.get("content", []) if b.get("type") == "text")
return JSONResponse({"answer": text or "(๋น ์๋ต)"})
except urllib.error.HTTPError as e:
return JSONResponse({"answer": "(LLM ์ค๋ฅ " + str(e.code) + ")"})
except Exception:
return JSONResponse({"answer": "(LLM ํธ์ถ ์คํจ)"})
@app.get("/ztc_status")
def ztc_status():
return {"ready": _ZT["ready"], "err": _ZT["err"], "repo": ZTC_REPO}
@app.post("/verify")
async def verify_ep(req: Request):
if not _rate_ok("v:" + _ip(req)):
return JSONResponse({"ok": False, "error": "rate"})
try:
data = await req.json()
except Exception:
return JSONResponse({"ok": False})
q = str(data.get("query") or "")[:600]; a = str(data.get("answer") or "")[:2000]
if not q or not a:
return JSONResponse({"ok": False})
if not _ZT["ready"]:
return JSONResponse({"ok": False, "error": "ztc_loading", "detail": _ZT["err"]})
try:
return JSONResponse({"ok": True, "ztc": ztc_score(q, a)})
except Exception as e:
return JSONResponse({"ok": False, "error": str(e)[:160]})
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|