Skip to main content

Lightweight tracing and divergence diagnosis for LLM agent chains.

Project description

tracedrift

Lightweight tracing and divergence diagnosis for LLM agent chains.

When an agent chain does something wrong, the failure usually surfaces several steps away from its cause — a retrieval step silently returns nothing, an LLM call improvises around it, and by the time you see the bad final answer you're staring at raw JSON logs trying to reconstruct what happened. tracedrift gives you a decorator-based tracer, a queryable SQLite trace store, a diagnosis pass that flags where a chain likely diverged from its normal behavior (not just where it crashed), and a way to replay a trace with one step's output swapped out to see if the rest of the chain recovers.

No mandatory cloud dependency, no framework lock-in — it works with raw API calls, LangChain, DSPy, or anything else that's just Python functions.

See docs/USAGE_GUIDE.md for instrumentation patterns, how to get useful signal out of diagnose, async/replay caveats, and performance notes — this README is the quickstart, that's the deep dive.

Install

pip install -e .

(PyPI release TBD — this is a fresh package.)

Quickstart

import tracedrift

def retrieve(query):
    with tracedrift.span("retrieve", input={"query": query}) as s:
        s.output = my_vector_db.search(query)
    return s.output  # read back so replay overrides work — see "Replay" below

def call_llm(docs):
    with tracedrift.span("llm_call", input={"docs": docs}) as s:
        s.output = my_llm.complete(docs)
    return s.output

@tracedrift.trace_agent(name="qa_agent")
def run_agent(query):
    docs = retrieve(query)
    return call_llm(docs)

run_agent("what's our refund policy?")

Traces are written to tracedrift.db (SQLite) by default. Change it with tracedrift.configure("path/to/db") before running.

Inspecting traces

tracedrift list                    # recent traces
tracedrift show <trace_id>         # span tree with timing and status
tracedrift diagnose <trace_id>     # where did this trace likely go wrong?

diagnose doesn't just look for exceptions. It compares each span's output against that span's own history (by name) across prior traces and flags:

  1. Explicit errors — a span that raised. Highest confidence, reported first.
  2. Silent empty output — a span returned None/[]/"" when it historically almost never does. This catches the common case where nothing crashes but a step quietly failed.
  3. Type drift — a span's output type differs from what it's always returned before (e.g. usually a list, this time a str).

These are intentionally simple, interpretable heuristics rather than an ML classifier — the goal is that you can trust why something got flagged, not just that it was.

Replay with intervention

Once you suspect a step, you can rerun the same call with that step's output forced to something else, to check whether the rest of the chain would have recovered:

result = tracedrift.replay_with_intervention(
    run_agent,
    args=("what's our refund policy?",),
    overrides={"retrieve": [<docs you think it should have found>]},
)

Important caveat: this overrides what gets recorded as a span's output, not the code that runs inside the with span(...) block. For an override to actually change downstream behavior, your traced function needs to read s.output back out after the block (as in the quickstart above) rather than returning a value it computed before assigning s.output. This is a deliberate simplicity tradeoff — true arbitrary step-skipping would require capturing and replaying closures, which is out of scope for a library this size.

Design notes

  • Storage: SQLite by default, one file, no server. Storage is a small wrapper around raw SQL — swap it out if you need Postgres for concurrent writers.
  • Nesting: spans nest automatically via contextvars, so this is safe across asyncio tasks (though not yet tested under heavy concurrent load — contributions welcome).
  • Serialization: inputs/outputs are stored as JSON via json.dumps(..., default=str), so non-JSON-serializable objects get stringified rather than crashing the tracer.

Async

trace_agent and span both work with async def code — no separate API:

async def retrieve(query):
    async with tracedrift.span("retrieve", input={"query": query}) as s:
        s.output = await my_vector_db.search(query)
    return s.output

@tracedrift.trace_agent(name="qa_agent")
async def run_agent(query):
    docs = await retrieve(query)
    ...

trace_agent detects async def functions automatically and awaits them. Nesting relies on contextvars, which asyncio copies into each Task at creation time — spans nest correctly as long as they run within the same Task as the enclosing trace_agent call; spans started in a separately created Task (e.g. via asyncio.create_task) won't see updates made after that task started.

Cost/token tracking

Record usage on a span with s.set_usage(...) — tracedrift doesn't know anything about specific providers' response shapes, so pull the numbers out yourself and pass them in:

with tracedrift.span("llm_call", input=docs) as s:
    resp = my_llm.complete(docs)
    s.output = resp.text
    s.set_usage(
        prompt_tokens=resp.usage.prompt_tokens,
        completion_tokens=resp.usage.completion_tokens,
        cost_usd=resp.usage.cost_usd,
    )

tracedrift show <trace_id> prints per-span usage and a trace-level total. Programmatically, tracedrift.trace_usage(trace_id) sums prompt/completion/ total tokens and cost across every span in a trace that called set_usage (returns {} if none did).

Web UI

tracedrift serve                 # http://127.0.0.1:8765
tracedrift serve --port 9000

A minimal, read-only, stdlib-only local server: trace list, span tree per trace, diagnosis and usage totals inline. No JS framework, no build step, no auth — it's meant for browsing your own local tracedrift.db during debugging, not for exposing over a network.

What's not here yet

  • Framework-specific adapters (LangChain callback handler, DSPy module wrapper)

Contributing

See CONTRIBUTING.md for dev setup, project layout, and design principles the codebase holds itself to. Please also read the Code of Conduct. Found a security issue? See SECURITY.md instead of opening a public issue.

License

MIT

Project details


Download files

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

Source Distribution

tracedrift-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

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

tracedrift-0.1.0-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

Details for the file tracedrift-0.1.0.tar.gz.

File metadata

  • Download URL: tracedrift-0.1.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tracedrift-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3977235401a95955b4f720822de7e30477a449ca0845661b396ff050740c777d
MD5 3b77743e0fe236dfeb6ca522e10774dc
BLAKE2b-256 e1295d2f3cdf7a8a28f7058c0d113292e991df194d73cd9606d07cac36292b9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tracedrift-0.1.0.tar.gz:

Publisher: release.yml on EtiSandeep/tracedrift

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

File details

Details for the file tracedrift-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tracedrift-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tracedrift-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f08bbae3389bf2df8aa8418e520bdc97a7c52c9bd0482a2ce0a7ae75626a39b4
MD5 d4ef77965374730ee543502512b6ce02
BLAKE2b-256 2d0b31e67bdb929617f45762af67fb9688e13982a860c5720c58878271a9862b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tracedrift-0.1.0-py3-none-any.whl:

Publisher: release.yml on EtiSandeep/tracedrift

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

Supported by

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