← Back

Guide · QA + AI series · 28/31 · November 13, 2026 · 7 min read

Evals in CI: quality gates for AI features

How to bring evals into the pipeline at three tiers (PR, nightly and release) with a gate that blocks merges and controlled cost.

CI/CDEvalsDevOps

An eval that runs only when someone remembers ends up not running. The way to sustain the quality of an AI feature over time is the same as for the rest of software: a gate in the pipeline that blocks the change when something gets worse. What changes are the constraints. Evals cost money per run, take longer than a unit test and their result varies.

Putting every eval on every pull request makes the pipeline slow and expensive, and the team ends up skipping it. Putting in none lets regressions through. This guide organizes evals into three tiers, per PR, nightly and per release, each with a different scope, cost and consequence, and shows how to keep spending under control.

How it fits together

Diagram: Evals in CI: quality gates for AI features
Show the diagram as text
pull request touching prompts / evals / AI code
   ▼
PR tier: dev subset x3 ▶ exit code 0?
                           ├─ no  ▶ merge blocked + artifact
                           └─ yes ▶ merge
                                      ▼
            nightly: full test set x5 + judge + red team ▶ trend + alert
                                      ▼
            release: regression + performance ▶ human sign-off ▶ production

Requirements

Setup

name: evals
on:
  pull_request:
    paths: ["prompts/**", "evals/**", "src/ai/**"]
jobs:
  evals:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - name: Run evals
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          PROMPTFOO_CACHE_PATH: .promptfoo-cache
        run: npx promptfoo eval -c evals/bug-triage.yaml --repeat 3 -o results.json
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: eval-results, path: results.json }
.github/workflows/evals.yml
stage('Evals') {
  when { anyOf { changeset 'prompts/**'; changeset 'evals/**'; changeset 'src/ai/**' } }
  steps {
    withCredentials([string(credentialsId: 'llm-api-key', variable: 'OPENAI_API_KEY')]) {
      sh 'npm ci && npx promptfoo eval -c evals/bug-triage.yaml --repeat 3 -o results.json'
    }
  }
  post { always { archiveArtifacts artifacts: 'results.json' } }
}
Jenkinsfile (equivalent stage)

The three tiers

Step by step

1. The PR tier

Start with the PR tier: a small, fast subset that runs only when prompts, evals or AI code change.

It's a small, fast subset designed to answer within a few minutes. It runs only when the change touches prompts, eval config or code that calls the model. For a frontend style change, spending tokens makes no sense. Include the deterministic format and content checks, and a reduced set of cases with few repetitions. Its job isn't measuring quality precisely, but catching obvious breakage before the merge.

2. The exit code as the gate

The eval command returns a non-zero exit code when cases fail. That's enough to block the merge.

The mechanism is the same as for any test. The eval command ends with a non-zero code when cases fail, and the pipeline marks the step red. That's enough to block the merge through branch protection rules. Nothing special needs building. You do need to verify it works: force a failure on purpose on a test branch and confirm the pull request ends up blocked.

3. Artifacts always, on failure too

Always save the result as an artifact, including on failure. Without the detail nobody can diagnose.

Configure the step that saves results to run even when the eval fails. It's a config detail often forgotten, and then the pipeline goes red with no information about which case failed or what the model answered. The artifact must include inputs, outputs and verdicts per case. With that file, whoever opened the PR can diagnose in minutes without rerunning everything on their machine.

4. Controlling cost

Control cost: path filters, response caching, a subset on PRs and a CI API key with a spending cap.

There are four levers. Path filters, so evals don't run on changes that don't affect them. Response caching, so you don't pay twice for the same input with the same prompt. A subset at the PR tier instead of the full set. And a CI-only API key with a spending cap, so a config error or a loop doesn't consume the month's budget. Publish each run's spend next to the result.

5. The nightly tier

Add the nightly tier with the full set, five runs, the judge and red teaming. That's where you watch the trend, not a single PR.

Once a day, with the full test set, five repetitions, the calibrated judge and red teaming. This is where you measure precisely. The result doesn't block a particular PR: it alerts the team and feeds a trend chart. It's where you'll detect problems no PR introduced, such as a silent provider update or gradual degradation. Since it runs at night, time and cost are more tolerable.

6. The release tier

At release run regression and performance, and leave the final call to a person with the report in front of them.

Before shipping, full regression and performance tests run. The difference from the other tiers is who decides. Here the result is a report a person reads and signs. Automatic gates cover what can be expressed as a threshold. The decision to ship with a metric at the limit or with a known risk is a business judgment, and someone has to take responsibility for it with the information in front of them.

Illustrative example

A team sets up the three tiers. The PR one runs fifteen cases with three repetitions, only on changes to the prompts and evals folders, and takes four minutes. In the first week it blocks a pull request where a wording change broke the JSON format in two cases. The author opens the artifact, sees the two outputs and fixes it in ten minutes. The nightly tier runs one hundred and twenty cases with five repetitions. One Tuesday the pass rate drops four points with no PR merged. The run card shows the model that answered wasn't the expected one, because of an unpinned alias nobody had noticed.

Common mistakes

Checklist: how to know it is done

Where AI fell short

The same code can pass a gate and fail hours later. With models, a single green doesn't replace the trend.

Frequently asked questions

What do I do with an eval that sometimes passes and sometimes fails on PRs?

First check whether the case is unstable by design, in which case it goes to the nightly tier with repetitions, or whether the PR tier's threshold is too demanding for so few runs. A gate that fails at random loses the team's trust very fast.

Does it work with any CI system?

Yes. All you need is to run a command, read its exit code, filter by paths and save files. The guide carries examples for two systems, and the same structure carries over to the rest.

Who watches the nightly tier?

It must have a named owner. An alert landing in a channel where nobody answers is the same as having no alert. Define who reviews it every morning and what they do when the trend drops.

Day 28 of 31 of the 31 days of QA + AI series. Commands and versions reviewed in November 2026: rerun them before relying on them, AI tooling changes fast.

← Previous: Performance (3): AI generating and analyzing load tests

Next: QA infra for AI: ephemeral runners, local models, secrets and cost →