Cara Membuat Eval Agen AI yang Di-host Sendiri
Pelajari loop eval tanpa vendor: kumpulkan trace nyata, buat kasus golden, jalankan pemeriksaan deterministik, gunakan juri LLM, dan lacak hasil per commit.
Apa yang dimaksud dengan eval yang di-host sendiri untuk agen AI
Eval yang di-host sendiri untuk agen AI terdiri atas empat hal yang Anda simpan di repository sendiri: file berisi kasus yang disimpan, skrip yang menjalankan agen pada kasus tersebut, serangkaian pemeriksaan untuk menilai setiap jawaban, dan tabel hasil yang dapat Anda kueri. Tidak ada satu pun dari komponen tersebut yang memerlukan vendor. Seluruh loop ini dapat dibuat dengan beberapa ratus baris Python dan satu file SQLite.
Agen tersebut berfungsi dalam demo karena Anda sendiri yang memilih lima input. Pada minggu kedua, agen mulai gagal karena satu baris prompt berubah, model berubah, atau deskripsi tool berubah, sementara tidak ada pengukuran yang mencakup perubahan tersebut. Loop eval mengubah "sekarang hasilnya terasa lebih buruk" menjadi "tingkat kelulusan turun dari 58 dari 60 menjadi 51 dari 60 pada commit 4f1c9ab".
Loop ini memiliki empat langkah, dan panduan ini membahas satu bagian untuk setiap langkah: kumpulkan trace nyata, jadikan trace yang menarik sebagai kasus, nilai setiap kasus pada setiap perubahan, lalu simpan tingkat kelulusan di samping commit yang menghasilkannya. Loop yang sama dapat digunakan pada apa pun yang Anda jalankan untuk agen tersebut, dan framework agen yang di-host sendiri dan layak dijalankan terutama berbeda dalam hal seberapa banyak trace yang diberikan secara otomatis kepada Anda.
Mengapa agent rusak pada minggu kedua
Agent terdiri atas prompt, model, serangkaian definisi tool, dan konteks yang diambil saat runtime. Keempatnya dapat berubah tanpa perubahan pada kode aplikasi, sehingga code review biasa tidak menemukan perubahan yang perlu ditindaklanjuti.
Penyebab yang paling umum adalah perubahan prompt. Anda menambahkan satu kalimat untuk mencegah respons yang kasar. Kalimat tersebut mengubah perilaku pada input yang belum diuji ulang, dan trace menunjukkannya dengan jelas: trace minggu lalu untuk pertanyaan yang sama berisi create_refund tool call, sedangkan trace minggu ini tidak berisi apa pun, dan responsnya justru berupa permintaan maaf yang sopan. Tidak ada error yang muncul, sehingga tidak ada alert yang dipicu.
Penyebab kedua adalah model. Catat string model persis yang dikirim pada setiap run, claude-haiku-4-5-20251001 bukan singkatan yang hanya Anda ingat, karena penurunan pass rate pada hari saat Anda mengganti model hanya dapat didiagnosis jika model tersebut tercatat pada baris datanya.
Penyebab ketiga adalah tool. Mengubah redaksi deskripsi tool dapat mengubah waktu saat model memutuskan untuk memanggilnya. Jika tool Anda disediakan melalui MCP server yang berjalan pada VPS, skemanya berada dalam proses lain, sehingga dapat berubah tanpa Anda sadari dan tanpa diff apa pun di repository Anda. Penyebab keempat adalah retrieval: pertanyaan yang sama mengakses index yang dibangun ulang pada malam sebelumnya, lalu jawabannya mengikuti dokumen baru.
Bangun golden set dari trace yang sudah Anda kumpulkan
Jangan membuat kasus evaluasi secara sembarang. Ambil kasus dari traffic. Jika Anda sudah menjalankan tracing Langfuse yang di-host sendiri untuk agent Anda, setiap request disimpan bersama input, pemanggilan tool, dan output-nya. Data tersebut merupakan bahan mentah yang tepat untuk sebuah kasus.
Ekspor rentang root observation melalui public API. API ini menggunakan basic authentication, dengan public key Anda sebagai username dan secret key Anda sebagai password.
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'Baca satu record sebelum menulis parsing apa pun. Baris data dikembalikan di bawah data, tetapi nama field yang menyimpan pertanyaan dan jawaban bergantung pada cara agent Anda menginstrumentasikan span. Karena itu, petakan field berdasarkan data yang benar-benar Anda lihat, bukan berdasarkan asumsi. Kemudian tulis kasus secara manual, satu objek JSON per baris, di evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}Lima aturan berikut menjaga agar set tetap berguna untuk dijalankan:
- Mulai dengan 40 hingga 80 kasus. Jika kurang dari 20, satu kasus yang tidak stabil dapat mengubah tingkat kelulusan sebesar 5 poin, sehingga angka yang berubah tanpa alasan akan diabaikan.
- Setiap bug production yang Anda perbaiki harus menjadi kasus pada hari yang sama. Kebiasaan ini membuat set berkembang ke arah yang tepat.
- Satu perilaku per kasus. Kasus yang sekaligus memeriksa jumlah refund dan nada respons tidak memberi informasi saat gagal.
idtidak pernah berubah, karena id tersebut digunakan untuk membandingkan proses hari ini dengan proses bulan lalu.- Lakukan redaksi sebelum commit. File ini akan dimasukkan ke git, jadi hapus nama pelanggan dan nomor pesanan yang bukan milik Anda.
Utamakan penilaian dengan pemeriksaan deterministik karena tidak memerlukan biaya
Setiap hal yang memiliki jawaban benar diperiksa dengan assertion biasa. Tidak ada pemanggilan model, biaya, atau ambiguitas. Pemeriksaan deterministik mendeteksi regresi struktural, yaitu masalah yang merusak sistem di sekitar agent Anda: JSON tidak dapat di-parse, tool tidak pernah dipanggil, frasa terlarang muncul kembali, atau jawaban tidak mencantumkan sumber.
Hanya satu fungsi yang perlu mengetahui agent Anda. Semua bagian lain dalam harness bersifat umum.
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failuresPertahankan batas jumlah pemanggilan tool dalam daftar tersebut. Agent yang menyelesaikan kasus dengan 3 pemanggilan hari ini tetapi memerlukan 11 pemanggilan besok telah mengalami regresi, meskipun jawaban akhirnya benar, karena setiap pemanggilan menimbulkan biaya.
LLM sebagai penilai, dan empat cara penilaian ini dapat gagal
Apa pun yang lolos dari assertions memerlukan grader yang membaca. LLM judge adalah pemanggilan model kedua: model menerima pertanyaan, jawaban agent, dan satu kriteria, lalu mengembalikan verdict. Ini satu-satunya cara praktis untuk menilai “apakah balasan menjawab hal yang ditanyakan pengguna”.
Empat aturan membuat judge dapat digunakan:
- Verdict biner, bukan skor 1 sampai 10. Skala biasanya menghasilkan 7 dan 8 untuk hampir semua hal, sehingga angkanya tidak pernah berubah dan Anda tidak memperoleh informasi apa pun.
- Satu kriteria per pemanggilan. Tanyakan jumlah refund atau tone, bukan keduanya sekaligus.
- Berikan jawaban yang diharapkan kepada judge jika kasus tersebut memilikinya. Penilaian berdasarkan referensi jauh lebih mudah daripada penilaian secara abstrak.
- Paksa bentuk output dan lakukan parsing secara ketat.
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)Berikutnya, mode kegagalannya. Setiap mode memiliki pengujian yang dapat Anda jalankan sore ini. Pengujian ini penting karena judge yang tidak diperiksa menghasilkan angka yang terlihat presisi, tetapi tidak bermakna.
Bias panjang. Jawaban yang lebih panjang lebih sering lolos. Uji dengan cara berikut: ambil sepuluh jawaban yang gagal menurut judge, tambahkan dua paragraf filler yang meyakinkan tetapi tidak menambahkan fakta baru pada masing-masing jawaban, lalu nilai kembali. Jika ada verdict yang berubah menjadi pass, itu adalah bias panjang, dan rubric yang harus diperbaiki.
Preferensi terhadap model sendiri. Judge sering menilai output dari model family-nya sendiri dengan lebih baik daripada output dari model lain. Uji dengan cara berikut: nilai 30 jawaban yang sama menggunakan judge dari dua family berbeda, lalu bandingkan verdict kasus demi kasus. Untuk kasus yang hasilnya berbeda, baca kasus tersebut sendiri.
Bias posisi. Jika Anda menggunakan judge untuk membandingkan dua jawaban, A dan B, tukar urutannya lalu jalankan kembali. Verdict yang berubah setelah pertukaran menunjukkan bahwa pairwise comparison belum aman untuk rubric tersebut.
Pergeseran rubric. Kriteria yang tidak jelas menghasilkan judge yang mudah menyetujui. “Apakah jawaban ini membantu” hampir selalu menghasilkan pass. “Apakah jawaban menyebutkan jumlah refund dalam dolar” hanya menghasilkan pass untuk jawaban yang memang Anda maksud. Tulis ulang setiap kriteria sampai kriteria tersebut menyebutkan fakta yang diperiksa.
Satu pengaman mencakup keempat mode tersebut. Simpan 30 kasus yang telah Anda beri label secara manual, lalu bandingkan skor judge dengan label Anda setiap kali mengubah model judge atau prompt judge. Jika hasilnya berbeda dengan label Anda pada lebih dari satu kasus dari sepuluh kasus, perbaiki rubric sebelum mempercayai pass rate yang dihasilkan. Judge adalah kode, sehingga harus diberi versioning dan direview seperti kode.
Gunakan model murah terlebih dahulu, lalu eskalasikan ke model frontier
Menilai setiap kasus dengan model termahal pada setiap commit akan membuat tagihan evaluasi melampaui biaya agent yang diuji. Urutkan grader berdasarkan harga, lalu hentikan proses segera setelah jawabannya jelas.
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]Angka tersebut mengasumsikan sekitar 1,200 token input dan 120 token output untuk setiap pemanggilan judge. Ukuran ini realistis untuk satu pertanyaan, satu jawaban, dan satu kriteria. Menilai 1,000 kasus memerlukan biaya 1.80 dolar AS pada Claude Haiku 4.5 dan 9.00 pada Claude Opus 5. Selisihnya terlihat kecil sampai Anda mengalikannya dalam jumlah besar. Set evaluasi yang terdiri dari 60 kasus, dinilai pada setiap commit dengan 40 commit per minggu, menghasilkan 2,400 pemanggilan judge per minggu sebelum siapa pun menjalankan job malam.
Dua diskon dapat diterapkan dengan mudah pada pekerjaan evaluasi, dan keduanya dapat digabungkan. Proses evaluasi tidak interaktif, sehingga Batch API mengurangi separuh harga input dan output sebagai imbalan atas pengiriman asinkron. Ini adalah baris pertama pada grafik. Rubrik dan instruksi identik byte demi byte pada setiap pemanggilan, sehingga prompt caching sesuai digunakan. Pembacaan cache dikenai biaya sepersepuluh dari harga input dasar, sedangkan penulisan cache selama lima menit dikenai biaya 1.25 kali harga input dasar. Karena itu, cache sudah balik modal setelah satu penggunaan ulang. Harga tersebut adalah harga resmi Anthropic per Agustus 2026. Sonnet 5 menggunakan harga perkenalan hingga 31 August 2026, sehingga batang ketiga meningkat setelah tanggal tersebut.
Urutan prosesnya:
- Pemeriksaan deterministik pada setiap kasus. Tidak ada biaya API.
- Judge dengan model kecil pada kasus yang lolos dari pemeriksaan tersebut.
- Judge dengan model frontier hanya pada kasus yang dinyatakan gagal oleh model kecil, atau dinyatakan lulus dengan tingkat keyakinan rendah.
- Review manusia pada sampel kecil, sekali seminggu.
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]Pendekatan ini menukar sebagian akurasi penilaian dengan penghematan biaya. Karena itu, ukur pertukaran tersebut, bukan menganggap hasilnya. Sebulan sekali, nilai seluruh set dengan judge ketat dan bandingkan kedua kolomnya. Jika terdapat perbedaan pada lebih dari beberapa kasus, rubrik Anda terlalu longgar untuk model kecil. Rubrik itulah yang perlu diperbaiki. Mengendalikan pengeluaran agent itu sendiri adalah tugas terpisah, yang dibahas dalam pengendalian biaya untuk AI agent di VPS.
Pantau tingkat kelulusan dari waktu ke waktu pada sistem milik Anda
Tingkat kelulusan yang tidak dapat dikaitkan dengan commit hanyalah perasaan. Simpan satu baris untuk setiap kasus pada setiap proses, dengan commit dan model di dalam baris tersebut.
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;Muat skema dengan sqlite3 evals/results.db < evals/schema.sql, lalu baca trennya dengan sqlite3 -box evals/results.db < evals/passrate.sql. Proses harian selama setahun untuk 60 kasus menghasilkan sekitar 22,000 baris, sehingga penyimpanan tidak pernah menjadi proyek tersendiri. Menjalankan SQLite di production pada VPS membahas pengaturan yang mulai diperlukan jika file ini dibagikan antar-mesin.
Runner mencetak informasi yang sama untuk seseorang:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amountJalankan suite pada perubahan yang dapat merusak agent, yaitu perubahan prompt, model, dan tool, bukan pada setiap commit di seluruh repository. Hook pre-push mencakup subset yang cepat:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-pushProses lengkap lebih lambat dan sebaiknya dijalankan berdasarkan jadwal. Service dan timer systemd pada VPS yang berjalan setiap malam menjalankan seluruh rangkaian terhadap prompt yang telah di-deploy. Cara ini menangkap perubahan yang berasal dari luar repository Anda, misalnya tool hosted yang perilakunya berubah.
Tinjauan manusia dengan pengambilan sampel, bukan secara menyeluruh
Penilaian dikalibrasi terhadap label manusia, sehingga seseorang harus membuat label tersebut. Tinjau sampel setiap minggu: semua kasus yang gagal dinilai oleh judge, ditambah sepuluh kasus yang lolos dan dipilih secara acak. Kasus yang lolos secara acak merupakan bagian penting, karena judge yang diam-diam mulai meloloskan jawaban buruk akan terlihat sempurna pada dashboard apa pun yang dibangun dari hasil penilaiannya sendiri.
Lima belas kasus dengan durasi tiga menit per kasus memerlukan waktu 45 menit per minggu. Proses ini menghasilkan koreksi pada rubrik ketika penilaian Anda dan judge berbeda, serta kasus baru untuk jenis kegagalan yang belum pernah dibayangkan. Tulis hasil penilaian manusia ke dalam tabel yang sama dengan graded_by disetel ke human, sehingga kesesuaian antara judge dan manusia dapat diperoleh melalui query, bukan hanya diingat.
Hal-hal yang rusak pada eval harness itu sendiri
anthropic.RateLimitError pada proses penuh pertama. Enam puluh kasus yang dijalankan sekaligus melampaui batas request atau token pada tier Anda. Batasi konkurensi menjadi empat worker, lalu pindahkan proses nightly ke Batch API.
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) dari judge. Model memberikan jawaban dalam bentuk prosa atau membungkus JSON-nya dalam code fence. Coba sekali lagi, lalu catat kasus tersebut sebagai error. Jangan pernah menganggap kegagalan parsing sebagai pass, karena suite yang mengubah error menjadi pass akan mendekati 100% sementara kualitas agent memburuk.
Kasus yang tidak konsisten. Input yang sama berhasil pada satu proses, tetapi gagal pada proses berikutnya karena agent mengambil sampel output-nya. Jalankan kasus yang tidak konsisten tersebut tiga kali dan catat proporsinya, bukan menghapus kasusnya. Kasus yang berhasil dua dari tiga proses merupakan bug robustness nyata dan akan ditemukan oleh pelanggan.
Golden set yang mengalami pembusukan. Seseorang mengedit jawaban yang diharapkan agar suite berstatus green. Tinjau diff pada evals/cases.jsonl dengan ketelitian yang sama seperti diff pada agent, karena file tersebut merupakan definisi tertulis Anda tentang hasil yang benar.
Suite yang tidak pernah gagal. Tingkat pass yang tetap 100% selama sebulan berarti set tersebut tidak lagi mengikuti produk. Ambil sepuluh trace terbaru, temukan kasus yang ditangani agent dengan buruk, lalu tambahkan kasus-kasus tersebut. Setelah itu, buat sesuatu gagal dengan sengaja dan pastikan proses berubah menjadi red. Inilah pemeriksaan bahwa mutation testing berlaku untuk test suite dan satu-satunya cara untuk mengetahui bahwa set Anda masih mampu menemukan kegagalan.
FAQ
Berapa banyak kasus yang diperlukan dalam set evaluasi agen AI?
Mulailah dengan 40 hingga 80 kasus, lalu kembangkan set tersebut berdasarkan kegagalan nyata. Jika jumlahnya di bawah sekitar 20 kasus, satu hasil yang tidak konsisten dapat mengubah tingkat kelulusan sebesar 5 poin, sehingga angka tersebut tidak lagi informatif. Jika jumlahnya mencapai beberapa ratus kasus, setiap proses evaluasi memerlukan biaya dan waktu nyata, sementara setiap kasus tambahan hanya memberikan sedikit cakupan. Ukuran yang penting bukan jumlah kasus, melainkan proporsi jenis kegagalan produksi yang telah diketahui yang muncul setidaknya satu kali dalam set tersebut.
Dapatkah saya mempercayai juri LLM untuk menilai agen saya?
Hanya setelah Anda mengukurnya berdasarkan label Anda sendiri. Simpan 30 kasus yang telah Anda nilai secara manual, lalu ukur kinerja juri terhadap kasus-kasus tersebut setiap kali Anda mengubah model juri atau prompt juri. Juri dapat menunjukkan bias terhadap panjang, yaitu jawaban yang diperpanjang lebih sering dinyatakan lulus, serta preferensi terhadap model sendiri, yaitu output dari keluarga modelnya sendiri dinilai dengan lebih baik. Keduanya dapat diuji: tambahkan isi pada jawaban yang gagal lalu nilai kembali, atau nilai jawaban yang sama dengan juri dari keluarga model lain. Jika juri tidak sesuai dengan label Anda pada lebih dari satu dari setiap sepuluh kasus, rubrik tersebut terlalu samar untuk digunakan.
Model mana yang harus menilai evaluasi?
Gunakan model yang murah, lalu eskalasikan bila perlu. Assertion deterministik tidak memerlukan biaya, sehingga dijalankan terlebih dahulu pada setiap kasus. Model kecil menangani kasus yang jelas lulus. Hanya kasus yang gagal dan verdict dengan tingkat keyakinan rendah yang diteruskan ke model frontier. Berdasarkan harga list pada August 2026, penilaian terhadap 1,000 kasus memerlukan biaya sekitar 1.80 dolar AS dengan Claude Haiku 4.5 dan sekitar 9.00 dengan Claude Opus 5. Karena proses evaluasi berjalan secara asynchronous, Batch API mengurangi separuh kedua biaya tersebut.
Apakah evaluasi menggantikan monitoring produksi?
Tidak, karena keduanya menjawab pertanyaan yang berbeda. Rangkaian evaluasi memberi tahu apakah perubahan yang akan Anda rilis membuat serangkaian kasus tetap menjadi lebih baik atau lebih buruk. Tracing dan monitoring memberi tahu apa yang sedang dialami pengguna nyata saat ini, termasuk input yang belum tercakup oleh kasus mana pun. Keduanya saling melengkapi: trace menyediakan kasus baru, sedangkan rangkaian evaluasi menentukan apakah perbaikan Anda benar-benar berhasil.