Eval Suites
Eval suites turn Arena from “one-off blind battles” into “reproducible eval
a team can point at any model tier.” A suite is a YAML file of prompts.
Running it fires one battle per prompt, uses the configured
judge to cast votes automatically, and records
per-model win / loss / tie tallies + total spend under a run_id so you
can diff runs over time.
File format
Section titled “File format”Drop a file under suites/ (create the directory if it doesn’t exist):
name: team-evaldescription: | Real prompts we ship against. Judged by GPT-4o.category: codingprompts: - id: sql-window-fn prompt: | Given a table `events(user_id, ts, event_type)`, write a SQL query that returns each user's third `signup` event, ordered by ts. - id: python-refactor prompt: | Refactor this function to be idempotent and testable:
def process_signup(user_id): send_email(user_id, "welcome") record_metric("signup", user_id) return True - id: explain-concept prompt: | Explain "backpressure" in a streaming system to someone who understands HTTP but not distributed systems. One paragraph.Rules the loader enforces:
- One suite per file. Filename is convention;
name:is authoritative. namemust be unique across all files.promptsmust be a non-empty list.- Every prompt needs a unique
id(used for diff’ing runs) and a non-emptyprompt. categoryscopes to models with that category in their config; defaults togeneral.
Suites are read on server start. Add a file → restart.
Running a suite
Section titled “Running a suite”Suites REQUIRE a configured judge — otherwise every
prompt would stall waiting for a human vote. POST without a judge is a
400 error.
export ARENA_URL=https://arena.exampleexport ARENA_API_TOKEN=<one of your ARENA_API_TOKENS>
# List available suitescurl -sSH "Authorization: Bearer $ARENA_API_TOKEN" \ $ARENA_URL/api/suites | jq
# Kick off a runRUN=$(curl -sSXPOST -H "Authorization: Bearer $ARENA_API_TOKEN" \ $ARENA_URL/api/suites/team-eval/run | jq -r .run_id)
# Poll until donewhile :; do DETAIL=$(curl -sSH "Authorization: Bearer $ARENA_API_TOKEN" \ $ARENA_URL/api/suites/runs/$RUN) STATUS=$(echo "$DETAIL" | jq -r .status) echo "status: $STATUS" [ "$STATUS" = "completed" ] && break sleep 5done
# Show the tallyecho "$DETAIL" | jq '{total_cost, tally, battles: (.battles | length)}'What “run” gives you
Section titled “What “run” gives you”The run_id payload from GET /api/suites/runs/{run_id} returns:
status—running,completed, orerroredbattles_completed/battles_errored/battles_totaltotal_cost— provider + judge cost combined for the whole runbattles— one row per prompt withprompt_id,battle_id(linkable as/battle/<id>),winner, anderrorif it failedtally— per-model{wins, losses, ties, battles}aggregated across prompts
total_cost is the audit-of-record spend for the run. It uses real API
usage numbers where the provider returns them, not just what pricing you
configured.
Composing suites
Section titled “Composing suites”- Keep them small. 5-20 prompts per file so a run finishes in a minute or two. Bigger suites can wait on the async pattern in a future release.
- Give prompts meaningful IDs.
sql-window-fndiff’s better thanp1when you compare runs across models. - Version them. Commit suite YAMLs to your own repo. They’re the contract with your team about what “good enough” means.
Common shape: model-selection eval
Section titled “Common shape: model-selection eval”Point one suite at every candidate model, run once per candidate, diff
total_cost and per-model win share:
model X: 12 wins, 6 losses, 2 ties, $0.31 totalmodel Y: 8 wins, 10 losses, 2 ties, $0.04 totalIf Y wins 40% of the time at 1/8 the cost, that’s your answer.