Strategi verifikasi LLM lokal perlu dirancang berbeda dari testing aplikasi deterministik biasa. Masalah utamanya bukan hanya apakah respons model “benar”, tetapi apakah pipeline evaluasi tetap stabil ketika bobot model, prompt, parameter inferensi, retrieval context, atau dataset berubah sedikit.

Pada konteks LLM lokal atau sovereign AI seperti inisiatif GPT-NL, kebutuhan ini makin penting karena organisasi sering mengelola model, data, dan infrastruktur sendiri. Artinya, tim tidak bisa bergantung pada vendor tertutup untuk konsistensi perilaku model. Dibutuhkan workflow verifikasi yang bisa mendeteksi regresi nyata tanpa menghasilkan terlalu banyak false alarm akibat test yang flaky.

Mengapa pipeline evaluasi LLM sering flaky

Flaky test pada sistem berbasis LLM biasanya muncul karena ada campuran antara komponen deterministik dan non-deterministik. Jika semuanya diuji dengan cara yang sama, hasil evaluasi akan mudah berubah antar-run meskipun kode aplikasi tidak berubah.

Sumber non-determinisme yang paling umum

  • Sampling inferensi: perubahan pada temperature, top-p, atau strategi decoding dapat mengubah keluaran.
  • Perbedaan model: update bobot, quantization, backend inferensi, atau runtime berbeda bisa memunculkan output yang tidak identik.
  • Prompt drift: perubahan kecil pada system prompt, few-shot examples, atau format instruksi dapat berdampak besar.
  • Dataset evaluasi yang berubah: penambahan contoh, perbaikan label, atau perubahan skema anotasi bisa menggeser skor.
  • Retrieval atau context assembly: urutan dokumen, ranking hasil pencarian, chunking, dan preprocessing context sering menjadi sumber variasi.
  • Parsing output model: model mungkin tetap “benar” secara semantik, tetapi gagal memenuhi format yang diharapkan.
  • Concurrency dan timeout: request paralel, batas memori, atau timeout dapat menghasilkan hasil berbeda, terutama pada deployment lokal.

Karena itu, target verifikasi bukan membuat semua pengujian menjadi sepenuhnya deterministik, melainkan memisahkan apa yang memang harus deterministik dari apa yang cukup stabil secara statistik.

Prinsip dasar: bedakan verifikasi kode, kontrak, dan kualitas model

Kesalahan umum adalah memakai satu jenis tes untuk semua lapisan. Misalnya, seluruh endpoint diuji langsung ke model sungguhan lalu assert hasil exact match. Pendekatan ini rapuh. Sebaliknya, gunakan test pyramid yang jelas.

Test pyramid untuk aplikasi berbasis LLM

  1. Unit test untuk logika deterministik: preprocessing, retrieval assembly, validation, parser, scorer, fallback, redaction, dan business rules.
  2. Contract test untuk menjamin format output model dapat diproses sistem dengan aman.
  3. Golden set untuk task kritis yang butuh standar kualitas minimum stabil.
  4. Evaluasi offline terkurasi untuk perubahan model, prompt, atau parameter inferensi.
  5. Canary verification sebelum rilis penuh.
  6. Observability pasca-deploy untuk mendeteksi regresi yang tidak tertangkap saat pra-rilis.

Dengan struktur ini, jika ada kegagalan, tim lebih cepat tahu apakah sumber masalah ada pada kode aplikasi, kontrak integrasi, atau kualitas model itu sendiri.

Lapisan 1: unit test untuk komponen yang harus deterministik

Unit test seharusnya tidak bergantung pada model sungguhan kecuali benar-benar perlu. Fokusnya adalah komponen yang bisa dibuat stabil sepenuhnya.

Apa yang perlu diuji dengan unit test

  • Normalisasi input pengguna.
  • Pemotongan dan chunking dokumen.
  • Pemilihan prompt template berdasarkan tipe tugas.
  • Post-processing output model.
  • Parser JSON atau extractor field.
  • Threshold business rule, misalnya kapan jawaban ditandai needs_human_review.
  • Redaksi data sensitif untuk sistem sovereign/local.

