"""JEV-9B — typed decisions in one forward pass (System 1) + System 2 escalation.
Interactive demo for https://huggingface.co/autotrust/JEV-9B — AutoTrust's integrated
System 1 + System 2 model built with the Blocks-of-Experts recipe on a frozen
Qwen3.5-9B backbone. System 1 answers typed questions (`noul` yes/no, `choice`
over 2-16 options, `score` on a 0-5 scale) in one prefill pass with a calibrated
probability distribution; System 2 is the pristine base model (adapter off) and can
be escalated to for deliberate step-by-step reasoning.
"""
import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
import spaces # noqa: E402 -- MUST precede torch / transformers / any CUDA-touching import
import html # noqa: E402
import json # noqa: E402
import time # noqa: E402
import gradio as gr # noqa: E402
import torch # noqa: E402 # noqa: F401 (imported for dtype selection downstream)
import jev_core as jc # noqa: E402
N_QUESTIONS = 4
LETTERS = jc.LETTERS
KINDS = list(jc.KINDS)
SCORE_LEVELS = [str(i) for i in range(6)]
TOPIC_OPTIONS = [
"open-source software", "programming languages & tools", "AI / machine learning",
"security & privacy", "hardware & retro computing", "science & health",
"automotive & energy", "politics & government", "business & finance", "culture & lifestyle",
]
INTENT_OPTIONS = [
"asking a question", "sharing personal experience", "providing factual information",
"arguing an opinion", "attacking another commenter", "moderating the discussion",
]
print(
f"[jev-9b] loaded {jc.REPO}: backbone dtype={next(jc._BACKBONE.parameters()).dtype}, "
f"head dtype={jc._HEAD.weight.dtype}, temperatures={jc.TEMPERATURES}",
flush=True,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _parse_options(text: str) -> list:
"""Options are comma-separated; newlines are accepted as separators too."""
if not text:
return []
return [o.strip() for o in text.replace("\n", ",").split(",") if o.strip()]
def _build_questions(fields) -> list:
"""Turn the (type, instructions, options) UI rows into jev_core.decide calls."""
questions = []
for i in range(N_QUESTIONS):
kind = (fields[3 * i] or "").strip()
instructions = (fields[3 * i + 1] or "").strip()
raw_options = fields[3 * i + 2] or ""
if not instructions:
continue # a row with no instructions is skipped
if kind not in KINDS:
raise gr.Error(f"Question {i + 1}: unknown question type {kind!r}.")
if kind == "choice":
options = _parse_options(raw_options)
if not 2 <= len(options) <= 16:
raise gr.Error(
f"Question {i + 1} is a `choice` question, which needs 2-16 "
f"comma-separated options; got {len(options)}."
)
elif kind == "score":
options = SCORE_LEVELS # fixed 0-5 levels; the instructions describe the scale
else: # noul
options = ["false", "true"]
questions.append({"kind": kind, "instructions": instructions, "options": options})
if not questions:
raise gr.Error("Add at least one question — give it some instructions text.")
return questions
def _bars_html(items: list, winner) -> str:
"""Render labelled probability bars; the winner is highlighted."""
out = []
for label, prob in items:
hot = label == winner
out.append(
'
'
f'
'
f"{html.escape(str(label))}"
'
'
f'
'
f"{prob * 100:.1f}% "
)
return "".join(out)
def _answer_html(q: dict, probs: dict) -> str:
"""One question -> headline answer plus a labelled probability bar per option."""
kind, head_txt = q["kind"], html.escape(q["instructions"])
items = list(probs.items())
if kind == "choice":
winner = max(probs, key=probs.get)
headline = html.escape(str(winner))
note = f"confidence {probs[winner]:.3f}"
elif kind == "score":
ev = sum(int(k) * v for k, v in probs.items())
winner = max(probs, key=probs.get)
headline = f"expected level {ev:.2f} / 5"
note = f"most likely level {winner} ({probs[winner] * 100:.1f}%)"
items = [(f"level {k}", v) for k, v in items]
else: # noul
p_yes = probs["true"]
winner = "true" if p_yes >= 0.5 else "false"
headline = "true" if p_yes >= 0.5 else "false"
note = f"P(true) = {p_yes:.3f}"
return (
''
f'
'
f'{kind} {head_txt}
'
f'
{headline} '
f'{note}
'
f"{_bars_html(items, winner)}
"
)
def _split_reasoning(text: str) -> tuple:
"""Split a System 2 reply into (reasoning, answer) if it carries think tags."""
open_tag, close_tag = "", ""
if close_tag in text:
reasoning = text.split(close_tag)[0].replace(open_tag, "").strip()
answer = text.split(close_tag, 1)[1].strip()
return reasoning, answer
return "", text.strip()
def _matched_option(answer: str, options: list):
"""Return the option the System 2 answer's last line picked, if any."""
if not answer.strip():
return None
last = answer.strip().splitlines()[-1].strip().rstrip(".").strip("`").lower()
for i, o in enumerate(options):
ol = str(o).lower()
if last == ol or last == f"{LETTERS[i]}) {ol}" or last == LETTERS[i].lower():
return o
for o in options: # fall back to a contained mention
if str(o).lower() in last:
return o
return None
# ---------------------------------------------------------------------------
# GPU handlers
# ---------------------------------------------------------------------------
@spaces.GPU(duration=45)
def decide(state: str, *fields):
"""Answer several typed questions about one piece of state text in one forward pass each.
Args:
state: the context to reason about — a customer message, a forum post, a diff.
fields: four (type, instructions, options) triples. `type` is choice / score / noul;
`options` is a comma-separated list (used by `choice`, ignored by the others).
Returns:
(answers as HTML, a one-line status, the raw answers as JSON)
"""
state = (state or "").strip()
if not state:
raise gr.Error("Give the model some state text to reason about.")
questions = _build_questions([f if isinstance(f, str) else "" for f in fields])
state_used, truncated, n_tokens = jc.truncate_state(state)
t0 = time.perf_counter()
answers = [jc.decide(q["kind"], state_used, q["instructions"], q["options"])
for q in questions]
ms = (time.perf_counter() - t0) * 1000.0
note = (f" — state truncated from {n_tokens} to {jc.MAX_STATE_TOKENS} tokens (head 60% / tail 40%)"
if truncated else "")
status = (
f"**{len(questions)} question{'s' if len(questions) != 1 else ''} answered** — "
f"{ms:.0f} ms of GPU compute for {len(questions)} prefill pass"
f"{'es' if len(questions) != 1 else ''}, state {n_tokens} tokens{note}. "
"System 1: one forward pass per question, no tokens generated."
)
payload = json.dumps(
{"state": state, "system": 1, "answers": answers}, indent=2, ensure_ascii=False
)
return "".join(_answer_html(q, a) for q, a in zip(questions, answers)), status, payload
@spaces.GPU(duration=150)
def escalate(state: str, question: str, options_text: str, threshold: float,
always_system2: bool, thinking: bool, max_new_tokens: float):
"""Confidence-gated escalation: System 1 answers in one pass; System 2 reasons when unsure.
Args:
state: the context to reason about.
question: the question to answer.
options_text: comma-separated options (2-16). Falls back to yes/no if blank.
threshold: escalate to System 2 when System 1's top probability is below this.
always_system2: skip the gate and always ask System 2 to reason.
thinking: run System 2 in thinking mode (step-by-step reasoning).
max_new_tokens: token budget for the System 2 reply.
Returns:
(answer HTML, status, System 2 reply, reasoning, raw JSON)
"""
state = (state or "").strip()
question = (question or "").strip()
if not state:
raise gr.Error("Give the model some state text to reason about.")
if not question:
raise gr.Error("Give the model a question.")
options = _parse_options(options_text)
kind = "choice" if options else "noul"
if kind == "choice" and not 2 <= len(options) <= 16:
raise gr.Error(f"`choice` needs 2-16 comma-separated options; got {len(options)}.")
state_used, _trunc, n_tokens = jc.truncate_state(state)
t0 = time.perf_counter()
probs = jc.decide(kind, state_used, question, options if kind == "choice" else None)
s1_ms = (time.perf_counter() - t0) * 1000.0
top = max(probs, key=probs.get)
gate = always_system2 or probs[top] < float(threshold)
s1_html = (
''
f'
'
f'system 1 {html.escape(question)}
'
f'
{html.escape(str(top))} '
f''
f"P({html.escape(str(top))}) = {probs[top]:.3f} — one prefill pass
"
f"{_bars_html(list(probs.items()), top)}
"
)
if not gate:
status = (
f"**System 1 was confident** — top probability {probs[top]:.3f} ≥ threshold "
f"{float(threshold):.2f}, so System 2 was not consulted. "
f"({s1_ms:.0f} ms of GPU compute, state {n_tokens} tokens.)"
)
payload = json.dumps({"state": state, "question": question, "options": list(probs),
"system1": probs, "escalated": False}, indent=2, ensure_ascii=False)
return s1_html, status, "", "", payload
opts = list(probs.keys())
t1 = time.perf_counter()
raw, completed = jc.generate(state_used, question, opts,
max_new_tokens=int(max_new_tokens),
enable_thinking=bool(thinking))
s2_ms = (time.perf_counter() - t1) * 1000.0
reasoning, answer = _split_reasoning(raw)
cut_note = ""
if not completed:
cut_note = (
f"\n\n*System 2 hit its {int(max_new_tokens)}-token budget mid-reply — "
"raise the budget, or untick thinking mode for a concise answer.*"
)
if thinking and not reasoning:
# the verbose thinking trace never reached its closing tag
reasoning, answer = raw, ""
picked = _matched_option(answer, opts) if completed else None
trigger = (
f"the 'always ask System 2' box was ticked (System 1's top probability was "
f"{probs[top]:.3f})"
if always_system2 else
f"System 1's top probability {probs[top]:.3f} was below the threshold "
f"{float(threshold):.2f}"
)
status = (
f"**Escalated to System 2** — {trigger}. System 1: "
f"{s1_ms:.0f} ms; System 2: {s2_ms:.0f} ms, state {n_tokens} tokens."
+ (" Reply cut short at the token budget." if not completed else "")
)
answer_md = answer
if picked:
answer_md = (
f"> **System 2 picks: `{picked}`**\n\n" + answer
)
answer_md += cut_note
payload = json.dumps({"state": state, "question": question, "options": opts,
"system1": probs, "escalated": True, "completed": completed,
"system2_answer": answer, "system2_reasoning": reasoning},
indent=2, ensure_ascii=False)
return s1_html, status, answer_md, reasoning, payload
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
CSS = """
main, .gradio-container, .gradio-container > .main { max-width: 1150px !important; margin: 0 auto !important; }
.dark .gradio-container { color: var(--body-text-color); }
"""
# Example rows, taken from the model authors' own published tests: the quickstart cases in
# the model card and the front-page / real-world cases in reports/realworld_9b.json
# (Apache-2.0). Each row is [state, (type, instructions, options) x 4].
EXAMPLES = [
[
"Customer says the parcel arrived damaged and wants their money back.",
"noul", "Is the customer asking for a refund?", "",
"noul", "Is the customer angry or frustrated?", "",
"choice", "Which team should handle this message?",
"billing, logistics & shipping, fraud & abuse, product feedback",
"score", "How polite is the customer, from 0 (abusive) to 5 (very polite)?", "",
],
[
"SKU AX-330 stock at 8% of safety level; supplier late twice this quarter.",
"choice", "Supplier response for this scenario.",
"issue_warning, renegotiate, dual_source, maintain",
"noul", "Is the supply risk already material?", "",
"score", "Rate the supply risk on a 0-5 scale.", "",
"noul", "", "",
],
[
"Two-tier encryption in the UK",
"choice", "Which topic is this story mainly about?", ", ".join(TOPIC_OPTIONS),
"noul", "Is this story about artificial intelligence or machine learning?", "",
"noul", "", "",
"noul", "", "",
],
[
"Please stop this. We've asked you before to observe the guidelines. HN is not a "
"site for political debates, especially ideological ones. We've banned accounts "
"repeatedly for this.",
"choice", "What is this comment mainly doing?", ", ".join(INTENT_OPTIONS),
"noul", "Does this comment insult or personally attack another person or group, "
"rather than just arguing the point?", "",
"noul", "", "",
"noul", "", "",
],
[
"STATUS: payments processor is down. Checkout API returns 500s for all "
"transactions; customers cannot pay. Revenue impact growing every minute.",
"score", "Rate operational urgency on a 0-5 scale.", "",
"noul", "Should the on-call engineer be paged now?", "",
"noul", "", "",
"noul", "", "",
],
]
with gr.Blocks(title="JEV-9B — typed decisions") as demo:
gr.Markdown(
"# JEV-9B — typed decisions in one forward pass\n"
"An integrated **System 1 + System 2** model built with the Blocks-of-Experts recipe: "
"a frozen **Qwen3.5-9B** backbone, a 40M-parameter LoRA + decision head for "
"**System 1** — typed questions answered in a *single prefill pass* with a **calibrated "
"probability distribution** — and the untouched base model as **System 2** for "
"deliberate step-by-step reasoning. Per the authors' measurements: mean KL ≈ 0.019 to "
"the closed TypeSafe Jev 1.13 on 25k held-out decisions, ECE 0.0007, and HumanEval "
"70.7% — byte-identical to the base model.\n\n"
"Model: [`autotrust/JEV-9B`](https://huggingface.co/autotrust/JEV-9B) (Apache-2.0) · "
"System 2 path = [`Qwen/Qwen3.5-9B`](https://huggingface.co/Qwen/Qwen3.5-9B) unchanged"
)
with gr.Tabs():
with gr.TabItem("System 1 — decide"):
gr.Markdown(
"Give the model one piece of **state** text and up to four typed questions. "
"Nothing is generated: the model ranks the options you hand it and returns a "
"probability distribution for each question — one forward pass per question."
)
state = gr.Textbox(
label="State — the text to reason about",
placeholder="A customer support message, a forum post, an incident summary… "
"up to 1,024 tokens (truncated head 60% / tail 40% beyond that)",
lines=5, max_lines=14,
)
gr.Markdown(
"#### Questions\n"
"`choice` = one of 2–16 options you list · `score` = a fixed 0–5 ordered "
"scale (describe the scale in the question) · `noul` = a yes/no statement, "
"answered as *P(true)*. Leave a row's instructions blank to skip it."
)
kind_boxes, instr_boxes, opt_boxes = [], [], []
for i in range(N_QUESTIONS):
with gr.Row():
k = gr.Dropdown(choices=KINDS, value="noul", label=f"Q{i + 1} type",
scale=1, allow_custom_value=True,
info="leave the instructions blank to skip a row")
ins = gr.Textbox(label=f"Q{i + 1} instructions", scale=4, lines=1,
placeholder="e.g. Which team should handle this?")
opt = gr.Textbox(label="options (comma-separated, choice only)", scale=5,
lines=1, placeholder="e.g. billing, technical, sales")
kind_boxes.append(k); instr_boxes.append(ins); opt_boxes.append(opt)
run = gr.Button("Decide", variant="primary")
out_status = gr.Markdown()
out_html = gr.HTML(label="Answers")
with gr.Accordion("Raw answers (JSON)", open=False):
out_json = gr.Code(language="json", lines=14, show_label=False, wrap_lines=True)
field_boxes = [c for triple in zip(kind_boxes, instr_boxes, opt_boxes) for c in triple]
decide_inputs = [state] + field_boxes
run.click(decide, inputs=decide_inputs,
outputs=[out_html, out_status, out_json], api_name="decide")
gr.Examples(
examples=EXAMPLES,
inputs=decide_inputs,
outputs=[out_html, out_status, out_json],
fn=decide,
cache_examples=True,
cache_mode="lazy",
label="Examples — from the model authors' published tests",
)
with gr.TabItem("System 1 → System 2 — escalate"):
gr.Markdown(
"The confidence-gated escalation pattern from the model card: System 1 answers "
"in one pass; if its top probability is below your threshold, the *same "
"weights* switch to System 2 — the pristine Qwen3.5-9B with the LoRA off — "
"which thinks it through and writes an answer. Expect the two to disagree "
"sometimes: they are integrated in serving, not in knowledge."
)
esc_state = gr.Textbox(
label="State — the text to reason about", lines=5, max_lines=14,
placeholder="The context your question is about…",
)
esc_question = gr.Textbox(
label="Question", lines=1,
placeholder="e.g. Which handler should receive this message?",
)
esc_options = gr.Textbox(
label="Options (comma-separated, 2–16; leave blank for a yes/no question)",
lines=1, placeholder="e.g. BLOCK, ESCALATE, ROUTE_TECH, ROUTE_SPAM",
)
with gr.Accordion("Escalation settings", open=True):
esc_threshold = gr.Slider(
0.5, 1.0, value=0.90, step=0.01, label="Confidence threshold",
info="Escalate to System 2 when System 1's top probability is below this. "
"0.90 is the value suggested in the model card.",
)
esc_always = gr.Checkbox(
value=False, label="Always ask System 2 (skip the gate)",
)
esc_thinking = gr.Checkbox(
value=True, label="System 2 thinking mode (step-by-step reasoning)",
)
esc_tokens = gr.Slider(
64, 2048, value=1024, step=64, label="System 2 token budget",
info="Thinking mode is verbose — if the reply gets cut short, raise this.",
)
esc_btn = gr.Button("Ask", variant="primary")
esc_status = gr.Markdown()
esc_html = gr.HTML(label="System 1 — one prefill pass")
esc_answer = gr.Markdown(label="System 2 — deliberate answer")
with gr.Accordion("System 2 reasoning trace", open=False):
esc_reasoning = gr.Markdown(label="Reasoning")
with gr.Accordion("Raw (JSON)", open=False):
esc_json = gr.Code(language="json", lines=12, show_label=False, wrap_lines=True)
esc_btn.click(
escalate,
inputs=[esc_state, esc_question, esc_options, esc_threshold, esc_always,
esc_thinking, esc_tokens],
outputs=[esc_html, esc_status, esc_answer, esc_reasoning, esc_json],
api_name="escalate",
)
gr.Markdown(
"---\n"
"**What to expect.** System 1 is a distillation of the closed TypeSafe Jev 1.13's "
"published output distributions: it mirrors the teacher's decisions, *including its "
"mistakes*, and inherits its blind spots on arithmetic, dates, counting and "
"adversarial inputs. It reads English best; the first 1,024 tokens of state are "
"used. `choice` handles up to 16 options — beyond ~8, the authors recommend "
"[JEV-27B](https://huggingface.co/autotrust/JEV-27B). Not for high-stakes decisions; "
"use confidence gating.\n\n"
"**Implementation.** Inference follows the authors' documented `transformers` + "
"`peft` path exactly (backbone with the LoRA adapter on, 24-slot fp32 head on the "
"last token, per-kind calibration temperature; System 2 generated with the adapter "
"off). Weights: 18 GB bf16, streamed into VRAM by ZeroGPU on each request. "
"Examples are taken from the model card quickstart and the authors' "
"`reports/realworld_9b.json` (Apache-2.0)."
)
demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)