Quickstart
Ten minutes, from nothing to a file of texts answered on your machine. You need Python 3.10 or later and about 3 GB of disk for the default model.
1. Install
python -m venv .venv && source .venv/bin/activate
pip install jul
jul setup
jul setup does four things and says which: it picks the backend (MLX on an Apple Silicon Mac, PyTorch
anywhere else), installs it if missing, downloads the default model once from the Hugging Face Hub, and runs
one real decision with its latency.
jul setup: jul-decision-wemm-4b on mlx
backend mlx (installed now)
preset jul-decision-wemm-4b (built-in): tau 0.0553, center options
weights usejul/WeMM-Embedding-4B-mlx-4bit -> ~/.cache/huggingface/...
check 'I was charged twice' -> billing (0.88), 55 ms per decision (first call 4.1s with the load)
Nothing else leaves your machine after that download: no account, no key, no telemetry.
2. A first answer, from the shell
jul ask choice "Which team should handle this ticket?" \
-o billing:"payments, invoices" -o technical:"bugs, errors" -o sales:"pricing, plans" \
--state "I was charged twice"
{"request_id": "…", "model": "jul-decision-wemm-4b",
"usage": {"input_tokens": 24, "output_tokens": 0, "total_tokens": 24, "truncated_tokens": 0},
"answers": {"choice": {"type": "choice", "choice": "billing",
"probabilities": {"billing": 0.88, "technical": 0.11, "sales": 0.01},
"confidence": 0.88}},
"latency_ms": 55.3}
Read it this way:
choiceis the option with the highest probability,confidenceis that probability;probabilitiessum to 1 over your options: the model can only pick one of them;output_tokensis always 0: nothing is generated, the answer is read from the model's hidden state (see Concepts).
The two other question types:
jul ask noul "Does the message report a software bug?" --state "The app crashes on export"
jul ask score "How frustrated is the customer?" -o "Calm" -o "Annoyed" -o "Very angry" \
--state "Third time I write. Nobody answers."
A noul answers the probability of yes ("noul": 0.93). A score answers the expected level from 0 to the
number of levels minus one ("score": 1.8, close to "Very angry"), with a probability per level.
3. The same, in Python
from jul import TypeSafeClient, Choice, Noul, Score
client = TypeSafeClient() # the default model; TypeSafeClient(model="minicpm5-2b") for another
response = client.system_one(
state={"ticket": "I was charged twice for my subscription this month."},
questions={
"team": Choice(instructions="Which team should handle this ticket?",
criteria={"billing": "payments, invoices, refunds",
"technical": "bugs, errors, crashes",
"sales": "pricing, plans, demos"}),
"is_bug": Noul(instructions="Does the message report a software bug?"),
"frustration": Score(instructions="How frustrated is the customer?",
criteria=["Calm", "Frustrated but civil", "Very angry"]),
},
)
response.choices["team"].choice # "billing"
response.nouls["is_bug"].noul # 0.12
response.scores["frustration"].score # 1.15
response.as_dict() # the JSON above
stateis the text to judge: a string, or any object (a dict is turned into JSON for the model).questionsis a dict: you name each question, and its answer comes back under the same name.- Every question of a call is about the same state. One call per text.
- The model loads on the first call (a few seconds) and stays in memory; the next calls take tens of milliseconds.
4. A whole file
Put the questions in a file once:
# questions.yaml (pip install "jul[yaml]", or write the same thing as JSON)
team:
type: choice
instructions: Which team should handle this ticket?
criteria:
billing: payments, invoices, refunds
technical: bugs, errors, crashes
sales: pricing, plans, demos
is_bug:
type: noul
instructions: Does the message report a software bug?
and the texts one per line, as a string or {"state": …, "id": …}:
{"id": "T-1", "state": "I was charged twice"}
{"id": "T-2", "state": "The export button does nothing"}
"Do you have a yearly plan?"
jul run questions.yaml --input tickets.jsonl --output answers.jsonl
answers.jsonl has one line per text, in the shape above without latency_ms, with the id copied over.
5. Where to go next
| You want to… | Next |
|---|---|
| know why it answered that, and what the knobs are | Concepts |
| get better answers without labels | Writing good questions, then a Context |
| get better answers with labels | autotune |
| use a smaller, faster or other model | The hub |
| call it from JavaScript, Go, a shell script | jul serve |
| ship it to a Lambda | AWS Lambda |
Already on Jev? Change the import and run your code unchanged:
# from typesafe_sdk import TypeSafeClient, Choice, Noul, Score
from jul import TypeSafeClient, Choice, Noul, Score
Arguments that only mean something for a remote API (api_key, base_url, timeout, max_retries, …) are
accepted and ignored.