Lompat ke konten utama
vourdev
Kembali ke Blog

Tutorial

3 Kesalahan Prompting LLM yang Bikin AI Halusinasi Kode (Dan Cara Memperbaikinya)

Pelajari 3 kesalahan fatal menyusun instruksi AI coding serta teknik few-shot dan context constraint untuk hasil kode TypeScript & Python yang presisi.

Gambar Cover 3 Kesalahan Prompting LLM yang Bikin AI Halusinasi Kode (Dan Cara Memperbaikinya)
Ditulis olehvourdev11 menit baca

Berapa kali Anda meminta Large Language Model (LLM) seperti GPT-4, Claude, atau Codestral membuat fungsi sederhana, tapi begitu dimasukkan ke IDE, kompilator langsung menjerit merah karena method yang dipanggil ternyata tidak pernah ada?

Fenomena ini sering dikira sebagai "keterbatasan model". Padahal, dalam 80% kasus di lapangan, halusinasi kode bukan disebabkan oleh LLM yang bodoh, melainkan cara kita menyusun instruksi (prompting) yang memberi ruang terlalu luas bagi model untuk melakukan tebakan probabilitas.

LLM dasarnya adalah next-token predictor. Model tidak memiliki runtime environment atau linter di dalam memorinya. Ketika diberi prompt yang ambigu, model akan memilih kata atau method yang secara statistik terlihat sangat meyakinkan, meskipun secara sintaksis dan arsitektur itu murni imajinasi.

Artikel ini membedah 3 kesalahan mendasar saat menyusun prompt AI coding dan bagaimana memperbaikinya menggunakan bounded context, few-shot prompting, dan schema constraints.

// BAD PROMPT RESULT (LLM Mencampur SDK v2 dan v3):

// const AWS = require('aws-sdk'); // v2 style

// const s3 = new AWS.S3();

// const command = new PutObjectCommand({...}); // v3 style! Crash di runtime.

// GOOD IMPLEMENTATION (Bounded Context Prompting):

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

interface UploadConfig {

bucketName: string;

fileKey: string;

contentType: string;

expiresInSeconds: number;

}

/**

  • Mengunggah file ke AWS S3 menggunakan AWS SDK v3 modular pattern.
  • Terikat secara eksplisit pada interface UploadConfig.

*/

export async function generatePresignedUploadUrl(

client: S3Client,

config: UploadConfig

): Promise<string> {

const command = new PutObjectCommand({

Bucket: config.bucketName,

Key: config.fileKey,

ContentType: config.contentType,

});

// Menggunakan presigner v3 yang terpisah dari instance S3Client

return await getSignedUrl(client, command, {

expiresIn: config.expiresInSeconds,

});

}

code

### Detail Implementasi Prompting

Untuk mendapatkan hasil sepresisi kode di atas, struktur prompt harus disusun dengan aturan berikut:

1. **Deklarasikan Dependency & Versi Target**: Sebutkan secara eksplisit `@aws-sdk/client-s3` v3.x.
2. **Sediakan Interface / Type Signatures**: Berikan kontrak input dan output sebelum meminta model menulis logika di dalam fungsi.
3. **Kunci Scope Panggilan Method**: Larang pengimporan paket monolithic seperti `aws-sdk`.

---

## Kesalahan 2: Mengandalkan Zero-Shot untuk Logika Kompleks & Custom DSL

*Zero-shot prompting* adalah teknik di mana kita langsung meminta model menyelesaikan tugas tanpa memberikan contoh pasangan input-output terlebih dahulu. Untuk tugas generik seperti sorting array atau regex dasar, *zero-shot* berjalan lancar. 

Namun, ketika Anda meminta LLM menulis kode menggunakan **internal library perusahaan, custom DSL (Domain Specific Language), atau alur arsitektur spesifik**, *zero-shot* hampir 100% menjamin timbulnya halusinasi kode. Model akan mengarang fungsi-fungsi abstrak yang terdengar keren tetapi tidak pernah ada di dalam *codebase* Anda.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 260" role="img" aria-labelledby="svg-title-2">

