🎯 A Thai Jev is here! OpenThai-SystemOne
Announced 20 September 2026

0.8B parametersv0.374.3 public benchFree, Apache-2.0Thai + English
Text + questions in → answers with a confidence score out, in a single pass.
Five days after TypeSafe launched Jev (a "System One model" that generates no text but answers decisions with probabilities in one pass), the OpenThai team has released an open-source version for Thai + English, named OpenThai-SystemOne.
- 0.8B parameters, runs on a laptop
- Qwen3.5-0.8B base, continued-pretrained on ~5B Thai tokens, with the 248k-word LM head replaced by a "256-slot decision head"
- Feed it a state (text/JSON) + choice / score / yes-no questions → every question answered with confidence in one forward pass
- On the same 13-subset public benchmark as Bespoke-Nimble-9B: ours 74.3 (v0.3) / Nimble-9B 74.8 / Jev 76.0 (within 0.5 of the 9B, ahead on 8 of 13 subsets). Thai tasks: intent 90.0%, news topic 98.1%, XNLI-th 77.1%
- Use it for: Text Classification, Sentiment Analysis, Agentic Computer Use (picking the action for an AI agent)
- Apache-2.0, all of it open: weights, training scripts, configs and synthetic data
The API contract mirrors TypeSafe AI's POST /v1/systemone (Jev), so existing code can point at this model unchanged.
Try it in your browser
Play with OpenThai-SystemOne in your browser + API docsiapp.co.th/docs/llm/openthai-systemone
Download and install
Code, configs and every training script — GitHubgithub.com/iapp-technology/openthai-systemone
pip install openthai-systemone # or: pip install "git+https://github.com/iapp-technology/openthai-systemone"
Try it
A real exchange with the model: one customer ticket, two questions, both answered in a single forward pass (91 input tokens, 0 output tokens).
curl -s https://api.iapp.co.th/v3/store/openthai/systemone -H "apikey: $IAPP_API_KEY" -H "content-type: application/json" -d '{
"state": {"ticket": "โดนหักเงินซ้ำสองครั้ง ขอเงินคืนด่วน"},
"questions": {
"department": {"type": "choice", "instructions": "ทีมใดควรรับผิดชอบ",
"criteria": {"billing": "การเงิน/คืนเงิน", "technical": "ระบบใช้งานไม่ได้", "sales": null}},
"refund": {"type": "noul", "instructions": "ลูกค้าขอเงินคืนหรือไม่"}
}}'
{"answers": {"department": {"choice": "billing", "probabilities": {"billing": 0.972, "technical": 0.009, "sales": 0.018}, "confidence": 0.87},
"refund": {"noul": 0.954}}, "usage": {"input_tokens": 91, "output_tokens": 0}}
🔑 Public API (free preview)
The endpoint https://api.iapp.co.th/v3/store/openthai/systemone takes any iApp API key (free to create). During the preview it costs 0 credits, limited to 1,000 decisions per day per key and 100 requests per minute; per-decision pricing will be announced in October 2026. The API runs v0.3, the same checkpoint as the Hugging Face weights. Details and a playground are at iapp.co.th/docs/llm/openthai-systemone, or run the model on your own machine as shown below.
Run it locally (CUDA, Apple MPS or CPU):
from openthai_systemone import SystemOneClient, Choice, Score, Noul
c = SystemOneClient("iapp/OpenThai-SystemOne")
r = c.system_one(
state={"ticket": "โดนหักเงินซ้ำสองครั้ง ขอเงินคืนด่วน โทรไปสามรอบแล้วไม่มีใครรับ"},
questions={
"department": Choice(instructions="ทีมใดควรรับผิดชอบ", criteria={"billing": "การเงิน/คืนเงิน", "technical": "ระบบใช้งานไม่ได้", "sales": None}),
"frustration": Score(instructions="ลูกค้าหงุดหงิดแค่ไหน", criteria=["ใจเย็น", "หงุดหงิดแต่สุภาพ", "โกรธมาก"]),
"refund": Noul(instructions="ลูกค้าขอเงินคืนอย่างชัดเจนหรือไม่"),
},
)
print(r.answers["department"].choice, r.answers["department"].probabilities)
print(r.answers["frustration"].score, r.answers["refund"].noul)
Serve it over HTTP with the same contract as POST /v1/systemone:
OPENTHAI_SYSTEMONE_MODEL=iapp/OpenThai-SystemOne uvicorn openthai_systemone.server:app --port 8000
Or load it with plain transformers (the remote code ships in the model repo):
from transformers import AutoModel
model = AutoModel.from_pretrained("iapp/OpenThai-SystemOne", trust_remote_code=True)
🎮 Demo: playing Doom, no vision, no text
The model controls a Doom marine (ViZDoom) in real time. Every 4 game tics the engine's symbolic state is serialised to text, for example:
health 100/100 | ammo 50 | kills 1
crosshair: empty; nearest visible enemy 39deg to the RIGHT
enemies: Zombieman 5m right -39deg VISIBLE; ChaingunGuy 19m right -24deg; Zombieman 19m ahead -12deg
items: GreenArmor 41m right -18deg
depth ahead: 35/255 (obstacle near)
last actions: ATTACK ATTACK ATTACK ATTACK
and the model answers two typed questions in one forward pass: a choice over the 7 actions (the key that gets pressed) and a noul "is an enemy in the crosshair". About 41 ms per decision on one H100 (about 24 decisions per second), 0 output tokens, and no Doom data in training: everything comes from reading the state and the option descriptions. It misses shots and dies on hard levels; the point is the speed and the calibrated probabilities, the same mechanics that route tickets or pick UI elements for an agent.
Run it on your machine (iApp API key or the local weights, live HUD in Thai or English, optional recording): github.com/iapp-technology/openthai-systemone-doom
🇹🇭 Thai showcase (real v0.3 outputs via the API, 22 September 2026)
Seven everyday Thai tasks, each answered in one forward pass; the probabilities are the model's actual output.
| Task | State (abridged) | Questions | Answers |
|---|---|---|---|
| Support ticket triage | "Transfer app broken since last night, error 502 every time … I need to pay my kid's tuition tomorrow" (Thai) | which team (4) · urgency (score) · is the wording polite | technical 72% (billing 27%) · 2.13 = urgent (85%) · polite 1% (wrong, see note) |
| News topic | "Cabinet approves a 400-baht nationwide minimum wage from 1 January; industry federation worried about SMEs" | topic (8) · is it about labour | economy 62% (labour 36%) · 98% |
| Assistant intent | "Set an alarm for 6:30 tomorrow, and play some soft music when I wake up" | intent (10) | alarm set 99.5% |
| Comment moderation | an abusive Thai comment | toxic · sentiment | toxic 94% · negative 97% |
| Grounded QA | passage about Rama VIII Bridge (opened 7 May 2002, 475 m long) | "how long is the bridge" answerable · "is the construction budget stated" | 98.5% · 0.6% |
| Agent tool selection | task "book a table for 4 tonight at 8pm"; history shows search_restaurants already found a free table | next tool (5) | make_reservation 96.5% |
| RAG relevance + extraction | question on withholding tax for building rent; passage states 5% | passage relevant · rate | 98% · 5% (76%) |
Note on the first row: the ticket uses the polite "รบกวน…นะครับ", so the correct answer is yes; the model said no at 99%. Politeness judgement on formal Thai is a known gap and gets a targeted set in v0.4. We keep the miss here because a showcase that only shows hits is not useful.
Three question types
| Type | You give | You get |
|---|---|---|
choice | instructions + up to 255 options (name → description or null) | choice, probabilities for every option, confidence |
score | instructions + 2–10 ordered level descriptions | score (probability-weighted, can be fractional), probabilities, confidence |
noul | a yes/no question | noul = p(yes) |
Every question in a request is answered together in one forward pass. confidence is 1 − normalised entropy, and abstain is the probability that none of the offered options fits.
📊 Numbers (v0.3, 22 September 2026)
All numbers are zero-shot: the model sees only the state, the instructions and the option names and descriptions. They are for the calibrated v0.3 release; earlier versions are in the versions table below.
Bespoke Labs' 13-subset public benchmark (same sources, splits, instructions and sampler used to compare against Jev). choice and noul report accuracy, score reports exact-level match. Bold marks subsets where the 0.8B model is ahead of Nimble-9B.
| Subset | Type | n | OpenThai-SystemOne 0.8B | Nimble-9B | Jev 1.13.0 | our ECE |
|---|---|---|---|---|---|---|
| aegis2 | noul | 250 | 83.2 | 81.2 | 80.4 | 0.065 |
| boolq | noul | 300 | 79.7 | 86.0 | 89.7 | 0.049 |
| civil_comments | noul | 300 | 79.0 | 70.3 | 81.0 | 0.087 |
| helpsteer2 | score | 250 | 41.6 | 39.0 | 34.1 | 0.371 |
| massive-de-DE | choice | 350 | 88.3 | 83.4 | 86.9 | 0.057 |
| massive-en-US | choice | 350 | 88.3 | 86.9 | 87.4 | 0.070 |
| multinli | choice | 299 | 89.0 | 85.3 | 82.9 | 0.053 |
| paws | noul | 250 | 94.0 | 82.8 | 89.2 | 0.035 |
| pubmedqa | choice | 250 | 64.0 | 75.6 | 77.2 | 0.259 |
| squad2 | noul | 299 | 89.3 | 80.6 | 82.9 | 0.041 |
| summeval-consistency | score | 144 | 75.0 | 75.7 | 81.2 | 0.076 |
| summeval-relevance | score | 240 | 21.7 | 49.2 | 35.0 | 0.356 |
| vitaminc-dev | choice | 599 | 72.5 | 76.6 | 80.1 | 0.118 |
| Macro average | 74.3 | 74.8 | 76.0 |
Honest reading: the 0.8B model averages 74.3, within 0.5 points of Nimble-9B (74.8), and is ahead on 8 of 13 subsets (aegis2, civil_comments, helpsteer2, massive-de, massive-en, multinli, paws, squad2). It is still clearly behind on pubmedqa (64.0) and on both summary-rating tasks (relevance 21.7; consistency 75.0, a regression from 84.0 in v0.2). ECE improved on 11 of 13 subsets. Nimble-9B and Jev numbers are as published by Bespoke Labs (18 September 2026); ours are measured with scripts/06_eval.py on scripts/06b_public_benchmarks.py rebuilds of the same subsets. Note: from v0.3 the train splits of SQuAD2, BoolQ, PAWS, Aegis2 and MASSIVE-de are in the training mix; the benchmark subsets still use validation/test splits only. For scale, Bespoke reports raw Qwen3.5-0.8B at 45.4 on their private 324-item holdout (Nimble-9B 90.1, Jev 93.2), not on this bench.
Thai held-out sets (full eval sets; sets marked * were held out as whole datasets; lower ECE means confidence is more trustworthy):
| Set | Task | Accuracy | ECE | Note |
|---|---|---|---|---|
| Prachathai67k | news topic (choice) | 98.1 | 0.004 | v0.2: 97.6 |
| Prachathai67k | per-topic yes/no (noul) | 94.2 | 0.008 | |
| MASSIVE-th | 60-way intent (choice) | 90.0 | 0.043 | v0.2: 88.6 |
| XNLI-th | natural language inference (choice) | 77.1 | 0.045 | |
| XNLI-th | entailment yes/no (noul) | 84.3 | 0.049 | |
| SIB-200 Thai* | 7-way topic (choice) | 77.9 | 0.084 | recovered from 74.0 in v0.2 |
| Thai contrastive pairs (one-fact flips) | choice / noul / score | 80.7 / 83.5 / 78.6 | 0.098 / 0.109 / 0.156 | |
| Wongnai | review stars 1–5 (score) | 63.5 exact (MAE 0.44) | 0.039 | |
| xLAM tool selection (English) | tool choice (choice) | 99.4 | 0.006 | |
| Wisesight* | 4-class social sentiment (choice) | 51.6 | 0.353 | still the weakest Thai set; use order-invariant mode |
| banking77* (English) | 77-way intent (choice) | 45.4 | 0.236 | v0.2: 37.8 (61.7 with order-invariant mode on v0.2) |
Treat confidence as reliable on the Thai sets and on NLI and topic tasks (ECE at or below 0.05). On the English multi-level rating tasks (helpsteer2, summeval-relevance) ECE is still high, so route low-confidence cases to a bigger model or a human.
Order-invariant mode — like Jev, a single forward pass is sensitive to the order of the options (measured on v0.2: 5–12% of arg-max choices flip under a random permutation for up to 10 options, 72% on 77-way banking77). The client and the API have an order-invariant mode that averages the answer over several cyclic option orders in one batched pass (order_invariant: true / permutations: n, automatic for choice questions with more than 10 options). On 360 held-out Thai questions: flips 18.9% → 3.3%, mean probability shift 0.19 → 0.08, accuracy 68.6 → 73.6, banking77 41.7 → 63.3. Cost is about 2× latency (batched), never more tokens in usage.input_tokens.
Latency — a 3-question request takes about 40 ms on an H100 and 154 ms on a MacBook M3 Max (Apple GPU via MPS); 170–220 ms round trip from a laptop through the API including network. A 255-option question takes 44 ms on an H100 and 1.5 s on the MacBook. Pure CPU is still slow (about 4.6 s), so use a GPU or MPS.
⚙️ How it works
- Backbone — the text tower of Qwen3.5-0.8B-Base (24 layers, hybrid Gated-DeltaNet / attention, 262k context) with the vision encoder removed, then continued-pretrained on ~5B tokens of Thai (web, Wikipedia, parallel Thai↔English, and machine-state text such as accessibility trees and JSON).
- Decision head — the 248k-token LM head is replaced by a 256-way slot head. Options are introduced by control tokens
<|ts_opt_0|> … <|ts_opt_254|>; the hidden state at each question's<|ts_answer|>token is projected to 256 logits, slots beyond the number of options are masked, and a softmax gives the distribution. Slot 255 is abstain (none of the options fits). - Reading the answer —
choice= argmax,score= Σ pᵢ·i over the ordered levels,noul= p(yes). - No position bias — option order is shuffled in training and abstain examples are mixed in, so this is an architecture, not a trick of reading logprobs over the letters A/B/C.
🔬 Training recipe
Every stage is in the GitHub repo, one script per stage, reproducible on a single H100.
scripts/00_prepare_base.py— drop Qwen3.5-0.8B's vision encoder, keep the text tower (752M parameters).scripts/01_cpt_data.py+scripts/02_cpt_train.py— Thai-heavy continued pretraining (filtered FineWeb-2 Thai, Thai Wikipedia, Thai instruction and summary text, OPUS Thai↔English, FineWeb-Edu replay, synthetic accessibility trees and JSON). One H100, about 40 hours. Held-out perplexity 31.4 → 5.38.scripts/03_decision_data.py— 1.81M decision records converted from 21 public datasets (Thai: wisesight*, wongnai, prachathai, XNLI-th, MASSIVE-th, SIB-200*, ThaiExam, iapp_wiki_qa, Thai toxicity; English: MNLI, MMLU, ARC, HelpSteer2, AG News, DBpedia, CLINC, banking77*, Mind2Web, xLAM tools). Sets marked * were held out entirely for evaluation.scripts/03b_synth_generate.py— about 100k synthetic Thai/English decision tasks generated with Qwen3.6-35B-A3B, each blind-relabelled and filtered for answer leakage (datasetiapp/openthai2.0-systemone-synth).scripts/04_decision_train.py— head swap + 12k-step SFT, then a calibration stage (Brier loss + per-type temperature) so that higher confidence really means higher accuracy. v0.2 continues from v0.1 for 3,000 more steps with a 22k-record synthetic Thai social-sentiment set (4-class with a "question" class, 3/5-class variants, yes/no flags, 5-level score), then re-calibrates. v0.3 continues from v0.2 for 5,000 more steps with 177k real train-split records (SQuAD2, BoolQ, PubMedQA-artificial, PAWS, Aegis2, ToxicChat, XQuAD-th, MASSIVE-de) and 78k targeted synthetic records for the weak spots (grounded yes/no QA, summary rating, 40–120-way near-duplicate intents, safety, adversarial paraphrases), lowers the sentiment-set weight from 4× to 2×, then re-calibrates.scripts/06b_public_benchmarks.py— rebuilds Bespoke Nimble's 13-subset public benchmark for a like-for-like comparison.
👨👩👧 The OpenThai 2.0 family
| Model | What | Size | Status |
|---|---|---|---|
| OpenThai 2.0 | Thai chat and reasoning LLM, reads images | 27B | released |
| OpenThai 2.0 Legal | Thai law model for RAG | 30B-A3B | released |
| OpenThai-SystemOne | Thai + English decision model (no text generation) | 0.8B | released |
| OpenThai-SystemOne CPT | the Thai-pretrained base underneath (research use) | 0.8B | private for now |
⚠️ Limits
- Version 0.3 — a 0.8B model is not a reasoning model. It will not do multi-step verification or arithmetic.
- It can only answer with the options you give, but it can still be wrong — it cannot emit an invalid option, but it can pick the wrong one. Use the
confidencefield and route low-confidence cases to a bigger model or a human. - Text only — no images. Up to 255 options per question (bucket into groups for more) and 64k tokens per request.
- Pure CPU is still slow — use a GPU or Apple MPS.
- Known v0.3 weak spots — the two 5-level rating tasks (summary relevance 22, helpfulness 42), fine-grained 77-way English intents (banking77 45 single-order), Thai social sentiment (Wisesight 52), PubMedQA 3-way (64), and politeness judgement on formal Thai (targeted set in v0.4).
- Sensitive to option order — like Jev, a single-pass answer can change when the options are shuffled. Use
order_invariant: true(automatic above 10 options) at about 2× latency. - English calibration is weaker than Thai — median ECE 0.15 versus 0.05 or below, because the training mix is Thai-heavy by design.
🗓 Versions
| Version | Date | Change | Public macro | Wisesight |
|---|---|---|---|---|
| v0.3 | 22 Sep 2026 | +5,000 SFT steps from v0.2 with 177k real train-split records + 78k targeted synthetic records for the weak spots (grounded QA, summary rating, fine-grained intents, safety, paraphrase), re-calibrated | 74.3 | 51.6 |
| v0.2 | 21 Sep 2026 | +3,000 SFT steps from v0.1 with a 22k-record synthetic Thai social-sentiment set, re-calibrated; order-invariant mode in the client and API | 63.2 | 51.5 |
| v0.1 | 20 Sep 2026 | initial release: Thai CPT 4.47B tokens, 12k-step SFT, calibration | 61.9 | 38.7 |
v0.3 moved SQuAD2 50.2 → 89.3, PAWS 68.0 → 94.0, Aegis2 61.6 → 83.2, MASSIVE-de 67.4 → 88.3, BoolQ 64.7 → 79.7, MASSIVE-en 79.1 → 88.3, PubMedQA 56.4 → 64.0, SummEval-relevance 14.2 → 21.7, VitaminC 68.6 → 72.5 and MultiNLI 87.3 → 89.0, with ECE improving on 11 of 13 subsets. SummEval-consistency regressed 84.0 → 75.0 (the synthetic consistency set only covers levels 1/3/5; levels 2/4 will be added). Thai held-out sets are all flat or up (SIB-200 74.0 → 77.9, banking77 37.8 → 45.4). The full changelog is in the model card.
🤝 Help us build it
- More Thai test sets — especially tasks with many options and UI/agent tasks. Open an issue on GitHub.
- GGUF / ONNX / MLX — the head is a plain Linear layer, so conversions are straightforward if you know the toolchain.
- Domain LoRA adapters — share yours; the plan is "one base, N adapters".
- Talk to us on the OpenThaiGPT Discord and the Facebook group.
📄 Licence and credits
Apache-2.0. Built by iApp Technology / OpenThaiGPT on Qwen3.5-0.8B-Base (Apache-2.0). Inspired by TypeSafe AI's System One models (Jev); this is an independent open re-implementation and is not affiliated with TypeSafe AI.
🙏 Thank you, Siam AI
The Thai continued pretraining, SFT and every evaluation ran on NVIDIA H100 GPUs generously provided by Siam AI Corporation. Thank you for your continued support of open Thai AI.
