Promptfoo: Test Your LLM Prompts Like Unit Tests
Tech Tips 8 min read advanced

Promptfoo: Test Your LLM Prompts Like Unit Tests

Promptfoo is an open-source CLI (24.1k GitHub stars) that treats LLM prompts as testable code using YAML test cases and assertions. OpenAI announced it was acquiring the company on March 9, 2026, committing to continue the open-source project. This guide covers installation (Node.js 22.22.0 or newer, Node 24 recommended), scaffolding a first eval with npx promptfoo@latest init, choosing assertion types by cost tier, guarding cost and latency, RAG and agent evals, CI gating strategy, the red teaming half of the tool, and three honest limits of eval suites.

Marcus Rivera
Marcus Rivera
Aug 10, 2026

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 scratch
  • npx 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 qualitynpx 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 qualitynpx 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:

  1. Deterministic assertions block the merge. is-json, regex, contains, cost, latency — fast, free, non-flaky.
  2. Model-graded assertions report but don't block. llm-rubric and factuality are themselves nondeterministic. Gating on them produces flaky CI, and flaky CI gets disabled.
  3. Cache aggressively. Promptfoo stores eval history and cached results in ~/.promptfoo (%USERPROFILE%\.promptfoo on Windows). You can relocate it with PROMPTFOO_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."