File size: 3,764 Bytes
db45a11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""JEV-9B Decision API — one clean JSON call over System 1.

Inference core (`jev_core.py`) is the model authors' documented `transformers` + `peft`
path, reused verbatim from the official demo Space (Apache-2.0). This file only wraps it
in a single machine-friendly Gradio endpoint (`/decide`) that returns JSON, instead of the
upstream multi-question HTML UI, so a local agent can call it headlessly.

ZeroGPU rules honoured here:
  * `import spaces` MUST precede torch / transformers / any CUDA-touching import
    (jev_core imports torch, so `spaces` is imported before it).
  * models are placed on `cuda` at MODULE level (inside jev_core, at import) — not
    inside the `@spaces.GPU` function.
"""

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 json  # noqa: E402

import gradio as gr  # noqa: E402

import jev_core as jc  # noqa: E402


def _clean_options(raw) -> list:
    """Accept a comma/newline-separated string (or a list) -> list of option strings."""
    if isinstance(raw, (list, tuple)):
        items = [str(o).strip() for o in raw]
    else:
        items = [o.strip() for o in str(raw or "").replace("\n", ",").split(",")]
    return [o for o in items if o]


@spaces.GPU(duration=45)
def decide(kind: str, state: str, question: str, options: str = "") -> str:
    """Answer ONE typed System 1 decision; return calibrated probabilities as JSON.

    kind:     "noul" | "choice" | "score"
    state:    the text to reason about (truncated to 1024 tokens, head 60% / tail 40%)
    question: the decision question
    options:  comma-separated options (choice only, 2-16); ignored for noul/score
    """
    kind = (kind or "").strip()
    state = (state or "").strip()
    question = (question or "").strip()

    if kind not in jc.KINDS:
        return json.dumps({"error": f"kind must be one of {list(jc.KINDS)}"})
    if not state:
        return json.dumps({"error": "state is required"})
    if not question:
        return json.dumps({"error": "question is required"})

    opts = _clean_options(options)
    if kind == "choice":
        if not 2 <= len(opts) <= 16:
            return json.dumps({"error": "choice needs 2-16 comma-separated options"})
        if len(set(opts)) != len(opts):
            return json.dumps({"error": "choice options must be unique"})
    else:
        opts = None

    state_used, truncated, n_tok = jc.truncate_state(state)
    dist = jc.decide(kind, state_used, question, opts)
    best = max(dist, key=dist.get)
    return json.dumps(
        {
            "kind": kind,
            "distribution": dist,
            "best": best,
            "confidence": dist[best],
            "state_tokens": n_tok,
            "truncated": truncated,
        }
    )


with gr.Blocks(title="JEV-9B Decision API") as demo:
    gr.Markdown(
        "## JEV-9B System 1 — typed decisions in one prefill pass\n"
        "`noul` (yes/no) · `choice` (2–16 options) · `score` (0–5). "
        "Returns a calibrated probability distribution as JSON."
    )
    with gr.Row():
        kind_in = gr.Dropdown(list(jc.KINDS), value="noul", label="kind")
        state_in = gr.Textbox(label="state", lines=3)
    question_in = gr.Textbox(label="question")
    options_in = gr.Textbox(label="options (comma-separated; choice only)")
    out_json = gr.Code(label="decision JSON", language="json")
    btn = gr.Button("Decide", variant="primary")
    btn.click(
        decide,
        inputs=[kind_in, state_in, question_in, options_in],
        outputs=[out_json],
        api_name="decide",
    )

demo.launch()