<title id="svg-title-2">Arsitektur Pipeline Few-Shot Prompting untuk LLM Coding</title>

<g stroke="currentColor" fill="currentColor" font-family="sans-serif">

<rect x="15" y="20" width="200" height="220" rx="10" fill="none" stroke-width="1.5" />

<text x="30" y="50" font-size="15" font-weight="bold" fill="#39d5f6">System Context</text>

<rect x="30" y="70" width="170" height="40" rx="5" fill="none" stroke-width="1" />

<text x="40" y="95" font-size="13">Role Definition</text>

<rect x="30" y="120" width="170" height="40" rx="5" fill="none" stroke-width="1" />

<text x="40" y="145" font-size="13">Custom Rules</text>

<rect x="30" y="170" width="170" height="50" rx="5" fill="none" stroke-width="1" />

<text x="40" y="193" font-size="13">Target Type Defs</text>

<line x1="215" y1="130" x2="265" y2="130" stroke="#39d5f6" stroke-width="2" />

<rect x="265" y="20" width="200" height="220" rx="10" fill="none" stroke-width="1.5" />

<text x="280" y="50" font-size="15" font-weight="bold" fill="#39d5f6">Few-Shot Examples</text>

<rect x="280" y="70" width="170" height="65" rx="5" fill="none" stroke-width="1" />

<text x="290" y="93" font-size="13">Example 1: Input</text>

<text x="290" y="118" font-size="13">Example 1: Output</text>

<rect x="280" y="145" width="170" height="65" rx="5" fill="none" stroke-width="1" />

<text x="290" y="168" font-size="13">Example 2: Input</text>

<text x="290" y="193" font-size="13">Example 2: Output</text>

<line x1="465" y1="130" x2="515" y2="130" stroke="#39d5f6" stroke-width="2" />

<rect x="515" y="20" width="190" height="220" rx="10" fill="none" stroke="#39d5f6" stroke-width="2" />

<text x="530" y="50" font-size="15" font-weight="bold">Target Request</text>

<rect x="530" y="70" width="160" height="40" rx="5" fill="none" stroke-width="1" />

<text x="540" y="95" font-size="13">New User Input</text>

<line x1="610" y1="110" x2="610" y2="140" stroke-width="1.5" />

<rect x="530" y="140" width="160" height="80" rx="5" fill="none" stroke="#39d5f6" stroke-width="1.5" />

<text x="540" y="165" font-size="14" font-weight="bold" fill="#39d5f6">Deterministic</text>

<text x="540" y="185" font-size="13" fill="#39d5f6">Code Output</text>

<text x="540" y="205" font-size="12">(No Unknown APIs)</text>

</g>

</svg>

Solusi: Teknik Few-Shot Pattern Matching

Few-shot prompting memberikan 1 hingga 3 contoh konkret pola transformasi yang Anda harapkan. Dengan menyuplai contoh input-output, kita menggeser peran LLM dari "pengarang cerita" menjadi "penyelaras pola" (pattern matcher).

Berikut adalah template instruksi few-shot dalam format JSON/TypeScript yang efektif memaksa LLM mengikuti arsitektur internal tanpa halusinasi:

typescript
/**
 * Contoh pola Few-Shot Prompting untuk mentransformasi SQL Query
 * ke Custom Internal Query Builder.
 */

// STEP 1: Berikan Kontrak Tipe Internal
interface InternalQuery {
  table: string;
  filters: Array<{ field: string; operator: "EQ" | "GT" | "IN"; value: unknown }>;
  limit?: number;
}

// STEP 2: Berikan 2 Contoh Pasangan (Few-Shot Examples)
const FEW_SHOT_PROMPT = `
Anda adalah transpiler kode. Tugas Anda mengubah query deskriptif menjadi objek InternalQuery.

### CONTOH 1:
Input: "Cari user aktif dengan id di atas 100"
Output:
{
  "table": "users",
  "filters": [
    { "field": "status", "operator": "EQ", "value": "active" },
    { "field": "id", "operator": "GT", "value": 100 }
  ]
}

### CONTOH 2:
Input: "Ambil 5 produk kategori electronics"
Output:
{
  "table": "products",
  "filters": [
    { "field": "category", "operator": "EQ", "value": "electronics" }
  ],
  "limit": 5
}

### TUGAS SEKARANG:
Input: "Ambil 10 transaksi pembayaran yang bernilai di atas 500000"
Output:
`;

