Skip to main content

Structured tracing for applications. JSONL files, hierarchical spans, zero infrastructure.

Project description

Pedro the Raccoon — traqo mascot

traqo

Structured tracing for applications. JSONL files, hierarchical spans, zero infrastructure.

from traqo import Tracer, trace
from pathlib import Path

@trace
def classify(text: str) -> str:
    response = llm.chat(text)
    return response

with Tracer(Path("traces/run.jsonl"), input={"query": "Is this a bug?"}):
    result = classify("Is this a bug?")

Your traces are just .jsonl files. Read them with grep, query them with DuckDB, or hand them to an AI assistant.

Why traqo?

  • Zero infrastructure -- no server, no database, no account. pip install traqo and go.
  • AI-first -- JSONL is text. AI assistants read your traces directly, no browser needed.
  • Hierarchical spans -- not flat logs. Reconstruct the full call tree across functions and files.
  • Everything is a span -- LLM calls, DB queries, HTTP requests. All spans with metadata.
  • Zero dependencies -- stdlib only. Integrations are optional extras.
  • Transparent -- traces are portable files. No vendor lock-in, no proprietary format.

Install

pip install traqo                   # Core (zero dependencies)
pip install traqo[openai]           # + OpenAI integration
pip install traqo[anthropic]        # + Anthropic integration
pip install traqo[langchain]        # + LangChain integration
pip install traqo[gemini]           # + Google Gemini integration
pip install traqo[all]              # Everything

Quick Start

1. Trace a function

from traqo import Tracer, trace
from pathlib import Path

@trace
def summarize(text: str) -> str:
    # your logic here
    return summary

@trace
def pipeline(docs: list[str]) -> list[str]:
    return [summarize(doc) for doc in docs]

with Tracer(
    Path("traces/my_run.jsonl"),
    input={"docs": ["doc1", "doc2"]},
    tags=["production"],
) as tracer:
    results = pipeline(["doc1", "doc2"])
    tracer.set_output({"count": len(results)})

@trace works with sync/async functions and generators. It detects and handles all automatically.

2. Auto-trace LLM calls

from traqo.integrations.openai import traced_openai
from openai import OpenAI

client = traced_openai(OpenAI(), operation="summarize")
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarize this..."}],
)
# Token usage, model, input/output all captured automatically as span metadata

Works the same way for Anthropic, Gemini, and LangChain:

from traqo.integrations.anthropic import traced_anthropic
from traqo.integrations.gemini import traced_gemini
from traqo.integrations.langchain import traced_model

All integrations auto-capture token usage, model parameters, streaming with TTFT, and tool calls.

3. Use metadata, tags, and kind

from traqo import Tracer, LLM, TOOL

with Tracer(Path("traces/run.jsonl"), tags=["prod"]) as tracer:
    with tracer.span(
        "classify",
        input={"text": "Is this a bug?"},
        metadata={"model": "gpt-4o", "provider": "openai"},
        tags=["llm"],
        kind=LLM,
    ) as span:
        result = call_llm(...)
        span.set_metadata("token_usage", {"input_tokens": 100, "output_tokens": 50})
        span.set_output(result)

Kind constants: LLM, TOOL, RETRIEVER, CHAIN, AGENT, EMBEDDING, GUARDRAIL (or use any string).

4. Access the current span from anywhere

from traqo import trace, update_current_span

@trace
def classify(text: str) -> str:
    update_current_span(metadata={"confidence": 0.95, "model": "gpt-4o"})
    return result

update_current_span() is a convenience helper — no-op when no span is active. For full control, use get_current_span() directly.

5. Read your traces

# Last line is always trace_end with summary stats
tail -1 traces/my_run.jsonl | jq .

# All LLM spans
grep '"kind":"llm"' traces/my_run.jsonl | jq .

# Filter by tag
grep '"tags"' traces/my_run.jsonl | jq .

# Errors
grep '"status":"error"' traces/**/*.jsonl

# Token usage from span metadata
grep '"token_usage"' traces/**/*.jsonl | jq '.metadata.token_usage'

Trace Viewer UI

Browse and inspect traces in your browser. Zero dependencies — uses Python's built-in HTTP server.

traqo ui ./traces                  # Serve traces on http://localhost:7600
traqo ui ./traces --port 8080     # Custom port
traqo ui s3://my-bucket/traces/   # Browse traces from S3
traqo ui gs://my-bucket/traces/   # Browse traces from GCS
python -m traqo ui ./traces       # Alternative invocation

Cloud sources list files instantly via API, then download on click. Previously viewed traces show full summary data (duration, stats, tags) on the next page load.

Features: folder navigation, search/filter, span tree with waterfall timing, JSON viewer with syntax highlighting, token usage visualization, keyboard shortcuts (Escape to go back, ? for help).

API Reference

Tracer(path, *, input=None, metadata=None, tags=None, thread_id=None, capture_content=True, backends=None)

Creates a trace session writing to a JSONL file. Use as a context manager.

with Tracer(
    Path("traces/run.jsonl"),
    input={"query": "What is the weather?"},
    metadata={"run_id": "abc123"},
    tags=["production", "chatbot"],
    thread_id="conv-456",
    capture_content=False,  # Integrations omit LLM input/output
) as tracer:
    result = my_pipeline()
    tracer.set_output({"response": result})
