550 lines
17 KiB
Markdown
550 lines
17 KiB
Markdown
# 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
|
||
```
|