Kenapa Teknik Ini Berhasil?

  1. Perhatian Terfokus (Attention Weight): Mechanisme attention pada LLM melihat hubungan langsung antara token contoh pertama, contoh kedua, dan permintaan akhir.
  2. Keterbatasan Ruang Vokabuler: LLM terisolasi dari kemungkinan memanggil fungsi luar karena struktur objek target sudah dikunci oleh schema pada contoh few-shot.

Kesalahan 3: Menggunakan Negative Constraints & Format Output Ambiguitas

Arsitektur kode Python dan API schema
Penerapan schema constraint dan validation layer pada prompt AI. · Rubaitul Azad (Unsplash)

Banyak developer menulis prompt yang dipenuhi instruksi larangan, seperti:

"Tulis fungsi parser data. Jangan pakai regex, jangan pakai loop for, jangan gunakan library external, dan jangan kembalikan format selain JSON."

Secara neuro-linguistik dan arsitektur attention mechanism, LLM sangat buruk dalam memproses negasi ("jangan" / "do not").

Saat Anda memasukkan kata "regex" atau "loop for", kata-kata tersebut mendapatkan attention weight yang tinggi di dalam memori transformer. Token negasi "jangan" sering kali tidak cukup kuat untuk menetralkan weight dari kata benda/kata kerja yang dilarang. Akibatnya, LLM justru sering memunculkan hal-hal yang dilarang tersebut.

from pydantic import BaseModel, Field, ValidationError

from typing import List

import json

1. Definisi Schema Strict untuk Output LLM

class RefactoredFunction(BaseModel):

function_name: str = Field(description="Nama fungsi dalam snake_case")

parameters: List[str] = Field(description="Daftar nama parameter input")

return_type: str = Field(description="Tipe data return nilai")

code_body: str = Field(description="Isi kode Python tanpa markdown fence")

uses_external_lib: bool = Field(default=False, description="Flag jika menggunakan lib luar")

2. System Prompt Menggunakan Positive Constraint

SYSTEM_PROMPT = """

Tugas Anda adalah melakukan refactoring fungsi Python.

Aturan Wajib (Positive Constraints):

  1. Gunakan teknik Higher-Order Function (map/filter/reduce) untuk pemrosesan list.
  2. Gunakan type hinting pada seluruh argumen dan return value.
  3. Kembalikan output HANYA berupa JSON valid yang sesuai dengan Schema berikut.

"""

def parse_llm_response(raw_llm_output: str) -> RefactoredFunction:

"""

Memvalidasi hasil generasi LLM dengan Pydantic Schema.

Mencegah kode berhalusinasi masuk ke runtime downstream.

"""

try:

Membersihkan kemungkinan markdown code block wrapper

cleaned_json = raw_llm_output.strip().strip("``json").strip("``").strip()

data = json.loads(cleaned_json)

Validasi struktur data secara strict

validated_code = RefactoredFunction(**data)

return validated_code

except (json.JSONDecodeError, ValidationError) as e:

raise ValueError(f"LLM mengalami halusinasi format schema: {str(e)}")

Contoh penggunaan

raw_response = """{

"function_name": "filter_active_users",

"parameters": ["users"],

"return_type": "List[dict]",

"code_body": "def filter_active_users(users: List[dict]) -> List[dict]:\\n return list(filter(lambda u: u.get('is_active'), users))",

"uses_external_lib": false

}"""

parsed = parse_llm_response(raw_response)

print(f"Fungsi '{parsed.function_name}' berhasil diparse tanpa halusinasi!")

code

### Penjelasan Kode Baris demi Baris

