██╗ ██╗
██║ ██║
██║██╗ ██╗██║
██ ██║██║ ██║██║
╚█████╔╝╚██████╔╝███████╗
╚════╝ ╚═════╝ ╚══════╝
JUST USE LESS · v0.4 · APACHE-2.0 · EVERY MODEL, ONE WAY TO DECIDE.
One JuL to
rule them all.
Not a router for text: JuL turns any model into a typed decider. Run Kev-format decision models, Laya, embedding LLMs, a 90 MB encoder in a Lambda, Ollama, OpenAI or Mistral embeddings, or your own, behind one API. Measure them on your data, keep the one that wins, and route the unsure questions to Jev, a Kev server or any System One endpoint. Same calls as Jev's SDK: change one import.
Local by default: 0 tokens generated · 0 bytes sent · no key · $0 per call.
It has nothing to say, and it says it in 55 milliseconds._
ANY MODEL · ONE COMMAND · MEASURED
Any model, tested as a decider.
C:\> jul models add qwen3-emb \
--repo ollama:qwen3-embedding:0.6b --backend api
qwen3-emb on api: center options, tau 0.05019
dev accuracy 0.635 ± 0.034 (n=200); ECE 0.172; 2237 ms per decision
C:\> jul ask choice "Which team?" -o billing -o tech \
--state "I was charged twice" --model qwen3-emb --backend api
A model on the Hugging Face Hub, an embeddings API (Ollama, OpenAI, Mistral, Voyage, any
OpenAI-compatible), your own fine-tune: jul models add fits it on development sets and tells you
how good a decider it is. Then the same code uses it. Some models are weak zero-shot: measure, keep the one
that wins on your data.
Numbers above are from a real run (Ollama on a CPU, hence 2.2 s), output trimmed. Every model, backend and provider →
LOCAL FIRST · A BIGGER DECIDER WHEN UNSURE
Escalate only the hard ones.
question ──► local 4B (97 ms on an A10G, no API bill)
├─ confidence ≥ 0.8 ──► answer
└─ unsure ──► Jev · Clef · Nimble · your server
On the decision bench (2,108 Choice, Noul and Score questions), with
jul-decision-wemm-4b local and Jev as the next tier at a 0.8 bar:
80% of the questions stay local, and accuracy goes from 0.849 (local alone) to 0.866.
Choice and Noul mostly stay local (95% and 89%); Score questions mostly go up (93%).
Computed from the recorded predictions of both systems on the bench, not a live run; the share depends on your data and your bar. How to set it up →
ONE API · EVERY DECIDER YOU ALREADY HEARD OF
Run them, or route to them.
DEVICE WHAT JUL DOES HOW Kev models runs them locally (decision.json) jul models add NAME --repo HF_REPO Kev server routes unsure questions to it jul serve --escalate-to https://... Laya runs it locally (its own runtime) --model laya | laya:multilingual Jev (TypeSafe) routes unsure questions to it jul serve --escalate-to typesafe Clef · Nimble routes unsure questions to them --escalate-to cloudflare | ollama Embedding LLMs runs them locally (MLX, PyTorch) jul models add NAME --repo HF_REPO Encoders runs them on CPU, in a Lambda (ONNX) --backend onnx Embeddings APIs reads them over HTTP --repo ollama:M | openai:M | mistral:M Your code unchanged from jul import TypeSafeClient
Same Choice / Noul / Score calls and the same response
whichever runs. The hub, in detail →
CHOOSE YOUR MODEL · AG NEWS, BANKING77, EMOTION · 300 EXAMPLES
ACCURACY · ZERO-SHOT
85.7%
LATENCY · P50
55ms
MEMORY · 4-BIT
2.6GB
0 tokens generated · 0 bytes sent · no key · $0 per call.
AG NEWS · BANKING77 · EMOTION · 300 EXAMPLES
Accuracy, zero-shot.
HOW IT ANSWERS
Read the mind, skip the mouth.
STATE "I was charged twice"
│
┌──────────────┴──────────────┐
│ WeMM-4B 4-bit (your laptop) │
│ layers 1 → 31, then stop. │
└──────────────┬──────────────┘
│ hidden state
▼
cosine(state, option) ◄── options
│ (encoded once,
▼ cached)
softmax(cos / τ)
│
▼
billing ████████████████ 0.88
technical ██ 0.11
sales ▏ 0.01
- The state and each option pass through the same prompt. The answer is the option whose hidden vector sits closest.
- Everything that does not depend on the state (prompt prefix, option vectors, center) is computed once. A call only pays for its own tokens.
- The forward pass stops at the last layer it reads. No decoding loop, no sampling, no JSON to repair.
- A temperature fitted on dev sets turns cosines into probabilities you can threshold on.
- Yes/no and score questions ask how the question and the text relate, so the
jul-decision-*models also read them together: a cross model, as LoRA adapters on the same weights for WeMM.
WHY LOCAL
Small model. Short answer. Your box.
- Private by construction. Tickets, emails, medical notes: the text never leaves the process.
- Latency you can loop over. 55 ms is cheap enough for every row, every request, every keystroke.
- No meter running. No API key, no rate limit, no invoice. Ten thousand calls cost electricity.
- Runs anywhere. MLX on Apple Silicon, PyTorch on CUDA, CPU or MPS.
- Your model. Swap the backbone, register your own with one command, fine-tune the head on your data.
ALREADY ON JEV?
Change one import.
- from typesafe_sdk import TypeSafeClient, Choice, Noul, Score + from jul import TypeSafeClient, Choice, Noul, Score client = TypeSafeClient(model="wemm-4b-4bit") r = client.system_one( state={"ticket": "I was charged twice this month."}, questions={ "team": Choice( instructions="Which team should handle this?", criteria={"billing": "payments, refunds", "technical": "bugs, errors", "sales": "pricing, plans"}), "is_bug": Noul(instructions="Is it a bug report?"), }, ) r.choices["team"].choice # "billing" r.choices["team"].probabilities # {"billing": 0.88, ...}
Same imports, same calls, same response shapes. Arguments that only mean something remotely are accepted and ignored.
ZERO-SHOT · AG NEWS, BANKING77, EMOTION · 300 EXAMPLES
Up to 0.877 zero-shot, 0.897 with labels.
| jul wemm-4b ² | 0.95 | 0.88 | 0.80 | 0.877 | 0.112 | 78 ms | 4.5 GB |
|---|---|---|---|---|---|---|---|
| jul wemm-9b ² | 0.97 | 0.84 | 0.78 | 0.863 | 0.114 | 138 ms | 9.0 GB |
| jul-decision-wemm-4b-4bit ⁴ | 0.90 | 0.87 | 0.80 | 0.857 | 0.084 | 55 ms | 2.6 GB |
| jul f2llm-4b ² | 0.89 | 0.82 | 0.81 | 0.840 | 0.090 | 46 ms | 3.0 GB |
| jul f2llm-1.7b ² | 0.91 | 0.67 | 0.87 | 0.817 | 0.082 | 24 ms | 1.0 GB |
| jul minicpm5-2b-decision ¹ | 0.91 | 0.79 | 0.69 | 0.796 | 0.133 | 217 ms | 1.3 GB |
| jul harrier-0.6b ² | 0.72 | 0.52 | 0.76 | 0.667 | 0.144 | 13 ms | 0.31 GB |
| jul-decision-e5-small ³ | 0.72 | 0.59 | 0.36 | 0.557 | 0.140 | 4 ms | 0.09 GB |
| jul minicpm5-2b | 0.80 | 0.59 | 0.46 | 0.617 | 0.113 | 64 ms | 2.7 GB |
Click a column to sort. Highlighted: the best value in each column.
- ² Embedding LLMs (Tencent, CodeFuse-AI, Microsoft), read by jul like any other model:
jul models add. - Banking77 and Emotion are in MTEB, which embedding models train on. AG News is not: there, wemm-9b scores 0.97 against 0.91.
- ¹ Trained on these three tasks' training splits. On six sources it never trained on: 0.721.
- ⁴ WeMM-Embedding-4B, 4-bit, with LoRA adapters for yes/no and scores, switched off for Choice: this benchmark asks only Choice questions, so these are its vectors' numbers. On Kev's typed decisions, never trained on, the adapters take yes/no from 0.762 to 0.841, one model in memory.
- With 1,000 labeled examples, jul-decision-wemm-4b-4bit reaches 0.897 and 0.94 on Banking77. Harrier-0.6B reaches 0.814 in 0.31 GB at 13 ms.
- ³ multilingual-e5-small trained on jul decisions (ONNX, 8-bit, M4 Pro): 4 ms per text. With a hybrid head (vectors + TF-IDF) it reaches 0.780. Its cross model takes yes/no from 0.579 to 0.726 on Kev's typed decisions. It fits in an AWS Lambda: 17 ms, $0.59 per million.
- 100 rows per dataset: ±5 points per cell, ±3 on the mean. If a bag of words solves your task, use it: we measured that too.
TWO LINES · ON PYPI
Up in one coffee.
C:\> pip install jul
C:\> jul setup
backend ....... mlx (Apple Silicon)
model ......... wemm-4b-4bit, 2.6 GB, downloaded once
check ......... 1 decision OK
jul setup picks the backend (MLX on Apple Silicon, PyTorch elsewhere), installs it if missing, downloads the model once and runs one real decision.
No terminal handy? Let an agent do it →
OR FROM A PROMPT
Let a big LLM install the small one.
Paste this into Claude Code, Codex, or any agent that has a shell.
Install jul (https://pypi.org/project/jul/), a local library that answers typed questions with a 4B model, and check that it works on this machine. 1. Create a virtualenv with Python >= 3.10. 2. In that venv: pip install jul then jul setup jul setup picks the backend (MLX on Apple Silicon, else PyTorch), installs it, downloads WeMM-Embedding-4B (2.6 GB, 4-bit) and runs one test decision. 3. Then run: jul ask choice "Which team should handle this ticket?" -o billing:"payments, invoices" -o technical:"bugs, errors" --state "I was charged twice" and show me the JSON. Do not use sudo and do not install anything outside the venv. If a step fails, show me the error and what you suggest before trying something else. At the end, tell me in two lines: the backend, the latency per decision, and the answer to the ticket.
BY JÉRÔME GUYON · JUL-SHOWCASES
Seen in the wild.
- A form that branches itself. JuL picks the next question from the answers so far. No
if/then. - Re-rank by intent. One
Scoreper item: deep engineering up, clickbait down. - Mute the noise. One
Noulper notification: the OTP stays, the flash sale goes. - Fast mode or full model? A prompt's difficulty, decided before it is sent.
- Millions of tickets. 50,000 real support tickets in 668 s on a Mac, 82.9% accuracy, $0.
- Autotune, measured. 82.0% → 96.5% after a 6-second head, throughput unchanged.
- An on-device browser agent. JuL picks each action, the Apple Foundation Model types, and it books a train on SNCF Connect.
Seven small demos, each one an idea from jevable.com, run locally through from jul import TypeSafeClient.
JuL, Just use Less. A headless decision runtime for local models. Apache-2.0.
CREDITS bdauzats ........ author jlestel ......... contributor JeromeGuyon ..... contributor
github.com/usejul/jul · pip install jul · weights on Hugging Face · Not affiliated with TypeSafe AI or Jev.
C:\JUL> exit_
Game of Life. Also runs locally.