SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-13

Eval Self-Hosted untuk Menguji AI Agent

Pelajari siklus eval yang Anda miliki sendiri: susun kasus dari trace nyata, gunakan pemeriksaan deterministik, lalu lacak pass rate per commit.

Apa itu eval self-hosted untuk AI agent

Eval self-hosted untuk AI agent terdiri atas empat hal yang Anda simpan di repository sendiri: file berisi kasus yang telah disimpan, skrip yang menjalankan agent pada kasus tersebut, serangkaian pemeriksaan untuk menilai setiap jawaban, dan tabel hasil yang dapat Anda kueri. Tidak ada satu pun komponen tersebut yang memerlukan vendor. Seluruh siklusnya dapat dibuat dengan beberapa ratus baris Python dan satu file SQLite.

Agent berhasil dalam demo karena Anda sendiri yang memilih lima input. Pada minggu kedua, agent mulai gagal karena satu baris prompt berubah, model berubah, atau deskripsi tool berubah, sementara tidak ada pengukuran yang mencakup perubahan tersebut. Siklus eval mengubah "sekarang hasilnya terasa lebih buruk" menjadi "pass rate turun dari 58 dari 60 menjadi 51 dari 60 pada commit 4f1c9ab".

Siklus ini memiliki empat langkah, dan panduan ini membahas satu langkah per bagian: kumpulkan trace nyata, jadikan trace yang menarik sebagai kasus, nilai setiap kasus pada setiap perubahan, lalu simpan pass rate di samping commit yang menghasilkannya. Siklus yang sama dapat digunakan pada agent apa pun yang Anda jalankan, dan framework agent self-hosted yang layak dijalankan terutama berbeda dalam hal seberapa banyak trace yang tersedia secara otomatis untuk Anda.

Mengapa agent rusak pada minggu kedua

Agent terdiri atas prompt, model, definisi tool, dan konteks yang diambil saat runtime. Keempatnya dapat berubah tanpa perubahan pada kode aplikasi, sehingga code review biasa tidak menemukan hal yang perlu dipermasalahkan.

Penyebab yang paling umum adalah perubahan prompt. Anda menambahkan satu kalimat untuk mencegah balasan yang kasar. Kalimat itu mengubah perilaku pada input yang tidak 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 tool call apa pun dan balasannya justru berupa permintaan maaf yang sopan. Tidak ada error yang muncul, sehingga tidak ada alert yang terpicu.

Penyebab kedua adalah model. Catat string model yang persis dikirim bersama setiap proses, claude-haiku-4-5-20251001 bukan singkatan yang hanya Anda ingat sendiri, karena penurunan pass rate pada hari saat Anda mengganti model hanya dapat didiagnosis jika model tersebut tercantum dalam baris data.

Penyebab ketiga adalah tool. Mengubah susunan kata dalam deskripsi tool memengaruhi kapan model memutuskan untuk memanggilnya. Jika tool Anda berasal dari MCP server yang berjalan pada VPS, skemanya berada di proses lain, sehingga dapat berubah tanpa Anda sadari dan tanpa diff apa pun dalam repository Anda. Penyebab keempat adalah retrieval: pertanyaan yang sama mengakses index yang dibangun ulang pada malam sebelumnya, lalu jawabannya mengikuti dokumen baru.

Bangun set acuan dari trace yang sudah Anda kumpulkan

Jangan membuat kasus evaluasi sendiri. Ambil kasus dari trafik. Jika Anda sudah menjalankan tracing Langfuse yang di-host sendiri untuk agen Anda, setiap request disimpan bersama input, pemanggilan tool, dan output-nya. Data tersebut merupakan bahan mentah yang tepat untuk membuat kasus.

