Skip to main content

cendor-cassette

Record an agent run once; replay it forever — deterministic, offline, and free. Unlike vcrpy (HTTP-only), it captures the whole run: every LLM call and tool call, in order.

Agent tests with no API key — a recorded run replays in microseconds per call, offline.

PyPI license · pip install cendor-cassette

Using an AI coding assistant? npx @cendor/init (TS) / uvx cendor-init (Python) wires it up — or point it at cendor.ai/docs/for-ai-assistants.

from cendor.core import instrument
from cendor import cassette

client = instrument(OpenAI())          # the same instrumented seam used in production

@cassette.use("triage_happy_path.json")   # record first run, replay after (auto mode)
def test_triage():
    result = my_agent.run("My card was charged twice")
    assert "refund" in result.tools_called
    assert cassette.semantic_match(result.answer, "offers a refund")

Highlights

  • Whole-run capture — every LLM and tool call, in order (not just HTTP, like vcrpy).
  • Four modesauto (record then replay) · record · replay (fail on an unrecorded call) · rerecord (run live, report drift() without overwriting the committed cassette).
  • Decorator or context manager@cassette.use("run.json") / with cassette.using(...) (handy in pytest fixtures).
  • Meaning-based assertionssemantic_match(actual, expected) (offline lexical default; opt into a free offline local-embedding scorer, a BYO-provider embedder, or an LLM judge). semantic_drift() filters rerecord noise down to real regressions.
  • Pluggable matching + redaction — a normalizer ignores volatile fields; secrets/PII redacted on write, but matching hashes the un-redacted request so redaction never collapses two distinct calls (redact=True|False|callable).
  • Parallel-safe — recording is scoped to the active using()/use() context (a ContextVar), so concurrent blocks never capture each other's calls; cassettes are written atomically. Under pytest-xdist, give each worker its own cassette path (e.g. suffix with PYTEST_XDIST_WORKER) so workers don't race on one file.
  • Faithful replay — dict-response providers (Ollama/Bedrock) replay as dicts and SDK-object providers as attribute objects; stream=True and stream=False calls match their own recordings (cassette format v2; committed v1 cassettes still replay).
  • promote() turns a production JSONL trace into a replayable regression test (LLM and tool calls).

Semantic matching (opt-in)

semantic_match defaults to lexical_score — offline, deterministic, zero-dependency. For meaning-aware (negation-sensitive) checks, pass a scorer into the existing hook. cassette binds no model and adds no dependency unless you ask for one. Four tiers, hermetic-and-free → meaning-aware-but-costly:

  1. Lexical (default) — lexical_score. Hermetic, deterministic, free, zero-dep.
  2. Local embeddings (recommended) — local_embedding_scorer(), free/offline/deterministic via model2vec static embeddings (numpy-only, no torch, ~8–30 MB). Behind pip install 'cendor-cassette[embeddings]'.
  3. BYO provider embeddingsembedding_scorer(embed_fn) wraps any provider (OpenAI text-embedding-3-small/large, Google gemini-embedding, Cohere embed-v3; Anthropic has no embeddings API → use Voyage). Non-hermetic: a cloud embedder calls the network at score time. openai_embedding_scorer(client, model="text-embedding-3-small") is a thin convenience over an already-built OpenAI-shaped client.
  4. LLM-judge — a scorer that calls your own instrumented client (a documented recipe, never a shipped dependency). Non-hermetic, non-deterministic, costs money.
from cendor import cassette

score = cassette.local_embedding_scorer()                 # free, offline, deterministic
assert cassette.semantic_match(result.answer, "offers a refund", scorer=score)
assert not cassette.semantic_match("we will not offer a refund", "offers a refund", scorer=score)

drift() stays byte-exact; at temperature > 0 it flags every run. semantic_drift(threshold=0.8, scorer=None) re-scores each divergence's recorded-vs-live text and keeps only those below the threshold (real regressions, with a score), so cosmetic rewording is ignored. The alternative for byte-stable drift: record/replay at temperature=0.

Wrap-around, test-time only — records via core's bus, replays via a core interceptor; no second patch, no network.

See docs/cassette.md · CHANGELOG. Part of the Cendor stack — github.com/cendorhq/cendor-libs. Powered by PowerAI Labs. Apache-2.0; provided "as is", without warranty — use at your own risk (LICENSE §7–8).

Download files

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

Source Distribution

cendor_cassette-1.1.1.tar.gz (32.1 kB view details)

Uploaded Source

Built Distribution

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

cendor_cassette-1.1.1-py3-none-any.whl (22.6 kB view details)

Uploaded Python 3

File details

Details for the file cendor_cassette-1.1.1.tar.gz.

File metadata

  • Download URL: cendor_cassette-1.1.1.tar.gz
  • Upload date:
  • Size: 32.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for cendor_cassette-1.1.1.tar.gz
Algorithm Hash digest
SHA256 0c730ae03171de53761289f355ecba13634ce34f7ba5563515432213bc209cf8
MD5 2ded603f5b2cf08a19b6721385069412
BLAKE2b-256 44b9842d3c8e2b4449491716fa6241c4b8b810405cecd97bb12d9cd224d1b357

See more details on using hashes here.

Provenance

The following attestation bundles were made for cendor_cassette-1.1.1.tar.gz:

Publisher: release.yml on cendorhq/cendor-libs

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

File details

Details for the file cendor_cassette-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: cendor_cassette-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 22.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for cendor_cassette-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ea5d5ea82215c2e56b3a4bf0fbef77d6523533183c4ad139acd931e8af9a3ab6
MD5 23b69893f814538b8480eb23a448c73e
BLAKE2b-256 dd895770543fee611f8ee543ed37785e3f94134420f2b88159aa536e724d0723

See more details on using hashes here.

Provenance

The following attestation bundles were made for cendor_cassette-1.1.1-py3-none-any.whl:

Publisher: release.yml on cendorhq/cendor-libs

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

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 files

1.1.0

2 files

1.0.2

2 files

1.0.1

2 files

Supported by

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