Your test suite has 400 assertions. Your prompts have zero.
This is the quiet embarrassment of most production LLM applications in 2026. The deterministic code around the model is covered to 85%, gated in CI, and blocked from merging on failure. The prompt — the component that actually decides what your users see — gets changed by whoever is on call, validated by "I tried it three times and it looked fine," and shipped.
Promptfoo closes that gap by treating prompts as testable code. You define test cases in YAML, run them against any provider, and get a pass/fail report you can gate a pull request on. It is open source, sits at 24.1k GitHub stars, and on March 9, 2026 OpenAI announced it was acquiring the company — while publicly committing to keep building the open-source project.
This guide covers the parts that matter for a real deployment: getting a first eval running, choosing the right assertion type, gating CI, and knowing where evals stop being enough.
Install
Promptfoo is a Node CLI. Version requirements are stricter than you might expect:
npm install -g promptfoo
Per the official docs, npm and npx require Node.js >=22.22.0, and Node.js 24 LTS is recommended. If you are on an older runtime, upgrade first:
nvm install 24
nvm use 24
node --version
For CI, pin the Node version to 24. For Docker, use a node:24 base image. You can also install via Homebrew on Mac and Linux, or add it as a library with npm install promptfoo --save.
Your first eval in one command
Skip the blank-page problem. Promptfoo ships runnable examples:
npx promptfoo@latest init --example getting-started
This creates a directory with a promptfooconfig.yaml and a README.md. Export a provider key, then run it:
export OPENAI_API_KEY=sk-abc123
cd getting-started
npx promptfoo@latest eval
npx promptfoo@latest view
eval runs every prompt against every provider against every test case — the full cross product. view opens a local web UI for side-by-side output comparison.
Two alternatives if you prefer a different entry point:
npx promptfoo@latest init— interactive CLI walkthrough, builds a config from scratchnpx promptfoo@latest eval setup— browser-based setup flow for prompts, providers, and test cases
The config is three things
A promptfooconfig.yaml has exactly three concepts. Once you see them, the whole tool is obvious.
1. Prompts — with {{variable}} placeholders:
prompts:
- 'Convert the following English text to {{language}}: {{input}}'
You can also load from files with file://prompts.txt, which is what you want once prompts exceed one line.
2. Providers — the models under test. Promptfoo supports 60+ providers:
providers:
- openai:chat:gpt-5.4
- openai:chat:gpt-5.4-mini
- anthropic:messages:claude-opus-4-6
- google:gemini-3.1-pro-preview
- file://path/to/custom/provider.py
That last line is the underrated one. A custom Python or JavaScript provider means you can point Promptfoo at your application endpoint rather than a raw model — which is the only way to test the thing users actually hit.
3. Tests — inputs plus assertions:
tests:
- vars:
language: French
input: Hello world
assert:
- type: contains
value: 'Bonjour le monde'
- vars:
language: Spanish
input: Where is the library?
assert:
- type: icontains
value: 'Dónde está la biblioteca'
Picking the right assertion type
This is where teams go wrong. They reach for an LLM grader immediately because the output is "fuzzy," then wonder why their eval suite costs $40 a run and is itself nondeterministic.
Use the cheapest assertion that can catch the failure. Promptfoo's assertion types fall into rough tiers:
| Tier | Types | Cost | Use when |
|---|---|---|---|
| Deterministic | equals, contains, icontains, starts-with, regex, is-json, contains-json |
Free | Format, required strings, schema compliance |
| Programmatic | javascript, python |
Free | Custom scoring logic, business rules |
| Similarity | similar, levenshtein |
Cheap | "Close enough" semantic or string matching |
| Model-graded | llm-rubric, factuality |
Expensive | Tone, instruction-following, groundedness |
| Operational | cost, latency |
Free | Performance regressions and budget ceilings |
Start at the top of that table and only descend when you must.
The two most valuable in practice are the least glamorous. is-json and contains-json catch the single most common production LLM failure — malformed structured output — for free, deterministically, in milliseconds.
Reusable assertions with `defaultTest`
Rules that apply to every case belong in defaultTest, not copy-pasted across fifty test blocks:
defaultTest:
assert:
- type: llm-rubric
value: Do not mention that you are an AI or chat assistant
- type: javascript
# Shorter is better
value: Math.max(0, Math.min(1, 1 - (output.length - 100) / 900));
Note that the javascript assertion returns a score between 0 and 1, not a boolean. Graded scoring is how you measure improvement rather than just pass/fail — useful when you are tuning, useless as a merge gate.
Guarding cost and latency
These two assertions belong in every suite and almost nobody uses them:
defaultTest:
assert:
# USD per inference
- type: cost
threshold: 0.002
# milliseconds
- type: latency
threshold: 3000
A prompt change that quietly doubles token count will pass every quality check you write. cost catches it before your invoice does.
Comparing models without editing config
Provider overrides work from the command line, which makes A/B comparison a one-liner:
npx promptfoo@latest eval -r google:gemini-3.1-pro-preview google:gemini-2.5-pro
This substitutes the providers in your existing config and prints a side-by-side table. When a new model drops on a Tuesday afternoon, this is how you answer "should we switch?" in ten minutes instead of a week.
Beyond prompts: RAG and agent evals
Prompt-level testing is table stakes. The harder targets have first-class support:
RAG quality — npx promptfoo@latest init --example eval-rag scaffolds grading on factuality, answer relevance, context recall, context relevance, and context faithfulness. These five metrics separate "the model hallucinated" from "retrieval handed it garbage," which is the diagnosis you actually need.
Agent quality — npx promptfoo@latest init --example openai-agents-basic sets up an OpenAI Agents SDK workflow with tool calls, then tests tool use and response quality across multi-turn scenarios. Promptfoo also supports trajectory checks — verifying how an agent reached an answer, not just whether the answer was right. For agents, trajectory is usually the more important signal, because a correct answer reached through six unnecessary tool calls is a cost and latency bug waiting to surface at scale.
Gate it in CI
An eval suite that runs when someone remembers is a documentation artifact, not a safety net. Promptfoo ships a GitHub Action integration so evals run on every PR.
The pragmatic pattern:
- Deterministic assertions block the merge.
is-json,regex,contains,cost,latency— fast, free, non-flaky. - Model-graded assertions report but don't block.
llm-rubricandfactualityare themselves nondeterministic. Gating on them produces flaky CI, and flaky CI gets disabled. - Cache aggressively. Promptfoo stores eval history and cached results in
~/.promptfoo(%USERPROFILE%\.promptfooon Windows). You can relocate it withPROMPTFOO_CACHE_PATH— useful for CI cache mounts.
The security half nobody uses
Promptfoo is two products wearing one CLI. The eval half is what most developers install. The red teaming half is why OpenAI bought the company.
# see: promptfoo.dev/docs/red-team/quickstart/
Red teaming scans your LLM application for adversarial failures — prompt injection, jailbreaks, sensitive data leakage — with a plugin and strategy architecture, plus mappings to frameworks like the NIST AI RMF. Promptfoo also ships Guardrails for runtime protection, Model Security, Code Scanning for IDE and CI/CD, and an MCP Proxy for securing Model Context Protocol traffic.
OpenAI's stated plan is to fold these capabilities into OpenAI Frontier, its platform for building and operating "AI coworkers" — adding native red-teaming for prompt injections, jailbreaks, data leaks, tool misuse, and out-of-policy agent behavior. OpenAI's announcement describes Promptfoo's suite as "trusted by over 25 percent of Fortune 500 companies" and explicitly commits to continuing the open-source project.
If you are shipping an agent that touches customer data, the red team quickstart is a better use of your next hour than another llm-rubric assertion.
Where evals stop helping
Three honest limits.
Model-graded assertions inherit the grader's flaws. llm-rubric is an LLM judging an LLM. It drifts across grader model versions, and it will confidently pass outputs that violate rules it half-understood. Pin your grader model and re-baseline when you change it.
Your test set is your ceiling. Promptfoo tells you whether behavior changed on cases you thought to write. It says nothing about the failure mode you never imagined. Mine production logs for new cases continuously; a static suite decays.
Passing evals is not shipping quality. Assertions are a regression net, not a quality bar. They tell you that today's prompt is not worse than yesterday's — which is genuinely valuable and also much less than it sounds like.
The Bottom Line
Promptfoo is the lowest-effort way to stop shipping prompt changes on vibes. Twenty minutes gets you a config, a dozen deterministic assertions, and a CI gate that catches malformed JSON and cost regressions before your users do. Start with is-json, contains, cost, and latency; add llm-rubric only where nothing cheaper works; and treat model-graded results as reporting rather than gating. The OpenAI acquisition makes the roadmap slightly less predictable, but the open-source CLI is doing real work today — and the alternative is still "I tried it three times and it looked fine."


