AI
OpenAI Evals Dihentikan: Migrasikan Eval Anda ke Promptfoo
Oktober 202611 menit baca

OpenAI mengumumkan deprecation platform Evals pada 3 Juni 2026. Eval yang ada menjadi read-only pada 31 Oktober 2026, dan dashboard serta API Evals dijadwalkan dimatikan pada 30 November 2026. OpenAI merekomendasikan Promptfoo sebagai penggantinya.
Ekspor tiga hal untuk setiap eval: objek eval beserta data_source_config dan testing_criteria, run terakhir yang selesai beserta model dan template input_messages-nya, serta output item dari run itu yang menyimpan setiap baris asli sebagai datasource_item bersama skor per grader. Ketiganya cukup untuk membangun ulang suite dan memeriksa bahwa grader baru sepakat dengan skor lama.
Hanya sebagai riwayat. promptfoo import menerima ekspor dashboard OpenAI Evals seperti eval_items_*.jsonl dan menyimpannya sebagai catatan eval historis lengkap dengan skor grader, tetapi tidak mendukung respons list output item dari API dan tidak membuat config yang bisa dijalankan. Cookbook OpenAI menjelaskan pembuatan ulang eval secara manual di promptfooconfig.yaml.
string_check eq, ne, like, dan ilike dipetakan ke equals, not-equals, contains, dan icontains dengan hasil identik. text_similarity cosine dipetakan ke similar, metrik similarity lainnya ke bleu, gleu, meteor, dan rouge-n, sedangkan score_model atau label_model ke llm-rubric dengan threshold. Grader python menjadi assertion python dengan signature get_assert(output, context), bukan grade(sample, item).
OpenAI mengumumkan akuisisi Promptfoo pada 9 Maret 2026 dan berkomitmen tetap memelihara tool open-source-nya, sambil mengintegrasikan teknologinya ke platform enterprise OpenAI Frontier. Menyimpan konfigurasi eval, data test, dan grader sebagai file biasa di git tetap menjadi lindung nilai paling aman, karena semuanya tetap portabel apa pun yang terjadi pada produk hosted.

