Spaces:
Running on Zero
Running on Zero
File size: 6,218 Bytes
43389b3 d801175 43389b3 d801175 43389b3 | 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 | """LLM as Jev-like classifier. Single-file Space."""
import json
import gradio as gr
import spaces
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
LABELS = "0123456789abcdefghijklmnopqrstuvwxyz"
DEFAULT_MODEL = "Qwen/Qwen3-0.6B"
MODELS = [DEFAULT_MODEL, "Qwen/Qwen3-1.7B", "Qwen/Qwen3-4B",
"Qwen/Qwen2.5-0.5B-Instruct", "Qwen/Qwen2.5-1.5B-Instruct"]
_cache = {}
def get_model(mid):
if mid not in _cache:
tok = AutoTokenizer.from_pretrained(mid, trust_remote_code=True)
tok.pad_token = tok.pad_token or tok.eos_token
tok.padding_side = "left"
m = AutoModelForCausalLM.from_pretrained(mid, trust_remote_code=True, dtype="auto", device_map="auto").eval()
_cache[mid] = (m, tok)
return _cache[mid]
def prompt(state, question, options):
opts = "\n".join(f"<option_{i}>{o}</option_{i}>" for i, o in enumerate(options))
return (f"For the following situation and question, select the desired option, by just returning the option number\n\n"
f"<Situation>{state}</Situation>\n<Question>{question}</Question>\n\nOptions:\n\n{opts}\n\nAnswer: ")
def ids_for(tok, n):
ids = []
for c in LABELS[:n]: # prompt ends with space, so no " c" variant needed
e = tok.encode(c, add_special_tokens=False)
assert len(e) == 1, f"label {c!r} is not single-token: {e}"
ids.append(e[0])
return ids
@torch.no_grad()
def predict_batch(prompts, ns, model, tok):
enc = tok(prompts, return_tensors="pt", padding=True).to(model.device)
last = model(**enc).logits[:, -1, :] # [B, V]
mn, b = max(ns), len(ns)
idx = torch.zeros((b, mn), dtype=torch.long, device=last.device)
for i, n in enumerate(ns):
idx[i, :n] = torch.tensor(ids_for(tok, n), device=last.device)
mask = torch.arange(mn, device=last.device)[None] < torch.tensor(ns, device=last.device)[:, None]
scores = torch.where(mask, last.gather(1, idx), float("-inf"))
return torch.softmax(scores, 1).tolist()
@spaces.GPU(duration=120)
def classify(mid, state, qjson):
qs = json.loads(qjson)
qs = [{"key": k, **v} for k, v in qs.items()] if isinstance(qs, dict) else qs
prompts, ns, metas = [], [], []
for q in qs:
t = q.get("type", "choice")
key = q.get("key", "q")
if t == "noul":
o = ["no", "yes"]
meta = {"key": key, "type": t, "options": o}
prompts.append(prompt(state, q.get("question", q.get("instructions", "")), o))
else:
o = q["options"]
meta = {"key": key, "type": t, "options": o, "min": q.get("min", 0), "max": q.get("max", 100)}
prompts.append(prompt(state, q.get("question", ""), o))
ns.append(len(o))
metas.append(meta)
model, tok = get_model(mid)
out = {}
for pv, m in zip(predict_batch(prompts, ns, model, tok), metas):
pv = [round(float(x), 6) for x in pv[:len(m["options"])]]
probs = dict(zip(m["options"], pv))
conf = max(pv)
if m["type"] == "choice":
out[m["key"]] = {"choice": m["options"][pv.index(conf)], "probabilities": probs, "confidence": conf}
elif m["type"] == "noul":
out[m["key"]] = {"noul": pv[1], "probabilities": probs, "confidence": conf}
else:
lo, hi = m["min"], m["max"]
out[m["key"]] = {"score": round(sum(p * (lo + i * (hi - lo) / (len(pv) - 1)) for i, p in enumerate(pv)), 4),
"probabilities": probs, "confidence": conf}
return out
EXAMPLE = json.dumps({
"category": {"type": "choice", "question": "Which dept?", "options": ["billing", "technical", "sales"]},
"urgency": {"type": "score", "question": "How urgent?", "options": ["low", "medium", "high"], "min": 0, "max": 100},
"is_urgent": {"type": "noul", "question": "Urgent?"},
}, indent=2)
with gr.Blocks(title="QwenJev — Turn Context into Decisions") as demo:
gr.Markdown("""
# QwenJev — Turn Context into Decisions
### Make any LLM work as a decision model or zero-shot classifier
Turn ordinary context into **structured decisions and probabilities**—without
generating free-form text.
1. Describe what is happening in **Context**.
2. Define one or more decisions in **Questions**.
3. Click **Evaluate decisions** to get typed, name-keyed results.
""")
with gr.Row():
with gr.Column():
mid = gr.Dropdown(
MODELS, value=DEFAULT_MODEL, label="1. Model",
info="The small Qwen3-0.6B model is the fastest default.",
allow_custom_value=True)
state = gr.Textbox(
label="2. Context",
info="Paste the facts or situation the model should judge.",
lines=6, value="Stripe failing 3 days. Losing sales. ASAP.")
qin = gr.Textbox(
label="3. Questions (JSON)",
info="Each named entry becomes one independent decision.",
lines=18, value=EXAMPLE)
with gr.Accordion("Question types", open=False):
gr.Markdown("""
- **choice** — select one item from `options`.
- **score** — distribute probability over ordered `options` and
return their expected value between `min` and `max`.
- **noul** — return a yes/no probability; no `options` required.
The JSON key (for example, `urgency`) becomes the result key.
""")
btn = gr.Button("Evaluate decisions", variant="primary")
with gr.Column():
gr.Markdown("""
### Structured results
Every answer is restricted to the requested choices.
- **probabilities** show the model's relative preference among the
allowed answers.
- **confidence** is the highest option probability. It is a raw model
score, not a calibrated guarantee of correctness.
""")
out = gr.JSON(label="Decisions and probabilities")
btn.click(classify, [mid, state, qin], [out])
if __name__ == "__main__":
demo.launch()
|