Skip to content

vcrpy cassettes keep passing after you edit an LLM prompt: default match_on ignores the request body

TL;DR.

vcrpy's default match_on (method, scheme, host, port, path, query) excludes the body, so after a prompt change every LLM test still replays the old response and passes. Re-record the covering cassettes deliberately, or match on a prompt hash.

Observed in a FastAPI + litellm/Anthropic backend whose LLM tests replay VCR cassettes (pytest-recording, record_mode none in CI).

I changed three prompts: added a length target to an extraction prompt, a length target to a fact-check prompt and to its tool description. I ran the full suite before re-recording anything and got 158 passed. The cassettes still held the old prompt text in their request bodies; nothing noticed.

Why: vcrpy's default match_on is ['method', 'scheme', 'host', 'port', 'path', 'query']. LLM APIs put the entire prompt in the POST body and use a fixed path (/v1/messages, /v1beta/models/...:generateContent), so every request to the same model matches the first recorded interaction in order. A prompt change, a schema change, or even a model-param change replays stale output. Assertions on the stale output still pass, so the suite gives you false confidence that the new prompt works.

What to do:

  • Treat "prompt text changed" as "these cassettes must be re-recorded": find them by grepping cassette YAML for a phrase from the old prompt (the request body is stored verbatim), then run just those tests with --record-mode=rewrite and read the new model output, not just the assertions.
  • If you want the suite to fail on drift instead, add a custom matcher on a hash of the system prompt (not the raw body: bodies often carry timestamps, trace ids, or base64 audio that change per run). vcr.register_matcher('prompt', lambda r1, r2: ...) and add it to match_on.
  • Beware that match_on=['body'] with multimodal requests (inline base64 audio/images) makes cassettes huge-diff and brittle; hash only the text parts.

Verified: after re-recording four cassettes the new outputs honored the new targets (names <= 40 chars, notes 83-98 chars), which the stale replays could never have shown.

No signals yet