Text Classification
Transformers
Safetensors
qwen3_5
image-text-to-text
decision-model
typed-decisions
classification
routing
calibrated-probabilities
Instructions to use ironbcc/Seb-9B-v2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ironbcc/Seb-9B-v2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="ironbcc/Seb-9B-v2")# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("ironbcc/Seb-9B-v2") model = AutoModelForMultimodalLM.from_pretrained("ironbcc/Seb-9B-v2", device_map="auto") - Notebooks
- Google Colab
- Kaggle
add serve/system1_server.py
Browse files- serve/system1_server.py +198 -0
serve/system1_server.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Local HTTP server speaking TypeSafe's System One contract (POST /v1/systemone) on top of a System1 backend.
|
| 2 |
+
Run: python system1_server.py --model <path> [--adapter <safetensors|dir>] [--port 8020] [--model-name system1-9b]
|
| 3 |
+
Probabilities come from the same path as scripts/evaluate_system1.py predict (render_messages -> backend.score ->
|
| 4 |
+
calibrated_probabilities at T=1). Confidence formulas mirror the Kev reference (Apache-2.0, kev/api.py: choice_confidence,
|
| 5 |
+
score_confidence, round_prob), themselves mirroring TypeSafe system-one-adapter 0.2.1 confidence_metrics."""
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import threading
|
| 9 |
+
import time
|
| 10 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 11 |
+
|
| 12 |
+
from kev_convert import kev_record_to_rows
|
| 13 |
+
from system1_client import (
|
| 14 |
+
WIDE_CHOICE_KEYS, MAX_PAYLOAD_CHARS, OverlengthError, calibrated_probabilities, render_messages, request_from_row)
|
| 15 |
+
|
| 16 |
+
MAX_BODY_BYTES = 4 * MAX_PAYLOAD_CHARS # generous: JSON escaping and multi-question requests; rows are re-bounded per question
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class ContractError(ValueError):
|
| 20 |
+
pass
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def round_prob(x):
|
| 24 |
+
return round(float(x), 4)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _normalize(p):
|
| 28 |
+
t = sum(p)
|
| 29 |
+
return [1 / len(p)] * len(p) if t == 0 else [x / t for x in p]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def choice_confidence(p):
|
| 33 |
+
K = len(p)
|
| 34 |
+
return 1.0 if K == 1 else (max(_normalize(p)) - 1 / K) / (1 - 1 / K)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def score_confidence(p):
|
| 38 |
+
L = len(p)
|
| 39 |
+
if L == 1:
|
| 40 |
+
return 1.0
|
| 41 |
+
p = _normalize(p)
|
| 42 |
+
mode = max(range(L), key=p.__getitem__)
|
| 43 |
+
D = sum(abs(i - (L - 1) / 2) for i in range(L)) / L
|
| 44 |
+
return max(0.0, 1.0 - sum(pi * abs(i - mode) for i, pi in enumerate(p)) / D)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def request_to_rows(req):
|
| 48 |
+
"""Validate a System One request -> ({qid: row}, {qid: candidate names}). Rows come from kev_convert with a dummy label
|
| 49 |
+
(never rendered), so option text equals what the evaluator and Jev conversion produce."""
|
| 50 |
+
if not isinstance(req, dict):
|
| 51 |
+
raise ContractError("request: expected object")
|
| 52 |
+
if "state" not in req or not isinstance(req["state"], (str, dict, list)):
|
| 53 |
+
raise ContractError("state: required, string, object or array")
|
| 54 |
+
qs = req.get("questions")
|
| 55 |
+
if not isinstance(qs, dict) or not qs:
|
| 56 |
+
raise ContractError("questions: expected nonempty object")
|
| 57 |
+
dummy, rows, names = {}, {}, {}
|
| 58 |
+
for qid, q in qs.items():
|
| 59 |
+
if not isinstance(q, dict):
|
| 60 |
+
raise ContractError(f"{qid}: expected object")
|
| 61 |
+
kind, crit = q.get("type"), q.get("criteria")
|
| 62 |
+
if kind == "choice":
|
| 63 |
+
if not isinstance(crit, (dict, list)) or not 2 <= len(crit) <= len(WIDE_CHOICE_KEYS):
|
| 64 |
+
raise ContractError(f"{qid}: choice requires 2-{len(WIDE_CHOICE_KEYS)} options per choice")
|
| 65 |
+
label = next(iter(crit))
|
| 66 |
+
elif kind == "score":
|
| 67 |
+
if not isinstance(crit, list):
|
| 68 |
+
raise ContractError(f"{qid}: score criteria must be an ordered list")
|
| 69 |
+
label = 0
|
| 70 |
+
elif kind == "noul":
|
| 71 |
+
if crit is not None and not isinstance(crit, dict):
|
| 72 |
+
raise ContractError(f"{qid}: noul criteria must be an object")
|
| 73 |
+
label = True
|
| 74 |
+
else:
|
| 75 |
+
raise ContractError(f"{qid}: unknown question type {kind!r}")
|
| 76 |
+
dummy[qid] = {"type": kind, "instructions": q.get("instructions"), "criteria": crit, "label": label}
|
| 77 |
+
try:
|
| 78 |
+
for row in kev_record_to_rows({"state": req["state"], "questions": dummy}, "serve", "none", "serve", keys=WIDE_CHOICE_KEYS):
|
| 79 |
+
qid = row["meta"]["kev_question_id"]
|
| 80 |
+
rows[qid] = row
|
| 81 |
+
names[qid] = list(row["meta"].get("candidate_ids", {}).values())
|
| 82 |
+
except (ValueError, KeyError, TypeError) as e:
|
| 83 |
+
raise ContractError(str(e))
|
| 84 |
+
return rows, names
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def answer_for(row, names, probs):
|
| 88 |
+
kind = row["type"]
|
| 89 |
+
if kind == "noul":
|
| 90 |
+
return {"type": "noul", "noul": round_prob(probs["Y"])}
|
| 91 |
+
if kind == "choice":
|
| 92 |
+
p = [probs[k] for k in row["options"]]
|
| 93 |
+
return {"type": "choice", "choice": names[max(range(len(p)), key=p.__getitem__)], "confidence": round_prob(choice_confidence(p)),
|
| 94 |
+
"probabilities": {n: round_prob(v) for n, v in zip(names, p)}}
|
| 95 |
+
p = [probs[k] for k in row["options"]]
|
| 96 |
+
return {"type": "score", "score": round_prob(sum(i * v for i, v in enumerate(p))),
|
| 97 |
+
"probabilities": {str(i): round_prob(v) for i, v in enumerate(p)}, "confidence": round_prob(score_confidence(p))}
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class Service:
|
| 101 |
+
def __init__(self, backend, model_name):
|
| 102 |
+
self.backend, self.model_name, self.lock = backend, model_name, threading.Lock() # ponytail: one global lock, GPU is serial anyway
|
| 103 |
+
|
| 104 |
+
def _tokens(self, text_or_messages):
|
| 105 |
+
tok = getattr(self.backend, "tokenizer", None)
|
| 106 |
+
if tok is None:
|
| 107 |
+
return 0
|
| 108 |
+
if isinstance(text_or_messages, str):
|
| 109 |
+
return len(tok.encode(text_or_messages, add_special_tokens=False))
|
| 110 |
+
prompt = tok.apply_chat_template(text_or_messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)
|
| 111 |
+
return len(tok.encode(prompt, add_special_tokens=False))
|
| 112 |
+
|
| 113 |
+
def systemone(self, req):
|
| 114 |
+
start = time.perf_counter()
|
| 115 |
+
rows, names = request_to_rows(req)
|
| 116 |
+
answers, in_tokens = {}, 0
|
| 117 |
+
for qid, row in rows.items():
|
| 118 |
+
try:
|
| 119 |
+
messages = render_messages(request_from_row(row))
|
| 120 |
+
except OverlengthError as e:
|
| 121 |
+
raise ContractError(f"{qid}: payload too large, over the maximum context length: {e}")
|
| 122 |
+
except ValueError as e:
|
| 123 |
+
raise ContractError(f"{qid}: {e}")
|
| 124 |
+
with self.lock:
|
| 125 |
+
try:
|
| 126 |
+
scores = self.backend.score(messages, list(row["options"]))
|
| 127 |
+
except OverlengthError as e:
|
| 128 |
+
raise ContractError(f"{qid}: context too large, over the maximum context length: {e}")
|
| 129 |
+
in_tokens += self._tokens(messages)
|
| 130 |
+
if not isinstance(scores, dict) or set(scores) != set(row["options"]):
|
| 131 |
+
raise RuntimeError(f"{qid}: backend returned incomplete scores")
|
| 132 |
+
answers[qid] = answer_for(row, names[qid], calibrated_probabilities(scores, 1.0))
|
| 133 |
+
return {"model": self.model_name, "answers": answers,
|
| 134 |
+
"usage": {"input_tokens": in_tokens, "output_tokens": self._tokens(json.dumps(answers))},
|
| 135 |
+
"latency_ms": round((time.perf_counter() - start) * 1000, 1)}
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def make_server(backend, model_name="system1", host="127.0.0.1", port=8020):
|
| 139 |
+
svc = Service(backend, model_name)
|
| 140 |
+
|
| 141 |
+
class Handler(BaseHTTPRequestHandler):
|
| 142 |
+
def _send(self, code, body):
|
| 143 |
+
data = json.dumps(body).encode()
|
| 144 |
+
self.send_response(code)
|
| 145 |
+
self.send_header("Content-Type", "application/json")
|
| 146 |
+
self.send_header("Content-Length", str(len(data)))
|
| 147 |
+
self.end_headers()
|
| 148 |
+
self.wfile.write(data)
|
| 149 |
+
|
| 150 |
+
def do_GET(self):
|
| 151 |
+
if self.path == "/health":
|
| 152 |
+
self._send(200, {"status": "ok"})
|
| 153 |
+
elif self.path == "/v1/models":
|
| 154 |
+
self._send(200, {"object": "list", "data": [{"id": svc.model_name, "object": "model", "owned_by": "local"}]})
|
| 155 |
+
else:
|
| 156 |
+
self._send(404, {"error": "not found"})
|
| 157 |
+
|
| 158 |
+
def do_POST(self):
|
| 159 |
+
if self.path != "/v1/systemone":
|
| 160 |
+
return self._send(404, {"error": "not found"})
|
| 161 |
+
n = int(self.headers.get("Content-Length") or 0)
|
| 162 |
+
if n > MAX_BODY_BYTES:
|
| 163 |
+
return self._send(422, {"error": f"payload too large: {n} bytes"})
|
| 164 |
+
try:
|
| 165 |
+
req = json.loads(self.rfile.read(n))
|
| 166 |
+
except (ValueError, UnicodeDecodeError) as e:
|
| 167 |
+
return self._send(400, {"error": f"malformed JSON: {e}"})
|
| 168 |
+
try:
|
| 169 |
+
self._send(200, svc.systemone(req))
|
| 170 |
+
except ContractError as e:
|
| 171 |
+
self._send(422, {"error": str(e)})
|
| 172 |
+
except Exception as e:
|
| 173 |
+
self._send(500, {"error": f"{type(e).__name__}: {e}"})
|
| 174 |
+
|
| 175 |
+
def log_message(self, *a):
|
| 176 |
+
pass
|
| 177 |
+
|
| 178 |
+
return ThreadingHTTPServer((host, port), Handler)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def main():
|
| 182 |
+
ap = argparse.ArgumentParser(description=__doc__)
|
| 183 |
+
ap.add_argument("--model", required=True)
|
| 184 |
+
ap.add_argument("--adapter")
|
| 185 |
+
ap.add_argument("--port", type=int, default=8020)
|
| 186 |
+
ap.add_argument("--host", default="127.0.0.1")
|
| 187 |
+
ap.add_argument("--model-name", default="system1-9b")
|
| 188 |
+
ap.add_argument("--backend", choices=("mlx", "torch"), default="mlx", help="torch: CUDA via transformers")
|
| 189 |
+
a = ap.parse_args()
|
| 190 |
+
import system1_client
|
| 191 |
+
backend = system1_client.TorchBackend if a.backend == "torch" else system1_client.MlxBackend
|
| 192 |
+
server = make_server(backend(a.model, adapter_path=a.adapter), a.model_name, a.host, a.port)
|
| 193 |
+
print(f"serving {a.model_name} on http://{a.host}:{a.port}", flush=True)
|
| 194 |
+
server.serve_forever()
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
if __name__ == "__main__":
|
| 198 |
+
main()
|