--- base_model: unsloth/qwen2.5-coder-3b-instruct-bnb-4bit library_name: peft license: apache-2.0 language: - id tags: - nl2sql - text-to-sql - indonesian - sql - qlora - unsloth - peft pipeline_tag: text-generation datasets: - qrizan/nl2sql-id-schema --- # NL2SQL-ID QLoRA - Indonesian Text-to-SQL (3B) Model QLoRA adapter (r=16) hasil fine-tuning **Qwen2.5-Coder-3B-Instruct** untuk menerjemahkan pertanyaan bisnis **Bahasa Indonesia** menjadi SQL. > **Proyek eksperimen.** Model dilatih khusus untuk satu skema e-commerce 5 tabel (customers, orders, order_items, products, payments). Bisa dipakai di database lain **hanya jika** skemanya persis sama. Untuk skema berbeda, perlu fine-tuning ulang dengan data baru. --- ## Hasil 3-way comparison pada 365 contoh eval_dev (nilai yang belum pernah dilihat model): | Sistem | Exec Acc | Valid SQL | Error dominan | |--------|:--------:|:---------:|---------------| | Base (Qwen2.5-Coder-3B, zero-shot) | 0.27% | 3.0% | `invalid_sql` (354) | | gpt-4o-mini (zero-shot) | 48.5% | 100% | `wrong_join` (99) | | **Model ini** | **97.26%** | **100%** | `other` (10) | Final pada eval_test (365 held-out, tidak pernah disentuh selama pengembangan): | Metrik | Nilai | |--------|-------| | Execution Acc | **91.78%** | | Valid SQL | 100% | | Gap vs dev | 5.48pp (generalisasi baik) | --- ## Skema Database Model dilatih dan hanya bekerja untuk skema 5 tabel berikut. Prompt harus selalu menyertakan DDL ini: | Tabel | Kolom | |-------|-------| | `customers` | id, name, city | | `orders` | id, customer_id, created_at, status | | `order_items` | id, order_id, product_id, qty, price | | `products` | id, name, category, price | | `payments` | id, order_id, amount, paid_at, method | Query yang didukung: `SELECT`, `JOIN` (max 4 tabel), `WHERE`, `GROUP BY`, `ORDER BY`, `SUM`/`COUNT`/`AVG`, `HAVING`, filter tanggal (`strftime`, `>= AND <`), `LIMIT`. --- ## Cara Pakai ### Load dari HF Hub ```python from unsloth import FastLanguageModel model, tokenizer = FastLanguageModel.from_pretrained( model_name="qrizan/nl2sql-id-qlora-3b", max_seq_length=768, load_in_4bit=True, ) FastLanguageModel.for_inference(model) ``` ### Inference (self-contained, tanpa dependensi proyek) ```python import re # Salin skema database anda ke sini SCHEMA_TEXT = """ customers(id, name, city) orders(id, customer_id, created_at, status) order_items(id, order_id, product_id, qty, price) products(id, name, category, price) payments(id, order_id, amount, paid_at, method) """ pertanyaan = "berapa total penjualan kategori elektronik bulan Januari 2025?" # System prompt harus persis seperti saat training (src/prompts.py::get_system_prompt) system = ( "Anda adalah asisten SQL yang menerjemahkan pertanyaan bisnis " "Bahasa Indonesia menjadi query SQLite.\n\n" f"Skema database:\n{SCHEMA_TEXT}\n\n" "Aturan:\n" "- Gunakan tabel dan kolom sesuai skema di atas.\n" "- Tulis reasoning singkat dalam tag ...:\n" " Tabel: (tabel yang dipakai)\n" " Join: (kondisi join)\n" " Filter: (kondisi WHERE)\n" " Agregasi: (fungsi agregasi, atau '-' jika tidak ada)\n" "- Setelah , tulis SQL tanpa markdown fence.\n" "- Hanya SELECT, tanpa INSERT/UPDATE/DELETE." ) messages = [ {"role": "system", "content": system}, {"role": "user", "content": pertanyaan}, ] prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer(prompt, return_tensors="pt").to(model.device) outputs = model.generate(**inputs, max_new_tokens=512, do_sample=False) response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) # Ekstrak SQL setelah m = re.search(r"\s*(.*)", response, re.DOTALL | re.IGNORECASE) sql = m.group(1).strip() if m else response.strip() # Bersihkan markdown fence jika ada sql = re.sub(r"```(?:sql)?\s*", "", sql, flags=re.IGNORECASE).replace("```", "").strip() print(sql) # SELECT SUM(oi.qty * oi.price) FROM order_items oi # JOIN products p ON oi.product_id = p.id # JOIN orders o ON oi.order_id = o.id # WHERE p.category = 'elektronik' # AND strftime('%Y-%m', o.created_at) = '2025-01' ``` ### Format Output Model menghasilkan reasoning chain diikuti SQL: ``` Tabel: order_items, products, orders Join: oi.product_id = p.id, oi.order_id = o.id Filter: p.category = 'elektronik', strftime('%Y-%m', o.created_at) = '2025-01' Agregasi: SUM(qty * price) SELECT SUM(oi.qty * oi.price) ... ``` SQL diekstrak setelah ``. Baris `SCHEMA_TEXT` bisa diganti dengan DDL database anda - asal strukturnya sama dengan 5 tabel di atas. --- ## Training | Parameter | Nilai | |-----------|-------| | Model base | Qwen2.5-Coder-3B-Instruct (4-bit) | | Metode | QLoRA (r=16, alpha=16, dropout=0.05) | | Data train | 820 contoh (pool A: elektronik/fashion/makanan/furnitur, Jan-Jun 2025) | | Epoch | 5 | | Learning rate | 2e-4 | | Batch size | 16 (2 x grad_accum 8) | | GPU | Colab T4 (~15 menit) | | Trainable params | 29.9M (0.96%) | | Adapter size | 115 MB | Train dan eval menggunakan nilai berbeda (pool A vs pool B: kategori, bulan, kota) untuk menguji generalisasi, bukan hafalan. Hasil dev 97.26%, test held-out 91.78%. --- ## Batasan - **Skema spesifik.** Hanya untuk 5 tabel di atas. Skema berbeda perlu fine-tuning ulang. - Query `SELECT` saja - tidak mendukung `INSERT`/`UPDATE`/`DELETE` - Bahasa Indonesia dengan kosakata bisnis, tidak diuji pada bahasa lain - Maksimum ~768 token input --- ## Lisensi Apache 2.0 - bebas dipakai, dimodifikasi, didistribusikan. ## Kode Lengkap Pipeline training, evaluasi, demo: [github.com/qrizan/llm-posttraining-experiments](https://github.com/qrizan/llm-posttraining-experiments)