L4 · 9 Okt 2026
Mengapa fraud engine yang lebih baik dirilis dalam shadow mode
- llm-evaluation
- shadow-mode
- fraud-detection
azure-openai
- case-study
False positive turun dari 36,4% ke 9,1%. Tetap saja, saya belum bisa menyerahkan verdict ke engine yang baru.
Hasil itu berasal dari paket pengadaan yang bersih tapi noisy di gold set development. Recall High-risk justru turun dari 1,000 ke 0,841. Paket yang seharusnya tertangkap malah makin banyak terlewat.
Saya menyelesaikan risk engine generasi kedua ini pada akhir September 2026, untuk platform internal review dokumen berbasis AI di Metrodata. Release gate untuk recall sudah saya tetapkan sebelum evaluasi. Engine ini tidak lolos.
Akhirnya saya merilisnya ke produksi dalam shadow mode. Engine berjalan di samping pipeline live dan menyimpan output, tetapi pengguna tidak pernah melihat output itu.
Paket bersih yang terus kena flag
Platform ini me-review paket dokumen transaksi: invoice, kuitansi, purchase order, formulir vendor, dan kontrak. Azure Document Intelligence menangani OCR. Azure OpenAI menghasilkan temuan per dokumen, lalu verdict lintas dokumen dan risk score 0 sampai 100. Fungsinya decision support, bukan hakim otomatis.
Yang paling merepotkan versi awal justru paket bersih dengan administrasi berantakan. Stempel tidak ada, format tanggal tidak lazim, atau hasil scan miring. Model membaca noise itu sebagai fraud.
Saya khawatir reviewer akan kehilangan kepercayaan. Kalau paket yang jelas aman saja kena red flag, flag berikutnya mungkin ikut diragukan, termasuk yang benar. Menurut penalaran saya, satu false positive pada paket bersih lebih merusak kepercayaan daripada satu flag yang terlewat. Ini bukan hasil pengukuran.
Karena itu, input bersih yang noisy saya ukur sebagai slice tersendiri. Rata-rata seluruh paket bisa menutupi masalah yang ingin saya bereskan.
Guardrail-nya perlu diperbaiki dulu
Sebelum mengubah prompt, saya membangun evaluation harness. Isinya gold set berlabel, masing-masing 21 sampai 90 kasus ditambah generated mutations. Saya memisahkan set development dan held-out, membekukan baseline, lalu mengukur false-positive rate (FPR), false-negative rate, recall High-risk, fail-open rate, dan prompt-injection success dengan confidence interval. Gate dijalankan lewat workflow eval khusus dan CI lokal.
Baseline pertama menandai 90,9% paket bersih yang noisy di set development. Guardrail-nya juga bermasalah: fail-open rate mencapai 76,2%.
Guardrail ini seharusnya melunakkan verdict ketika temuannya jelas tidak berbahaya. Namun aturannya memakai mayoritas. Kalau kira-kira separuh temuan cocok dengan safe pattern, verdict dilonggarkan. Beberapa pattern-nya terlalu luas.
Saya memperketat keduanya. Semua temuan harus cocok dengan safe pattern, dan safe list dibatasi untuk noise administratif. Setiap penyesuaian wajib terlihat di output. Guardrail harus fail closed, bukan meloloskan paket tanpa penjelasan.
Secara offline, FPR paket bersih yang noisy turun dari 90,9% ke 36,4% di development, dan dari 100% ke 50,0% di held-out. Fail-open turun dari 76,2% ke 0%. Recall High-risk berada di 0,91.
Upgrade model berikutnya memperlihatkan masalah lain. Ada blok prompt yang menyebut pembeli sebagai pihak tepercaya. Dengan model baru, framing itu membuat verdict bias. Setelah saya pangkas, run evaluasi live menunjukkan FPR turun dari 26,1% ke 21,7%, dan prompt-injection success dari 11,1% ke 3,7%.
Saya juga membandingkan wording yang netral terhadap pihak-pihak di dokumen dengan framing lama lewat paired run: 23 kasus, masing-masing diulang 3 kali. FPR-nya 18,8% dengan wording netral, berbanding 36,9% dengan framing lama. Ini hasil evaluasi, bukan hasil produksi.
Prompt ternyata bagian dari classifier. Perubahan prompt maupun versi model perlu diperlakukan sebagai rilis, lengkap dengan menjalankan ulang gate di setiap perubahan.
Verdict tidak lagi ditentukan LLM
FPR 36,4% masih terlalu tinggi. Saya sudah kehabisan perubahan aman untuk desain yang menyerahkan semua keputusan ke LLM, jadi saya menulis spec engine v2:
- Normalizer merapikan nama, nomor rekening, dan tanggal hasil ekstraksi sebelum dibandingkan.
- Katalog rules deterministik memunculkan temuan, seperti ketidakcocokan antardokumen, dengan severity masing-masing.
- Rubric risk engine menggabungkan temuan lewat noisy-OR dalam satu grup, mengambil nilai maksimum antargrup, lalu menerapkan severity floor. Temuan kritis tidak boleh tenggelam karena dirata-rata.
- Evidence verifier memastikan tiap temuan menunjuk ke teks yang benar-benar ada di dokumen hasil ekstraksi.
- Duplicate fingerprint dengan HMAC menangkap dokumen yang dipakai ulang di paket lain.
- LLM tetap menghasilkan temuan, semantic review, dan narasi. Skor tidak lagi ditentukan oleh LLM.
Perhitungan skornya kira-kira seperti ini. Ini versi sederhana, bukan kode produksi:
p_group = 1 - prod(1 - p_i for p_i in group)
score = max(p_group for each group)
score = max(score, floor[worst_severity])
Setiap skor membawa trace rules yang aktif, sehingga verdict bisa dijelaskan dan di-replay.
Precision naik, recall turun
Saya membandingkan engine v2, baseline N1, dengan pipeline LLM terbaik, baseline B2, secara offline di gold set v0.2.1. Gate sudah ditetapkan sebelum run:
- FPR paket bersih yang noisy: 36,4% menjadi 9,1%.
- FPR untuk semua paket yang seharusnya Low risk: 17,4% menjadi 9,1%.
- Recall High-risk: 1,000 menjadi 0,841.
- Set held-out v0.3: FPR 50,0% menjadi 0,0%, tetapi recall High-risk hanya 0,429.
Precision membaik, tetapi engine ini melewatkan paket High-risk yang tertangkap pipeline lama. Recall held-out 0,429 menunjukkan masalahnya bukan sekadar kebetulan di set development. Gate G3, yaitu gate recall High-risk, gagal.
Pembacaan saya: threshold rules menukar recall dengan precision, sementara perhatian saya saat tuning terlalu tertuju pada false positive. Di sinilah gate yang ditulis sebelum run berguna. Tanpanya, FPR yang lebih bagus bisa membujuk saya merilis sistem yang lebih buruk.
Ukuran sampelnya juga perlu diingat. Beberapa slice kecil. Perubahan FPR paket bersih yang noisy konsisten dengan sekitar sebelas paket, jadi satu paket bisa menggeser angka sekitar sembilan poin. Itu inferensi saya; ukuran slice persisnya tidak tercatat di sini.
Masuk produksi, tapi tidak menentukan verdict
Saya punya tiga pilihan: menyalakan engine dan menerima penurunan recall, lanjut tuning offline, atau membiarkannya melihat paket nyata tanpa mengambil keputusan. Saya memilih yang ketiga. Shadow mode memberi ruang untuk belajar dari data nyata tanpa membuat pengguna menanggung trade-off itu.
Pipeline yang sudah ada tetap menghasilkan verdict dan laporan untuk pengguna. Engine v2 berjalan di sampingnya, lalu menyimpan output hanya untuk perbandingan:
verdict = current_pipeline(packet) # user sees
shadow = engine_v2(packet) # stored only
save_shadow(packet.id, shadow)
Catatan rollout mencatat 0 dari 3.780 run ketika shadow engine mengubah output pengguna. Bagi saya, itu bukti jalur shadow terisolasi. Bukan bukti engine-nya akurat.
Volume kasus nyata di platform ini masih kecil. Catatan saya juga tidak merinci isi 3.780 run tersebut.
Yang baru terlihat setelah deployment
Jalur deployment sungguhan memperlihatkan defect yang tidak tertangkap gold set. Pada salah satu rollout, temuan shadow mencatat tiga masalah kualitas data:
- Rule conflicting values aktif karena ada titik di akhir nama pemilik rekening pada salah satu dokumen.
- Ekstraksi per file bisa berakhir parsial tanpa terlihat jelas.
- Pesan status OCR menyesatkan.
Ketiganya sudah diperbaiki. Saya tidak mencatat metrik sebelum dan sesudah perbaikan, dan tidak punya detail root cause selain deskripsi tersebut.
Masalah serupa muncul saat beralih ke ekstraksi Document Intelligence Layout. Nomor rekening yang terpotong ke baris berikutnya dengan tanda hubung menghasilkan 7 temuan palsu ketidakcocokan rekening bank. Setelah perbaikan normalisasi, evaluasi live di v0.2.1 mencatat recall High-risk 1,000 dan FPR 0.
Hasil development itu bagus. Engine-nya tetap di shadow mode.
Apa yang perlu saya lihat sebelum promosi
Ini penalaran saya, bukan rencana yang sudah tercatat. Sebelum menyerahkan verdict ke engine v2, saya ingin:
- Recall High-risk lolos G3 di held-out, bukan hanya development, dan konsisten di run berulang.
- FPR paket bersih yang noisy tetap jauh di bawah pipeline saat ini pada kedua set.
- Fail-open tetap 0%.
- Verdict shadow sejalan dengan keputusan reviewer untuk paket nyata. Ini membutuhkan lebih banyak kasus nyata daripada yang ada sekarang.
- Setiap perbedaan antara kedua engine di-review manual, karena masing-masing berarti fraud yang terlewat atau false alarm yang berhasil dihindari.
Saya menaruh angka held-out di samping development karena recall 0,429 itu memberi tahu saya lebih banyak daripada metrik development mana pun. Satu run development yang bagus memang menggembirakan, tetapi belum cukup untuk memberi engine ini wewenang menentukan verdict.