Spaces:
Running on Zero
Running on Zero
multimodalart HF Staff
GLiDE-0.4B decision model demo: vendored glide package, app, examples from model card
c1be530 verified Download app.py from hugging-apps/glide-decision-model: direct link, hf CLI and curl.
- Browser
- Download file 13.4 kB
-
https://huggingface.co/spaces/hugging-apps/glide-decision-model/resolve/main/app.py
- Command line
-
hf download hf://spaces/hugging-apps/glide-decision-model/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/glide-decision-model/resolve/main/app.py
13.4 kB
| """GLiDE-0.4B — a decision model: state in, typed questions out, calibrated probabilities. | |
| Model: https://huggingface.co/fastino/GLiDE-0.4B | |
| Author: Fastino — https://fastino.ai | |
| Given a state (text or JSON) and one or more typed questions, GLiDE returns a probability | |
| for every option of every question. The probabilities are read from a 255-way decision | |
| readout — nothing is generated. | |
| The `glide/` package is vendored verbatim from the model repository and the checkpoint | |
| is streamed from the Hub at startup (~0.8 GB, bf16). | |
| """ | |
| import html as _html | |
| import json | |
| import time | |
| import spaces # must come before torch | |
| import torch | |
| import gradio as gr | |
| from glide import Glide, RequestError | |
| MODEL_ID = "fastino/GLiDE-0.4B" | |
| model = Glide.from_pretrained(MODEL_ID) # device="cuda", bf16, vision=True defaults | |
| # -------------------------------------------------------------------------------------- | |
| # request building | |
| # -------------------------------------------------------------------------------------- | |
| def _parse_state(text: str): | |
| """A JSON object/array is passed through structurally; anything else is plain text.""" | |
| s = (text or "").strip() | |
| if s[:1] in "{[": | |
| try: | |
| v = json.loads(s) | |
| if isinstance(v, (dict, list)): | |
| return v | |
| except json.JSONDecodeError: | |
| pass | |
| return text or "" | |
| def _build_questions(questions_json: str) -> dict: | |
| try: | |
| questions = json.loads(questions_json) | |
| except json.JSONDecodeError as e: | |
| raise gr.Error(f"Questions are not valid JSON: {e}") from None | |
| if not isinstance(questions, dict) or not questions: | |
| raise gr.Error( | |
| 'Questions must be a non-empty JSON object, e.g. ' | |
| '{"intent": {"type": "choice", "instructions": "...", "criteria": {"a": "A", "b": "B"}}}' | |
| ) | |
| return questions | |
| def _score_legend(q: dict) -> dict: | |
| """Map the numeric level index strings GLiDE returns to the user's level descriptions.""" | |
| crit = q.get("criteria") if isinstance(q, dict) else None | |
| if isinstance(crit, list): | |
| return {str(i): str(d) for i, d in enumerate(crit)} | |
| if isinstance(crit, dict): | |
| try: | |
| keys = sorted(crit, key=lambda x: float(x)) | |
| return {str(i): str(crit[k]) for i, k in enumerate(keys)} | |
| except (TypeError, ValueError): | |
| pass | |
| return {} | |
| # -------------------------------------------------------------------------------------- | |
| # rendering | |
| # -------------------------------------------------------------------------------------- | |
| BADGE = {"noul": "#2563eb", "choice": "#7c3aed", "score": "#ea580c"} | |
| def _bar(label: str, p: float, highlight: bool) -> str: | |
| pct = max(0.0, min(1.0, float(p))) * 100 | |
| colour = "var(--color-accent)" if highlight else "var(--border-color-accent)" | |
| weight = "600" if highlight else "400" | |
| return ( | |
| '<div style="display:flex;align-items:center;gap:8px;margin:3px 0;font-size:13px">' | |
| f'<div style="flex:0 0 46%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:{weight}" ' | |
| f'title="{_html.escape(label)}">{_html.escape(label)}</div>' | |
| '<div style="flex:1;height:9px;border-radius:5px;background:var(--background-fill-secondary);overflow:hidden">' | |
| f'<div style="width:{pct:.1f}%;height:100%;background:{colour}"></div></div>' | |
| f'<div style="flex:0 0 48px;text-align:right;font-variant-numeric:tabular-nums;font-weight:{weight}">{p:.2f}</div>' | |
| "</div>" | |
| ) | |
| def _card(qid: str, ans: dict, question: dict) -> str: | |
| t = ans["type"] | |
| head = ( | |
| '<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:6px">' | |
| f'<span style="font-weight:600;font-size:15px">{_html.escape(qid)}</span>' | |
| f'<span style="font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:1px 6px;border-radius:9px;' | |
| f'color:#fff;background:{BADGE[t]}">{t}</span>' | |
| ) | |
| if t == "noul": | |
| p = ans["noul"] | |
| head += ( | |
| f'<span style="margin-left:auto;font-size:20px;font-weight:700;font-variant-numeric:tabular-nums">{p:.2f}</span>' | |
| f'<span style="font-size:11px;opacity:.65">conf {ans["confidence"]:.2f}</span></div>' | |
| ) | |
| body = _bar("yes", p, p >= 0.5) + _bar("no", 1 - p, p < 0.5) | |
| elif t == "choice": | |
| head += ( | |
| f'<span style="margin-left:auto;font-size:15px;font-weight:700">{_html.escape(str(ans["choice"]))}</span>' | |
| f'<span style="font-size:11px;opacity:.65">conf {ans["confidence"]:.2f}</span></div>' | |
| ) | |
| best = ans["choice"] | |
| body = "".join(_bar(k, v, k == best) for k, v in ans["probabilities"].items()) | |
| else: # score | |
| legend = _score_legend(question) | |
| head += ( | |
| f'<span style="margin-left:auto;font-size:15px;font-weight:700">level {ans["score"]}</span>' | |
| f'<span style="font-size:11px;opacity:.65">conf {ans["confidence"]:.2f}</span></div>' | |
| ) | |
| best = ans["score"] | |
| body = "".join( | |
| _bar(f"{k} · {legend[k]}" if k in legend else str(k), v, k == str(best)) | |
| for k, v in ans["probabilities"].items() | |
| ) | |
| head += ( | |
| f'<div style="font-size:11px;opacity:.7;margin:-2px 0 4px;color:var(--body-text-color)">' | |
| f'expected level {ans["expected_level"]:.2f}</div>' | |
| ) | |
| return ( | |
| '<div style="border:1px solid var(--border-color-primary);border-radius:10px;padding:12px 14px;margin-bottom:10px;' | |
| 'background:var(--background-fill-primary);color:var(--body-text-color)">' + head + body + "</div>" | |
| ) | |
| def _render(result: dict, questions: dict, latency_ms: float) -> str: | |
| answers = result["answers"] | |
| cards = "".join(_card(qid, a, questions.get(qid, {})) for qid, a in answers.items()) | |
| tokens = result.get("usage", {}).get("input_tokens", 0) | |
| meta = ( | |
| '<div style="font-size:12px;opacity:.7;margin-bottom:10px;color:var(--body-text-color)">' | |
| f'{len(answers)} question(s) · {tokens} input tokens · {latency_ms:.0f} ms on GPU · ' | |
| 'bf16 · nothing generated</div>' | |
| ) | |
| return f'<div style="color:var(--body-text-color)">{meta}{cards}</div>' | |
| # -------------------------------------------------------------------------------------- | |
| # inference | |
| # -------------------------------------------------------------------------------------- | |
| def decide(state: str, questions: str): | |
| """Answer typed questions about one state and return calibrated probabilities. | |
| Args: | |
| state: the situation to evaluate. Plain text, or a JSON object / array | |
| (passed through structurally, e.g. {"ticket": "...", "customer_tier": "gold"}). | |
| questions: a JSON object mapping your own question id to a question of type | |
| "choice" (criteria = {option: description}, up to 255 options), | |
| "noul" (yes/no, criteria = optional {"true": ..., "false": ...}) or | |
| "score" (criteria = [ordered level descriptions, ...]). | |
| Returns: | |
| An HTML summary with a probability bar per option of every question, and the | |
| raw System One response ({"model", "answers", "usage"}). | |
| """ | |
| q = _build_questions(questions) | |
| t0 = time.perf_counter() | |
| try: | |
| result = model.decide(state=_parse_state(state), questions=q) | |
| except RequestError as e: | |
| raise gr.Error(f"GLiDE cannot serve this request: {e}") from None | |
| if torch.cuda.is_available(): | |
| torch.cuda.synchronize() | |
| latency_ms = (time.perf_counter() - t0) * 1000 | |
| return _render(result, q, latency_ms), result | |
| # -------------------------------------------------------------------------------------- | |
| # examples — the two quickstart requests from the model card, verbatim | |
| # -------------------------------------------------------------------------------------- | |
| EX1_STATE = json.dumps( | |
| {"ticket": "I was charged twice for order #4821 and the second charge is still pending.", "customer_tier": "gold"}, | |
| indent=2, | |
| ) | |
| EX1_QUESTIONS = json.dumps( | |
| { | |
| "intent": {"type": "choice", "instructions": "What does the customer want?", | |
| "criteria": {"refund": "Get money back", "cancel": "Cancel the order", | |
| "track": "Track a delivery", "other": "Something else"}}, | |
| "urgent": {"type": "noul", "instructions": "Should this ticket be escalated today?"}, | |
| "priority": {"type": "score", "instructions": "What support priority should this ticket get?", | |
| "criteria": ["low: no impact on the customer", "medium: the customer is inconvenienced", | |
| "high: the customer has lost money or cannot use the service"]}, | |
| }, | |
| indent=2, | |
| ) | |
| EX2_STATE = "Order #4821 was delivered to the wrong address and the customer wants it re-sent today." | |
| EX2_QUESTIONS = json.dumps( | |
| { | |
| "action": {"type": "choice", "instructions": "What should support do next?", | |
| "criteria": {"resend": "Re-ship the order", "refund": "Refund the order", | |
| "investigate": "Open a carrier investigation"}}, | |
| "urgent": {"type": "noul", "instructions": "Is the request time-sensitive?"}, | |
| }, | |
| indent=2, | |
| ) | |
| EXAMPLES = [ | |
| [EX1_STATE, EX1_QUESTIONS], | |
| [EX2_STATE, EX2_QUESTIONS], | |
| ] | |
| EXAMPLE_LABELS = ["Support ticket — intent, urgency, priority (model-card quickstart)", | |
| "Wrong address — next action + urgency (model-card HTTP example)"] | |
| # -------------------------------------------------------------------------------------- | |
| # UI | |
| # -------------------------------------------------------------------------------------- | |
| CSS = """ | |
| #col-container { max-width: 1180px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| INTRO = f"""# GLiDE-0.4B — state in, typed questions, calibrated probabilities out | |
| [`{MODEL_ID}`](https://huggingface.co/{MODEL_ID}) is a **decision model** from | |
| [Fastino](https://fastino.ai). Given a state (text or JSON) and one or more typed questions, it returns a | |
| **probability for every option of every question** — read from a 255-way decision readout in a single forward | |
| pass. Nothing is generated. | |
| Three question types: **choice** (one of up to 255 named options), **noul** (yes / no → P(yes)) and | |
| **score** (ordered levels). 0.40B params, bf16, 51.10 on the Decision Index 0.3.1 public suite. This model has | |
| no vision encoder — images are rejected. | |
| [Fastino](https://fastino.ai) · [model card](https://huggingface.co/{MODEL_ID}) · | |
| [Fastino API](https://docs.fastino.ai/inference/systemone) | |
| """ | |
| with gr.Blocks(title="GLiDE-0.4B decision model") as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown(INTRO) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| state = gr.Textbox( | |
| label="State — the situation to evaluate", | |
| placeholder='e.g. {"ticket": "I was charged twice for order #4821…", "customer_tier": "gold"} (plain text or JSON)', | |
| lines=10, | |
| value=EX1_STATE, | |
| ) | |
| with gr.Column(scale=1): | |
| questions = gr.Code( | |
| label="Questions (JSON)", | |
| language="json", | |
| lines=10, | |
| value=EX1_QUESTIONS, | |
| ) | |
| run = gr.Button("Decide", variant="primary") | |
| answers_html = gr.HTML(label="Answers") | |
| raw = gr.JSON(label="Raw System One response") | |
| with gr.Accordion("Advanced — question schema & limits", open=False): | |
| gr.Markdown( | |
| '**Question schema** — `{"<your id>": {"type": …, "instructions": …, "criteria": …}}`\n\n' | |
| "| type | `criteria` | answer |\n|---|---|---|\n" | |
| "| `choice` | `{option: description or null}`, 1–255 options | `choice`, `probabilities`, `confidence` = top − second |\n" | |
| "| `noul` | optional `{\"true\": …, \"false\": …}` | `noul` = P(yes), `confidence` = \\|2·P − 1\\| |\n" | |
| "| `score` | `[level, level, …]` ordered, or `{numeric level: description}` | `score` = top level, `expected_level`, `probabilities` |\n\n" | |
| "`state` and `instructions` may be any JSON value — objects are rendered as `key: value` lines. " | |
| "Each question prompt holds up to **7,999 tokens** (state + question + options); longer prompts keep " | |
| "their first 1,024 tokens and their end, so they are still answered. Answers are read from a " | |
| "255-way decision readout — nothing is generated, so outputs are deterministic at a fixed temperature." | |
| ) | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| example_labels=EXAMPLE_LABELS, | |
| inputs=[state, questions], | |
| outputs=[answers_html, raw], | |
| fn=decide, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Examples from the GLiDE-0.4B model card", | |
| ) | |
| gr.on( | |
| triggers=[run.click], | |
| fn=decide, | |
| inputs=[state, questions], | |
| outputs=[answers_html, raw], | |
| api_name="decide", | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True) | |