Exu
Train encoder-only decision models that answer with probability distributions instead of generated text. You give a state and a question with explicit options; the model returns a distribution over those options in one forward pass.
Exu is a toolkit, not a method. The training method it implements is RLCD, Reinforcement Learning for Calibrated Decisions, the name TypeSafe AI gives to the recipe behind Jev, its System One model. The reward is a strictly proper scoring rule: its expected score is uniquely maximized by the true distribution, and the direct baseline optimizes it as is. RLCD optimizes the same reward under logit perturbations, and propriety alone does not guarantee that its unperturbed predictions are calibrated, so compare the two on held-out data. Calibration is not coaxed out of the model with a prompt, it is what the objective asks for.
The name honours Exu, the Orixá of the crossroads, of movement and of communication, who in Afro-Brazilian religions opens the paths and governs the choice made at every crossing. A model that answers with a distribution over explicit options is a crossroads by construction.
This is the agnostic base. It is not tied to a language, a domain, or an encoder. Swap the encoder, write your questions, and train.
Pre-alpha. The API, the checkpoint format and the results can change.
Why
A reflex decision (route a ticket, score urgency, flag fraud) may need probabilities, but it does not need text generation. Generating a token takes hundreds of milliseconds, needs a parser, and the "confidence" a chat model writes is just more text. Here the output is already a distribution, so there is nothing to parse and nothing to hallucinate.
Three primitives cover most decisions, and all three are the same mechanism:
| Primitive | Question | Output |
|---|---|---|
choice |
pick one of N explicit options | distribution over options |
score |
place the state on an ordinal rubric | distribution over levels |
noul |
boolean question | distribution over No and Yes |
The options are written at request time and read by the model as text, so a new task is a new question, not a new output layer. No retraining to add a label.
Install
uv add exu-base # or: pip install exu-base
To work on this repository itself, install from the source tree:
uv sync --extra dev
The contract
from exu import DecisionQuestion, Option
route = DecisionQuestion.choice(
"Where should this ticket go?",
[
Option("billing", "payment, invoice or refund"),
Option("support", "access or outage"),
],
)
risk = DecisionQuestion.score(
"How risky is this request?",
["no risk", "review", "block"],
)
fraud = DecisionQuestion.noul("Is this message attempting fraud?")
Train
exu-train \
--mode rlcd \
--train data/train.jsonl --train-split train \
--validation data/train.jsonl --validation-split validation \
--test data/train.jsonl --test-split test \
--calibration data/train.jsonl --calibration-split calibration \
--output artifacts/my-model \
--encoder google-bert/bert-base-multilingual-cased \
--epochs 4 --batch-size 8 --option-shuffle --calibrate
--mode rlcd uses the perturbed-logit policy. --mode baseline optimizes the
same strictly proper score directly, with no sampling. Train the baseline first:
it is the bar the RLCD mode has to beat on held-out NLL. So far it has not been
beaten: in every comparison measured on real data (eight, across three backbones
and four datasets, see BENCHMARKS.md) the direct baseline was as good or better,
and RLCD made confident misses ten to a hundred times more often. Start with
--mode baseline; treat --mode rlcd as the method under evaluation, not the
default. The setting its authors report gains in, soft targets from a teacher
model, has not been tested here.
The step-by-step version, from writing the records to reading the report and the failure modes, is docs/usage-guide.md.
Evaluate
exu-evaluate \
--checkpoint artifacts/my-model \
--data data/train.jsonl --split test \
--order-permutations 4 --latency
Prints NLL, Brier, accuracy, ECE, ordinal scores, per-kind and per-family breakdowns, selective coverage, the three trivial baselines, order robustness and latency.
Serve
from exu import DecisionRuntime
runtime = DecisionRuntime.load("artifacts/my-model")
decision = runtime.decide("I was charged twice for the same invoice.", route)
print(decision.label, decision.confidence) # billing 0.87
Or from a shell, one question with flags or a JSONL batch with --input:
exu-decide --checkpoint artifacts/my-model \
--state "I was charged twice for the same invoice." \
--instruction "Where should this ticket go?" \
--option "billing=payment, invoice or refund" --option "support=access or outage"
Data
One JSONL record per decision. The target is a distribution, not a label, so soft targets from several annotators or a teacher model are first-class.
{
"id": "t-1",
"state": {"text": "I was charged twice for the same invoice."},
"question": {
"kind": "choice",
"instruction": "Where should this ticket go?",
"options": [
{"name": "billing", "description": "payment, invoice or refund"},
{"name": "support", "description": "access or outage"}
]
},
"target": [1.0, 0.0],
"split": "train",
"family": "routing",
"language": "en"
}
See docs/dataset-format.md for the full field list and the rules that keep held-out honest.
How it works
state + typed question -> one sequence per question -> bidirectional encoder -> question-type embedding and two extra transformer layers -> read the hidden state at each option's marker -> softmax over options. Training samples Gaussian
perturbations of the logits and rewards each candidate distribution with a
strictly proper scoring rule. Then a temperature map, fitted on held-out data,
brings confidence in line with accuracy.
The full walkthrough is in docs/exu-guide.md, and docs/algorithm.md has the reward and policy math.
Development
make setup # install dependencies
make test # pytest
make lint # ruff
make check # lint + test + build, exactly what CI runs
make site # build the static explainer into _site/
Run make for the full list.
Status
Working: typed-decision contract, token-budgeted sequence builder with marker injection defense, direct and RLCD training, temperature calibration, calibration-aware evaluation, portable checkpoint, offline inference runtime.
Not done: a published checkpoint, a human-annotated calibration set, learned act-or-escalate training (the cost-based baseline is in place), and a multi-turn prefix objective.
Small synthetic fixtures are for plumbing only. They are not evidence of quality.
Every run on real data so far, with datasets, encoders, seeds and the results that did not go RLCD's way, is in BENCHMARKS.md.
Prior art
Two projects are the inspiration for this one, and neither contributed code.
TypeSafe's Jev came first and is the bigger influence:
the typed-decision contract of choice, score and noul, answers that carry a
distribution plus a separate confidence, the idea of gating action on that
confidence, and the RLCD name for the training method. The "System One" framing
is theirs too.
Laya (Apache-2.0, by Nandakishor) is the open build recipe this repository follows: one sequence per question with a mask marker per option, an explicit header and option token budget, the log + spherical + ranked-probability reward, and one temperature per question type and option-count bucket. The benchmark and latency numbers quoted in the docs come from Laya's measurements, not from runs made here.
This is an independent implementation. No source code was copied from either project.
One more reference, in a different setting. Nandakishor M, Confidence-Aware Routing for Large Language Model Reliability Enhancement (arXiv:2510.01237, 2025) is by the same author as Laya. It estimates confidence before generation and routes a query across four pathways: local generation, retrieval, a larger model, or human review. That is the routing idea the act-or-escalate gate applies to a single encoder here. It does not describe this design, and nothing in this repository implements it.
License
Code under MIT. Datasets and checkpoints carry their own licenses. Before publishing an artifact, confirm the encoder, tokenizer, data and resulting weights are compatible.
Release files for exu-base 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| exu_base-0.1.0.tar.gz | 228.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| exu_base-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 285.4 kB
Release files / exu_base-0.1.0.tar.gz
| Download URL | exu_base-0.1.0.tar.gz |
|---|---|
| Size | 228.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6a8495413eb5c56da44ffae1cf5c20039ffbdf222ad69ca5aef22ec797f488a1
|
|
BLAKE2b-256 checksum How to use checksums |
1e05d4d16936fc971850034181082da58a930059dd497948228650d0c2374659
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / exu_base-0.1.0-py3-none-any.whl
| Download URL | exu_base-0.1.0-py3-none-any.whl |
|---|---|
| Size | 57.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
85a54e4ea77582f8d8e335f4cbd37d868cda06886c4ea834b384d987bad59f75
|
|
BLAKE2b-256 checksum How to use checksums |
cab084ee87ff7aa5d2a40ae6387fc9d6980ed4342e8a184ac2fe3a02e6235e25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|