Parameter Type Default Description
path Path required JSONL file path. Parent dirs created automatically.
input Any None Trace input, written to trace_start.
metadata dict {} Arbitrary metadata written to trace_start.
tags list[str] [] Tags for filtering/categorization, written to trace_start.
thread_id str None Conversation/thread grouping ID, written to trace_start.
capture_content bool True If False, integration wrappers omit LLM message inputs/outputs. The @trace decorator has separate capture_input/capture_output flags.
backends list[Backend] None Storage backends notified on events and trace completion. The local JSONL file is always written regardless.

Methods:

Method Description
span(name, *, input=, metadata=, tags=, kind=) Span context manager. Yields a Span object.
set_output(value) Set trace-level output (written to trace_end).
log(name, data) Write a custom event.
child(name, path) Create a child tracer writing to a separate file.

Span

Mutable handle yielded by tracer.span(). Set output and metadata during execution.

with tracer.span("my_step", input=data, tags=["important"], kind="tool") as span:
    result = do_work()
    span.set_output(result)
    span.set_metadata("latency_ms", 42)
    span.update_metadata({"extra": "info"})
Method Description
set_output(value) Set span output (written to span_end)
set_metadata(key, value) Set a metadata key
update_metadata(dict) Merge a dict into metadata

@trace

Decorator that wraps a function in a span. Works with sync/async functions and generators.

@trace
def my_step(data: list) -> dict:
    return process(data)

@trace("custom_name", capture_input=False, kind=TOOL)
def sensitive_step(secret: str) -> str:
    return handle(secret)

@trace(ignore_arguments=["password"], kind=TOOL)
def login(user: str, password: str) -> bool:
    return authenticate(user, password)

Parameters: name, capture_input, capture_output, ignore_arguments, metadata, tags, kind.

When no tracer is active, @trace is a pure passthrough with zero overhead.

get_current_span() -> Span | None

Returns the current active span, or None.

update_current_span(*, output=, metadata=, tags=, **kw_metadata)

Convenience helper to update the active span. No-op when no span is active.

from traqo import trace, update_current_span

@trace
def my_function(text: str) -> str:
    update_current_span(metadata={"custom_key": "custom_value"})
    return process(text)

get_tracer() -> Tracer | None

Returns the active tracer for the current context, or None.

from traqo import get_tracer

tracer = get_tracer()
if tracer:
    tracer.log("checkpoint", {"count": len(results)})

disable() / enable()

import traqo
traqo.disable()  # All tracing becomes no-op
traqo.enable()   # Re-enable

Or via environment variable: TRAQO_DISABLED=1

Child Tracers

For concurrent agents or workers that produce many events. Each child writes to its own file, linked to the parent.

with Tracer(Path("traces/pipeline.jsonl")) as tracer:
    child = tracer.child("reentrancy_agent", Path("traces/agents/reentrancy.jsonl"))
    with child:
        run_agent(...)

The parent trace records child_started / child_ended events and includes child summaries in trace_end.

JSONL Format

Every line is a self-contained JSON object. Five event types:

Type When Key Fields
trace_start Tracer enters tracer_version, input, metadata, tags, thread_id
span_start Span begins id, parent_id, name, input, metadata, tags, kind
span_end Span ends id, duration_s, status, output, metadata, tags, kind
event Custom checkpoint name, data
trace_end Tracer exits duration_s, output, stats, children

The kind field categorizes spans (e.g. "llm", "tool", "retriever"). The tags field is a list of strings for filtering. Both are omitted when not set.

The metadata dict is the universal extension point. LLM-specific data like model, provider, and token_usage are stored there.

Query with DuckDB

-- All LLM spans with token usage
SELECT metadata->>'model' as model,
       count(*) as calls,
       sum((metadata->'token_usage'->>'input_tokens')::int) as total_in,
       sum((metadata->'token_usage'->>'output_tokens')::int) as total_out,
       avg(duration_s) as avg_duration
FROM read_json('traces/**/*.jsonl')
WHERE kind = 'llm'
GROUP BY model;

-- All traces for a conversation thread
SELECT * FROM read_json('traces/**/*.jsonl')
WHERE thread_id = 'conv-123'
AND type = 'trace_start';

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

traqo-0.3.0.tar.gz (256.2 kB view details)

Uploaded Source

Built Distribution

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

traqo-0.3.0-py3-none-any.whl (62.3 kB view details)

Uploaded Python 3

File details

Details for the file traqo-0.3.0.tar.gz.

File metadata

  • Download URL: traqo-0.3.0.tar.gz
  • Upload date:
  • Size: 256.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for traqo-0.3.0.tar.gz
Algorithm Hash digest
SHA256 4fb10722519e917cd20d4362d3518b0bda5c352f826ab7bd3afdf862281a7314
MD5 500de9dc651d82adebae9b390422e672
BLAKE2b-256 c97be4f16b72295881162f76588b76c072eab198934ef88cf71eda08ba6a53b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for traqo-0.3.0.tar.gz:

Publisher: publish.yml on Cecuro/traqo

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

File details

Details for the file traqo-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: traqo-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 62.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for traqo-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e53d9a014ab527b4c967622518e4581e88dea69191aceb0076e577455aa9515
MD5 fde91cdee450443ce4ea5407ebb1303c
BLAKE2b-256 ff5a9ecc45f60fec8dac577cda2e46bb96b7970adfed6d099977589a3284113d

See more details on using hashes here.

Provenance

The following attestation bundles were made for traqo-0.3.0-py3-none-any.whl:

Publisher: publish.yml on Cecuro/traqo

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