Debug backend menjadi jauh lebih sulit ketika sebuah pipeline matematika mulai bergantung pada output AI yang tidak selalu konsisten. Masalah utamanya bukan sekadar model salah menjawab, melainkan respons yang kadang valid, kadang hampir valid, kadang berubah format, sehingga parser, cache, dan job downstream gagal dengan pola yang sulit direproduksi.

Studi kasus ini membahas backend yang memakai AI untuk membantu menghasilkan kandidat langkah pembuktian, transformasi aljabar, atau usulan ekspresi antara, lalu sistem internal memverifikasi hasil tersebut. Pendekatan ini terinspirasi oleh arah kerja matematika modern yang memanfaatkan AI sebagai asisten ide, bukan sebagai sumber kebenaran final. Ketika desain ini dilanggar dan output AI diperlakukan seolah deterministik, pipeline mulai retak di tempat yang tidak terduga.

Konteks sistem: AI sebagai pembantu komputasi matematika

Bayangkan sebuah layanan backend yang menerima tugas seperti berikut:

  • Mengubah ekspresi matematika ke bentuk kanonik
  • Mengusulkan substitusi atau identitas yang mungkin relevan
  • Menghasilkan kandidat langkah pembuktian
  • Memberi penjelasan terstruktur yang kemudian divalidasi oleh symbolic engine internal

Arsitekturnya tampak masuk akal:

  1. API menerima job matematika.
  2. Backend memanggil model AI untuk menghasilkan kandidat solusi.
  3. Output AI diparsing menjadi struktur data internal.
  4. Validator deterministik memeriksa ekuivalensi, tipe objek, domain variabel, dan konsistensi langkah.
  5. Jika valid, job diteruskan ke pipeline berikutnya: penyimpanan hasil, scoring, atau komposisi pembuktian lanjutan.

Masalah muncul saat asumsi tak tertulis ikut menyusup:

  • Format respons AI selalu sama.
  • Jika prompt sama, respons semestinya sama.
  • Jika ada cache, dua respons yang mirip boleh dianggap identik.
  • Jika parser berhasil sekali, berarti protokolnya cukup aman.

Keempat asumsi itu sering salah.

Gejala produksi: kadang lolos, kadang gagal, downstream rusak

Insiden biasanya tidak langsung terlihat sebagai masalah AI. Gejalanya lebih sering muncul sebagai:

  • Job validasi matematika gagal sporadis
  • Antrian downstream macet karena menerima payload tak lengkap
  • Cache hit tinggi tetapi hasil verifikasi tidak konsisten
  • Rasio retry naik tanpa pola yang jelas
  • Ekspresi yang sama kadang ditandai valid, kadang invalid

Timeline insiden yang realistis

Berikut contoh timeline yang sering terjadi di tim backend:

  1. 09:10 - Tim melihat peningkatan error pada worker validasi langkah matematika.
  2. 09:18 - Dashboard menunjukkan sebagian besar error berasal dari parser JSON hasil AI.
  3. 09:30 - Dugaan awal: deployment parser baru menyebabkan regresi.
  4. 10:05 - Rollback parser tidak memperbaiki semua error.
  5. 10:40 - Ditemukan sebagian payload lolos parsing, tetapi gagal pada pemeriksaan ekuivalensi simbolik.
  6. 11:20 - Tim mendapati request dengan input identik menghasilkan respons AI yang berbeda struktur.
  7. 12:00 - Insiden membesar karena cache menyajikan respons lama untuk kunci yang dianggap sama, padahal metadata verifikasi berbeda.
  8. 13:15 - Root cause mulai jelas: non-determinisme output AI + parser rapuh + normalisasi cache yang terlalu agresif.

Contoh input/output yang memicu masalah

Misalnya backend meminta model menghasilkan kandidat transformasi untuk ekspresi berikut:

Input matematika:
prove_or_simplify: "(x^2 - y^2) / (x - y)"
constraint: "assume x != y"
expected_format: JSON

Pada satu request, model memberi respons seperti ini:

{
  "candidate_steps": [
    {
      "rule": "difference_of_squares",
      "result": "x + y"
    }
  ],
  "final_result": "x + y"
}

Respons ini lolos parser dan validator.

