Skip to main content

Shuntly

CI Package
Python CI PyPI
TypeScript CI NPM

A lightweight wiretap for LLM SDKs: capture all requests and responses with a single line of code.

Shuntly wraps LLM SDKs to record every request and response as JSON. Calling shunt() wraps and returns a client with its original interface and types preserved, permitting consistent IDE autocomplete and type checking. Shuntly provides a collection of configurable "sinks" to write records to stderr, files, named pipes, or any combination.

While debugging LLM tooling, maybe you want to see exactly what is being sent and returned. When launching an agent, maybe you want to record every call to the LLM. Shuntly can capture it all without TLS interception, a proxy or web-based platform, or complicated logging infrastructure.

Install

pip install shuntly

Integrate

Given an LLM SDK (e.g. anthropic, openai, google-genai, etc.), simply call shunt() with the instantiated SDK class. The returned object has the same type and interface.

from anthropic import Anthropic
from shuntly import shunt

# Without providing a sink Shuntly output goes to stderr
client = shunt(Anthropic(api_key=API_KEY))

# Now use the client as before
message = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)

Each call to messages.create() writes a complete JSON record:

{
  "timestamp": "2025-01-15T12:00:00+00:00",
  "hostname": "dev1",
  "user": "alice",
  "pid": 42,
  "client": "anthropic.Anthropic",
  "method": "messages.create",
  "request": {"model": "claude-sonnet-4-20250514", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]},
  "response": {"id": "msg_...", "content": [{"type": "text", "text": "Hi!"}]},
  "duration_ms": 823.4,
  "error": null
}

Diversify

Shuntly presently supports the following SDKs and clients:

Client Package Methods
anthropic.Anthropic PyPI messages.create, messages.stream
openai.OpenAI PyPI chat.completions.create
google.genai.Client PyPI models.generate_content
litellm PyPI completion
any-llm PyPI completion
ollama, ollama.Client PyPI chat, generate

For anything else, method paths can be explicitly provided:

client = shunt(my_client, methods=["chat.send", "embeddings.create"])

View

Shuntly JSON output can be streamed or read with a JSON viewer like fx. These tools provide JSON syntax highlighting and collapsible sections.

View Realtime Shuntly from stderr

Shuntly output, by default, goes to stderr; this is equivalent to providing a SinkStream to shunt():

from shuntly import shunt, SinkStream
client = shunt(Anthropic(api_key=API_KEY), SinkStream())

Given a command, you can view Shuntly stderr output in fx with the following:

$ command 2>&1 >/dev/null | fx

View Realtime Shuntly via a Pipe

To view Shuntly output via a named pipe in another terminal, the SinkPipe sink can be used. First, name the pipe when providing SinkPipe to shunt():

from shuntly import shunt, SinkPipe
client = shunt(Anthropic(api_key=API_KEY), SinkPipe('/tmp/shuntly.fifo'))

Then, in a terminal to view Shuntly output, create the named pipe and provide it to fx

$ mkfifo /tmp/shuntly.fifo; fx < /tmp/shuntly.fifo

Then, in another terminal, launch your command.

View Shuntly from a File

To store Shuntly output in a file, the SinkFile sink can be used. Name the file when providing SinkFile to shunt():

from shuntly import shunt, SinkFile
client = shunt(Anthropic(api_key=API_KEY), SinkFile('/tmp/shuntly.jsonl'))

Then, after your command is complete, view the file:

$ fx /tmp/shuntly.jsonl

Store Shuntly Output with File Rotation

For long-running applications, SinkRotating writes JSONL records to a directory with automatic file rotation and cleanup. Files are named with UTC timestamps (e.g. 2025-02-15T210530Z.jsonl).

from shuntly import shunt, SinkRotating
client = shunt(Anthropic(api_key=API_KEY), SinkRotating('/tmp/shuntly'))

When a file exceeds max_bytes_file (default 10 MB), a new file is created. When the directory exceeds max_bytes_dir (default 100 MB), the oldest files are pruned. Set max_bytes_dir=0 to disable pruning and retain all files. Both limits are configurable:

client = shunt(Anthropic(api_key=API_KEY), SinkRotating(
    '/tmp/shuntly',
    max_bytes_file=50 * 1024 * 1024,  # 50 MB per file
    max_bytes_dir=500 * 1024 * 1024,  # 500 MB total
))

Send Shuntly Output to Multiple Sinks

Using SinkMany, multiple sinks can be written to simultaneously.

from shuntly import shunt, SinkStream, SinkFile, SinkMany

client = shunt(Anthropic(), SinkMany([
    SinkStream(),
    SinkFile('/tmp/shuntly.jsonl'),
]))

Custom Sinks

Custom sinks can be implemented by subclassing Sink and implementing write():

from shuntly import Sink, ShuntlyRecord

class SinkPrint(Sink):
    def write(self, record: ShuntlyRecord) -> None:
        print(record.client, record.method, record.duration_ms)

What is New in Shuntly

0.8.0

Added support for Mozilla any_llm.completion()

Added support for Ollama interfaces.

0.7.0

Added new SinkRotating for rotating log handling.

0.6.0

Added support for the LiteLLM completion interface.

0.5.0

Corrected interleaved writes in SinkPipe.

0.4.0

Renamed Record to ShuntlyRecord.

Export shunt() without Shuntly class.

0.2.0

Fully tested and integrated support for OpenAI and Google SDKs.

SinkPipe is now interruptible.

0.1.0

Initial release.

Metadata

Release files for shuntly 0.8.0

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

Source distribution (sdist)

Source distribution for shuntly 0.8.0
File Size Uploaded
shuntly-0.8.0.tar.gz 11.5 kB Details

Built distribution (wheel)

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

Total release size: 21.2 kB

Release files / shuntly-0.8.0.tar.gz

Download URL shuntly-0.8.0.tar.gz
Size 11.5 kB
Tags Source
SHA-256 checksum
How to use checksums
89ae21a8612c6df504828b7e9ce6006a43dbfb9942478191764da35ad58a284a
BLAKE2b-256 checksum
How to use checksums
17b6a4b4709afd7a0078854025e6a151d035e58de38b4970f9e06209e7ea9b98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / shuntly-0.8.0-py3-none-any.whl

Download URL shuntly-0.8.0-py3-none-any.whl
Size 9.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
26e04472b7c08d5f8a07a056992d707a09558e0db63b15d27d25be6b941535f4
BLAKE2b-256 checksum
How to use checksums
898278431f0c769b166944aafdbbe0ffaf1de299f6b61c1af7f60c87804ed602
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

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