Contoh unit test parser output

def parse_classification_output(text: str) -> dict:
    data = json.loads(text)
    label = data["label"]
    confidence = float(data["confidence"])

    if label not in {"approve", "reject", "review"}:
        raise ValueError("invalid label")
    if not 0.0 <= confidence <= 1.0:
        raise ValueError("invalid confidence")

    return {"label": label, "confidence": confidence}


def test_parse_classification_output_valid():
    raw = '{"label":"approve","confidence":0.91}'
    assert parse_classification_output(raw) == {
        "label": "approve",
        "confidence": 0.91,
    }


def test_parse_classification_output_invalid_label():
    raw = '{"label":"unknown","confidence":0.91}'
    with pytest.raises(ValueError):
        parse_classification_output(raw)

Kenapa ini penting? Karena kegagalan parsing sering terlihat seperti “model jelek”, padahal akar masalahnya justru kode pascaproses yang tidak tahan terhadap variasi keluaran.

Kapan wajib mock model

Mock sebaiknya dipakai pada unit test dan sebagian integration test ketika tujuan pengujian adalah memverifikasi perilaku aplikasi, bukan kualitas model. Misalnya:

  • Memastikan retry berjalan saat output tidak valid.
  • Memastikan fallback ke aturan manual jika model timeout.
  • Memastikan audit log tersimpan saat respons diterima.

Jangan panggil model sungguhan untuk test seperti ini. Jika tidak, test jadi lambat, mahal secara komputasi, dan mudah flaky.

Lapisan 2: contract test untuk format output yang aman diproses

Pada banyak sistem LLM, masalah terbesar bukan isi jawaban, melainkan apakah respons masih sesuai dengan kontrak yang dapat diparsing. Contract test berada di tengah: lebih realistis daripada unit test, tetapi lebih sempit daripada evaluasi kualitas penuh.

Tujuan contract test

  • Memastikan model menghasilkan struktur output yang sesuai skema.
  • Memastikan field wajib selalu ada.
  • Memastikan nilai field dapat divalidasi.
  • Memastikan prompt dan parser tetap kompatibel saat salah satunya berubah.

Contoh skema kontrak output

{
  "answer": "string",
  "citations": ["string"],
  "risk_level": "low | medium | high",
  "needs_human_review": true
}

Untuk contract test, gunakan beberapa input representatif yang sengaja mencakup kasus normal, kasus ambigu, dan kasus kosong. Kriteria lulus sebaiknya bukan exact match seluruh teks, tetapi:

  • JSON valid.
  • Semua field wajib ada.
  • Enum valid.
  • Panjang jawaban tidak nol.
  • Jika task membutuhkan sitasi, minimal satu sitasi valid muncul.

Catatan: Jika sistem Anda memakai output terstruktur, validasi skema biasanya lebih stabil daripada membandingkan kalimat demi kalimat. Ini salah satu cara paling efektif mengurangi flaky test.

Lapisan 3: golden set untuk task kritis

Golden set adalah sekumpulan contoh kecil, stabil, dan dikurasi manual untuk use case yang paling sensitif. Ini bukan dataset besar untuk eksperimen, tetapi set acuan yang dijaga ketat.

Kapan golden set sangat penting

  • Ringkasan dokumen resmi.
  • Klasifikasi risiko atau compliance.
  • Ekstraksi informasi dari formulir atau surat.
  • RAG untuk pengetahuan internal yang tidak boleh halusinatif.
  • Sistem dengan kebutuhan bahasa lokal atau domain publik tertentu, termasuk konteks LLM lokal seperti GPT-NL.

Karakteristik golden set yang baik

  • Ukuran kecil sampai menengah agar cepat dijalankan.
  • Mewakili kasus bisnis paling penting, bukan kasus acak.
  • Dijaga versinya seperti kode.
  • Memiliki label atau ekspektasi yang jelas.
  • Perubahan isi harus melalui review, bukan diperbarui otomatis.