Namun pada request lain dengan input sama, model memberi:

{
  "candidate_steps": [
    {
      "rule": "factor_then_cancel",
      "result": "(x-y)(x+y)/(x-y) = x+y"
    }
  ],
  "note": "valid only when x != y",
  "final_result": "x+y"
}

Secara matematis ini masih masuk akal, tetapi parser internal mungkin mengasumsikan field result selalu berisi ekspresi tunggal, bukan penjelasan campuran. Akibatnya:

  • Parser AST gagal
  • Validator menerima string yang bukan ekspresi murni
  • Job ditandai gagal walau isi semantisnya benar

Dalam kasus lain, model bahkan mengembalikan:

Langkah yang mungkin:
1. Gunakan identitas selisih kuadrat.
2. Hasil akhirnya x + y, dengan syarat x != y.

Kalau sistem diam-diam mencoba mengekstrak ekspresi akhir dengan regex, bug menjadi lebih berbahaya: kadang berhasil, kadang salah ambil substring.

Hipotesis awal yang salah

Pada insiden semacam ini, tim sering tersesat oleh hipotesis yang tampak masuk akal tetapi keliru.

1. “Ini pasti bug parser terbaru”

Sering ada bug parser, tetapi rollback parser tidak menyelesaikan masalah bila akar utamanya adalah kontrak output yang tidak pernah benar-benar ketat. Parser lama mungkin hanya lebih permisif, bukan lebih benar.

2. “Model sedang menurun kualitasnya”

Kualitas model memang bisa berubah tergantung prompt, konteks, sampling, atau provider. Tetapi problem produksi biasanya bukan sekadar kualitas jawaban, melainkan fakta bahwa sistem backend mengandalkan format dan stabilitas yang tidak dijamin.

3. “Retry lebih banyak akan menyelesaikan”

Retry tanpa aturan dapat memperburuk situasi:

  • Respons berbeda masuk ke cache yang sama
  • Biaya API naik
  • Satu job menghasilkan beberapa versi kandidat yang tidak konsisten
  • Race condition antar worker makin sering

4. “Kalau sudah ada validasi matematis, format tidak terlalu penting”

Salah. Sebelum validasi matematis jalan, sistem harus lebih dulu memahami struktur output. Banyak pipeline rusak bukan karena matematika salah, tetapi karena representasi antar komponen tidak tegas.

Root cause: kombinasi beberapa bug kecil yang saling memperkuat

Jarang ada satu penyebab tunggal. Biasanya insiden datang dari beberapa lapisan masalah.

Output AI non-deterministik

Meskipun prompt sama, respons dapat berbeda dalam:

  • Urutan field
  • Pemilihan istilah
  • Bentuk ekspresi
  • Tingkat detail penjelasan
  • Penggunaan Markdown, teks bebas, atau JSON campuran

Untuk sistem yang mengandalkan parsing ketat, variasi kecil ini cukup untuk memecahkan pipeline.

Parsing rapuh berbasis regex atau asumsi string

Banyak integrasi AI dimulai dengan cepat menggunakan regex seperti:

const match = responseText.match(/"final_result"\s*:\s*"([^"]+)"/);

Ini mudah rusak ketika:

  • Nilai mengandung kutip yang di-escape
  • Field berpindah posisi
  • Model menambahkan komentar
  • Respons bukan JSON murni

Regex boleh dipakai untuk diagnostik, tetapi bukan sebagai fondasi kontrak data produksi.

Asumsi format yang tidak terdokumentasi

Masalah klasiknya: prompt mengatakan “kembalikan JSON”, tetapi backend diam-diam juga mengasumsikan:

  • Field tertentu selalu ada
  • String ekspresi selalu parseable oleh engine simbolik internal
  • Tidak ada penjelasan alami dalam field ekspresi
  • Tidak ada variasi nama rule

Kalau asumsi ini tidak ditegakkan dengan validasi skema, maka bug baru hanya menunggu waktu.

Cache yang menyamakan respons berbeda

Ini sering menjadi lapisan yang memperumit diagnosis. Contoh bug cache:

  • Kunci cache hanya memakai hash input matematika, bukan versi prompt atau mode model
  • Normalisasi whitespace berlebihan membuat dua payload berbeda dianggap sama
  • Hasil verifikasi dan hasil mentah AI disimpan di entri yang sama
  • Retry kedua membaca respons dari retry pertama tetapi metadata attempt tertimpa