Ringkasan Utama
OpenAI Evals menjadi read-only pada 31 Oktober 2026, lalu dashboard dan API-nya dimatikan pada 30 November 2026. Ekspor definisi, run terakhir, dan baris dataset setiap eval lewat API, impor JSONL dari dashboard ke promptfoo hanya sebagai arsip riwayat, lalu bangun ulang setiap grader sebagai assertion promptfoo dalam suite YAML yang dijalankan GitHub Actions.
Penghentian OpenAI Evals punya dua tanggal, dan yang pertama tinggal sebulan lagi. Pada 31 Oktober 2026 eval yang sudah ada menjadi read-only; pada 30 November 2026 dashboard dan API Evals dijadwalkan dimatikan. Suite regresi yang menjaga prompt production, misalnya bot helpdesk ERP yang mengklasifikasikan tiket dan menyusun balasan soal tagihan, ikut hilang pada tanggal kedua kalau tidak ada yang memindahkannya. Halaman deprecations OpenAI sendiri menyebut promptfoo sebagai jalur penggantinya.
Ini panduan migrasi yang dikejar tenggat. Isinya: apa yang harus diekspor sebelum tanggal read-only dan lewat panggilan API mana, kenapa perintah import di promptfoo memberi Anda riwayat tetapi bukan suite yang bisa dijalankan, bagaimana setiap tipe grader OpenAI dipetakan ke assertion promptfoo dan di mana skornya tidak akan sama, serta cara menjalankan suite hasil migrasi sebagai gerbang merge di GitHub Actions. Setiap perilaku yang dikutip berasal dari dokumentasi dan cookbook OpenAI atau referensi command-line promptfoo.
OpenAI mengumumkan deprecation platform Evals pada 3 Juni 2026. Halaman deprecations mencantumkan tiga tanggal: pengumuman itu, 31 Oktober 2026 saat eval yang ada menjadi read-only, dan 30 November 2026 saat dashboard dan API Evals dijadwalkan dimatikan. Grader yang didokumentasikan untuk alur kerja eval termasuk dalam transisi yang sama, jadi definisi grader di dalam testing_criteria Anda juga punya batas waktu, bukan hanya dashboard di sekelilingnya.
Halaman yang sama menjadwalkan API v1/prompts dan reusable prompt object untuk dimatikan pada tanggal 30 November yang sama. Tim yang menyimpan prompt dan eval-nya di dashboard punya dua migrasi dengan satu tenggat, dan urutan termurah adalah memindahkan teks prompt ke repository lebih dulu, karena eval hasil migrasi perlu merujuk ke sana.
Anggap 31 Oktober sebagai tenggat sebenarnya, bukan 30 November. OpenAI tidak merinci operasi apa yang masih diizinkan pada eval read-only, jadi rencanakan seolah-olah Anda tidak bisa mengedit eval, menambah grader, atau memulai run baseline baru setelah tanggal itu. Ekspor apa pun yang bergantung pada pembuatan run baru harus selesai bulan ini.
Satu eval di platform tersebar di tiga objek, dan mengekspor hanya objek pertama adalah kesalahan yang paling sering terjadi. Objek eval menyimpan data_source_config dan testing_criteria. Sebuah run menyimpan model dan template input_messages yang dipakai untuk merender setiap baris. Output item dari sebuah run menyimpan setiap baris asli sebagai datasource_item, ditambah hasil per grader berupa score dan flag passed. Script di bawah menelusuri ketiganya untuk setiap eval dalam satu project dan menuliskannya ke disk.
# export_openai_evals.py — run it while the API still answers.
# Evals are listed per project, so run it once per project key.
import json, pathlib
from openai import OpenAI
client = OpenAI()
OUT = pathlib.Path("evals-export")
for ev in client.evals.list(limit=100): # the SDK pages through every eval
folder = OUT / ev.id
folder.mkdir(parents=True, exist_ok=True)
# The definition: data_source_config + testing_criteria (the graders).
(folder / "eval.json").write_text(ev.model_dump_json(indent=2))
runs = [r for r in client.evals.runs.list(ev.id) if r.status == "completed"]
if not runs:
continue # an eval that never ran has no dataset to recover
latest = max(runs, key=lambda r: r.created_at)
# What eval.json does not hold: data_source.model and the
# input_messages template the rows were rendered into.
(folder / "run.json").write_text(latest.model_dump_json(indent=2))
with (folder / "tests.jsonl").open("w", encoding="utf-8") as tests, \
(folder / "baseline.jsonl").open("w", encoding="utf-8") as base:
for out in client.evals.runs.output_items.list(latest.id, eval_id=ev.id):
# datasource_item is the original row. Nesting it under vars.item
# lets the old "item." template references work unchanged.
tests.write(json.dumps({
"description": f"row {out.datasource_item_id}",
"vars": {"item": out.datasource_item},
}) + "\n")
# The old scores, per grader: your parity baseline later.
base.write(json.dumps({
"row": out.datasource_item_id,
"scores": {r.name: r.score for r in out.results},
}) + "\n")Script ini menulis tests.jsonl langsung dalam format test promptfoo, satu test per baris dengan baris asli disarangkan di bawah vars.item. Penyarangan itu disengaja: template OpenAI merujuk namespace item, dan dengan bentuk yang sama, template prompt lama dan referensi grader lama bisa dipakai tanpa perlu mengganti nama. File baseline mencatat skor lama setiap grader pada setiap baris, dan itulah satu-satunya cara membuktikan nanti bahwa hasil port berperilaku seperti aslinya.
Jangan merencanakan migrasi dengan mengandalkan ekspor sekali klik yang menghasilkan config promptfoo siap pakai. Versi terbaru panduan cookbook OpenAI menjelaskan pembuatan ulang eval secara manual dan menyebut prosesnya tidak memerlukan fitur ekspor OpenAI Evals. Yang memang tersedia adalah jalur riwayat: promptfoo import menerima ekspor dashboard OpenAI Evals bernama seperti eval_items_*.jsonl dan menyimpannya sebagai catatan eval historis di promptfoo.
Referensi promptfoo menjelaskan apa yang dipertahankan oleh import tersebut: setiap item sumber di bawah vars.item, nilai grader, status pass dan fail bila ekspornya memuatnya, serta output model dan pemakaian token bila ekspornya menyertakan data sample. Baris grader yang punya skor tetapi tanpa status pass tetap tercatat sebagai skor saja, tidak diubah menjadi kegagalan. Referensi itu juga menyatakan bahwa import mendukung ekspor JSONL dari dashboard, bukan respons list output item dari API.
# History: the dashboard JSONL export of a completed run.
npx promptfoo import eval_items_OutputDataItemStatusParam.ALL.jsonl
npx promptfoo view # imported runs appear as historical evals
# Wrong: saving GET /v1/evals/{eval_id}/runs/{run_id}/output_items to a
# file and importing that. promptfoo documents the dashboard JSONL only;
# the API list response is not a supported import shape.
# Right: the API export feeds the REBUILD (tests.jsonl, baseline.jsonl),
# the dashboard export feeds the ARCHIVE. Keep both.Jadi simpan kedua artefak itu, untuk alasan yang berbeda. Ekspor dashboard adalah arsip: run lama yang masih bisa dibuka di promptfoo view setelah platformnya hilang, berguna saat ada yang bertanya berapa skor bot sebelum pergantian model kuartal lalu. Ekspor API dari bagian sebelumnya adalah bahan mentah untuk suite yang benar-benar akan Anda jalankan mulai sekarang.
Panduan graders OpenAI mendefinisikan empat keluarga grader yang dipakai di eval: string check, text similarity, score model, dan eksekusi kode Python, ditambah label model grader di referensi API. Masing-masing punya assertion promptfoo yang menjalankan tugas yang sama, tetapi hanya string check yang benar-benar setara. Kolom ketiga adalah yang perlu dibaca sebelum memercayai skor hasil port.
| Grader OpenAI | Assertion promptfoo | Kesetaraan dengan skor lama |
|---|---|---|
| string_check, eq | equals | Persis. Keduanya case-sensitive dan menghasilkan pass atau fail. |
| string_check, ne | not-equals | Persis. Assertion promptfoo apa pun bisa dibalik dengan prefix not-. |
| string_check, like dan ilike | contains dan icontains | Persis. like adalah uji substring case-sensitive, ilike versi case-insensitive. |
| text_similarity, cosine | similar | Mendekati. Keduanya memakai embedding text-embedding-3-large secara default; bawa pass_threshold sebagai threshold. |
| text_similarity, bleu, gleu, meteor, rouge | bleu, gleu, meteor, rouge-n | Metrik sama, implementasi berbeda. Buat baseline ulang sebelum dijadikan gerbang. |
| text_similarity, fuzzy_match | assertion python yang memanggil rapidfuzz | Persis bila memanggil fungsi rapidfuzz yang sama. levenshtein bukan padanannya. |
| score_model dan label_model | llm-rubric dengan threshold | Tidak persis. Prompt dan model judge yang berbeda akan memberi skor berbeda. |
| python, grade(sample, item) | python, get_assert(output, context) | Persis setelah signature ditulis ulang; logika penilaiannya bisa dipindah apa adanya. |
Baris-baris itu terbagi dua kelompok. Grader deterministik, yaitu string check dan grader python yang logikanya tidak diubah, seharusnya menghasilkan skor lama pada setiap baris, jadi selisih apa pun di sana adalah bug porting. Baris yang dinilai model akan bergeser, dan cookbook OpenAI menyatakannya terang-terangan: skor berbasis similarity mungkin tidak identik antar sistem, dan grader LLM-as-a-judge yang dibuat ulang harus divalidasi sebelum dipakai untuk keputusan regresi.
Ambil contoh konkret: eval helpdesk ERP dengan tiga kriteria, yaitu string check bahwa balasan menyebut refund, score model grader yang menilai apakah balasan tetap berpijak pada teks kebijakan, dan grader python yang membandingkan balasan dengan jawaban referensi. Config di bawah membangunnya ulang, dan setiap baris menunjuk kembali ke field hasil ekspor yang menjadi asalnya.
# evals/support-reply/promptfooconfig.yaml
# Ported from the dashboard eval "Support answer quality".
description: ERP helpdesk reply quality
prompts:
- file://prompt.json # run.json -> input_messages template
providers:
- id: openai:responses:gpt-6-astra # run.json -> data_source.model
tests: file://tests.jsonl # one test per old datasource_item
defaultTest:
options:
# Pin the judge. Unset, promptfoo picks one from the API keys present,
# and a judge you did not choose is a score you cannot compare.
provider: openai:gpt-5-mini
assert:
# string_check, operation ilike, reference "refund" -> icontains
- type: icontains
value: refund
metric: mentions_refund
# score_model, range [0, 1], pass_threshold 0.7 -> llm-rubric
- type: llm-rubric
value: >-
The reply answers the question using only the policy text in the
ticket, quotes the invoice number exactly, and does not promise a
refund date.
threshold: 0.7
metric: policy_grounded
# python grader -> python assertion (see graders/reference_similarity.py)
- type: python
value: file://graders/reference_similarity.py
metric: reference_similarity
# evals/support-reply/prompt.json — the old template, copied verbatim.
# [
# { "role": "system", "content": "You answer ERP billing tickets. ..." },
# { "role": "user", "content": "{{ item.ticket_text }}" }
# ]Dua detail lebih penting daripada kelihatannya. Provider judge dikunci di level defaultTest, karena llm-rubric di promptfoo sebaliknya memilih model penilai berdasarkan API key yang tersedia, dan judge yang tidak dikunci membuat skor minggu ini tidak bisa dibandingkan dengan skor bulan depan. Lalu, saat llm-rubric memakai threshold, promptfoo menegakkan vonis pass dari judge sekaligus threshold-nya, sehingga sebuah baris bisa gagal dengan skor lumayan bila judge berkata tidak.
# Before: OpenAI python grader (and text_similarity fuzzy_match, which
# uses rapidfuzz too). Signature fixed by the platform:
# def grade(sample, item) -> float
# sample["output_text"], item["reference_answer"]
# After: graders/reference_similarity.py, a promptfoo python assertion.
from rapidfuzz import fuzz, utils
PASS_THRESHOLD = 0.8 # copy the old grader's pass_threshold, do not re-pick it
def get_assert(output: str, context) -> dict:
item = context["vars"]["item"] # the old "item" namespace, intact
# The same WRatio call, so each row's score is comparable to baseline.jsonl.
score = fuzz.WRatio(
output, item["reference_answer"], processor=utils.default_process
) / 100.0
return {
"pass": score >= PASS_THRESHOLD,
"score": score,
"reason": f"WRatio {score:.2f} against the reference answer",
}
# Wrong: porting fuzzy_match as
# - type: levenshtein
# threshold: 0.9
# levenshtein's threshold is a maximum edit distance, not a 0-1 similarity.
# Every long answer fails, and the suite looks like a model regression.Grader python adalah port yang paling mudah sekaligus paling mudah salah secara halus. Isi fungsinya tetap; signature-nya berubah dari grade(sample, item) yang mengembalikan float menjadi get_assert(output, context), yang boleh mengembalikan boolean, skor, atau objek hasil berisi pass, score, dan reason. Mengembalikan objek sepadan dengan baris tambahannya, karena string reason itulah yang dibaca reviewer saat job CI gagal.
Begitu suite berada di repository, ia sebaiknya berjalan di tempat perubahan prompt di-review. promptfoo eval keluar dengan kode 100 saat minimal satu test gagal atau pass rate turun di bawah PROMPTFOO_PASS_RATE_THRESHOLD, dan dengan kode 1 untuk error lainnya, persis kontrak yang dibutuhkan job CI.
# .github/workflows/evals.yml
name: evals
on:
pull_request:
paths: ["evals/**", "src/prompts/**"]
jobs:
promptfoo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: npm ci # promptfoo pinned in devDependencies
- run: pip install rapidfuzz # for the ported python graders
- uses: actions/cache@v4
with:
path: ~/.cache/promptfoo
key: ${{ runner.os }}-promptfoo-${{ hashFiles('evals/**') }}
- name: Run ported evals
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Default is 100: a single failed row fails the job. Start from the
# pass rate the old dashboard eval actually had, then ratchet up.
PROMPTFOO_PASS_RATE_THRESHOLD: "95"
run: npx promptfoo eval -c evals/support-reply/promptfooconfig.yaml -o results.json
- if: always() # keep the evidence when the gate fails
uses: actions/upload-artifact@v4
with:
name: promptfoo-results
path: results.jsonThreshold ini berupa persentase dan default-nya 100, artinya satu baris gagal saja sudah menggagalkan job. Suite yang di-port dari eval dashboard yang secara historis lolos 96 persen baris akan memblokir setiap pull request sejak hari pertama dengan default itu. Tetapkan threshold dari baseline hasil ekspor, lalu naikkan seiring baris yang flaky diperbaiki atau dihapus, daripada mulai dari nol toleransi dan membuat tim terbiasa mengabaikan check merah.
Kunci promptfoo sebagai devDependency dan panggil lewat npm ci, bukan npx promptfoo@latest. Grader adalah kode; rilis yang mengubah cara sebuah metrik dihitung akan menggeser skor Anda tanpa ada perubahan pada prompt, dan itulah satu-satunya regresi yang tidak bisa dijelaskan oleh suite regresi.
Urutan di bawah mendahulukan semua langkah yang bergantung pada platform yang masih menerima pekerjaan, dan menyisakan langkah yang murni lokal untuk belakangan.
Jangan menghapus atau menulis ulang test hanya karena grader hasil port tidak sepakat dengan skor lama. Ketidaksepakatan itu bisa berupa bug porting atau perbedaan nyata antar judge, dan keduanya layak dicatat tertulis di pull request. Menghapus baris diam-diam adalah cara suite 200 baris berubah menjadi suite 140 baris yang selalu lolos.
OpenAI mengumumkan akuisisi Promptfoo pada 9 Maret 2026, berkomitmen tetap memelihara tool open-source-nya, dan menyatakan teknologinya akan diintegrasikan ke OpenAI Frontier, platform agent untuk enterprise. Artinya, pengganti yang direkomendasikan untuk produk OpenAI yang dihentikan kini adalah tool milik vendor yang sama. Itu bukan alasan untuk menghindarinya, tetapi alasan untuk bergantung pada bagian yang Anda kendalikan.
Bagian yang Anda kendalikan adalah konfigurasinya. Sebuah promptfooconfig.yaml, sebuah tests.jsonl, dan satu folder grader python adalah file biasa: di-diff saat review, diberi versi bersama prompt yang diujinya, dan tetap berjalan dengan baris provider lain bila model di balik bot berganti. Suite yang hilang pada bulan November adalah suite yang hanya tersimpan di dashboard hosted, dan hasil paling aman dari migrasi ini adalah memastikan itu menjadi yang terakhir yang bisa hilang dengan cara tersebut.
Aturan yang perlu dibawa: eval adalah kode test, jadi tempatnya di samping kode yang diujinya. Ekspor definisi, run, dan baris sebelum 31 Oktober, arsipkan riwayat dashboard dengan promptfoo import, bangun ulang grader sebagai assertion dengan grader deterministik yang hasilnya sama persis, dan biarkan exit code CI, bukan dashboard, yang memutuskan apakah sebuah perubahan prompt boleh dirilis.
Sumber