Mengizinkan LLM mengevaluasi kodenya sendiri memicu confirmation bias: model cenderung mengonfirmasi asumsi yang salah dengan membuat mock halusinatif atau menulis assertion tautologis (misal assert result == result). Masalah ini diselesaikan melalui pola Architect-Reviewer dengan memisahkan agen pembuat kode (Builder) dari agen penentu spesifikasi (Architect/Reviewer), didukung oleh test harness deterministik di tingkat runtime.
Kegagalan Evaluasi Diri pada AI-Generated Tests
Saat satu LLM diminta menulis implementasi sekaligus unit test-nya, tiga kegagalan struktural sering muncul:
- Mocking Halusinatif: Model mem-mock metode atau atribut internal modul runtime yang sebenarnya tidak ada, membuat tes lulus meski kode produksi rusak.
- Assertion Flaky & Tautologis: Model menyesuaikan assertion dengan output runtime aktual yang cacat daripada kontrak spesifikasi awal.
- State Pollution: Test buatan AI sering meninggalkan side-effects pada disk, env var, atau database memory karena ketiadaan cleanup terisolasi.
Pola Architect-Reviewer mengatasi ini dengan membagi peran: Architect memegang kontrak antarmuka kaku, Builder mengenerasi kode tes minimal, dan Reviewer memverifikasi hasil eksekusi subproses secara independen.
Arsitektur Loop: Builder vs Reviewer
Loop ini memisahkan fase validasi statis dan eksekusi dinamis sebelum feedback dikirim kembali ke LLM:
- Architect: Mendefinisikan function signature, batasan dependensi, dan kriteria penerimaan.
- Builder (LLM): Menulis file unit test berbasis spesifikasi Architect.
- Static Gate (AST Linting): Memastikan kode bebas syntax error, mock ilegal, atau tautological assert sebelum eksekusi dimulai.
- Dynamic Gate (Subprocess Harness): Menjalankan tes dalam subproses Python terisolasi dengan timeout ketat.
- Reviewer: Membaca status code dan log error terfilter. Jika gagal, Reviewer menghasilkan prompt perbaikan terkompresi tanpa membawa seluruh context history.
Harness Verifikasi Deterministik dengan Python Stdlib
Harness berikut memverifikasi test suite secara statis melalui modul ast, lalu mengeksekusinya via subprocess. Log dipotong agar tidak menyebabkan context bloat pada iterasi berikutnya.
import ast
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class ExecutionResult:
passed: bool
stage: str
feedback: str
def validate_ast(test_code: str) -> tuple[bool, str]:
"""Validasi statis: cegah sintaks cacat dan assertion kosong."""
try:
tree = ast.parse(test_code)
except SyntaxError as e:
return False, f"SyntaxError line {e.lineno}: {e.msg}"
for node in ast.walk(tree):
# Deteksi 'assert True' atau 'assert 1'
if isinstance(node, ast.Assert):
if isinstance(node.test, ast.Constant) and bool(node.test.value) is True:
return False, f"Tautological assert detected at line {node.lineno}"
return True, ""
def run_isolated_test(test_file: Path, timeout_sec: int = 5) -> ExecutionResult:
"""Validasi dinamis: jalankan unittest dalam subproses terisolasi."""
# ponytail: isolasi subproses cukup; gunakan container/bwrap jika menguji kode untrusted eksternal
cmd = [sys.executable, "-m", "unittest", str(test_file)]
try:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout_sec,
cwd=test_file.parent
)
except subprocess.TimeoutExpired:
return ExecutionResult(
passed=False,
stage="dynamic",
feedback=f"Execution timed out after {timeout_sec}s. Infinite loop suspected."
)
if proc.returncode == 0:
return ExecutionResult(passed=True, stage="dynamic", feedback="ALL_TESTS_PASSED")
# Ringkas trace: ambil maksimal 8 baris terakhir log kegagalan untuk mencegah context bloat
raw_error = proc.stderr.strip().splitlines()
condensed_trace = "\n".join(raw_error[-8:])
return ExecutionResult(
passed=False,
stage="dynamic",
feedback=f"Test failure (Exit {proc.returncode}):\n{condensed_trace}"
)
Mitigasi Context Bloat dan Infinite Loops
Dua resiko utama pada autonomous code healing adalah LLM terjebak dalam siklus perbaikan tanpa henti (infinite loop) dan context window membengkak akibat traceback output yang panjang.
1. Trimming Traceback
Runner dinamis di atas hanya mengambil 8 baris terakhir output error. Memberikan 200 baris output log eksekusi lengkap akan mengaburkan fokus instruksi perbaikan LLM Builder dan menghabiskan token secara sia-sia.
2. Hard Retry Budgeting
Terapkan batas maksimal percobaan (maksimum 3 kali). Jika threshold terlampaui, hentikan loop dan tandai bahwa test suite tersebut membutuhkan intervensi manual atau revisi kontrak oleh manusia.
def execution_loop(builder_llm, test_file_path: Path, max_retries: int = 3) -> bool:
for attempt in range(1, max_retries + 1):
code = test_file_path.read_text()
# Jalur Statis
is_valid_ast, ast_err = validate_ast(code)
if not is_valid_ast:
test_file_path.write_text(builder_llm.fix(code, feedback=ast_err))
continue
# Jalur Dinamis
result = run_isolated_test(test_file_path)
if result.passed:
return True
# Hentikan jika jatah perbaikan habis
if attempt == max_retries:
break
# Generate ulang kode berdasarkan ringkasan kegagalan
fixed_code = builder_llm.fix(code, feedback=result.feedback)
test_file_path.write_text(fixed_code)
return False
Pemisahan Validasi: Kapan Statis, Kapan Dinamis
| Tahap | Pemeriksaan | Tujuan |
|---|---|---|
| Statis (AST / Tokenizer) | Syntax parsing, import whitelisting, deteksi constant assertion. | Menolak kode cacat secara instan (0ms overhead) sebelum dieksekusi. |
| Dinamis (Subprocess Runner) | Assertion logic, side-effects, exception bubbling, edge-case coverage. | Memastikan kode memenuhi behavior fungsional secara empiris. |
Menjalankan kode tanpa validasi statis membuang siklus eksekusi untuk kesalahan sepele seperti IndentationError. Sebaliknya, mengandalkan validasi statis saja tidak dapat mendeteksi broken mocks atau logika bisnis yang melenceng.
Debugging dan Praktik Terbaik
- Hindari Import Shell Globbing: Jangan biarkan subproses menjalankan file uji coba melalui shell wildcard. Tunjuk path file absolut secara eksplisit.
- Pasang Isolation Timeout: Selalu terapkan
timeoutpadasubprocess.run. Pengujian kode berbasis antrean atau threading yang dibuat oleh AI rentan memicu deadlock. - Determinisme Feedback: Reviewer hanya boleh mengembalikan output log deterministik ke Builder, bukan prompt evaluasi subjektif seperti "Coba buat tes ini lebih bagus".
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!