Akibatnya, log terlihat kontradiktif: input sama, cache hit, tetapi hasil downstream berbeda karena isi cache tidak merepresentasikan state yang sebenarnya diverifikasi.

Langkah diagnosis yang efektif

Saat menghadapi bug seperti ini, tujuan utama diagnosis adalah membedakan tiga lapisan: respons mentah AI, hasil parsing, dan hasil verifikasi deterministik. Tanpa pemisahan itu, semua error tampak serupa.

1. Simpan raw response secara terkontrol

Jangan hanya menyimpan hasil parsing. Simpan juga:

  • request_id
  • job_id
  • prompt_template_version
  • model_route atau provider logical name
  • cache_key
  • raw_response_sha256
  • parse_status
  • validation_status

Bila data sensitif menjadi concern, simpan hash dan sampel terbatas, atau lakukan redaksi pada bagian tertentu. Namun untuk insiden format, memiliki jejak raw response sangat penting.

2. Pisahkan error parse dari error matematis

Gunakan kategori error yang eksplisit. Misalnya:

{
  "job_id": "job-7842",
  "stage": "ai_candidate_parse",
  "error_code": "SCHEMA_FIELD_INVALID",
  "field": "candidate_steps[0].result",
  "value_preview": "(x-y)(x+y)/(x-y) = x+y"
}

dan untuk validasi:

{
  "job_id": "job-7842",
  "stage": "symbolic_verification",
  "error_code": "NON_EQUIVALENT_EXPRESSION",
  "lhs": "...",
  "rhs": "..."
}

Pemisahan ini mempercepat isolasi masalah. Jika mayoritas error ada di parsing, Anda sedang menghadapi kontrak data. Jika mayoritas ada di verifikasi, Anda sedang menghadapi kualitas kandidat atau bug validator.

3. Rekonstruksi request yang sama end-to-end

Jangan puas dengan “inputnya sama”. Pastikan semua faktor yang memengaruhi benar-benar sama:

  • Template prompt
  • Instruksi sistem
  • Konteks percakapan sebelumnya
  • Mode sampling bila ada
  • Penggunaan cache
  • Versi parser
  • Versi validator simbolik

Dalam banyak kasus, tim mengira request identik padahal salah satu metadata berubah.

4. Audit cache key dan payload cache

Periksa apakah kunci cache memasukkan semua dimensi yang relevan. Contoh desain yang lebih aman:

cache_key = sha256(
  canonical_math_input +
  prompt_template_version +
  ai_contract_version +
  model_route +
  verifier_version
)

Jangan mencampur output mentah AI dengan status verifikasi final dalam objek cache yang sama kecuali lifecycle-nya memang identik.

5. Buat korpus kasus gagal

Kumpulkan contoh nyata dari produksi:

  • Lolos parse, gagal verifikasi
  • Gagal parse karena format campuran
  • Respons kosong atau terpotong
  • Field ada tetapi tipenya salah
  • Ekspresi valid tetapi menggunakan notasi yang tak didukung parser AST

Korpus ini sangat berharga untuk tes regresi.

Logging yang relevan untuk debug backend

Logging yang baik bukan berarti mencatat semuanya. Yang diperlukan adalah jejak minimal yang cukup untuk membedakan state sistem.

Contoh struktur log

{
  "timestamp": "2026-06-14T10:42:11Z",
  "job_id": "math-job-1288",
  "request_id": "req-a91d",
  "stage": "ai_call",
  "prompt_template_version": "math-candidate-v3",
  "ai_contract_version": "candidate-schema-v2",
  "cache_key": "b7f1...",
  "cache_hit": false,
  "raw_response_sha256": "9d20...",
  "parse_status": "failed",
  "error_code": "SCHEMA_FIELD_INVALID",
  "attempt": 1
}

Tambahkan juga metrik agregat:

  • Rasio parse failure per prompt version
  • Rasio verification failure per model route
  • Distribusi cache hit terhadap success rate
  • Persentase retry yang akhirnya sukses

