Hands-on · step by step
Tutorial: a golden-set eval suite that blocks bad prompt merges
by @elena — Evals & observability for LLM apps
Four steps to LLM evals in CI with promptfoo and Postgres — checkpoints, tuning knobs and the failure to guard first.
Before you start — about 45 minutes · promptfoo · Postgres · GitHub Actions
We rebuilt LLM evals in CI three times before it held. The version below is the one that survived a bad Tuesday and a worse Friday.
Step 1 — Decide what "working" means
Before touching code, write down three numbers: worst acceptable latency, cost per request, and what should happen when the upstream is unavailable. Everything after this is implementation.
Step 2 — Wire promptfoo
The whole flow is one path. No special branches for "large" inputs — express those as different limits, not different code.
# promptfoo config — fail the build, don't just print a score
prompts: [prompts/support.txt]
providers: [openai:gpt-4.1-mini, anthropic:claude-sonnet-4-5]
tests:
- vars: { question: "where is my refund?" }
assert:
- type: contains
value: "business days"
- type: latency
threshold: 2500
Why this works
The limit is enforced before the expensive call, not after it. Reserving budget up front means a burst degrades the caller that caused it instead of everyone downstream.
Step 3 — Guard the failure path
a 200-row golden set is enough; chasing 5,000 examples delays the actual fix
Write that guard before you write the feature. It costs an hour and saves a postmortem.
Step 4 — Verify it
npx promptfoo eval -c promptfooconfig.yaml --fail-on-error # exit code 1 blocks merge
- Lower the limit and confirm the command fails loudly
- Kill the upstream and confirm you get a typed error, not a timeout
- Check the metric you emit per outcome: ok, throttled, upstream_error
Tuning knobs
| Knob | Default | What we run | Why |
|---|---|---|---|
| concurrency | 8 | 24 | Most of the wait is network, not compute |
| retry budget | infinite | 2, with jitter | Unbounded retries turn an outage into an avalanche |
| timeout | 30s | 8s, inherited from the caller | Nobody waits 30s for a chat reply |
None of this was clever. It was just the work nobody wanted to do first.
Comments (2)
Log in to join the discussion.