Gunakan ekspektasi yang tahan variasi

Untuk menghindari flaky test, jangan selalu pakai exact string match. Pilih oracle yang sesuai dengan jenis task:

  • Klasifikasi: label harus sama, confidence opsional.
  • Ekstraksi: bandingkan field terstruktur.
  • RAG: cek apakah jawaban didukung sitasi yang benar.
  • Ringkasan: gunakan rubric atau pengecekan poin kunci, bukan identitas kata per kata.

Jika perlu, simpan beberapa jawaban yang masih diterima untuk satu input, tetapi lakukan ini secara ketat agar golden set tidak berubah menjadi “semua jawaban boleh”.

Lapisan 4: evaluasi offline terkurasi untuk model, prompt, dan parameter inferensi

Ketika Anda mengganti model lokal, memodifikasi prompt, mengubah chunk size retrieval, atau menyetel parameter inferensi, gunakan evaluasi offline yang terpisah dari test aplikasi biasa. Tujuannya adalah mengukur perubahan kualitas secara lebih representatif.

Apa yang sebaiknya dibekukan per run evaluasi

  • Versi model atau artefak bobot.
  • Prompt template.
  • Parameter inferensi penting.
  • Versi dataset evaluasi.
  • Versi retriever, embedding, atau index bila memakai RAG.

Tanpa pembekuan ini, skor antar-run sulit dibandingkan. Dalam praktiknya, simpan manifest evaluasi sederhana.

{
  "model_id": "local-llm-build-2026-08-01",
  "prompt_version": "support-triage-v12",
  "dataset_version": "golden-and-offline-2026-07",
  "inference": {
    "temperature": 0.0,
    "top_p": 1.0,
    "max_tokens": 300
  },
  "retrieval": {
    "index_version": "kb-2026-07-28",
    "top_k": 5
  }
}

Metrik pass/fail yang lebih stabil

Pilih metrik berdasarkan task, lalu tetapkan ambang yang realistis. Hindari metrik yang terlalu sensitif terhadap variasi bahasa jika tidak diperlukan.

  • Klasifikasi: accuracy, macro F1, confusion matrix per kelas penting.
  • Ekstraksi: exact match per field, precision/recall field.
  • RAG: citation coverage, groundedness, answerability.
  • Moderasi atau risk tagging: recall untuk kelas berisiko sering lebih penting daripada accuracy total.

Untuk pass/fail CI, biasanya lebih stabil memakai kombinasi berikut:

  • Skor agregat tidak turun melewati ambang tertentu.
  • Tidak ada regresi pada subset kritis.
  • Tidak ada lonjakan output tidak valid.

Contoh aturan:

  • Contract validity harus 100% pada golden set.
  • Recall kelas high-risk tidak boleh turun.
  • Skor keseluruhan boleh turun sedikit hanya jika subset kritis tetap aman.

Ini lebih kuat daripada satu angka tunggal yang mudah menutupi regresi penting.

Kapan perlu model evaluator

Model evaluator atau LLM-as-a-judge bisa berguna untuk task generatif seperti ringkasan, tetapi jangan jadikan satu-satunya sumber kebenaran untuk gate rilis. Gunakan terutama untuk analisis tambahan, bukan sebagai keputusan biner tunggal, karena evaluator sendiri dapat tidak stabil atau bias.

Canary verification sebelum rilis penuh

Sebelum model baru dipakai seluruh trafik, lakukan canary verification pada subset request nyata atau replay trafik historis yang aman. Tujuannya bukan sekadar mengukur rata-rata kualitas, tetapi melihat apakah ada pola kegagalan baru.

Apa yang diverifikasi pada fase canary

  • Rasio respons valid versus invalid.
  • Latency dan timeout pada beban realistis.
  • Perubahan distribusi label atau tindakan otomatis.
  • Kenaikan kasus fallback atau human review.
  • Sampel jawaban untuk domain sensitif.