Jika ada lonjakan parse failure setelah perubahan prompt, sinyalnya akan terlihat cepat.

Guardrail yang seharusnya ada sejak awal

Masalah ini bisa diperkecil drastis jika AI diperlakukan sebagai penghasil kandidat, bukan komponen deterministik inti.

1. Validasi skema sebelum menyentuh pipeline berikutnya

Selalu validasi respons AI terhadap skema eksplisit. Skema tidak harus rumit, tetapi harus tegas.

{
  "type": "object",
  "required": ["candidate_steps", "final_result"],
  "properties": {
    "candidate_steps": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["rule", "result"],
        "properties": {
          "rule": { "type": "string" },
          "result": { "type": "string" }
        },
        "additionalProperties": false
      }
    },
    "final_result": { "type": "string" }
  },
  "additionalProperties": false
}

Jika respons melanggar skema, anggap sebagai kandidat tidak valid, bukan sebagai kebenaran parsial yang boleh diteruskan.

2. Parser AST, bukan manipulasi string seadanya

Untuk ekspresi matematika, ubah string menjadi AST sedini mungkin. Keuntungannya:

  • Memisahkan presentasi dari struktur
  • Mendeteksi token ilegal
  • Memudahkan normalisasi bentuk ekspresi
  • Mencegah validasi palsu akibat substring yang kebetulan cocok

Kalau engine simbolik internal memiliki grammar terbatas, lebih baik menolak notasi yang tidak didukung daripada mencoba menebak maksud model.

3. Retry yang aman dan terbatas

Retry masuk akal hanya untuk kategori kegagalan tertentu, misalnya:

  • Timeout jaringan
  • Payload terpotong
  • Respons tidak memenuhi skema

Namun retry harus:

  • Dibatasi jumlahnya
  • Dilog dengan attempt terpisah
  • Tidak menimpa hasil verifikasi dari attempt lain
  • Tidak mencampur respons antar attempt di cache yang sama

Jangan retry tanpa batas untuk kegagalan verifikasi matematis. Jika kandidat salah secara semantik, retry acak bukan strategi yang dapat dijelaskan.

4. Fail closed, bukan fail open

Jika parse gagal atau skema tidak valid, jangan teruskan data “semoga cukup bagus” ke worker berikutnya. Pada pipeline matematika, soft failure di hulu sering berubah menjadi korupsi data di hilir.

Prinsip praktis: output AI yang tidak lolos kontrak harus berhenti di boundary integrasi, bukan dibersihkan diam-diam oleh beberapa komponen sekaligus.

Perbaikan arsitektur: AI hanya memberi kandidat, backend tetap deterministik

Perbaikan paling penting bukan sekadar menambal parser, melainkan memperjelas peran AI dalam sistem.

Desain yang rapuh

  1. AI mengembalikan “jawaban”
  2. Backend menerima jawaban itu sebagai fakta
  3. Job downstream menggunakannya untuk komputasi lanjutan

Desain ini menganggap model sebagai komponen logika inti, padahal perilakunya tidak deterministik.

Desain yang lebih aman

  1. AI mengembalikan kandidat transformasi atau langkah.
  2. Boundary service memvalidasi skema dan mem-parse ke AST.
  3. Verifier deterministik memeriksa ekuivalensi, domain, dan aturan.
  4. Hanya hasil verifikasi yang boleh masuk ke pipeline inti.
  5. Output mentah AI tetap disimpan untuk audit, tetapi tidak pernah menjadi sumber kebenaran final.

Contoh alur implementasi

Client
  -> Math API
    -> Candidate Generator (AI)
      -> Schema Validator
        -> Expression Parser / AST Builder
          -> Deterministic Verifier
            -> Canonical Result Store
              -> Downstream Jobs

Keuntungan pendekatan ini:

  • Respons AI boleh bervariasi, asalkan kandidat yang lolos verifikasi konsisten
  • Parser dan verifier menjadi batas kepercayaan yang jelas
  • Cache dapat dipisah antara AI candidate cache dan verified result cache

Pemisahan cache yang benar

Gunakan dua tingkat cache:

  • Candidate cache: menyimpan raw output atau kandidat terstruktur dari AI, terikat kuat ke prompt/version/model.
  • Verified cache: menyimpan hasil deterministik yang sudah lolos verifikasi, terikat ke input kanonik dan versi verifier.

