A metric you do not gate on is decoration
Hit Rate, faithfulness, answer correctness - all of them tell you how Recall is doing right now. None of them stop a bad change from shipping unless something acts on the number. Engineers already solved this problem for code: a test suite that runs on every change and a CI job that refuses to deploy when tests fail. An eval suite is the same machinery pointed at model behaviour. Today we make Recall's evals block a regression, not just report one.
The eval suite is a test suite 9 min live
Reframe the whole track in one sentence: your golden set plus your metrics is a suite of tests, and every change to Recall is a commit that must pass it. A new system prompt, a swapped model, a tweaked retrieval k - each triggers a full run of the suite, and the result is a pass or a fail, not a paragraph of vibes. promptfoo is a config-driven runner built for exactly this: you declare prompts, providers, and tests, and it runs the matrix.
LiveAssertions: contains, similar, llm-rubric4 min▶
A test needs an assertion - the thing that turns an output into a pass or a fail. promptfoo gives you a ladder of them, from cheap and strict to smart and fuzzy, and you pick per test.
- contains / equals - the output must contain (or exactly equal) a string. Cheap, deterministic, perfect for "the refund answer must mention 5 business days".
- similar - an embedding assertion: the output must be semantically close to an expected answer above a similarity threshold. Forgives phrasing, catches meaning drift.
- llm-rubric - an LLM grades the output against a rubric you write in plain language ("the answer is polite and cites a policy"). This is the LLM-as-judge from b4, wired as a test assertion.
Self-studyA promptfoo config for Recall4 min read▶
Everything lives in promptfooconfig.yaml: the prompts under test, the providers (models) to run them on, and the tests - each with its variables and its assertions. This is the whole suite as one declarative file.
Run the whole thing with one command. promptfoo executes every prompt against every provider for every test, applies the assertions, and prints a pass/fail matrix.
contains or equals first - they are free and deterministic. Use similar when phrasing varies. Save llm-rubric for judgements a string match cannot make, since it costs a model call per test. A suite that is all rubric is slow and non-deterministic; a suite that is all string-match misses meaning drift.
The CI threshold gate 9 min live
Running the suite is half the job. The other half is making its result block a deploy. In CI that means one thing: an exit code. A job that exits 0 lets the pipeline continue; a job that exits non-zero stops it. So the gate is simple - run the suite, compare the pass rate to a threshold, and exit 1 when the threshold is breached. The pipeline does the rest.
LiveThe bash gate: parse, compare, exit5 min▶
promptfoo can write its results as JSON. A short script reads the failure count out of that JSON, computes the pass rate, checks it against your threshold, and sets the exit code. CI reads the exit code and gates the deploy. Note the escaped comparison and redirect operators - bash uses -gt and -lt for integer comparison and > to redirect output.
contains "5 business days" assertion now fails on four questions because the new model rounds to "about a week", and the llm-rubric password test fails twice. Pass rate drops to 88%, below the 95% threshold, the gate script exits 1, and the pipeline blocks the merge with eval-report.txt attached. The cost saving was real; the silent quality drop would have shipped without this gate. The exit code caught it before a single user saw the worse answer.Self-studyThe same gate from LangSmith's dataset side4 min read▶
promptfoo drives the suite from a YAML config. LangSmith drives the same discipline from a stored dataset: you register a dataset of examples, define evaluators, and call evaluate(). The result is a run you can compare against previous runs and threshold in exactly the same way.
evaluate()'s result is turned into an exit code - varies by version and by your pipeline (GitHub Actions, GitLab, and so on). Treat the bash gate here as the shape of the solution, not a copy-paste for your setup. The durable idea is: run the suite, reduce it to a pass rate, threshold it, and let the exit code gate the deploy. OpenAI's evals framework follows the same run-and-threshold shape from its own harness.
Define Recall's gate ★ 10 min · design it
A gate is a decision made in advance. Write Recall's now, before a risky change forces the question in a hurry.
Set the threshold. Decide the pass rate below which a change to Recall must be blocked. Base it on today's suite result, not a wish. Write it as one number with one sentence of justification.
Pick assertion one. Choose a strict, cheap assertion to gate on - a contains on a fact that must appear (a specific policy number, a refund window). Name the question it guards.
Pick assertion two. Choose a smarter assertion - a similar or llm-rubric that catches meaning drift a string match would miss. Write the rubric or the expected answer in one line.
Trace the exit code. In one line each: what does the pipeline do when the gate exits 0, and what does it do when it exits 1? If you cannot state both, the gate is not wired yet.
Before session b9 ◐ 50 min total
- Write a
promptfooconfig.yamlwith at least four of Recall's golden questions and one assertion of each type:contains,similar, andllm-rubric. Runpromptfoo evaland read the matrix. - Adapt the bash gate to your own JSON output: confirm it exits 1 when you deliberately break one test, and exits 0 when you fix it. Verify with
echo $?after each run. - Change Recall's prompt in a way you expect to help, re-run the suite, and check whether the gate agrees - a reminder that intuition and the number can disagree.
- Read: promptfoo config reference and CI/CD guide; skim the LangSmith
evaluate()and OpenAI evals docs for the same run-and-threshold shape.
Official sources covered
Taught from official docs. This page covers promptfoo's config and assertions in full; the CI wiring and the other runners are shown at concept level.
Three questions before you go 🎯 ◐ 90 seconds
1 · In a CI gate, what actually blocks the deploy when the eval fails?
CI gates on the exit code: 0 lets the pipeline continue, non-zero stops it. The gate script exits 1 when the pass rate breaches the threshold, and that exit code is what blocks the deploy.
2 · Which promptfoo assertion uses an LLM to grade the output against criteria you write?
llm-rubric hands the output to an LLM with your plain-language rubric - the LLM-as-judge wired in as a test. contains is a string match; similar is an embedding-distance check.
3 · Why set the pass-rate threshold from the currently shipped version's score?
The gate's job is to catch a regression - a change that makes the system worse than today. Anchoring the threshold to the known-good baseline makes it a real drop detector rather than an unreachable target.