Reel
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.
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
reelname on PyPI was already taken by an unrelated async-subprocess library, so the distribution name isreel-vcr. The CLI binary, GitHub repo, and Python import path (import reel) all stay asreel.
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 modes —
exact,normalized(default),ignore-fields(great for per-callrequest_id),fuzzy(embedding similarity, optionalreel[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
pytest11entry point;reel_cassettefixture /@cassettedecorator /@pytest.mark.cassette - Analytics CLI — inspect / cost / diff / stats with composable filters and pricing tables for the major models
- Structured logs —
--log-format jsonforjq-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
Release files for reel-vcr 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| reel_vcr-0.1.0.tar.gz | 278.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reel_vcr-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 375.6 kB
Release files / reel_vcr-0.1.0.tar.gz
| Download URL | reel_vcr-0.1.0.tar.gz |
|---|---|
| Size | 278.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5ca057d43c16159a9a8a95c9ac1c570a5336ce7d9f151c139744bd178d9eb540
|
|
BLAKE2b-256 checksum How to use checksums |
8e24ac78346be60fc32486305f2f1edacb7fd0999fbd2e086352bf208c8c918a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 15, 2026.
Transparency logRelease files / reel_vcr-0.1.0-py3-none-any.whl
| Download URL | reel_vcr-0.1.0-py3-none-any.whl |
|---|---|
| Size | 96.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d592931448ab44f1c06f09ec076435e23a52c6a889da5b42d79ea2392a0d631
|
|
BLAKE2b-256 checksum How to use checksums |
80e471f33502e1d7dcbe59c1e232e5ff11ab32a61501718c19c20812e0f802c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 15, 2026.
Transparency log