noul
How likely the answer is yes.
0.0 .. 1.0
Ask yes/no, pick-one and rubric questions. cerno reads the answer from the model’s own probability distribution over a single token — nothing to parse, no number the model was asked to invent, and nothing leaves your machine.
cernere, Latin: to sift, to distinguish, to decide.
noulHow likely the answer is yes.
0.0 .. 1.0
choiceWhich one of up to 20 options.
the option, plus a probability for each
scoreWhere on a rubric of 2–10 levels.
the level, its legend, and a weighted mean
POST /v1/systemonecurl localhost:3000/v1/systemone \
-H 'content-type: application/json' -d '{
"state": "Ticket: Server room cooling failed, 31 degrees and rising.",
"questions": [
{"id": "urgent", "noul": "Is this urgent?"},
{"id": "team", "choice": {"question": "Which team?",
"options": ["IT", "Facility", "HR"]}},
{"id": "sev", "score": {"question": "How severe?",
"levels": ["negligible", "minor", "moderate", "major", "critical"]}}
]}'
{"answers": {
"urgent": {"type": "noul", "noul": 0.9946, "label_mass": 0.999},
"team": {"type": "choice", "choice": "Facility", "index": 1,
"confidence": 0.967, "label_mass": 0.999},
"sev": {"type": "score", "score": 5, "expected_score": 4.90,
"legend": "critical", "confidence": 0.807,
"label_mass": 0.999}},
"model": "gemma4:e2b-it-qat",
"usage": {"input_tokens": 281, "questions": 3},
"timing_ms": {"total": 85}}
Read as
urgentnoul
0.9946
probability of yes
teamchoice
confidence 0.967
sevscore
critical 5 of 5
expected 4.90 · confidence 0.807
Three questions, 85 ms, on a 4 GB model. label_mass is how
much of the model’s own probability fell on the offered letters — the one sign that it was
about to write something else.
A generative model answers by writing words, and then something has to parse the words back into a decision. cerno never lets it get that far.
A, B, C.P(A), P(B), P(C) from the logprobs.That is where the speed comes from — one token, not a sentence — and where the probabilities come from, since they are the model’s own.
| Measured | Consequence |
|---|---|
The first generated token was <|channel|>, a chat-template control token | think: false on every request |
The default top_k: 40 truncates the distribution before logprobs are reported, and renormalises what survives | top_k: 0, top_p: 1, min_p: 0 are pinned and not configurable |
“Nein” is not one token — it arrives as Ne + in — and “Ja” competes with JA, Ja, ja | Single-letter labels only; variants of one label are folded in probability space |
| When a model is certain, the losing label drops out of the top-20 window entirely | The weakest reported logprob becomes an upper bound, and the answer is flagged truncated |
gemma4:26b answers clear cases at P = 1.0000 | Temperature scaling, per request or per model |
| A 26B model answers in 87 ms; a 4 GB one in 36 ms | A small model is the default |
The service and the terminal front end are on crates.io; installing needs a Rust toolchain and nothing else.
ollama pull gemma4:e2b-it-qat # once, 4.3 GB
cargo install cerno-server cerno-tui
cerno-server # terminal 1: the service on 127.0.0.1:3000
cerno-tui # terminal 2: the terminal front end
The first request takes 5–10 s while Ollama loads the model; after that it
is around 150 ms. http://localhost:3000/docs is the Swagger UI.
Three clients, all published as cerno-sdk, written by hand against the same
OpenAPI document and tested against the same conformance cases.
cargo add cerno-sdkuse cerno_sdk::Client;
let client = Client::new("http://localhost:3000")?;
let answers = client.systemone(text)
.noul("urgent", "Is this urgent?")
.choice("team", "Which team?", ["IT", "Facility"])
.send().await?;
uv add cerno-sdkfrom cerno import Client
client = Client("http://localhost:3000")
answers = (client.systemone(text)
.noul("urgent", "Is this urgent?")
.choice("team", "Which team?", ["IT", "Facility"])
.send())
npm install cerno-sdkimport { Client } from "cerno-sdk";
const client = new Client("http://localhost:3000");
const answers = await client.systemone(text)
.noul("urgent", "Is this urgent?")
.choice("team", "Which team?", ["IT", "Facility"])
.send();
curl gives you the winner; cerno-tui shows you how close the
runner-up was, which is the difference between “Facility” and “Facility, but it was nearly
IT”. A run at a higher temperature next to one at 1.0 shows the calibration working
directly.
Type a state, add questions with a, send with Ctrl+S. t and T step the calibration temperature, m cycles the models the service offers, ? lists the keys. The form is saved on exit and comes back on the next start.
Every runtime gets the request fields that keep its distribution untruncated, and every
host gets the same three primitives. Pick one with CERNO_HOST.
llama-servernimble, tev1Which model to use is measured, not assumed: a 4 GB model matches the 26B reference’s accuracy and is better calibrated. See the model comparison.