ai-experiment/CLASSIFY.md

550 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Panduan Classification Runner (Laya / laya.cpp)
Dokumen ini menjelaskan cara pakai `classify_runner.py` — runner klasifikasi teks pakai
**Laya**, model keputusan (*decision model*) multilingual 322M parameter, yang dijalankan lewat
runtime native **laya.cpp** di CPU.
Untuk ringkasan proyek dan setup runner lain, lihat [README.md](README.md).
---
## Daftar isi
1. [Cara kerja singkat](#1-cara-kerja-singkat)
2. [Menjalankan](#2-menjalankan)
3. [Cara baca output](#3-cara-baca-output)
4. [Tiga tipe pertanyaan](#4-tiga-tipe-pertanyaan)
5. [Pertanyaan sendiri](#5-pertanyaan-sendiri)
6. [Mode server (lebih cepat)](#6-mode-server-lebih-cepat)
7. [Output JSON](#7-output-json)
8. [Semua opsi CLI](#8-semua-opsi-cli)
9. [Konfigurasi](#9-konfigurasi)
10. [Setup dari nol](#10-setup-dari-nol)
11. [Catatan penting](#11-catatan-penting)
---
## 1. Cara kerja singkat
Laya **bukan** classifier satu-label biasa. Kamu memberi dia sebuah *state* (teks, email, ticket,
atau JSON) plus beberapa **pertanyaan bertipe**. Tiap pertanyaan mendeklarasikan ruang jawabannya
sendiri, jadi **tidak perlu training/finetune** untuk memakainya di kasus baru.
Semua pertanyaan dijawab dalam **satu forward pass**, dengan probabilitas — jadi tidak ada teks
yang perlu diparse dan tidak ada risiko hallucination seperti pada LLM generatif.
```
state + pertanyaan → satu forward pass → jawaban bertipe + probabilitas
```
Runner mendukung tiga mode pemanggilan:
| Mode | Kapan dipakai | Kondisi | Kecepatan |
|---|---|---|---|
| CLI | satu kali jalan | tanpa setup | ~5–7 detik |
| Server | dipakai berulang | harus nyalakan server | ~0,6 detik |
---
## 2. Menjalankan
### Cara paling dasar
```bash
./usage-classify.sh "Saya dikenakan biaya dua kali untuk invoice 4411, tolong kembalikan uang saya."
```
### Kalau teksnya panjang, pakai file
```bash
./usage-classify.sh --state-file ticket.txt
```
### Kalau state-nya punya beberapa kolom
Kalau teks diawali `{` atau `[`, runner otomatis mengirimnya sebagai **struktur JSON**, bukan teks
biasa. Jadi email dengan kolom subject/body bisa langsung:
```bash
./usage-classify.sh '{"subject":"Refund not received","body":"I cancelled two weeks ago and still have no refund."}'
```
Atau dari file JSON:
```bash
./usage-classify.sh --state-file ticket.json
```
---
## 3. Cara baca output
Contoh output nyata:
```
State : Saya dikenakan biaya dua kali untuk invoice 4411, tolong kembalikan uang saya.
Questions : 3 (cli)
Backend : CPU (12th Gen Intel(R) Core(TM) i7-12700H)
Elapsed : 6.953 s
Inference : 0.732 s
Tokens : 176 in / 0 out
department : billing
billing=1.0000 technical=0.0000 sales=0.0000 other=0.0000
urgency : 1.7297 (immediate)
0=0.0306 1=0.2091 2=0.7603
refund : 0.7751
act_probability=1.0000
```
### Bagian atas
| Baris | Arti |
|---|---|
| `State` | State yang dikirim (dipotong 120 karakter kalau panjang) |
| `Questions` | Berapa pertanyaan dijawab, dan mode yang dipakai (`cli` atau `server`) |
| `Backend` | Backend dan perangkat yang dipakai. Hanya muncul di mode CLI |
| `Elapsed` | Total waktu wall-clock, **termasuk load bobot model** |
| `Inference` | Waktu forward pass saja. Hanya muncul di mode CLI |
| `Tokens` | Jumlah token input yang diproses (output selalu 0, karena model ini tidak generatif) |
> **Kenapa `Elapsed` jauh lebih besar dari `Inference`?**
> Model ini tidak generatif, jadi forward pass-nya cuma ~0,6 detik. Sisanya (~5 detik) adalah
> waktu baca bobot 644MB dari disk. Ini normal. Kalau kamu pemakai berulang, pakai
> [mode server](#6-mode-server-lebih-cepat).
### Bagian jawaban
Nama di kiri adalah **ID pertanyaan** yang kamu tentukan sendiri. Formatnya berbeda per tipe:
**`choice`** — jawaban berupa salah satu label. Baris kedua menampilkan probabilitas tiap opsi,
diurutkan dari yang tertinggi:
```
department : billing
billing=1.0000 technical=0.0000 sales=0.0000 other=0.0000
```
**`score`** — jawaban berupa angka desimal (rata-rata tertimbang dari distribusi), diikuti label
level yang paling dekat dalam kurung. Baris kedua menampilkan probabilitas tiap level **dalam
urutan legend** (tidak diurutkan):
```
urgency : 1.7297 (immediate)
0=0.0306 1=0.2091 2=0.7603
```
**`noul`** — jawaban berupa peluang pernyataan itu benar (0.0 = pasti salah, 1.0 = pasti benar).
Tipe ini tidak punya objek `probabilities`, jadi baris kedua menampilkan
`act_probability`, yaitu peluang bahwa kasus ini perlu di-*escalate* ke manusia:
```
refund : 0.7751
act_probability=1.0000
```
---
## 4. Tiga tipe pertanyaan
| Tipe | Isi `criteria` | Output | Kapan pakai |
|---|---|---|---|
| `choice` | objek label → deskripsi | label terpilih + peluang tiap label | klasifikasi ke kategori |
| `score` | **array** berurutan | angka desimal + label level | intensitas, prioritas, sentimen |
| `noul` | tidak ada | peluang pernyataan `true` | deteksi ya/tidak |
Contoh lengkap ketiga tipe (ini isi preset `triage` di `config/classify.py`):
```json
{
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "invoices, payments, refunds",
"technical": "bugs, outages, system errors",
"sales": "pricing, plans, new contracts",
"other": "everything else"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is the request?",
"criteria": ["not urgent", "soon", "immediate"]
},
"refund": {
"type": "noul",
"instructions": "Does the customer ask for money back?"
}
}
```
### Aturan penting
- **`choice` jangan lebih dari ~20 opsi.** Semua opsi berbagi satu budget token yang tetap, jadi
kalau label terlalu banyak, tiap label cuma kebagian sedikit token dan akurasinya jatuh tajam.
- **`score` pakai array, bukan objek.** Urutan array itu penting — itulah yang menentukan level
0, 1, 2, dan seterusnya.
- **`instructions` ditulis dalam bahasa Inggris** walau isinya bisa bahasa apa pun. Instruksi
bagian kepala (256 token) dan state diletakkan terpisah oleh `[SEP]` dalam model, jadi
instruksi tidak ikut mengotori bahasa input.
- **Nama kolom output bebas kamu pilih.** Kolom di kiri output itu key yang kamu pakai di
objek `questions`, bukan dari `instructions`.
### Multibahasa
Checkpoint-nya memang varian multilingual (100+ bahasa), jadi input non-Inggris diharapkan
berfungsi:
```bash
$ ./usage-classify.sh "二重に請求されました。返金をお願いします。"
refund : 0.9976
$ ./usage-classify.sh "双重收费了,请退款。"
refund : 0.9892
```
---
## 5. Pertanyaan sendiri
Preset `triage` cuma comprise 3 pertanyaan default. Untuk kasus kamu, bikin file JSON:
```bash
cat > my_questions.json <<'EOF'
{
"bahasa": {
"type": "choice",
"instructions": "What language is this text written in?",
"criteria": {
"id": "Indonesian",
"en": "English",
"fr": "French",
"zh": "Chinese"
}
},
"komplain": {
"type": "noul",
"instructions": "Does the customer complain or express dissatisfaction?"
},
"sentimen": {
"type": "score",
"instructions": "How positive is the tone?",
"criteria": ["very negative", "neutral", "positive", "very positive"]
}
}
EOF
./usage-classify.sh --questions my_questions.json "produk ini error terus sejak kemarin"
```
File-nya boleh berisi **objek questions langsung** (seperti di atas), **atau** objek request
lengkap yang punya key `"questions"`. Runner mendeteksi keduanya.
Kamu juga bisa simpan pertanyaan sebagai preset permanen di `config/classify.py` supaya bisa
dipakai cuma dengan `--preset nama`:
```python
PRESETS = {
"triage": { ... }, # bawaan
"bahasa": { ... }, # preset kamu
}
DEFAULT_PRESET = "triage"
```
```bash
./usage-classify.sh --preset bahasa "produk ini error terus sejak kemarin"
```
---
## 6. Mode server (lebih cepat)
Tiap kali runner dipanggil dalam mode CLI, ia me-*spawn* `laya-cli` dan **load ulang bobot
644MB dari disk**. Itu yang bikin total ~5–7 detik, padahal inferensinya cuma 0,6 detik.
Kalau kamu classifies berulang, biarkan model tetap *resident* di memory lewat HTTP server bawaan
laya.cpp:
```bash
third_party/laya.cpp/build-cpu/bin/laya-cli --server --port 8080 \
--model models/convaiinnovations/laya --variant multilingual --cpu &
```
Lalu arahkan runner ke sana:
```bash
LAYA_URL=http://127.0.0.1:8080 ./usage-classify.sh "teks kamu"
```
Hasilnya **~0,6 detik bukan ~6 detik** — sekitar 8x lebih cepat, dengan jawaban yang identik
(bisa diverifikasi langsung: bandingkan output CLI dan server untuk input yang sama).
### Urutan prioritas URL
1. `--url` di CLI
2. environment variable `LAYA_URL`
3. `LAYA_URL` di `config/classify.py`
### Kalau server mati
Runner **otomatis fallback** ke mode CLI, dengan informasi di stderr:
```
laya.cpp server at http://127.0.0.1:8080 unavailable (Connection refused). Falling back to the CLI.
```
Jadi tidak ada risiko kalau lupa nyalakan server atau server-nya crash.
### Endpoint lain yang tersedia
Server laya.cpp juga menyediakan `POST /predict` (bisa batch beberapa request sekaligus, dan
mengembalikan envelope CLI lengkap termasuk `elapsed_ms`), `GET /health`, dan `GET /v1/models`.
Runner memakai `/v1/systemone` yang bentuk balasannya lebih ringkas.
---
## 7. Output JSON
```bash
./usage-classify.sh "teks" --json
```
Menghasilkan envelope mentah laya.cpp dengan probabilitas presisi penuh (bukan 4 desimal) —
berguna kalau mau pipe ke program lain:
```json
{
"results": [
{
"model": "laya-rl-agent",
"answers": {
"refund": {
"type": "noul",
"confidence": 0.7751,
"action": { "act_probability": 1.0 },
"noul": 0.7751
}
},
"usage": { "input_tokens": 176, "output_tokens": 0 }
}
],
"elapsed_ms": 732.14,
"backend": "CPU",
"device": "12th Gen Intel(R) Core(TM) i7-12700H"
}
```
Bentuk tiap tipe jawaban:
| Tipe | Field yang ada |
|---|---|
| `choice` | `type`, `choice`, `probabilities`, `confidence`, `action.act_probability` |
| `score` | `type`, `score`, `legend`, `probabilities`, `confidence`, `action.act_probability` |
| `noul` | `type`, `noul`, `confidence`, `action.act_probability` |
---
## 8. Semua opsi CLI
```
usage: classify_runner.py [-h] [--state-file STATE_FILE] [--preset PRESET]
[--questions QUESTIONS] [--url URL] [--json]
[state]
positional arguments:
state State yang akan diklasifikasi (teks biasa atau JSON).
Hilangkan kalau mau baca dari --state-file.
options:
--state-file STATE_FILE File yang berisi state (teks biasa atau JSON)
--preset PRESET Nama preset pertanyaan dari config/classify.py
--questions QUESTIONS File JSON berisi objek questions, mengoverride --preset
--url URL Base URL server laya.cpp, mengoverride LAYA_URL dan config
--json Cetak envelope respons mentah sebagai JSON
-h, --help Tampilkan bantuan
```
> **Catatan:** `--state` **bukan** sebuah flag — state diberikan sebagai argumen posisi. Writer
> `--state` akan menghasilkan error, bukan diam-diam diperlakukan sebagai nama file.
Exit code:
| Kode | Arti |
|---|---|
| `0` | Berhasil |
| `1` | Gagal — preset tidak dikenal, file tidak ada, state kosong, atau inference error |
Contoh pemakaian error yang umum:
```bash
$ ./usage-classify.sh --preset salah "teks"
Unknown preset: salah. Available: triage
$ ./usage-classify.sh
Usage: ./usage-classify.sh <state> [--preset <name>] [--questions <file.json>] [--json]
```
---
## 9. Konfigurasi
Semua default ada di `config/classify.py`:
```python
LAYACPP = <repo>/third_party/laya.cpp
CLI = LAYACPP / "build-cpu" / "bin" / "laya-cli"
MODEL_ROOT = MODEL_DIR / "convaiinnovations" / "laya"
VARIANT = "multilingual"
CHECKPOINT = MODEL_ROOT / VARIANT / "model.safetensors"
BACKEND = "cpu" # cpu | cuda | vulkan | coreml
ALLOW_TRUNCATION = False
LAYA_URL = "" # "" = spawn CLI per jalan
TIMEOUT = 120
PRESETS = { "triage": {...} }
DEFAULT_PRESET = "triage"
```
| Variabel | Fungsi |
|---|---|
| `CLI` | Lokasi binary `laya-cli` hasil build |
| `MODEL_ROOT` | Direktori induk checkpoint |
| `VARIANT` | Varian checkpoint: `multilingual`, `english`, atau `typed-decisions` |
| `BACKEND` | Backend komputasi. `cpu` = pure CPU, tanpa GPU sama sekali |
| `ALLOW_TRUNCATION` | Kalau `True`, input kepanjangan dipotong diam-diam. Default: request ditolak |
| `LAYA_URL` | Fallback URL server kalau env dan flag tidak di-set |
| `PRESETS` | Kumpulan pertanyaan siap pakai |
| `DEFAULT_PRESET` | Preset yang dipakai kalau `--preset` tidak diberikan |
### Ganti ke GPU nanti
Ubah `BACKEND` jadi `"cuda"` atau `"vulkan"`. Tapi kamu harus pakai binary laya.cpp yang dikompilasi
dengan backend tersebut — build CPU-only (yang sekarang) tidak bisa. Detail di
[README.md](README.md#build-layacpp-cpu-only).
---
## 10. Setup dari nol
Di mesin ini **sudah selesai**. Kalau perlu diulang (misal di mesin lain):
```bash
./build-laya.sh
```
Script-nya **tidak butuh `sudo`** sama sekali:
- `cmake` dan `ninja` diambil dari pip wheel
- `nlohmann/json` diekstrak ke prefix lokal `third_party/nlohmann-install`
- satu-satunya kebutuhan sistem adalah `libicu-dev`
Script aman dijalankan berulang — setiap langkah dilewati kalau output-nya sudah ada.
Yang dilakukan script:
1. Clone `laya.cpp` beserta submodulnya (`ggml`, `cpp-httplib`) ke `third_party/laya.cpp`
2. Pasang `cmake` (versi 3.x) dan `ninja` lewat pip kalau belum ada
3. Ekstrak `nlohmann/json` ke prefix lokal
4. Build CPU-only ke `third_party/laya.cpp/build-cpu`
### Tiga hal yang wajib
| Flag | Kenapa wajib |
|---|---|
| `-DLAYA_CUDA=OFF` | Default-nya **ON**, dan konfigurasi akan gagal mencari `nvcc` |
| `cmake` versi 3.x | ggml yang di-pin laya.cpp deklarasikan `cmake_minimum_required(3.14...3.28)` dan **tidak bisa dikonfigurasi di CMake 4** |
| `--cpu` saat runtime | Default `laya-cli` adalah backend CUDA, jadi build CPU-only akan gagal start tanpa flag ini |
### Kenapa harus build dari sumber?
Upstream laya.cpp **tidak menyediakan** binary prebuilt khusus CPU — rilis Linux hanya CUDA 12,
CUDA 13, dan Vulkan. Kalau di mesin dengan GPU NVIDIA, kamu bisa unduh binary prebuilt dan
menghemat langkah build, tapi build CPU-only ini nol dependensi GPU.
### Struktur checkpoint
Checkpoint ada di `models/convaiinnovations/laya/multilingual/`.
> **Penting:** `laya-cli` menambahkan nama varian ke path `--model` untuk semua varian non-English.
> Jadi checkpoint multilingual **harus** berada di `<MODEL_ROOT>/multilingual/`.
>
> Ada salinan bobot yang identik di `models/convaiinnovations/laya-multilingual/`, tapi layout itu
> **tidak bisa** dipakai laya.cpp — direktori itu untuk implementasi referensi Python.
---
## 11. Catatan penting
Dua keterbatasan dari model card upstream yang belum diatasi runner ini. Keduanya penting
sebelum kamu mempercayai angkanya:
### 1. Probabilitasnya belum dikalibrasi
Model ini di-ship **uncalibrated** dengan `temperature = [1.0, 1.0, 1.0]` tanpa bucket per
jumlah opsi. Akibatnya dia **sistematis over-confident** — rata-rata confidence 0,75–0,83
padahal akurasinya jauh lebih rendah.
Jadi nilai `confidence=1.0` di output **belum tentu akurat**.
Untuk membaikkannya, refit satu temperatur per kombinasi (tipe pertanyaan, jumlah opsi) di data
held-out milikmu sendiri. Dari model card, ini memindahkan mean ECE dari **0,314 → 0,106**.
Lakukan ini sebelum memperlakukan probabilitas sebagai ambang keputusan.
### 2. Tipe `noul` bisa under-report `true`
Pada input yang jelas positif, satu pengukuran menempatkan `P(true)` di kisaran 0,5 sementara kasus
negatifnya benar-benar dekat 0. Kalau jawaban `noul` terasa lemah, **cek ulang** dengan versi dua
opsi:
```json
{
"cek": {
"type": "choice",
"instructions": "Does the text ask for a refund?",
"criteria": {
"A": "no, it does not ask for a refund",
"B": "yes, it asks for a refund"
}
}
}
```
### 3. Bahasa dengan sumber daya rendah masih lemah
SWE, Tamil, Amharic masih berada di rentang 0,110–0,250. Check akurasi di bahasa-bahasa yang memang
kamu pakai, jangan diasumsikan seragam.
### 4. Model multilingual lebih lemah untuk teks Inggris
Makro accuracy 0,619 (multilingual) vs 0,684 (checkpoint Inggris). Kalau mostly English, routing
ke checkpoint `laya` (Inggris) lebih akurat daripada memaksakan pakai multilingual.
---
## Ringkasan perintah
```bash
# Klasifikasi teks
./usage-classify.sh "teks kamu"
# Dari file
./usage-classify.sh --state-file ticket.txt
# Pertanyaan sendiri
./usage-classify.sh --questions my_questions.json "teks kamu"
# Output JSON untuk di-pipe
./usage-classify.sh "teks kamu" --json
# Mode cepat: nyalakan server sekali
third_party/laya.cpp/build-cpu/bin/laya-cli --server --port 8080 \
--model models/convaiinnovations/laya --variant multilingual --cpu &
LAYA_URL=http://127.0.0.1:8080 ./usage-classify.sh "teks kamu"
# Setup (sekali saja, tanpa sudo)
./build-laya.sh
```