Ekspor jendela observasi root melalui API publik. API ini menggunakan autentikasi dasar, dengan public key sebagai username dan secret key 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 parser apa pun. Baris data dikembalikan di bawah data, tetapi nama field yang menyimpan pertanyaan dan jawaban bergantung pada cara agen Anda menginstrumentasikan span. Karena itu, petakan field yang benar-benar Anda lihat, bukan field yang Anda perkirakan. 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 ini tetap berguna saat dijalankan:

  • Mulai dengan 40 hingga 80 kasus. Jika kurang dari 20, satu kasus yang flaky dapat mengubah pass rate sebesar 5 poin, sehingga angka yang berubah tanpa alasan akan diabaikan.
  • Setiap bug production yang Anda perbaiki menjadi sebuah kasus pada hari bug tersebut diperbaiki. Kebiasaan ini membuat set berkembang ke arah yang benar.
  • Satu perilaku per kasus. Kasus yang sekaligus memeriksa jumlah refund dan nada respons tidak memberi informasi saat gagal.
  • id tidak pernah berubah, karena id tersebut digunakan untuk membandingkan hasil run hari ini dengan hasil run bulan lalu.
  • Lakukan redaksi sebelum commit. File ini disimpan di git, jadi hapus nama pelanggan dan nomor pesanan yang bukan milik Anda.

Lakukan penilaian dengan pemeriksaan deterministik terlebih dahulu karena pemeriksaan ini tidak memerlukan biaya

Apa pun yang memiliki jawaban benar harus diperiksa dengan assertion sederhana. Tidak ada pemanggilan model, biaya, atau ambiguitas. Pemeriksaan deterministik menangkap regresi struktural. Regresi inilah 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. Bagian lain dalam harness bersifat generik.

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 failures

Sertakan batas penggunaan tool dalam daftar tersebut. Agent yang menyelesaikan suatu kasus dengan 3 pemanggilan hari ini dan 11 pemanggilan besok mengalami regresi meskipun jawaban akhirnya benar, karena setiap pemanggilan menimbulkan biaya.

LLM sebagai juri dan empat cara kegagalannya

Apa pun yang lolos dari assertion memerlukan grader yang dapat membaca. LLM judge adalah pemanggilan model kedua: model ini 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 nadanya, bukan keduanya sekaligus.
  • Berikan jawaban yang diharapkan kepada judge jika kasus tersebut memilikinya. Menilai berdasarkan referensi jauh lebih mudah daripada menilai 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 adalah mode kegagalannya. Masing-masing memiliki pengujian yang dapat Anda jalankan sore ini. Pengujian ini penting karena judge yang tidak diperiksa menghasilkan angka yang tampak presisi, tetapi tidak bermakna.

Bias panjang. Jawaban yang lebih panjang lebih sering lolos. Uji dengan mengambil sepuluh jawaban yang gagal menurut judge, menambahkan dua paragraf isian meyakinkan yang tidak menambahkan fakta baru pada masing-masing jawaban, lalu menilainya kembali. Jika ada verdict yang berubah menjadi lolos, itu adalah bias panjang, dan rubric adalah bagian yang perlu diperbaiki.

Preferensi terhadap model sendiri. Judge sering menilai output dari keluarga modelnya sendiri dengan lebih baik daripada output dari model lain. Uji dengan menilai 30 jawaban yang sama menggunakan judge dari dua keluarga model berbeda, lalu bandingkan verdict satu per satu. Jika 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 perbandingan berpasangan belum aman untuk rubric tersebut.

Pergeseran rubric. Kriteria yang tidak jelas menghasilkan judge yang cenderung menyetujui. "Apakah jawaban ini membantu" hampir selalu lolos. "Apakah jawaban menyatakan jumlah refund dalam dolar" hanya meloloskan jawaban yang memang Anda maksud. Tulis ulang setiap kriteria sampai kriteria tersebut menyebutkan fakta yang diperiksa.

Satu pengaman mencakup keempatnya. Simpan 30 kasus yang Anda beri label secara manual, lalu bandingkan skor judge dengan label Anda setiap kali mengubah model judge atau prompt judge. Jika hasilnya berbeda dari penilaian Anda pada lebih dari satu dari sepuluh kasus, perbaiki rubric sebelum mempercayai pass rate yang dihasilkannya. Judge adalah kode, jadi judge harus diberi versioning dan ditinjau seperti kode.

Gunakan model murah terlebih dahulu, lalu tingkatkan ke model frontier

Menilai setiap kasus dengan model termahal pada setiap commit dapat membuat tagihan evaluasi melampaui biaya agent yang diuji. Urutkan grader berdasarkan harga, lalu hentikan proses segera setelah hasilnya jelas.