Pada LLM lokal, canary juga membantu mendeteksi masalah operasional yang tidak terlihat di evaluasi offline, seperti tekanan memori, degradasi throughput, atau perbedaan backend inferensi antara staging dan produksi.

Strategi rollout yang aman

  1. Jalankan model baru pada shadow mode tanpa memengaruhi pengguna.
  2. Bandingkan output dan metrik operasional dengan model aktif.
  3. Naikkan trafik bertahap jika tidak ada regresi kritis.
  4. Siapkan rollback cepat berdasarkan model alias atau konfigurasi routing.

Observability pasca-deploy: verifikasi tidak berhenti di CI

Evaluasi pra-rilis tidak pernah menangkap semua kasus produksi. Karena itu, observability harus dianggap sebagai bagian dari strategi verifikasi LLM lokal.

Sinyal yang perlu dipantau

  • Validity rate: berapa persen output lolos parser dan validator.
  • Fallback rate: seberapa sering sistem masuk ke mode aman.
  • Human review rate: naik tajam bisa menandakan drift kualitas.
  • Latency dan error rate inferensi.
  • Distribusi label atau tipe jawaban.
  • Groundedness indicators untuk RAG, misalnya keberadaan sitasi yang valid.
  • Prompt/template usage agar perubahan routing mudah dilacak.

Praktik logging yang berguna

Log yang baik harus cukup untuk investigasi, tetapi tetap aman untuk data sensitif. Simpan metadata seperti:

  • Model ID.
  • Prompt version.
  • Dataset atau source identifier.
  • Inference settings utama.
  • Parser result.
  • Rule-based decision setelah output diterima.

Untuk lingkungan sovereign/local, pertimbangkan sanitasi atau tokenisasi data sebelum disimpan, terutama jika prompt dan dokumen sumber memuat informasi sensitif.

Kapan memakai mock dan kapan memakai model sungguhan

Pakai mock jika tujuannya memverifikasi aplikasi

  • Retry, timeout, fallback, circuit breaker.
  • Post-processing dan parser.
  • Audit logging.
  • Workflow orchestration.
  • Error handling API.

Pakai model sungguhan jika tujuannya memverifikasi perilaku end-to-end

  • Contract test pada output nyata.
  • Golden set untuk use case kritis.
  • Evaluasi offline saat model/prompt berubah.
  • Canary sebelum rollout.

Aturan sederhananya: jika kegagalan test harus mengindikasikan bug aplikasi, gunakan mock; jika kegagalan test harus mengindikasikan perubahan perilaku model, gunakan model sungguhan.

Contoh workflow CI: pisahkan fast checks dan slow checks

Salah satu penyebab pipeline tidak stabil adalah semua evaluasi dijalankan pada setiap commit. Ini membuat feedback lambat dan rawan noise. Lebih baik pisahkan pemeriksaan cepat dan lambat.

Struktur workflow yang disarankan

  • Fast checks pada setiap commit atau pull request.
  • Slow checks pada merge ke branch utama, nightly, atau sebelum rilis.

Contoh tahap fast checks

  1. Linting dan static analysis.
  2. Unit test deterministik.
  3. Contract test kecil dengan input terbatas.
  4. Validasi skema prompt/config/dataset manifest.

Contoh tahap slow checks

  1. Golden set penuh.
  2. Evaluasi offline terkurasi.
  3. Replay subset trafik historis.
  4. Benchmark operasional dasar: latency, timeout, memory envelope.

Contoh pseudo-config CI

jobs:
  fast-checks:
    steps:
      - run: pytest tests/unit
      - run: pytest tests/contracts -m "fast"
      - run: python scripts/validate_eval_manifest.py

  slow-evals:
    needs: [fast-checks]
    steps:
      - run: pytest tests/contracts -m "full"
      - run: python eval/run_golden_set.py --manifest eval/manifests/pr_candidate.json
      - run: python eval/run_offline_suite.py --manifest eval/manifests/pr_candidate.json
      - run: python eval/check_thresholds.py results/latest.json

