Skip to main content

toolsnap

PyPI Version Tests Codecov License: MIT

Zero-dependency, SDK-agnostic recorder and replayer for LLM agent. Record once, test the trajectory forever.

Why toolsnap?

LLM agents are non-deterministic at the model level, but their tool call trajectory i.e. which tools they call, in what order, with what arguments is the real observable behavior.

Existing approaches' shortcomings:

  • Live APIs in every test run: Slow and expensive. Worse: varying tool responses mean the agent takes different paths each run — trajectory assertions are unreliable.
  • Hand-written mocks: You write the return value before seeing what the real agent produces. If the agent never calls the tool, the mock never fails. You're testing a fiction.
  • Network-level recording: Records all HTTP — including every LLM request. Fixtures balloon in size and break on any SDK update, header change, or streaming format shift.

toolsnap takes a different approach: record the real trajectory once, then replay and assert on it forever.


What toolsnap is/isn't?

It is

  • A trajectory recorder and assertion library for LLM agent tool calls
  • A way to pin what the agent does so prompt or code changes that alter behavior surface immediately
  • Most valuable when tools call external services you cannot or should not hit in CI

It isn't

  • A mock framework: you never invent return values by hand
  • A network recorder: it operates at the Python function boundary, not HTTP
  • A way to eliminate LLM calls: the LLM still runs in replay mode; only the tool backends are fixed
  • An evaluation framework: it does not measure response quality or semantic correctness

When to use toolsnap?

Use when

  • Tools that call external APIs, databases, clocks, or any service you don't want in CI
  • Multi-step agents where tool call order matters
  • Teams that want to catch when a prompt change silently alters agent behavior
  • Any scenario where "does the agent still call the right tools?" is the key question

Don't use when

  • Tools that are already pure functions with no external calls — just unit test them directly
  • Testing LLM output quality or reasoning — use evals for that
  • HTTP-level fidelity (exact headers, status codes) — use vcrpy or pytest-recording

Installation

pip install toolsnap

Quick Start

Step 1: Record a real agent run

# main.py — run once against live APIs
from toolsnap import snap

# auto-saves to fixtures/search.jsonl
@snap
def search(query: str) -> list[str]:
    return real_search_api(query)

# auto-saves to fixtures/get_weather.jsonl
@snap
def get_weather(city: str) -> dict:
    return real_weather_api(city)

agent.run("what's the weather in london and find llm docs")
# fixtures/search.jsonl and fixtures/get_weather.jsonl written
python main.py

Step 2: Assert the trajectory in tests

# test_agent.py — tool backends don't run; the LLM still runs
from toolsnap import replay

# reads from fixtures/search.jsonl
@replay
def search(query: str) -> list[str]: ...

# reads from fixtures/get_weather.jsonl
@replay
def get_weather(city: str) -> dict: ...

def test_research_agent_trajectory():
    agent.run("what's the weather in london and find llm docs")
    # search() and get_weather() returned their recorded responses
    # assert on the agent's output or behaviour here
pytest test_agent.py   # tool backends free, LLM still runs, trajectory deterministic

Three ways to test

1. @snap / @replay — single-tool, decorator style

Best when you have one tool and want the simplest possible setup.

# Record
@snap("fixtures/search.jsonl")
def search(query: str) -> list[str]:
    return real_search_api(query)

# Test
@replay("fixtures/search.jsonl")
def search(query: str) -> list[str]: ...

def test_agent_uses_search():
    result = agent.run("find llm docs")  # search() returns recorded response
    assert result is not None

2. SnapSession — multi-tool, with trajectory assertions

Best for agents that coordinate several tools. Wraps them all under one fixture and provides the full assertion API.

from toolsnap import SnapSession, contains

def test_multi_tool_agent():
    with SnapSession.replay("fixtures/session.jsonl") as s:
        s.wrap(search)
        s.wrap(summarize)
        agent.run("find and summarize llm docs")

    s.assert_called("search", times=1)
    s.assert_called_with("search", query=contains("llm"))
    s.assert_call_order(["search", "summarize"])
    s.assert_no_errors()

3. toolsnap_session pytest fixture — CLI-controlled record/replay

Best for teams. Record and replay modes are controlled from the command line — no code changes needed.

# conftest.py
pytest_plugins = ["toolsnap.pytest_plugin"]

# test_agent.py
@pytest.mark.toolsnap_fixture("fixtures/session.jsonl")
def test_agent_trajectory(toolsnap_session):
    toolsnap_session.wrap(search)
    toolsnap_session.wrap(summarize)
    agent.run("find and summarize llm docs")
    toolsnap_session.assert_called("search", times=1)
    toolsnap_session.assert_call_order(["search", "summarize"])
pytest tests/                    # replay — tool backends free, LLM still runs
pytest tests/ --toolsnap-record  # re-record after prompt/code changes
pytest tests/ --toolsnap-strict=false  # allow unexpected calls to fall through

Assertion predicates

All assertion methods accept predicate objects for structural matching:

Predicate Matches when
contains("llm") value contains the substring
matches(r"\d{4}-\d{2}") value matches the regex
any_of("london", "paris") value is one of the given options
gt(0) / lt(100) value is greater / less than threshold
s.assert_called_with("search", query=contains("london"))
s.assert_called_with("embed", n_tokens=lt(512))

CLI: inspect and diff fixtures

After a prompt change, toolsnap diff shows exactly what shifted in the agent's trajectory:

toolsnap diff fixtures/before.jsonl fixtures/after.jsonl
# Diff: fixtures/before.jsonl → fixtures/after.jsonl
# ────────────────────────────────────────────────────
#   search      call 0  args unchanged   result CHANGED (3 items → 2 items)
# + summarize   call 0  ADDED

toolsnap show fixtures/session.jsonl    # pretty-print all records
toolsnap stats fixtures/session.jsonl   # call counts, avg/p95 latency, errors
toolsnap validate fixtures/session.jsonl
toolsnap list

Metadata

Release files for toolsnap 0.1.4

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

Source distribution (sdist)

Source distribution for toolsnap 0.1.4
File Size Uploaded Provenance
toolsnap-0.1.4.tar.gz 18.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for toolsnap 0.1.4
File Interpreter ABI Platform
toolsnap-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 39.4 kB

Release files / toolsnap-0.1.4.tar.gz

Download URL toolsnap-0.1.4.tar.gz
Size 18.8 kB
Tags Source
SHA-256 checksum
How to use checksums
a5922af34515daa09ff03df0cd4f2921fa77ad629370c6629f8384458d129525
BLAKE2b-256 checksum
How to use checksums
d02fec663ef52d630c0b1026cd2b04d1d9384eee1e84b74d1abe17317f1e2661
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 Jul 16, 2026.

Transparency log

Release files / toolsnap-0.1.4-py3-none-any.whl

Download URL toolsnap-0.1.4-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
19102842d08ce2b0e86b37990c1f992a2d571182bf7199a430a8b71902ffd645
BLAKE2b-256 checksum
How to use checksums
39401c5d0073153de2689d8829f5e6aa9da010f224491263e4d179fd76e22ca4
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 Jul 16, 2026.

Transparency log

Security for toolsnap 0.1.4

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for toolsnap-0.1.4.tar.gz

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 Jul 16, 2026.

Transparency log

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.

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