JUST USE LESS · v0.5 · 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: measure yours with jul bench test.jsonl --models accurate --escalate-to typesafe. 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
Any of them         benches them on your rows, with JuL    jul bench test.jsonl --models accurate,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.

Illustrative output · usage and latency as the CLI prints them.

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.

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.950.880.800.8770.11278 ms4.5 GB
jul wemm-9b ²0.970.840.780.8630.114138 ms9.0 GB
jul-decision-wemm-4b-4bit ⁴0.900.870.800.8570.08455 ms2.6 GB
jul f2llm-4b ²0.890.820.810.8400.09046 ms3.0 GB
jul f2llm-1.7b ²0.910.670.870.8170.08224 ms1.0 GB
jul-decision-minicpm5-2b v1.0 ¹0.910.790.690.7960.133217 ms1.3 GB
jul harrier-0.6b ²0.720.520.760.6670.14413 ms0.31 GB
jul-decision-e5-small ³0.720.590.360.5570.1404 ms0.09 GB
jul minicpm5-2b0.800.590.460.6170.11364 ms2.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.
A model that writes essays, installing one that answers in a single word. Neither of them minds.

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 Score per item: deep engineering up, clickbait down.
  • Mute the noise. One Noul per 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.