ChartCost to judge 1,000 eval cases, list prices, August 2026
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 tampak kecil hingga dikalikan dengan jumlah pemanggilan. Set evaluasi yang terdiri dari 60 kasus, dinilai pada setiap commit, dengan 40 commit per minggu, menghasilkan 2,400 pemanggilan judge per minggu bahkan sebelum siapa pun menjalankan pekerjaan nightly.

Dua diskon dapat diterapkan dengan mudah pada pekerjaan evaluasi, dan keduanya dapat digunakan bersamaan. Eval tidak bersifat 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. Dengan demikian, cache sudah menutup biayanya setelah satu kali cache hit. Harga tersebut adalah harga daftar Anthropic per August 2026. Sonnet 5 menggunakan harga perkenalan hingga 31 August 2026, sehingga batang ketiga meningkat setelah tanggal tersebut.

Urutannya adalah sebagai berikut:

  • Pemeriksaan deterministik pada setiap kasus. Tidak ada biaya API.
  • Judge dengan model kecil untuk kasus yang lolos dari pemeriksaan tersebut.
  • Judge frontier hanya untuk kasus yang dinyatakan gagal oleh judge kecil, atau dinyatakan lulus dengan tingkat keyakinan rendah.
  • Tinjauan 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 dan jangan berasumsi. Sekali sebulan, nilai seluruh set menggunakan judge ketat, lalu bandingkan kedua kolom. Jika hasilnya berbeda pada lebih dari beberapa kasus, rubrik Anda terlalu longgar untuk model kecil. Rubrik itulah yang perlu diperbaiki. Pengendalian biaya yang dikeluarkan oleh agent itu sendiri merupakan pekerjaan terpisah, yang dibahas dalam pengendalian biaya untuk agent AI pada VPS.

Pantau tingkat kelulusan dari waktu ke waktu pada sistem yang Anda miliki

Tingkat kelulusan yang tidak dapat dikaitkan dengan commit hanyalah perkiraan. Simpan satu baris untuk setiap kasus pada setiap eksekusi, 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. Eksekusi harian selama satu tahun untuk 60 kasus menghasilkan sekitar 22,000 baris, sehingga penyimpanan ini 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 amount

Jalankan 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-push

Eksekusi penuh lebih lambat dan sebaiknya dijadwalkan. Service dan timer systemd pada VPS yang berjalan setiap malam mengeksekusi seluruh rangkaian terhadap prompt yang telah di-deploy. Cara ini menangkap perubahan yang berasal dari luar repository Anda, seperti hosted tool yang perilakunya berubah.

Tinjauan manusia, berdasarkan sampel, bukan secara menyeluruh

Judge dikalibrasi berdasarkan label manusia, sehingga seseorang harus membuat label tersebut. Baca sampel setiap minggu: semua kasus yang gagal dinilai oleh judge, ditambah sepuluh kasus yang dinilai lulus dan dipilih secara acak. Kasus lulus yang dipilih secara acak adalah bagian yang penting, karena judge yang diam-diam mulai meluluskan jawaban buruk akan terlihat sempurna pada dashboard apa pun yang dibangun dari hasil penilaiannya sendiri.

Lima belas kasus dengan durasi tiga menit per kasus membutuhkan waktu 45 menit per minggu. Proses ini menghasilkan koreksi pada rubric ketika penilaian Anda berbeda dari penilaian judge, serta kasus baru untuk jenis kegagalan yang belum pernah dibayangkan. Tulis penilaian manusia ke dalam tabel yang sama dengan graded_by diatur ke human, sehingga kesesuaian antara judge dan manusia dapat diperoleh melalui query, bukan hanya diingat.

Hal-hal yang rusak pada harness evaluasi itu sendiri

anthropic.RateLimitError pada eksekusi penuh pertama. Enam puluh kasus yang dijalankan secara bersamaan melebihi batas request atau token untuk tier Anda. Batasi konkurensi hingga empat worker, lalu pindahkan eksekusi malam ke Batch API.

