Thai Jev: System 1 Decision Model (mmBERT)

An implementation of the Jev System 1 decision architecture built on top of jhu-clsp/mmBERT-base and fine-tuned on yothinS/thai-customer-support.

Unlike standard classifiers tied to fixed, rigid output heads, this model evaluates dynamic runtime schemas in a single non-autoregressive forward pass. It supports three typed decision primitives:

  • Choice: Categorical routing over arbitrary dynamic candidate strings with calibrated softmax distributions.
  • Noul: Calibrated binary boolean verification ([0.0, 1.0]) for arbitrary hypotheses.
  • Score: Continuous scalar evaluation ([0.0, 1.0]) guided by custom natural-language rubrics.

Model Architecture & Training

The architecture couples a shared multilingual ModernBERT transformer backbone with three specialized projection heads operating on mean-pooled token representations:

[State + Candidate / Rubric Prompt]
│
┌────────▼────────┐
│   mmBERT-base   │ (ModernBERT encoder)
└────────┬────────┘
         │ Mean Pooling
┌────────▼────────┐
│  d_model (768)  │
└────────┬────────┘
┌─────────┼─────────┐
▼         ▼         ▼
Choice   Noul      Score
Head     Head      Head
(GELU+MLP)(GELU+MLP)(GELU+Sigmoid)

Calibrated Multi-Task Loss

To prevent overconfidence and provide well-calibrated probabilities for downstream routing agents, training combines classification losses with a Brier score penalty:

  • Brier Weight: 0.5
  • Tail Reservation Strategy: A fixed reserve buffer (TAIL_RESERVE = 72) ensures ticket contexts are clipped before prompts and candidate options are appended, preventing candidate truncation.

Training Hyperparameters

Parameter Setting
Base Model jhu-clsp/mmBERT-base
Dataset yothinS/thai-customer-support
Max Sequence Length 192 tokens
Epochs 3
Optimizer AdamW ($\text{lr} = 2\times 10^{-5}$, $\text{weight decay} = 0.01$)
Learning Rate Schedule Linear warmup with decay (Warmup ratio: 0.06)
Gradient Accumulation 8 steps
Precision Mixed Precision (AMP torch.autocast)
Gradient Clipping Max norm 1.0

Input Formats

Each decision type formats the input string prior to tokenization:

  • Choice candidate format:
    สถานะ: {state} | คำถาม: {question} | ตัวเลือก: {option}
  • Noul hypothesis format:
    สถานะ: {state} | สมมติฐาน: {hypothesis}
  • Score rubric format:
    สถานะ: {state} | เกณฑ์ประเมิน: {rubric}

Quickstart & Inference

import os
import torch
import torch.nn as nn
from transformers import AutoModel, AutoTokenizer
from safetensors.torch import load_file
from huggingface_hub import hf_hub_download

# 1. Define Model Class
class aaronSystemOne(nn.Module):
    def __init__(self, backbone_id="jhu-clsp/mmBERT-base"):
        super().__init__()
        self.encoder = AutoModel.from_pretrained(backbone_id)
        d_model = self.encoder.config.hidden_size

        def make_head(sigmoid=False):
            layers = [
                nn.Linear(d_model, d_model // 2),
                nn.GELU(),
                nn.Dropout(0.1),
                nn.Linear(d_model // 2, 1)
            ]
            if sigmoid:
                layers.append(nn.Sigmoid())
            return nn.Sequential(*layers)

        self.choice_head = make_head()
        self.noul_head = make_head()
        self.score_head = make_head(sigmoid=True)

    def extract_embedding(self, input_ids, attention_mask):
        out = self.encoder(input_ids=input_ids, attention_mask=attention_mask)
        mask = attention_mask.unsqueeze(-1).float()
        return (out.last_hidden_state * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1e-9)

    def score_choices(self, input_ids, attention_mask):
        return self.choice_head(self.extract_embedding(input_ids, attention_mask)).squeeze(-1)

    def score_noul(self, input_ids, attention_mask):
        return self.noul_head(self.extract_embedding(input_ids, attention_mask)).squeeze(-1)

    def score_metric(self, input_ids, attention_mask):
        return self.score_head(self.extract_embedding(input_ids, attention_mask)).squeeze(-1)

# 2. Load Pipeline and Engine
# Note: Ensure modeling_jev.py is in your working directory
from modeling_jev import aaronEngine, Choice, Noul, Score

MODEL_ID = "YOUR_USERNAME/thai-jev-system-one-mmbert"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = aaronSystemOne(backbone_id=MODEL_ID)

# Support both local folder and remote Hugging Face repository
weights_path = f"{MODEL_ID}/model.safetensors"
if not os.path.exists(weights_path):
    weights_path = hf_hub_download(repo_id=MODEL_ID, filename="model.safetensors")

model.load_state_dict(load_file(weights_path))
model.to(device)
model.eval()

client = aaronEngine(model, tokenizer, device=device, max_length=192)
client.warmup()

# 3. Evaluate an Unseen Ticket
state = "ลูกค้าโวยวายหนักมาก บอกว่าบัตรเครดิตถูกชาร์จซ้ำซ้อน 3 รอบและขู่จะฟ้อง สคบ. ถ้าไม่ได้เงินคืนภายในวันนี้"

decision = client.judge(state, {
    "route_department": Choice(
        question="คำร้องนี้ควรส่งต่อไปยังทีมงานใด?",
        options=["ทีมกฎหมายและข้อพิพาท", "ทีมการเงินและบิลลิ่ง", "ทีมวิศวกรรมไอที"]
    ),
    "legal_threat": Noul("ลูกค้ามีการข่มขู่ว่าจะดำเนินการทางกฎหมายใช่หรือไม่"),
    "churn_risk": Score("ประเมินระดับความเสี่ยงที่ลูกค้าจะเลิกใช้บริการ (Churn Risk)")
})

print(decision)

Example Output

{
  "route_department": {
    "type": "Choice",
    "value": "ทีมการเงินและบิลลิ่ง",
    "probability": 0.8842,
    "confidence": 0.7931,
    "distribution": {
      "ทีมการเงินและบิลลิ่ง": 0.8842,
      "ทีมกฎหมายและข้อพิพาท": 0.0911,
      "ทีมวิศวกรรมไอที": 0.0247
    }
  },
  "legal_threat": {
    "type": "Noul",
    "value": true,
    "probability": 0.9412
  },
  "churn_risk": {
    "type": "Score",
    "value": 0.8625
  }
}

Intended Uses & Limitations

Best Use Cases

  • Triage & Intent Routing: Fast dispatching of incoming Thai customer tickets to target departments.
  • Guardrails & Escalation Checks: Instant binary verification of high-risk conditions (e.g., fraud claims, legal actions, VIP accounts).
  • System 1 Filtering: Operating as a high-throughput, low-latency filter in front of slower autoregressive LLMs (System 2).

Limitations

  • Maximum Context Length: The default sequence length is capped at 192 tokens. Extremely long conversations will have older turns truncated.
  • Domain Specificity: The fine-tuning target represents customer support operations. Performance on generic open-domain tasks may be limited without further fine-tuning.
  • Scoring Subjectivity: Score predictions depend directly on how clearly the rubric text reflects patterns represented during training.
Downloads last month
-
Safetensors
Model size
0.3B params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for yothinS/Aaron

Finetuned
(177)
this model

Dataset used to train yothinS/Aaron