Skip to content
MrJev

OpenThai-SystemOne

Thai and English decision model with a slot-softmax head whose request and response shape mirrors the official API, so existing SDK code can point at it.

View on GitHub →

Hands-on review

A 0.8B Thai and English decision model with a 256-slot head. It ran on four CPU cores, and the official SDK talks to its server unchanged.

Good for

  • Thai-language decisions from a model that never generates text
  • Dropping into existing SDK code: base URL change only, no other edits
  • A confidence number that actually collapses when the state says nothing

Watch out for

  • No `/v1/models` endpoint, so `client.models.list()` returns 404
  • The server echoes the caller's `model` field back, including `jev-latest`
  • No auth on the server and CORS defaults to `*`
  • 19 CPU tests and no CI to run them

Tested Sep 21, 2026 at 849bce692ae1 · python:3.12 in Docker on 4 CPU cores, no GPU; published weights from the Hub, its 19 tests, and the official typesafe-sdk 0.7.0 against its server

How we reviewed this: we installed the package in python:3.12 in Docker on four CPU cores with no GPU, ran its tests, downloaded the published weights and ran the README’s own Thai and English examples in fp32, then started its server and drove it with the official typesafe-sdk 0.7.0. We made no calls to TypeSafe — this model is meant to answer locally, so everything ran on the box.

What it is

A Qwen3.5-0.8B text tower (the vision encoder is dropped in stage 0 of the pipeline), continued-pretrained on Thai, with the language-model head replaced by a 256-way slot softmax. The prompt is assembled from control tokens rather than prose:

<|ts_state|> {state}
<|ts_q|><|ts_choice|> {instructions}
<|ts_opt_0|> billing: charges and refunds
<|ts_opt_1|> technical: the product is broken
<|ts_answer|>            <- hidden state here -> 256 logits, masked to the k options

Every question in a request gets its own <|ts_answer|> position, and all of them are read out of one forward pass. Slot 255 is a dedicated abstain. The temperature is learned per question type during a separate calibration stage, and confidence is 1 − normalised entropy over the valid options — a real definition, not a heuristic blend.

The repository is the whole pipeline, not just inference: eight numbered scripts from base-model surgery through continued pretraining, decision-data synthesis, SFT, calibration, evaluation and release, with the hardware and rough wall-clock for each stage in a table. The published weights are the output of that pipeline, Apache-2.0, with Qwen/Qwen3.5-0.8B-Base named as the base model on the card.

Running it

pip install -e ".[server,dev]" with CPU torch, then pytest: 19 passed in 12 seconds, all of them CPU-only. The weights are a single 1.5 GB safetensors file; loading them in fp32 on four cores took about a minute.

Both README examples work as written. The Thai ticket — a duplicate charge, a refund demanded, three unanswered phone calls — and its English equivalent both routed to billing with the frustration score near the top of its three-level scale and the refund noul close to 1, in about 680 ms per call for all three questions together, fp32 on four cores with no GPU.

The part worth dwelling on is what happens when the state doesn’t contain the answer. We asked the same department and refund questions about “The weather in Chiang Mai is pleasant in November”:

department: choice='billing'  probs={billing 0.366, technical 0.333, sales 0.301}  confidence=0.003
refund:     noul=0.007

A near-flat distribution, and a confidence of three-thousandths. That is the entropy definition earning its keep: the same situation put through OpenJev, which blends the top probability with the gap, reports 0.417 on a three-way tie. A threshold on this number would behave; on that one it would not.

One more thing we checked, because the prompt is built from control tokens: formatting.sanitize() rewrites any <|ts_ sequence in your text before tokenisation. We put <|ts_answer|> and <|ts_opt_0|> inside a state and the answer was unaffected. Thinking about token-level injection before anyone reports it is not common.

The SDK contract

The server exposes POST /v1/systemone with TypeSafe’s request and response shape. We pointed the official Python SDK at it with nothing but a base URL:

c = TypeSafeClient(api_key="not-needed", base_url="http://127.0.0.1:8077")
r = c.system_one(state=..., questions={"refund": Noul(...), "dept": Choice(...)})
# -> answers={'refund': NoulAnswer(noul=0.986),
#             'dept': ChoiceAnswer(choice='billing', confidence=0.855, probabilities={...})}

Both answers parsed into the official types. Two gaps, both small:

GET /v1/models doesn’t exist, so client.models.list() returns 404. Anything that enumerates models before calling will trip on it, and it is a handful of lines to add.

And system_one() ends with resp.model = req.model or resp.model. The SDK sends model="jev-latest" by default, so the response came back claiming to be jev-latest. Nothing downstream can tell from the payload which model answered. Echoing the requested name is a reasonable compatibility choice; returning the served name, or at least both, would be a better one.

The server takes no API key and OPENTHAI_SYSTEMONE_CORS defaults to *. That is fine on a laptop and worth a reverse proxy anywhere else.

Maintenance

Four commits on 20 September, one release, Apache-2.0 on both the code and the weights, the base model credited, and a named sponsor for the H100 time. The benchmark tables live on the model card and are the authors’ own figures; we did not reproduce them, and this review says nothing about how it scores.

There is no .github directory. Nineteen tests that run on CPU in twelve seconds are exactly the kind that should be running on every push.

Verdict

The best-built of the CPU-friendly open models we looked at this week, and the only one here that answers Thai. If you have an existing integration, the base-URL swap really is the whole migration; add a /v1/models handler to your fork if your client enumerates, and put something in front of the port before it leaves your laptop.

For English-only alternatives, see Laya for Node, Von and the rest of the open models category.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 21, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Open Models & Reproductions

Laya

★ 18k▲ 2.9k

NandhaKishorM/laya

Non-autoregressive decision engine over 100+ languages: three checkpoints and a router that detects the script and dispatches per request. Its benchmarks end with a limits section naming the datasets it does not generalise to and the headline figure that came from a training split.

PythonReviewed

kev

★ 4.7k▲ 4.3k

jaredpalmer/kev

Jev-style decision models from 0.5B to 8B, built as LoRA adapters on Qwen and served behind a Jev-compatible /v1/systemone API.

PythonReviewed

SemIf

★ 3.9k▲ 2.1k

TheoLeeCJ/SemIf

Jev-style decisions from a frozen 4B model on a single RTX 3090, with a browser demo. Formerly OpenJev.

PythonReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.