Python API
Everything from jul import … exposes, with every argument. The names, fields and shapes of the first
two sections are Jev's SDK's: code written for typesafe_sdk runs unchanged. The others are what JuL adds.
from jul import (TypeSafeClient, AsyncTypeSafeClient, # the client
Choice, Noul, NoulCriteria, Score, # questions
SystemOneResponse, ChoiceAnswer, NoulAnswer, ScoreAnswer, Usage, # answers
Context, TuningReport, # adapting to your data
Escalation, EscalatedResponse, SystemOneHTTP, # the hub: other deciders
Bundle, pack, # deploying a fixed need
Preset, PRESETS, __version__)
TypeSafeClient
TypeSafeClient(model=None, context=None, method=None, one_word_only=False, backend=None,
context_home=None, api_key=None, base_url=None, timeout=None, max_retries=None,
on_long=None, **ignored)
| Argument | Default | What it does |
|---|---|---|
model |
"jul-decision-wemm-4b" |
a preset name, an alias (accurate, fast), a model added with jul models add, or "laya" / "laya:<checkpoint>" (Laya) |
context |
None |
a Context, or the name of a saved one, used by every call |
method |
per question type | force a reading for every call: "vector", "cross", "letters" |
one_word_only |
False |
one prompt instead of two: faster, a little less accurate |
backend |
$JUL_BACKEND, else mlx on Apple Silicon, else torch |
"mlx", "torch", "onnx", "api" |
context_home |
$JUL_HOME/contexts |
where named contexts are read and saved |
on_long |
$JUL_ON_LONG, else "cut" |
a state over a reading's limit: "cut" answers on what was read, "error" refuses the call (input limits) |
api_key, base_url, timeout, max_retries, … |
— | Jev's remote arguments: accepted and ignored |
Constructing a client loads nothing and needs no backend installed. The model loads on the first call and stays in memory; only one model is held at a time (asking another one drops the first).
| Member | What it does |
|---|---|
system_one(state, questions, context=None, model=None, method=None, route_above=None, on_long=None, **ignored) |
answers every question about one state, returns a SystemOneResponse |
autotune(context, questions, labeled, model=None, save=True, features="vector", formulations=None) |
trains a head per question, returns {name: TuningReport}; see below |
model |
the preset name in use |
backend |
the backend, resolved on first access |
context |
the client's default context |
close() |
releases the model; also with TypeSafeClient() as client: |
system_one
| Argument | What it is |
|---|---|
state |
the text: a str as is, anything else serialized as JSON (a dict, a list, a dataclass…) |
questions |
{name: Choice | Noul | Score}, at least one |
context |
overrides the client's context for this call |
model |
another preset for this call (loads it, drops the previous one) |
method |
overrides the reading for this call |
route_above |
decision models only: above this many options, read as vectors; 0 disables it |
on_long |
overrides the client's on_long for this call: "cut" or "error" |
response_model, retry, extra_body, … |
Jev's arguments: accepted and ignored |
Raises ValueError for an empty questions, an unknown model= passed to the call (the constructor raises
it for its own) or a Score with fewer than two levels, jul.truncation.InputTooLong (a ValueError) for a
state over a reading's limit with on_long="error", TypeError for a question that is not one of the three types.
AsyncTypeSafeClient
The same constructor and members, awaitable: await client.system_one(...), await client.autotune(...),
await client.close(), async with AsyncTypeSafeClient() as client:. The work runs in a worker thread, so
the event loop keeps turning.
Questions
Choice
Choice(instructions="", criteria={})
Pick one option. criteria is {key: description}, or a plain list of keys (then the key is also what the
model reads). At least one option. See Writing good questions.
Noul
Noul(instructions="", criteria=None)
Yes or no. criteria is optional: NoulCriteria(true=…, false=…), or a dict with true/false (or
yes/no) keys. Without it, yes and no read as "Yes." and "No.".
Score
Score(instructions="", criteria=[])
A level on a scale. criteria lists the level descriptions, lowest first, at least two.
Answers
SystemOneResponse
| Field | Type | What it is |
|---|---|---|
answers |
dict[str, ChoiceAnswer | NoulAnswer | ScoreAnswer] |
every answer, by question name, in the order asked |
choices |
dict[str, ChoiceAnswer] |
the Choice answers only |
nouls |
dict[str, NoulAnswer] |
the Noul answers only |
scores |
dict[str, ScoreAnswer] |
the Score answers only |
model |
str |
the preset that answered |
usage |
Usage |
tokens read |
request_id |
str |
a UUID per call |
as_dict() |
dict |
the Jev HTTP response shape, ready for json.dumps |
ChoiceAnswer, NoulAnswer, ScoreAnswer
| Class | Fields |
|---|---|
ChoiceAnswer |
choice (the key with the highest probability), probabilities ({key: p}, summing to 1), confidence (the chosen key's probability) |
NoulAnswer |
noul: the probability of yes, in [0, 1] |
ScoreAnswer |
score (the expected level, from 0 to n−1), probabilities ({"0": p, "1": p, …}), legend ({"0": "Calm", …}), confidence (1 − the spread of the distribution, scaled to [0, 1]: 1 when one level takes everything) |
Probabilities are rounded to 4 decimals. Each answer has as_dict(), which adds "type".
Usage
input_tokens (tokens fed to the model for this call), output_tokens (always 0: nothing is generated),
total_tokens, and truncated_tokens: the largest cut of the call, the tokens a reading dropped to fit its
limit (0 when everything was read; each cut is also logged on the jul.truncation logger, see
input limits). With --backend api, the counts are characters.
Context
Context(description="", examples=[], labeled=[], name=None, use_description=False)
| Argument | What it does |
|---|---|
description |
one sentence about the data; reaches the prompts only with use_description=True (measured harmful on average) |
examples |
unlabeled texts of the task: their mean vector becomes the center. Warns under 10; ~50 is the sweet spot |
name |
needed to save it and to reuse it by name |
use_description |
put description in the prompts |
| Member | What it does |
|---|---|
save(name=None, home=None) |
writes ~/.jul/contexts/<name>/: centers, heads, calibrations, examples |
Context.load(name, home=None) |
reads it back (FileNotFoundError if absent) |
Context.list_saved(home=None) |
the saved names |
Context.delete(name, home=None) |
removes it, returns whether it existed |
Anywhere a context is expected, its saved name works too: client.system_one(state, q, context="tickets").
See Adapting to your data.
autotune
reports = client.autotune(context, questions, labeled, model=None, save=True,
features="vector", formulations=None)
| Argument | What it is |
|---|---|
context |
a Context or a name: where the heads are stored (created if new) |
questions |
the same {name: question} you will ask |
labeled |
[(state, {question_name: answer}), …]: the option key for a Choice, True/False for a Noul, the level index for a Score |
features |
what the head reads: "vector", "lexical" (TF-IDF, needs jul[tune]), "hybrid" (both) |
formulations |
the prompts read, by name ("one_word", "question_options", "question"), a list for all questions or a dict per question |
save |
save the context when it has a name |
Returns {name: TuningReport}. A head is activated only if it beats zero-shot in cross-validation;
otherwise only a calibration is kept. Then client.system_one(..., context=...) uses it with no other
change. Not available on decision models (pointer reading). See autotune.
TuningReport
question, n_examples, n_evaluated, n_options, zero_shot_accuracy, head_accuracy (None if no
head was trained), activated, reason, temperature, features. print(report) gives the summary,
as_dict() the fields.
Escalation
Escalation(tiers, min_confidence=0.8, default=0.8)
Several deciders in order. The first answers every question; a question answered below the bar goes to the next tier, with the other unsure ones only, and so on.
| Argument | What it is |
|---|---|
tiers |
[(name, decider), …], names unique. A decider is anything with system_one(state, questions): a TypeSafeClient, a SystemOneHTTP, another Escalation |
min_confidence |
one bar for every question, or {question_name: bar} |
default |
the bar of a question missing from that dict |
How sure an answer is: confidence for a Choice or a Score, max(noul, 1 − noul) for a Noul. The
answer kept is the last tier's that answered, even if less sure (two models' confidences do not compare).
A tier that fails (network, HTTP error) is skipped and its error recorded; a malformed request
(ValueError, TypeError) is raised, never sent on. system_one returns an EscalatedResponse: a
SystemOneResponse plus escalation, per question:
{"tried": ["local", "jev"], "tier": "jev", "confidence": 0.91, "met_bar": True}
SystemOneHTTP
SystemOneHTTP(base_url, model="jev-latest", api_key=None, timeout=30.0, exact_url=False)
A decider over HTTP: any server speaking POST /v1/systemone (Jev at https://api.typesafe.ai, Ollama's
Nimble at http://localhost:11434, Kev, another jul serve). base_url is the server root, with or without
/v1. Redirects are never followed, so a key never reaches another host. Raises jul.escalate.RemoteError
on any failure. jul.escalate.remote_tier("typesafe" | "ollama:nimble" | "cloudflare:clef-flash" | URL) and
jul.escalate.cloudflare_tier() build one with the provider's usual key variable.
Bundle and pack
pack(client, questions, out, context=None, model=None, reading="auto") -> Path
Bundle.load(path, backend=None, model=None, cross_model=None) -> Bundle
pack computes everything that does not depend on the state (prompts, option vectors, centers, heads)
into a directory. Bundle answers with the state as its only input:
| Member | What it does |
|---|---|
bundle.system_one(state) |
a SystemOneResponse, as the client would answer |
bundle.system_one_batch(states) |
the same for many states, read in batches |
bundle.question_names |
the packed questions |
bundle.models |
the models it needs: ["vector"], ["cross"] or both |
reading="vector" packs every question on the vectors (one model to ship). Bundle.load(model=...) points
to the weights if they moved. Vector presets only. See Deploying a fixed need.
Presets
PRESETS is the dict of built-in presets (jul-decision-wemm-4b, wemm-4b-4bit, minicpm5-2b), each a
Preset with its name, repos per backend, formulations, tau, center, latency_ms, quality.
Presets you add live in ~/.jul/presets/<name>@<backend>.json; jul models lists them all.
jul.presets.resolve(name, backend) returns the one a client would use.
Errors you may catch
| Exception | When |
|---|---|
ValueError |
unknown model or backend, empty questions, a Score with one level, a model not fitted for this backend |
jul.truncation.InputTooLong |
a ValueError: the state is over a reading's limit and on_long="error"; .reading, .limit, .over say which and by how much |
TypeError |
a question that is not Choice, Noul or Score, a context of the wrong type |
ImportError |
no backend installed (pip install "jul[mlx]" or "jul[torch]") |
FileNotFoundError |
a context name that was never saved |
jul.escalate.RemoteError |
a remote tier failed (only raised by SystemOneHTTP used alone) |
jul.backends.api.EmbeddingsError |
an embeddings API failed or refused |