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 cassettes — streaming: 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

Metadata

Release files for pytest-llm-vcr 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-llm-vcr 0.6.0
File Size Uploaded Provenance Actions
pytest_llm_vcr-0.6.0.tar.gz 26.9 kB Without provenance Details for pytest_llm_vcr-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pytest-llm-vcr 0.6.0
File Interpreter ABI Platform Actions
pytest_llm_vcr-0.6.0-py3-none-any.whl Python 3 none any Details for pytest_llm_vcr-0.6.0-py3-none-any.whl

Total release size: 42.0 kB

Release files / pytest_llm_vcr-0.6.0.tar.gz

Download URL pytest_llm_vcr-0.6.0.tar.gz
Size 26.9 kB
Tags Source
SHA-256 checksum
How to use checksums
c00b7bf776aa8b9f48db123e63322e0a12bae24636b998d68b990ab6c45c49a6
BLAKE2b-256 checksum
How to use checksums
739c116575a86aff90876a9fb4564be9f746665810036c08ffcddcacd8b1fce7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / pytest_llm_vcr-0.6.0-py3-none-any.whl

Download URL pytest_llm_vcr-0.6.0-py3-none-any.whl
Size 15.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
43129e5ff9f19a34e93b1e59b0d70ce21c35f642c28e79e8d41888a026dbc820
BLAKE2b-256 checksum
How to use checksums
3e3b0361a77fefe36af01ca33d81d7d929a4652d7f730567d7e14b4ca95d31c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Security for pytest-llm-vcr 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files No provenance

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