1. **`class RefactoredFunction(BaseModel)`**: Membuat struktur kontrak yang kaku. Jika LLM mencoba mengembalikan nama field yang berbeda (misalnya `func_name` alih-alih `function_name`), validation layer akan langsung menolaknya.
2. **`SYSTEM_PROMPT`**: Mengganti kata "jangan pakai loop" menjadi instruksi positif: *"Gunakan teknik Higher-Order Function (map/filter/reduce)"*. Ini mengarahkan *attention weight* LLM ke fungsi `map`/`filter`.
3. **`parse_llm_response`**: Menjadi *circuit breaker*. Jika LLM tiba-tiba mengalami *hallucination panic* dan mengembalikan teks narasi alami, fungsi ini menangkap *exception* sebelum merusak pipa eksekusi aplikasi Anda.

---

## Best Practices & Anti-Pattern Saat Pair Programming dengan AI

Untuk meminimalkan halusinasi saat coding sehari-hari, terapkan panduan praktis berikut dalam workflow tim Anda:

### 1. Injeksi Header Context (Type Definitions First)
Sebelum meminta LLM menulis implementasi logic, selalu berikan definisinya terlebih dahulu. Di TypeScript, berikan `interface` atau `type`. Di Python, berikan `Protocol` atau Abstract Base Class.

> **Anti-Pattern**: *"Tulis fungsi kalkulasi diskon untuk objek order ini: { id: 1, items: [...] }"*
> 
> **Best Practice**: *"Diberikan interface Order dan DiscountRule berikut [sertakan kode interface]. Tulis fungsi calculateFinalPrice(order: Order, rules: DiscountRule[]): number."*

### 2. Gunakan System Prompt Berorientasi Linter
Atur persona LLM pada System Prompt agar bertindak seperti kompilator ketat, bukan copywriter yang ramah.

Anda adalah Linter dan Compiler TypeScript v5.0 yang sangat ketat.

Tugas Anda: Menghasilkan kode murni tanpa penjelasan naratif, tanpa salam, dan tanpa pemanggilan API yang tidak dieksplisitkan dalam context.

Jika konteks yang diberikan tidak cukup untuk menyelesaikan fungsi, kembalikan error JSON: {"error": "Missing Type Definition"}.

code

### 3. Batasi Scope Per-Turn (Single Responsibility Principle)
Jangan pernah menyalin satu file berisi 3.000 baris kode lalu menulis: *"Tolong refactor file ini dan perbaikisemua bug-nya."*

Kapasitas pemrosesan LLM (*context window*) memiliki titik jenuh (*lost in the middle*). Dekonstruksi tugas menjadi langkah-langkah kecil:
1. Turn 1: Analisis dan ekstrak interface/type.
2. Turn 2: Tulis fungsi murni (*pure function*) untuk logika inti.
3. Turn 3: Tulis unit test dengan Jest/Vitest untuk memverifikasi edge cases.

---

## Kesimpulan & Action Items

Halusinasi kode pada LLM bukanlah misteri gaib. Itu adalah reaksi logis dari model statistik yang diberi ruang pencarian (*search space*) terlalu luas tanpa batasan yang jelas.

Dengan memperbaiki 3 kesalahan prompting di atas:
1. **Mengikat Konteks (*Bounded Context*)**: Kunci library, versi, dan interface secara eksplisit.
2. **Mengabaikan Zero-Shot (*Few-Shot Pattern*)**: Berikan contoh konkret input-output untuk logika khusus atau custom framework.
3. **Mengganti Negative Constraint (*Positive & Schema Constraints*)**: Gunakan instruksi positif dan kunci output menggunakan validation layer seperti Zod atau Pydantic.

**Action Item Langkah Konkret Hari Ini**:
- Buka templat prompt AI yang sering Anda gunakan di IDE (Copilot, Cursor, atau ChatGPT).
- Hapus semua kata "Jangan gunakan X" dan ganti menjadi "Gunakan teknik Y".
- Buat satu file `.env` atau `system_prompt.md` di repositori proyek Anda yang berisi Type Definitions utama dan instruksi versi dependensi untuk selalu di-copy sebelum melakukan prompting.

Butuh penyesuaian khusus untuk project Anda?

Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami. Anda juga bisa melihat rincian layanan vour.dev atau menghitung estimasi biaya project lebih dulu.

Mulai Project