OpenThai-SystemOne NEW
OpenThai-SystemOne is an open-source (Apache 2.0) Thai and English System One decision model from iApp Technology and OpenThai. It does not generate text. You send a state (any text or JSON: a support ticket, a comment, a document, a UI accessibility tree) plus one or more typed questions, and it returns a calibrated probability for every option of every question in one forward pass. The HTTP contract mirrors the POST /v1/systemone shape used by TypeSafe AI's Jev, so SDK code written for that API can point at this endpoint unchanged.
| Question type | You give | You get |
|---|---|---|
choice | instructions and up to 255 named options (optional descriptions) | best option, a probability per option, confidence, abstain |
score | instructions and 2 to 10 ordered levels | a probability-weighted score (can be fractional) and per-level probabilities |
noul | a yes/no question | noul = P(yes) |
- Model page: OpenThai-SystemOne
- Open weights: huggingface.co/iapp/OpenThai-SystemOne
- Code and training recipe: github.com/iapp-technology/openthai-systemone
Getting Started
-
Prerequisites
- A free iApp API key: register, then API Keys, then Create New API Key
- A state to decide about, and the questions you want answered
-
Endpoint
Endpoint POST https://api.iapp.co.th/v3/store/openthai/systemoneAuth apikey: <key>headerBody JSON: state(string or any JSON) andquestions(map of typed questions)Limits 100 requests per minute and 1,000 decisions per day per key during the free preview; 255 options per choice, 2 to 10 levels perscore, 64K tokens per requestOutput JSON with one answer per question, usage.output_tokensis always 0
Please visit API Key Management page to view your existing API key or request a new one.
Try Demo
Four presets: Thai ticket triage, Thai comment moderation, choosing a UI element for an agent, and an English RAG relevance judge. Edit the state or the questions, then press Ask. Sign in and your API key is filled in automatically.
Code Examples
The same request in every language: a Thai support ticket, three questions, one call.
- cURL
- Python
- JavaScript
curl -s https://api.iapp.co.th/v3/store/openthai/systemone \
-H "apikey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": {"ticket": "โดนหักเงินซ้ำสองครั้งเมื่อวานนี้ ขอเงินคืนด่วนนะครับ โทรไปสามรอบแล้วไม่มีใครรับ"},
"questions": {
"department": {"type": "choice", "instructions": "ทีมใดควรรับผิดชอบ",
"criteria": {"billing": "การเงิน/คืนเงิน", "technical": "ระบบใช้งานไม่ได้", "sales": null}},
"frustration": {"type": "score", "instructions": "ลูกค้าหงุดหงิดแค่ไหน",
"criteria": ["ใจเย็น", "หงุดหงิดแต่สุภาพ", "โกรธมาก"]},
"refund": {"type": "noul", "instructions": "ลูกค้าขอเงินคืนอย่างชัดเจนหรือไม่"}
}
}'
import requests
resp = requests.post(
"https://api.iapp.co.th/v3/store/openthai/systemone",
headers={"apikey": "YOUR_API_KEY"},
json={
"state": {"ticket": "โดนหักเงินซ้ำสองครั้งเมื่อวานนี้ ขอเงินคืนด่วนนะครับ โทรไปสามรอบแล้วไม่มีใครรับ"},
"questions": {
"department": {"type": "choice", "instructions": "ทีมใดควรรับผิดชอบ",
"criteria": {"billing": "การเงิน/คืนเงิน", "technical": "ระบบใช้งาน ไม่ได้", "sales": None}},
"frustration": {"type": "score", "instructions": "ลูกค้าหงุดหงิดแค่ไหน",
"criteria": ["ใจเย็น", "หงุดหงิดแต่สุภาพ", "โกรธมาก"]},
"refund": {"type": "noul", "instructions": "ลูกค้าขอเงินคืนอย่างชัดเจนหรือไม่"},
},
},
timeout=30,
)
answers = resp.json()["answers"]
print(answers["department"]["choice"], answers["frustration"]["score"], answers["refund"]["noul"])
# route anything with low confidence to a bigger model or a person
if answers["department"]["confidence"] < 0.6:
print("escalate")
const res = await fetch("https://api.iapp.co.th/v3/store/openthai/systemone", {
method: "POST",
headers: { apikey: "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({
state: { ticket: "โดนหักเงินซ้ำสองครั้งเมื่อวานนี้ ขอเงินคืนด่วนนะครับ โทรไปสามรอบแล้วไม่มีใครรับ" },
questions: {
department: { type: "choice", instructions: "ทีมใดควรรับผิดชอบ",
criteria: { billing: "การเงิน/คืนเงิน", technical: "ระบบใช้งานไม่ได้", sales: null } },
frustration: { type: "score", instructions: "ลูกค้าหงุดหงิดแค่ไหน",
criteria: ["ใจเย็น", "หงุดหงิดแต่สุภาพ", "โกรธมาก"] },
refund: { type: "noul", instructions: "ลูกค้าขอเงินคืนอย่างชัดเจนหรือไม่" },
},
}),
});
const { answers, usage } = await res.json();
console.log(answers.department.choice, answers.frustration.score, answers.refund.noul, usage);
Response
Measured output of the request above (166 input tokens, 0 output tokens, one forward pass):
{
"model": "openthai-systemone",
"answers": {
"department": {"type": "choice", "choice": "billing",
"probabilities": {"billing": 0.963, "technical": 0.011, "sales": 0.026},
"confidence": 0.83, "abstain": 0.085},
"frustration": {"type": "score", "score": 1.95,
"legend": {"0": "ใจเย็น", "1": "หงุดหงิดแต่สุภาพ", "2": "โกรธมาก"},
"probabilities": {"0": 0.012, "1": 0.031, "2": 0.957}, "confidence": 0.82},
"refund": {"type": "noul", "noul": 0.946}
},
"usage": {"input_tokens": 166, "output_tokens": 0}
}
Reading the answers
choice:choiceis the best option,probabilitiessum to 1 over the options you offered,confidenceis 1 minus the normalised entropy, andabstainis the probability that none of the offered options fits (an iApp extension).score:scoreis the probability-weighted level index (fractional),probabilitiesis per level,legendmaps index to label.noul:noulis P(yes).- Route low-
confidenceanswers to a larger model or a person. The model cannot emit an option you did not offer, but it can still be wrong.
Use Cases
Ticket routing, comment moderation, checking whether an LLM answer is grounded, choosing which UI element an agent should act on, document and field classification, review scoring. Each is one request that answers every question at once, in tens of milliseconds on a server GPU.
Performance
| Setting | Measured |
|---|---|
| Gateway round trip, 3-question Thai ticket (this page's example, 20 Sep 2026) | 0.23 s |
| Server GPU (H100), batch 1, 3 questions | 40 to 70 ms |
| Laptop GPU (MacBook M3 Max, MPS) | 154 ms |
| Output tokens | 0, every request |
Benchmarks
Zero-shot on Bespoke Labs' public 13-subset System One benchmark, same subsets, splits, instructions and sampler. Nimble-9B and Jev figures are as published by Bespoke Labs on 18 Sep 2026; ours are measured with the released evaluation script. choice and noul report accuracy, score reports exact-level match. Bold marks where the 0.8B model is ahead of the 9B.
| Subset | Type | n | OpenThai-SystemOne 0.8B | Nimble-9B | Jev 1.13.0 |
|---|---|---|---|---|---|
| aegis2 | noul | 250 | 58.0 | 81.2 | 80.4 |
| boolq | noul | 300 | 63.7 | 86.0 | 89.7 |
| civil_comments | noul | 300 | 78.0 | 70.3 | 81.0 |
| helpsteer2 | score | 250 | 42.8 | 39.0 | 34.1 |
| massive-de-DE | choice | 350 | 64.6 | 83.4 | 86.9 |
| massive-en-US | choice | 350 | 75.7 | 86.9 | 87.4 |
| multinli | choice | 299 | 85.6 | 85.3 | 82.9 |
| paws | noul | 250 | 67.2 | 82.8 | 89.2 |
| pubmedqa | choice | 250 | 53.6 | 75.6 | 77.2 |
| squad2 | noul | 299 | 50.2 | 80.6 | 82.9 |
| summeval-consistency | score | 144 | 84.0 | 75.7 | 81.2 |
| summeval-relevance | score | 240 | 13.8 | 49.2 | 35.0 |
| vitaminc-dev | choice | 599 | 67.1 | 76.6 | 80.1 |
| Macro average | 61.9 | 74.8 | 76.0 |
Raw Qwen3.5-0.8B prompted with letter log-probs scores 45.4 on the same benchmark. Honest reading: the 0.8B model is ahead of the 9B on 4 of 13 subsets (NLI, summary consistency, helpfulness scoring, toxicity) and clearly behind on reading-comprehension yes/no tasks (squad2 at chance, boolq, pubmedqa) and on summary relevance scoring, where its score head is miscalibrated (ECE 0.79).
Thai held-out sets, never used in training. ECE is the expected calibration error, lower is better:
| Set | Type | n | Accuracy | ECE |
|---|---|---|---|---|
| MASSIVE-th intent (60-way) | choice | 5,007 | 86.4 | 0.048 |
| Prachathai67k topics | choice | 3,501 | 97.7 | 0.005 |
| XNLI-th | choice | 2,490 | 76.5 | 0.028 |
| SIB-200 Thai topic (7-way), whole dataset held out | choice | 204 | 77.5 | 0.074 |
| Wongnai review stars (1 to 5) | score | 6,203 | 63.3 | 0.010 |
| xLAM tool selection (English) | choice | 884 | 99.4 | 0.007 |
| Wisesight sentiment (4-class), whole dataset held out | choice | 2,671 | 38.7 | 0.341 |
| banking77 intent (77-way, English), whole dataset held out | choice | 3,076 | 32.7 | 0.165 |
Confidence is reliable on the Thai sets and on NLI and topic tasks (ECE at or below 0.05 after calibration). On the English public benchmark the median ECE is 0.15, so route low-confidence English yes/no decisions to a larger model. Full tables, Brier scores and the before/after calibration comparison are in the model card.
Limits
- Version 0.1. A 0.8B model, not a reasoning model.
- Answers only the options you offer, and can still be wrong. Use
confidenceto route uncertain cases. - Known weak spots in v0.1, numbers above: Thai social-media sentiment (Wisesight 38.7), fine-grained 77-way English intents (banking77 32.7), extractive-QA style yes/no (squad2 at chance) and summary relevance scoring. v0.2 targets the first.
- Text only. No images.
- At most 255 options per
choice, 2 to 10 levels perscore, 64K tokens per request.
Run it locally
The weights are open. Three lines get you the same answers on your own machine:
pip install "git+https://github.com/iapp-technology/openthai-systemone"
OPENTHAI_SYSTEMONE_MODEL=iapp/OpenThai-SystemOne uvicorn openthai_systemone.server:app --port 8000
curl -s http://localhost:8000/v1/systemone -H "Content-Type: application/json" -d '{"state": "...", "questions": {...}}'
Technical Reference
Request
POST https://api.iapp.co.th/v3/store/openthai/systemone, header apikey, body application/json:
| Field | Type | Description |
|---|---|---|
state | string or JSON | What the decision is about. Any JSON is accepted and serialised for the model. |
questions | object | A map of question name to question. Every question is answered in the same forward pass. |
questions.<name>.type | choice, score or noul | Question type. |
questions.<name>.instructions | string | What to decide, in Thai or English. |
questions.<name>.criteria | object or array | choice: object of option name to optional description (null for none), up to 255. score: array of 2 to 10 level labels in order. Not used by noul. |
Response
| Field | Description |
|---|---|
model | Model identifier. |
answers.<name> | One object per question, type matching the request. See Reading the answers. |
usage.input_tokens | Prompt tokens consumed. |
usage.output_tokens | Always 0. |
Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid apikey. |
| 422 | Malformed request, for example a score with fewer than 2 levels or a choice with more than 255 options. |
| 429 | Rate limit: 100 requests per minute or 1,000 decisions per day per key during the free preview. |
Architecture
The model is the text tower of Qwen3.5-0.8B, continued-pretrained on about 5B Thai tokens, with the language-model head replaced by a 256-way slot head. Options are introduced by control tokens; every answer position is projected to 256 logits, slots beyond the option count are masked, and a softmax gives the distribution. Slot 255 is "none of the above". Option order is shuffled in training, so there is no position bias.
Changelog
| Date | Version | Change |
|---|---|---|
| 20 Sep 2026 | v0.1-20260920 | Public launch. Hosted API on api.iapp.co.th, free preview capped at 1,000 decisions per day per key. Open weights and training recipe released under Apache 2.0. |