json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) dari judge. Model menjawab dalam bentuk prosa atau membungkus JSON-nya dalam code fence. Coba lagi sekali, lalu catat kasus tersebut sebagai error. Jangan pernah menganggap kegagalan parsing sebagai pass, karena suite yang mengubah error menjadi pass akan mendekati 100% sementara agent semakin buruk.

Kasus yang tidak stabil. Input yang sama berhasil pada satu eksekusi, tetapi gagal pada eksekusi berikutnya karena agent mengambil sampel output-nya. Jalankan kasus yang tidak stabil tersebut tiga kali dan catat proporsinya, bukan menghapus kasus itu. Kasus yang berhasil pada dua dari tiga eksekusi menunjukkan bug robustness yang nyata, dan pelanggan akan menemukannya.

Golden set yang mengalami pembusukan. Seseorang mengubah expected answer agar suite kembali 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. Pass rate yang tetap 100% selama sebulan berarti set tersebut sudah tidak lagi mencerminkan produk. Ambil sepuluh trace terbaru, temukan trace yang ditangani dengan buruk oleh agent, lalu tambahkan trace tersebut.

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 kurang dari sekitar 20 kasus, satu hasil yang tidak konsisten dapat mengubah tingkat kelulusan sebesar 5 poin. Akibatnya, angka tersebut tidak lagi memberikan informasi yang berarti. Jika jumlahnya sudah mencapai beberapa ratus kasus, setiap proses evaluasi memerlukan biaya dan waktu nyata, sedangkan setiap kasus tambahan hanya memberikan sedikit cakupan. Ukuran yang penting bukan jumlah kasusnya, melainkan persentase jenis kegagalan produksi yang sudah diketahui dan muncul setidaknya satu kali dalam set tersebut.

Dapatkah saya mempercayai penilai LLM untuk menilai agen saya?

Hanya setelah Anda mengukurnya berdasarkan label yang Anda buat sendiri. Simpan 30 kasus yang telah Anda nilai secara manual, lalu ukur penilai tersebut terhadap kasus-kasus itu setiap kali Anda mengubah model penilai atau prompt penilai. Penilai dapat menunjukkan bias terhadap panjang jawaban. Jawaban yang ditambahi teks dapat lebih sering lulus. Penilai juga dapat menunjukkan preferensi terhadap model sendiri. Output dari keluarga modelnya sendiri dapat dinilai dengan lebih baik. Keduanya dapat diuji. Tambahkan teks pada jawaban yang gagal, lalu nilai kembali jawaban tersebut. Anda juga dapat menilai jawaban yang sama dengan penilai dari keluarga model lain. Jika penilai tidak sesuai dengan label Anda pada lebih dari satu dari setiap sepuluh kasus, rubrik tersebut terlalu samar untuk digunakan.

Model mana yang sebaiknya menilai evaluasi?

Gunakan penilaian murah terlebih dahulu, lalu tingkatkan ke model yang lebih besar bila perlu. Pernyataan deterministik tidak memerlukan biaya, sehingga dapat dijalankan terlebih dahulu pada setiap kasus. Model kecil dapat menangani kasus yang jelas lulus. Hanya kasus yang gagal dan hasil dengan keyakinan rendah yang diteruskan ke model frontier. Berdasarkan harga daftar pada August 2026, penilaian terhadap 1,000 kasus memerlukan biaya sekitar 1.80 US dollars dengan Claude Haiku 4.5 dan sekitar 9.00 dengan Claude Opus 5. Karena proses evaluasi berjalan secara asinkron, Batch API mengurangi separuh biaya tersebut.

Apakah evaluasi menggantikan pemantauan produksi?

Tidak, karena keduanya menjawab pertanyaan yang berbeda. Rangkaian evaluasi memberi tahu apakah perubahan yang akan Anda rilis membuat sekumpulan kasus tetap menjadi lebih baik atau lebih buruk. Tracing dan pemantauan memberi tahu apa yang sedang dialami pengguna nyata, termasuk input yang tidak tercakup oleh kasus mana pun. Keduanya saling melengkapi. Tracing menyediakan kasus baru, sedangkan rangkaian evaluasi menentukan apakah perbaikan Anda benar-benar berhasil.