n4nAI

Automating prompt regression tests with the n4n API

Learn how to automate prompt regression tests with n4n API in this hands-on tutorial. Build a CI pipeline that catches prompt drifts using Python and pytest.

n4n Team2 min read483 words

Audio narration

Coming soon — every post will get a voice note here.

If you ship LLM features, you need to automate prompt regression tests with n4n API to catch silent behavior changes before they hit production. This hands-on tutorial builds a pytest harness that runs golden prompts against models, asserts on output shape and content, and runs on every commit.

Prerequisites

  • Python 3.11+ and pip
  • openai and pytest installed
  • An API key from n4n.ai; the gateway is OpenAI-compatible, fronts 240+ models, and provides automatic fallback when a provider is rate-limited or degraded.
  • A directory for test cases and a .env file with N4N_API_KEY=...
python -m venv .venv && source .venv/bin/activate
pip install openai pytest python-dotenv

Project layout

prompt_regression/
├── cases.json
├── test_prompts.py
└── .env

Load the key via python-dotenv so it is available as os.environ["N4N_API_KEY"].

Step 1: Configure the client

The n4n API speaks the OpenAI chat completions protocol. Point the base URL at the gateway and use your key.

# test_prompts.py
import os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(
    base_url="https://api.n4n.ai/v1",
    api_key=os.environ["N4N_API_KEY"],
)

Step 2: Define golden cases

Keep expectations as data, not code. Each case pins a model, the messages, and simple string assertions. This makes it easy to review prompt changes in PRs.

// cases.json
[
  {
    "id": "sql_select_top",
    "model": "openai/gpt-4o-mini",
    "messages": [
      {"role": "user", "content": "Write SQL to get top 5 users by revenue from table users."}
    ],
    "expect": {
      "contains": ["SELECT", "ORDER BY", "LIMIT 5"],
      "not_contains": ["DROP", "DELETE"]
    }
  },
  {
    "id": "json_sentiment",
    "model": "openai/gpt-4o-mini",
    "messages": [
      {"role": "user", "content": "Return JSON with key 'sentiment' and value 'positive' or 'negative' for: 'I love this product'."}
    ],
    "expect": {
      "contains": ["sentiment"],
      "json": true
    }
  }
]

Step 3: Write the pytest harness

Parametrize over the JSON file. Set temperature=0 to minimize non-determinism. The gateway forwards provider cache-control hints if you pass them, but for tests we keep it simple.

import json
import pytest

def load_cases(path="cases.json"):
    with open(path) as f:
        return json.load(f)

@pytest.mark.parametrize("case", load_cases())
def test_prompt_regression(case):
    resp = client.chat.completions.create(
        model=case["model"],
        messages=case["messages"],
        temperature=0,
    )
    text = resp.choices[0].message.content
    exp = case["expect"]
    for token in exp.get("contains", []):
        assert token in text, f"{case['id']}: missing {token}"
    for forbidden in exp.get("not_contains", []):
        assert forbidden not in text, f"{case['id']}: found {forbidden}"
    if exp.get("json"):
        data = json.loads(text)  # raises if invalid
        assert "sentiment" in data

Step 4: Run and read output

Execute the suite locally to confirm wiring.

pytest -q

Expected output:

..
2 passed in 1.4s

If a model returns LIMIT 10 instead of LIMIT 5, the test fails with sql_select_top: missing LIMIT 5, surfacing the regression immediately.

Step 5: Enforce structured outputs

For stricter contracts, use the response_format parameter. This works through the gateway exactly as with the native API.

def test_structured_sentiment():
    resp = client.chat.completions.create(
        model="openai/gpt-4o-mini",
        messages=[{"role":"user","content":"Return JSON with keys: name, age for Albert, 42"}],
        response_format={"type":"json_object"},
        temperature=0,
    )
    data = json.loads(resp.choices[0].message.content)
    assert set(data.keys()) == {"name","age"}
    assert data["name"] == "Albert"

Run it:

pytest test_prompts.py::test_structured_sentiment -q
.
1 passed in 0.9s

Step 5b: Reduce non-determinism

Even at temperature 0, some backends vary across deployments. Pass a seed when supported and retry once on parse errors to avoid flaky failures.

def complete(case):
    for _ in range(2):
        try:
            resp = client.chat.completions.create(
                model=case["model"],
                messages=case["messages"],
                temperature=0,
                seed=42,
            )
            return resp.choices[0].message.content
        except json.JSONDecodeError:
            continue
    raise AssertionError("model did not return stable JSON")

Step 6: Wire into CI

Commit the harness and add a GitHub Actions workflow. Secrets stay in the runner; the gateway’s per-token usage metering means you can attribute test spend.

# .github/workflows/prompt-regression.yml
name: prompt-regression
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install openai pytest python-dotenv
      - run: pytest -q
        env:
          N4N_API_KEY: ${{ secrets.N4N_API_KEY }}

Now every push automatically runs your prompt regression suite.

Step 7: Track cost and avoid flake

Because the gateway returns token usage, log it to watch test cost.

@pytest.mark.parametrize("case", load_cases())
def test_prompt_regression(case):
    resp = client.chat.completions.create(
        model=case["model"],
        messages=case["messages"],
        temperature=0,
    )
    print(f"{case['id']} used {resp.usage.total_tokens} tokens")
    # ... assertions

Automatic fallback keeps tests green when a provider is degraded, but you should still alert on repeated fallbacks. When you automate prompt regression tests with n4n API, treat the golden set as code: review additions in PRs, and bump expectations only after manual verification.

If you need to pin a specific provider path, the gateway honors client routing directives via the model identifier—prefixing with the provider namespace routes the request accordingly. This is useful when a regression only appears on one backend.

For semantic drift, add a cosine-similarity check against an embedded reference using a local model or the gateway’s embedding endpoint. That extends the harness beyond string matching without much overhead.

The full loop—pinned models, data-driven cases, CI execution, and token accounting—gives you a real safety net for prompt changes. To automate prompt regression tests with n4n API at scale, version your cases.json alongside prompts and fail the build on any mismatch.

Tagsn4nprompt-testingregression-testingautomation

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All regression testing for prompts posts →