Skip to main content

llm-vcr

Record and replay LLM HTTP traffic for deterministic, key-free pytest runs.

PyPI License: MIT Python 3.11+ CI

Status: v0.6 — SSE streaming replay, sequential tool-call cassettes, OpenAI + Anthropic semantic matching, model-date normalize, and llm-vcr diff.

60-second try

pip install pytest-llm-vcr
llm-vcr health
docker compose run --rm test    # cassette replay, no API key

Why this vs alternatives

Approach Strength Gap
llm-vcr LLM-aware matching, redaction, SSE + sequential cassettes httpx-focused today
VCR.py / pytest-recording Mature generic HTTP cassettes No model-date normalize or LLM-shaped diffs
Hand-written mocks Fast, no network Drift from live provider payloads
Live API in CI Highest fidelity Flaky, keyed, and expensive

Problem

Testing code that calls LLMs is slow, flaky, and expensive. Hand-written mocks drift from reality. Generic HTTP cassettes (VCR.py) don't understand LLM request shapes or redact API keys well.

Key features (v0.6)

  • pytest plugin@llm_vcr decorator, llm_vcr_client fixture, --llm-vcr-record
  • httpx transport — sync + async, including client.stream(...)
  • YAML cassettesstreaming: true + chunks for SSE
  • Matching — exact (default) or matcher="semantic" (volatile keys, model aliases, messages by role+content, tools by name); Anthropic Messages API via api.anthropic.com URL detection
  • llm-vcr diff — show normalized differences between JSON bodies or cassette interactions
  • Automatic redaction — strips api_key, x-api-key, token, authorization fields

Architecture

@pytest test
    └── @llm_vcr decorator
            └── VCRTransport (httpx)
                    ├── replay mode → read cassette YAML
                    └── record mode → live HTTP + save cassette
Component Technology Why
HTTP httpx Modern, sync+async, transport hooks
Cassettes YAML Readable diffs in PRs
Tests pytest entry point Zero-config discovery

Installation

pip install pytest-llm-vcr
pip install -e ".[dev]"  # from source

Local development

pip install -e ".[dev]"
pytest tests/ -v
llm-vcr health
llm-vcr diff left.json right.json

Docker

docker compose run --rm test
docker compose run --rm health

Configuration

Variable Default Description
LLM_VCR_CASSETTE_DIR tests/cassettes Cassette storage directory
LLM_VCR_RECORD false Force record mode

Usage

Replay (CI — no API key needed)

import httpx
from llm_vcr.plugin import llm_vcr

@llm_vcr("my_test")
def test_chat(client: httpx.Client) -> None:
    resp = client.post(
        "https://api.openai.com/v1/chat/completions",
        json={"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Say hello"}]},
    )
    assert resp.json()["choices"][0]["message"]["content"]

Record new cassettes

LLM_VCR_RECORD=true pytest tests/ --llm-vcr-record

Diff normalized request bodies

llm-vcr diff left.json right.json
llm-vcr diff cassette_a.yaml cassette_b.yaml --index 0

Model strings with a trailing -YYYY-MM-DD normalize equal so dated aliases do not show as diffs.

Anthropic Messages API

Point httpx at https://api.anthropic.com/v1/messages the same way as OpenAI — record once, replay in CI without a key. Query strings (e.g. beta flags) are stripped for matching. Use matcher="semantic" to ignore metadata and tool_use / tool_result ids:

@llm_vcr("anthropic_chat", matcher="semantic")
def test_anthropic(client: httpx.Client) -> None:
    resp = client.post(
        "https://api.anthropic.com/v1/messages",
        json={
            "model": "claude-3-5-sonnet",
            "max_tokens": 64,
            "messages": [{"role": "user", "content": "Say hello"}],
        },
        headers={"x-api-key": "sk-ant-...", "anthropic-version": "2023-06-01"},
    )
    assert resp.json()["content"][0]["text"]

x-api-key is redacted in cassette bodies when present.

Running tests

pytest tests/ -v

Roadmap

  • SSE streaming chunk replay
  • Tool-call multi-step loops (@llm_vcr(..., sequential=True))
  • Async httpx transport (fixture + replay)
  • Model-date normalize + llm-vcr diff
  • OpenAI + Anthropic semantic request matching (matcher="semantic")

Known limitations (v0.6)

  • Sequential matching is opt-in (sequential=True); default matching is still hash-based
  • Matching: exact hash (default) or semantic (volatile keys, model aliases, message/tool shape; Anthropic when host is api.anthropic.com)
  • Record mode for streaming stores chunks, not per-event timestamps

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_llm_vcr-0.6.0.tar.gz (26.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_llm_vcr-0.6.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file pytest_llm_vcr-0.6.0.tar.gz.

File metadata

  • Download URL: pytest_llm_vcr-0.6.0.tar.gz
  • Upload date:
  • Size: 26.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for pytest_llm_vcr-0.6.0.tar.gz
Algorithm Hash digest
SHA256 c00b7bf776aa8b9f48db123e63322e0a12bae24636b998d68b990ab6c45c49a6
MD5 514b8e2758fa835c467ddc21648e408d
BLAKE2b-256 739c116575a86aff90876a9fb4564be9f746665810036c08ffcddcacd8b1fce7

See more details on using hashes here.

File details

Details for the file pytest_llm_vcr-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: pytest_llm_vcr-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 15.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for pytest_llm_vcr-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 43129e5ff9f19a34e93b1e59b0d70ce21c35f642c28e79e8d41888a026dbc820
MD5 8293bf2115f8a7ac18fddbbd102b41b9
BLAKE2b-256 3e3b0361a77fefe36af01ca33d81d7d929a4652d7f730567d7e14b4ca95d31c3

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page