Poin pentingnya bukan format YAML tertentu, tetapi pemisahan level verifikasi berdasarkan biaya, durasi, dan stabilitas.

Cara mengurangi flaky test secara praktis

1. Gunakan decoding yang lebih stabil untuk evaluasi

Untuk pipeline evaluasi, biasanya lebih aman memakai pengaturan inferensi yang meminimalkan variasi, misalnya temperature rendah atau decoding yang lebih deterministik. Jangan pakai setting kreatif untuk test jika sistem produksi kritis menuntut konsistensi.

2. Bekukan artefak evaluasi

Versioning model, prompt, dataset, retriever index, dan parameter inferensi harus eksplisit. Jika salah satu berubah tanpa jejak, hasil evaluasi tidak dapat dipercaya.

3. Hindari exact match untuk teks bebas

Exact match hanya cocok untuk field terstruktur atau jawaban sangat pendek dengan ruang variasi kecil. Untuk generasi bebas, gunakan rubric, poin kunci, atau validasi grounding.

4. Pisahkan kegagalan format dari kegagalan kualitas

Jika JSON tidak valid, catat sebagai contract failure. Jika JSON valid tetapi isi salah, catat sebagai quality failure. Campuran keduanya akan menyulitkan debugging.

5. Pakai subset kritis sebagai gate keras

Tidak semua contoh harus diperlakukan sama. Untuk domain berisiko, buat subset kritis yang wajib lolos. Ini lebih aman daripada hanya mengandalkan skor rata-rata.

6. Jalankan pengukuran berulang bila perlu

Pada task yang masih menyimpan unsur stokastik, Anda dapat menjalankan evaluasi beberapa kali untuk melihat rentang variasi. Bukan untuk “memilih hasil terbaik”, tetapi untuk mengukur kestabilan sistem.

Kesalahan yang sering terjadi

  • Menguji seluruh sistem hanya dengan exact output match: hampir pasti flaky untuk task generatif.
  • Tidak memisahkan data evaluasi dan data tuning prompt: hasil terlihat bagus tetapi tidak mencerminkan generalisasi.
  • Mengubah golden set tanpa review: regresi jadi tidak terlihat karena baseline terus bergeser.
  • Terlalu banyak memakai model evaluator sebagai hakim tunggal: mudah menyembunyikan bias atau inkonsistensi.
  • Tidak menyimpan metadata run: saat skor turun, tim tidak tahu apa yang berubah.
  • Menjalankan slow eval di setiap commit: feedback lambat dan orang cenderung mengabaikan kegagalan.

Checklist implementasi minimum

  • Unit test untuk preprocessing, parser, validator, dan fallback.
  • Contract test untuk output terstruktur model.
  • Golden set kecil untuk task paling kritis.
  • Offline evaluation suite dengan dataset terkurasi.
  • Manifest evaluasi yang membekukan model, prompt, dataset, dan parameter inferensi.
  • CI yang memisahkan fast checks dan slow checks.
  • Canary verification sebelum rollout penuh.
  • Observability produksi untuk validity, fallback, latency, dan distribusi output.

Penutup

Strategi verifikasi LLM lokal yang baik bukan mencoba memaksa seluruh sistem menjadi deterministik, melainkan membangun lapisan pengujian yang sesuai dengan sifat tiap komponen. Komponen aplikasi diuji secara deterministik, kontrak output dijaga ketat, kualitas model divalidasi lewat golden set dan evaluasi offline, lalu rollout diamankan dengan canary dan observability.

Dengan pendekatan ini, perubahan pada model, prompt, dataset evaluasi, atau parameter inferensi tidak langsung berubah menjadi kekacauan di CI. Tim bisa membedakan mana noise, mana regresi nyata, dan mana bug aplikasi. Itulah fondasi penting untuk mengoperasikan LLM lokal atau sovereign secara andal di lingkungan produksi.