Download code/scripts/serve.py from nonsodev/essay-coach: direct link, hf CLI and curl.
- Browser
- Download file 2.97 kB
-
https://huggingface.co/nonsodev/essay-coach/resolve/main/code/scripts/serve.py
- Command line
-
hf download hf://nonsodev/essay-coach/code/scripts/serve.py
-
curl -L -o serve.py https://huggingface.co/nonsodev/essay-coach/resolve/main/code/scripts/serve.py
2.97 kB
| """The coach as a web endpoint on Modal (CPU, scales to zero, so idle costs nothing). | |
| MODAL_PROFILE=labs3 modal deploy scripts/serve.py # prints the URL; the page POSTs {"text": ...} to <url>/read | |
| MODAL_PROFILE=labs3 modal serve scripts/serve.py # hot-reloading dev copy | |
| Response: {"scores": {...six 1-5...}, "scores_raw": {...}, "parts": [{start,end,type,effectiveness,eff_probs,conf,text}], | |
| "notes": [{kind,text,...}], "band": {"value": 6.5, "note": "..."} or null, "ms": 1234}. | |
| """ | |
| import modal | |
| app = modal.App("essay-coach") | |
| vol = modal.Volume.from_name("essay-data") | |
| cache = modal.Volume.from_name("essay-hf-cache", create_if_missing=True) | |
| image = (modal.Image.debian_slim(python_version="3.11") | |
| .pip_install("torch==2.4.1", index_url="https://download.pytorch.org/whl/cpu") | |
| .pip_install("transformers==4.46.3", "sentencepiece", "protobuf", "tiktoken", "safetensors", "pandas", "numpy<2.3", | |
| "fastapi[standard]") | |
| .add_local_python_source("coach")) | |
| SCORES, PARTS, BAND = "scores-v1", "parts-v1", "band-v1" | |
| class Server: | |
| def load(self): | |
| import json | |
| import os | |
| import torch | |
| from coach.infer import Coach | |
| torch.set_num_threads(4) | |
| self.coach = Coach(f"/data/runs/{SCORES}", f"/data/runs/{PARTS}", device="cpu") | |
| p = f"/data/runs/{BAND}/band.json" | |
| self.band = json.load(open(p)) if os.path.exists(p) else None | |
| def band_of(self, read, text, task2=True): | |
| import numpy as np | |
| from coach.data import TRAITS, TYPES | |
| if not self.band: | |
| return None | |
| ps = read["parts"] | |
| f = [read["scores_raw"][t] for t in TRAITS] + [np.log1p(len(text.split()))] + [sum(p["type"] == t for p in ps) for t in TYPES] | |
| f += [np.mean([p["effectiveness"] == "Effective" for p in ps]) if ps else 0.0, float(task2)] | |
| x = (np.array(f) - np.array(self.band["mean"])) / np.array(self.band["std"]) | |
| v = float(x @ np.array(self.band["coef"]) + self.band["intercept"]) | |
| cv = self.band["cv"] | |
| return {"value": float(np.clip(round(v * 2) / 2, 1, 9)), "raw": v, | |
| "note": f"An estimate, not a grade: on {cv['n']} human-banded essays it lands within half a band {cv['within_half']*100:.0f}% of the time."} | |
| def read(self, body: dict): | |
| import time | |
| t0 = time.time() | |
| text = str(body.get("text", ""))[:12000] | |
| if len(text.split()) < 20: | |
| return {"error": "Paste at least a few sentences."} | |
| out = self.coach.read(text) | |
| out["band"] = self.band_of(out, text, task2=bool(body.get("task2", True))) | |
| out["ms"] = int((time.time() - t0) * 1000) | |
| return out | |