Ketika output LLM buruk, kebanyakan orang langsung memperbaiki prompt. Tapi sering kali masalahnya bukan di prompt — melainkan di context yang diberikan, atau di cara sistem itu diuji.
Tiga istilah ini punya scope yang berbeda dan solusi yang berbeda. Mencampurnya menyebabkan debugging yang tidak efisien dan perbaikan yang tidak tepat sasaran.
Prompt Engineering
Prompt engineering adalah seni merumuskan instruksi yang diberikan ke LLM untuk mendapatkan output yang diinginkan. Ini mencakup system prompt, few-shot examples, chain-of-thought, dan output format specification.
SYSTEM_PROMPT = """Anda adalah sistem triage keluhan untuk perusahaan properti PT JRP.
TUGAS:
- Analisis keluhan customer berdasarkan foto dan deskripsi
- Kategorikan berdasarkan jenis kerusakan
- Tentukan prioritas (HIGH/MEDIUM/LOW)
ATURAN:
- Selalu jawab dalam Bahasa Indonesia yang profesional
- Jika foto tidak jelas, MINTA foto ulang sebelum kategorisasi
- Jangan pernah membuat janji waktu perbaikan tanpa cek SLA
OUTPUT FORMAT (wajib JSON):
{
"category": "atap|dinding|listrik|plumbing|lainnya",
"priority": "HIGH|MEDIUM|LOW",
"needs_more_info": true|false,
"response_to_customer": "..."
}"""Context Engineering
Context engineering adalah tentang informasi apa yang dimasukkan ke context window LLM, bukan bagaimana instruksinya. Ini mencakup dokumen RAG, conversation history, tool results, dan structured data.
| Gejala | Root Cause | Solusi |
|---|---|---|
| LLM memberikan info yang salah atau outdated | Context engineering | Perbaiki RAG atau tambahkan data yang relevan |
| LLM menjawab dalam format yang salah | Prompt engineering | Perbaiki output format spec di system prompt |
| LLM menjawab tidak konsisten antar run | Bisa keduanya | Audit context quality + tambah few-shot examples |
| Tidak tahu mana yang bermasalah | Harness engineering | Bangun test suite untuk mengidentifikasi |
Harness Engineering
Harness engineering adalah pembuatan sistem pengujian untuk prompt dan pipeline LLM. Ini bagian yang paling sering dilewati — dan hasilnya, orang tidak punya cara sistematis untuk mengetahui apakah perubahan mereka membuat hal lebih baik atau lebih buruk.
# Test cases: input → expected output
TEST_CASES = [
{
"input": "Atap kamar saya bocor saat hujan deras",
"expected_category": "atap",
"expected_priority": "HIGH",
"expected_needs_more_info": False
},
{
"input": "Ada yang rusak di kamar saya", # vague
"expected_needs_more_info": True
},
]
def run_evaluation(prompt, test_cases):
results = []
for case in test_cases:
output = llm.invoke([
{"role": "system", "content": prompt},
{"role": "user", "content": case["input"]}
])
parsed = parse_json(output)
passed = all(
parsed.get(k) == v
for k, v in case.items() if k != "input"
)
results.append({"input": case["input"], "passed": passed})
accuracy = sum(1 for r in results if r["passed"]) / len(results)
return accuracy, results
# Sekarang bisa compare: prompt A vs prompt B secara objektif
acc_a, _ = run_evaluation(PROMPT_A, TEST_CASES)
acc_b, _ = run_evaluation(PROMPT_B, TEST_CASES)
print(f"Prompt A: {acc_a:.0%} | Prompt B: {acc_b:.0%}")Bangun test cases dari real user inputs sebelum mulai menulis prompt. Ini memaksa Anda mendefinisikan "output yang benar" secara eksplisit — dan mencegah optimasi terhadap contoh yang tidak representatif.
Loop Engineering
Loop engineering adalah layer keempat yang paling jarang dibahas, tapi semakin penting seiring meningkatnya penggunaan agentic AI. Ini adalah seni merancang bagaimana agent menggunakan hasil iterasi sebelumnya untuk memperbaiki output di iterasi berikutnya.
Berbeda dari tiga layer sebelumnya yang bersifat "set once," loop engineering menangani sistem yang berjalan iteratif — agent yang melakukan Plan → Act → Observe → Plan lagi. Ada tiga pola utama:
Reflection Loop
Agent mengevaluasi output-nya sendiri sebelum memberikan ke user. Setelah menghasilkan jawaban, agent bertanya: "Apakah ini sudah menjawab pertanyaan dengan baik?" Jika tidak, ia revisi sebelum lanjut.
def reflection_loop(llm, task: str, max_reflections: int = 2) -> str:
response = llm.invoke(f"Selesaikan tugas ini: {task}")
for _ in range(max_reflections):
# Agent evaluasi outputnya sendiri
critique = llm.invoke(f"""
Tugas asli: {task}
Draft jawaban: {response}
Evaluasi draft ini:
1. Apakah sudah menjawab tugas sepenuhnya?
2. Ada informasi penting yang terlewat?
3. Ada yang perlu diperbaiki?
Jika sudah baik, balas "LGTM". Jika tidak, jelaskan apa yang perlu diperbaiki.""")
if "LGTM" in critique:
break # output sudah cukup baik
# Revisi berdasarkan critique
response = llm.invoke(
f"Perbaiki jawaban ini berdasarkan feedback:
{critique}
Jawaban sebelumnya:
{response}"
)
return response
Self-Correction Loop
Digunakan ketika output harus memenuhi constraint tertentu — misalnya format JSON yang valid, atau output yang harus bisa diverifikasi secara programatik. Agent mencoba, verifikasi dilakukan, dan jika gagal agent memperbaiki berdasarkan error message.
import json
def self_correct_json(llm, prompt: str, schema: dict, max_retries: int = 3) -> dict:
last_error = None
for attempt in range(max_retries):
# Sertakan error dari percobaan sebelumnya jika ada
full_prompt = prompt
if last_error:
full_prompt += f"
Percobaan sebelumnya gagal dengan error: {last_error}
Perbaiki dan coba lagi."
raw = llm.invoke(full_prompt)
try:
parsed = json.loads(raw)
# Validasi schema tambahan jika diperlukan
required = schema.get("required", [])
missing = [f for f in required if f not in parsed]
if missing:
raise ValueError(f"Missing required fields: {missing}")
return parsed # sukses
except (json.JSONDecodeError, ValueError) as e:
last_error = str(e)
print(f"Attempt {attempt + 1} failed: {last_error}")
raise RuntimeError(f"Failed after {max_retries} attempts. Last error: {last_error}")
Critique-Revise Loop (Multi-Agent)
Pola paling powerful tapi juga paling mahal: dua LLM berbeda, satu sebagai "drafter" dan satu sebagai "critic." Ini menghindari blind spot yang terjadi ketika LLM yang sama mengevaluasi outputnya sendiri.
| Loop Pattern | Kapan Dipakai | Cost Relatif | Kekurangan |
|---|---|---|---|
| Reflection | Output perlu quality check sebelum dikirim ke user | △ 2× LLM calls | LLM bisa bias terhadap outputnya sendiri |
| Self-Correction | Output harus memenuhi constraint terukur (JSON, format) | △ 1–3× LLM calls | Tidak efektif untuk constraint subjektif |
| Critique-Revise | Kualitas tinggi, output akan dipakai langsung | ✗ 3–5× LLM calls | Paling mahal, perlu dua LLM berbeda |
Setiap iterasi loop berarti tambahan LLM call dan latency. Untuk use case yang latency-sensitive (chatbot real-time, autocomplete), loop engineering bisa membuat UX terasa lambat. Gunakan hanya ketika kualitas output lebih penting dari kecepatan respons.
Kesimpulan
Ketika output LLM buruk, lakukan diagnosis dulu sebelum mulai mengubah sesuatu. Empat layer untuk diperiksa secara berurutan:
Bangun harness dulu. Tanpa cara mengukur, Anda tidak pernah tahu apakah perubahan prompt Anda membantu, tidak berpengaruh, atau justru memperburuk kasus lain.
Episode berikutnya: Circuit Breaker untuk AI Agent — sistem pertahanan yang mencegah agent loop selamanya dan menghabiskan token tanpa batas di production.