Jangan menganggap dua kandidat AI yang berbeda tetapi kebetulan punya final_result sama sebagai entitas identik. Penjelasan langkah, domain syarat, dan bentuk AST tetap penting untuk audit dan validasi.

Contoh implementasi boundary service

Contoh berikut menunjukkan pola sederhana di backend JavaScript/TypeScript. Fokusnya bukan library tertentu, melainkan urutan tanggung jawab.

async function processMathJob(job) {
  const candidateResponse = await getAiCandidate(job);

  logInfo({
    jobId: job.id,
    stage: 'ai_call',
    rawResponseHash: sha256(candidateResponse.rawText)
  });

  const parsed = safeParseJson(candidateResponse.rawText);
  if (!parsed.ok) {
    return failJob(job, 'AI_JSON_PARSE_FAILED');
  }

  const schemaCheck = validateCandidateSchema(parsed.value);
  if (!schemaCheck.ok) {
    return failJob(job, 'AI_SCHEMA_INVALID', schemaCheck.errors);
  }

  const astResult = buildExpressionAst(parsed.value.final_result);
  if (!astResult.ok) {
    return failJob(job, 'EXPRESSION_PARSE_FAILED', astResult.error);
  }

  const verification = verifyCandidate(job.inputExpression, astResult.ast, job.constraints);
  if (!verification.ok) {
    return failJob(job, 'DETERMINISTIC_VERIFICATION_FAILED', verification.reason);
  }

  const canonical = canonicalize(astResult.ast);
  await storeVerifiedResult({
    jobId: job.id,
    canonicalResult: canonical,
    proofMetadata: verification.metadata
  });

  return completeJob(job, canonical);
}

Ada beberapa hal penting di sini:

  • Respons AI mentah tidak langsung dipercaya.
  • Skema dicek sebelum parsing ekspresi.
  • Verifikasi deterministik menjadi gerbang final.
  • Hasil yang disimpan adalah bentuk kanonik terverifikasi, bukan string mentah dari model.

Pengujian regresi yang seharusnya ditambahkan

Setelah insiden selesai, jangan berhenti di hotfix. Tambahkan tes yang menahan sistem dari bug serupa.

1. Snapshot raw response produksi yang sudah dianonimkan

Gunakan contoh nyata yang pernah gagal untuk menguji:

  • Parser JSON
  • Validator skema
  • Parser AST
  • Mapper error code

2. Property-based test untuk variasi format

Jika memungkinkan, uji variasi yang sering muncul:

  • Whitespace berbeda
  • Urutan field berubah
  • Field tambahan tak dikenal
  • Ekspresi dengan notasi alternatif

Tujuannya bukan membuat parser menerima semuanya, melainkan memastikan sistem menolak dengan cara yang konsisten dan aman.

3. Test cache key

Buat tes eksplisit untuk memastikan perubahan berikut menghasilkan kunci berbeda:

  • Versi prompt
  • Versi kontrak output
  • Versi verifier
  • Mode/model route yang berbeda

Bug cache sering lolos karena tim tidak pernah menguji desain kuncinya secara langsung.

Pelajaran utama dari insiden ini

Ada satu prinsip yang layak dibawa ke setiap integrasi AI pada backend teknis, terutama untuk matematika: AI boleh membantu eksplorasi, tetapi verifikasi final harus tetap deterministik.

Untuk pipeline matematika, ini berarti:

  • Jangan perlakukan output AI sebagai fakta final.
  • Gunakan skema eksplisit dan boundary service yang ketat.
  • Pisahkan raw candidate dari verified result.
  • Audit cache key dengan disiplin.
  • Log setiap transisi penting: raw response, parse status, validation status, verifier result.
  • Desain retry secara aman, terbatas, dan dapat dijelaskan.

Kalau arsitektur mengikuti prinsip tersebut, hasil AI yang tak konsisten tidak lagi merusak pipeline. Model tetap berguna sebagai penghasil kandidat, sementara backend menjaga integritas komputasi lewat parsing yang kuat, verifikasi simbolik, dan alur data yang dapat diaudit.