Kode yang dihasilkan oleh Large Language Model (LLM) sering memanggil fungsi fiktif, mengoper parameter yang tidak pernah didukung, atau mengasumsikan tanda tangan API yang sudah usang. Seperti disorot oleh praktisi perangkat lunak Joey Hess, ketergantungan buta pada output mesin tanpa mekanisme verifikasi ketat menimbulkan regresi struktural di lingkungan produksi. Artikel ini menguraikan arsitektur pipeline verifikasi deterministik untuk menangkal halusinasi dependensi pihak ketiga secara otomatis.

Anatomi Masalah: Halusinasi API Pihak Ketiga

LLM bekerja berdasarkan probabilitas token, bukan pemahaman semantik dari repositori target. Pada pustaka pihak ketiga, terdapat tiga kegagalan tipikal:

  • Phantom Methods: Memanggil fungsi yang tampak logis namun tidak pernah ada di modul upstream (misalnya requests.get_json() alih-alih requests.get().json()).
  • Signature Drift: Mengirimkan parameter yang sudah didepresiasi atau diubah pada versi minor/patch terbaru.
  • Hallucinated Packages: Mengimpor dependensi tipuan yang membuka celah serangan dependency confusion atau typosquatting.

Solusi untuk masalah ini bukan memperketat prompt sistem, melainkan menambahkan lapisan uji deterministik (linting semantik, kontrak, dan validasi runtime) pada pipeline CI/CD.

Deteksi Statis Menggunakan Heuristik AST Scanner

Linter standar sering kali membiarkan method invocation lolos jika target bertipe dinamis (seperti Any pada Python atau ekspresi JS non-TypeScript). Membangun AST (Abstract Syntax Tree) scanner internal memungkinkan tim membedah kode sumber dan mencocokkan signature modul terpasang secara mekanis sebelum runtime.

Skrip berikut menelusuri AST Python untuk memastikan fungsi pihak ketiga yang dipanggil memang benar-benar diekspos oleh modul yang terpasang di site-packages:

import ast
import importlib
import sys

class ExternalAPIVerifier(ast.NodeVisitor):
    def __init__(self):
        self.imports = {}
        self.violations = []

    def visit_Import(self, node):
        for alias in node.names:
            name = alias.asname or alias.name
            self.imports[name] = alias.name
        self.generic_visit(node)

    def visit_ImportFrom(self, node):
        if node.module:
            for alias in node.names:
                name = alias.asname or alias.name
                self.imports[name] = f"{node.module}.{alias.name}"
        self.generic_visit(node)

    def visit_Call(self, node):
        # Deteksi panggilan pola module.function()
        if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
            mod_alias = node.func.value.id
            method_name = node.func.attr
            if mod_alias in self.imports:
                full_mod = self.imports[mod_alias]
                self._validate_call(full_mod, method_name, node.lineno)
        self.generic_visit(node)

    def _validate_call(self, module_name, method_name, lineno):
        try:
            mod = importlib.import_module(module_name)
            if not hasattr(mod, method_name):
                self.violations.append(
                    f"Line {lineno}: '{module_name}' tidak memiliki fungsi '{method_name}' (Phantom Function)."
                )
        except ModuleNotFoundError:
            self.violations.append(f"Line {lineno}: Modul '{module_name}' belum terpasang.")

# ponytail: cek sederhana modul level satu; upgrade ke jedi/mypy daemon jika butuh chaining kompleks
def verify_source(source_code):
    tree = ast.parse(source_code)
    verifier = ExternalAPIVerifier()
    verifier.visit(tree)
    return verifier.violations

if __name__ == "__main__":
    code = """
import httpx
client = httpx.NonExistentClient()
response = httpx.send_magic_packet("https://api.internal")
"""
    errors = verify_source(code)
    for err in errors:
        print(f"FAIL: {err}")
    if errors:
        sys.exit(1)

Isolasi Boundary Mock dan Uji Kontrak Dependensi

Kesalahan fatal saat menguji kode LLM adalah penggunaan mock internal secara longgar. Ketika mock dibuat meniru tanda tangan fungsi yang salah, unit test tetap lulus meskipun kode akan meledak di produksi (false positive).

1. Strict Mocks via Autospeccing

Gunakan autospeccing agar test suite menolak argumentasi atau pemanggilan method yang tidak ada pada pustaka aslinya:

from unittest.mock import create_autospec
import pytest
import third_party_sdk

def test_ai_generated_adapter():
    # Autospec menjamin mock mencerminkan signature runtime modul upstream
    mock_client = create_autospec(third_party_sdk.Client, instance=True)
    
    # Eksekusi unit di bawah uji
    with pytest.raises(AttributeError):
        # Gagal langsung jika kode AI memanggil method fiktif
        mock_client.non_existent_method()

2. Consumer-Driven Contract Test

Kontrak memvalidasi ekspektasi aplikasi Anda terhadap payload dan signature aktual dependensi upstream. Jalankan uji kontrak ini secara berkala terhadap container upstream atau staging API langsung tanpa mock:

import pytest
import requests

def test_upstream_contract_invariant():
    """Memastikan skema respon upstream tidak melanggar asumsi kode."""
    target_url = "https://httpbin.org/json"
    response = requests.get(target_url, timeout=5)
    
    assert response.status_code == 200
    payload = response.json()
    
    # Assert structural invariants
    assert "slideshow" in payload, "Key 'slideshow' hilang dari struktur kontrak upstream"
    assert isinstance(payload["slideshow"], dict)
    assert "title" in payload["slideshow"]

Otomasi Pipeline CI/CD: Pre-build Verification

Gunakan script bash minimal dalam tahap pre-commit atau pipeline CI untuk memvalidasi seluruh dependensi pihak ketiga sebelum kompilasi atau bundling.

#!/usr/bin/env bash
set -euo pipefail

echo "[1/3] Memeriksa dependensi terinstal..."
pip check

echo "[2/3] Memindai codebase terhadap halusinasi API..."
python scripts/verify_ast.py

echo "[3/3] Audit vulnerabilitas dependensi..."
pip-audit --strict

echo "Validasi upstream sukses: Aman untuk build rilis."

Dengan menggabungkan analisis statis AST, mock ber-spek ketat, dan pemeriksaan integritas upstream otomatis, tim rekayasa dapat memanfaatkan efisiensi LLM tanpa membiarkan halusinasi kode merusak stabilitas produksi.