Skip to main content

VCR for LLM APIs — record and replay OpenAI/Anthropic/Gemini calls including streaming.

Project description

Reel

CI docs License Python

VCR for LLM APIs. Record real OpenAI / Anthropic / Gemini calls once, replay them deterministically forever — including streaming, tool calls, and timing. No SDK changes. No real network in CI. No surprise spend.

Docs: https://tathagat22.github.io/reel/


The 30-second pitch

# Start once
uv run reel auto -c tape.jsonl

# Point any LLM SDK at the proxy
export OPENAI_BASE_URL=http://127.0.0.1:7878/v1

# Run your code — first run records, every run after replays in ~3ms with $0 spend
python my_app.py

That's the entire workflow. The cassette is plain JSONL: diff it in PRs, grep it, share it. Secrets and PII are scrubbed at capture time, so it's safe to commit.

your code  ─►  127.0.0.1:7878  ─►  reel proxy  ─►  api.openai.com
                                       │
                                       ├── writes JSONL on first call
                                       └── replays on next calls

Why people pick it up

Pain What Reel does about it
pytest burns $$ on every CI run Replay mode — zero network, no API key needed
Prompt iteration is slow + expensive Captured responses replay in ~3ms; iterate the prompt locally for free
Production bug, no way to repro locally Hand a colleague the JSONL — they replay your prod traffic byte-for-byte
Three providers, three mock libraries One proxy serves OpenAI, Anthropic, and Gemini, including SSE streaming

Quickstart

# From PyPI once v0.1.0 is published:
pip install reel-vcr                # distribution name is reel-vcr;
                                     # binary + import path stay `reel`
# Or from source today:
git clone https://github.com/tathagat22/reel && cd reel && uv sync

uv run reel auto -c demo.jsonl &
export OPENAI_BASE_URL=http://127.0.0.1:7878/v1
python -c "from openai import OpenAI; print(OpenAI().chat.completions.create(model='gpt-5', messages=[{'role':'user','content':'Hi'}]).choices[0].message.content)"

Note: the bare reel name on PyPI was already taken by an unrelated async-subprocess library, so the distribution name is reel-vcr. The CLI binary, GitHub repo, and Python import path (import reel) all stay as reel.

Drop into an existing pytest suite

# tests/test_chatbot.py
def test_summarize(reel_cassette):       # ← that's the entire integration
    from openai import OpenAI
    OpenAI().chat.completions.create(...)

The pytest plugin auto-registers; no conftest.py edits needed. First run records to tests/cassettes/test_chatbot/test_summarize.jsonl; subsequent runs replay it.

pytest                           # first time: records (costs real money)
pytest                           # every time after: replays (free, ~3ms)
pytest --reel-mode replay        # CI mode — fails loud on uncaptured calls

Full guide: docs/guides/pytest.md.

Commands

Command What it does
reel auto -c <path> Replay if cached, else record (the dev-loop default)
reel record -c <path> Always forward + capture (initial capture, refresh)
reel replay -c <path> Cassette only; 404 on miss (CI mode)
reel ui -c <path> Local web UI to browse and search cassettes
reel inspect -c <path> Rich-table view of entries, with composable filters
reel cost -c <path> $$ aggregate — what you spent (or would have)
reel diff -l A -r B Show drift between two cassettes
reel stats -c <path> Counts, error rate, token totals, TTFT distribution
reel redact -c <path> Post-hoc scrub secrets / PII
reel doctor Health check: ports, upstreams, write perms
reel version Print the installed version

What's in the box

  • OpenAI / Anthropic / Gemini — single proxy, routed by path or explicit /<provider>/… URL prefix
  • SSE streaming with timing fidelity — chunks captured per-ms; replay reproduces TTFT and inter-chunk gaps; --timing realtime | fast | slow=N
  • Four match modesexact, normalized (default), ignore-fields (great for per-call request_id), fuzzy (embedding similarity, optional reel[fuzzy])
  • Capture-time redaction — OpenAI / Anthropic / Google / GitHub / AWS / Slack key shapes + Bearer tokens, always. PII (email + phone) on by default; opt out with REEL_REDACT_PII=0
  • pytest plugin — auto-discovered via pytest11 entry point; reel_cassette fixture / @cassette decorator / @pytest.mark.cassette
  • Analytics CLI — inspect / cost / diff / stats with composable filters and pricing tables for the major models
  • Structured logs--log-format json for jq-pipeable per-request observability
  • JSONL cassettes — git-friendly, append-only, ~5 KB per buffered call
  • Pre-commit hook to refuse any cassette that contains a detectable secret
  • 344 tests including multi-provider E2E + a pytester-driven plugin suite

Architecture

src/reel/
├── proxy/        # HTTP + SSE core (forwarder, modes, stream, logs)
├── adapters/     # openai · anthropic · gemini (one ProviderAdapter interface)
├── cassette/     # schema · writer · reader · matcher · body codec · store
├── redact/       # secret + PII scrubbing
├── analytics/    # filters · cost · diff · stats (pure over CassetteEntry)
├── inspector/    # `reel ui` — Starlette + HTMX + Pico.css
├── cli/          # typer commands wired into `reel` entry point
└── sdk/          # @cassette decorator + pytest plugin

Deeper dive: docs/architecture.md. Roadmap: docs/SPRINT_SHEET.md.

Status

Sprints 1-6 of 6 are shipped. Currently pre-alpha — usable today from source, PyPI publish + v0.1.0 tag is the last open item.

Development

uv sync
make check         # ruff + pyright (strict) + pytest — must pass before every commit
uv run reel auto -c ./scratch/test.jsonl

CI runs lint + format + typecheck + tests on Python 3.11 / 3.12 / 3.13 — see .github/workflows/ci.yml. Docs deploy to GitHub Pages — .github/workflows/docs.yml.

Contributing

PRs welcome — see CONTRIBUTING.md. The pre-commit hook will refuse any cassette with a detectable secret, so capture won't silently leak.

License

Apache 2.0

Project details


Download files

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

Source Distribution

reel_vcr-0.1.0.tar.gz (278.9 kB view details)

Uploaded Source

Built Distribution

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

reel_vcr-0.1.0-py3-none-any.whl (96.7 kB view details)

Uploaded Python 3

File details

Details for the file reel_vcr-0.1.0.tar.gz.

File metadata

  • Download URL: reel_vcr-0.1.0.tar.gz
  • Upload date:
  • Size: 278.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for reel_vcr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5ca057d43c16159a9a8a95c9ac1c570a5336ce7d9f151c139744bd178d9eb540
MD5 da44b64419c07704ef429d2cb62bea81
BLAKE2b-256 8e24ac78346be60fc32486305f2f1edacb7fd0999fbd2e086352bf208c8c918a

See more details on using hashes here.

Provenance

The following attestation bundles were made for reel_vcr-0.1.0.tar.gz:

Publisher: release.yml on tathagat22/reel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reel_vcr-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: reel_vcr-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 96.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for reel_vcr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d592931448ab44f1c06f09ec076435e23a52c6a889da5b42d79ea2392a0d631
MD5 b8d037bac7713c691b03b884da4faef6
BLAKE2b-256 80e471f33502e1d7dcbe59c1e232e5ff11ab32a61501718c19c20812e0f802c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for reel_vcr-0.1.0-py3-none-any.whl:

Publisher: release.yml on tathagat22/reel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page