Skip to main content

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

Project description

traqo

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

from traqo import Tracer, trace
from pathlib import Path

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

with Tracer(Path("traces/run.jsonl")):
    await 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.
  • 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[all]              # Everything

Quick Start

1. Trace a function

from traqo import Tracer, trace
from pathlib import Path

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

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

async with Tracer(Path("traces/my_run.jsonl")):
    results = await pipeline(["doc1", "doc2"])

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, duration, input/output all captured automatically

Works the same way for Anthropic and LangChain:

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

3. Read your traces

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

# All LLM calls
grep '"type":"llm_call"' traces/my_run.jsonl | jq .

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

# Token costs
grep '"type":"llm_call"' traces/**/*.jsonl | jq '.token_usage'

API Reference

Tracer(path, *, metadata=None, capture_content=True)

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

with Tracer(
    Path("traces/run.jsonl"),
    metadata={"run_id": "abc123", "model": "gpt-4o"},
    capture_content=False,  # Omit LLM input/output (keep tokens, duration)
):
    await my_pipeline()
Parameter Type Default Description
path Path required JSONL file path. Parent dirs created automatically.
metadata dict {} Arbitrary metadata written to trace_start.
capture_content bool True If False, LLM inputs/outputs omitted.

Methods:

Method Description
log(name, data) Write a custom event
llm_event(model=, input_messages=, output_text=, token_usage=, duration_s=, operation=) Write an llm_call event
span(name, inputs) Manual span context manager
child(name, path) Create a child tracer writing to a separate file

@trace(name=None, *, capture_input=True, capture_output=True)

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

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

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

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

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:
        await 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. Six event types:

Type When Key Fields
trace_start Tracer enters tracer_version, metadata
span_start Function/span begins id, parent_id, name, input
span_end Function/span ends id, duration_s, status, output, error
llm_call LLM invocation model, input, output, token_usage, duration_s
event Custom checkpoint name, data
trace_end Tracer exits duration_s, stats, children

Query with DuckDB

SELECT model, count(*) as calls,
       sum(token_usage.input_tokens) as total_in,
       sum(token_usage.output_tokens) as total_out,
       avg(duration_s) as avg_duration
FROM read_json('traces/**/*.jsonl')
WHERE type = 'llm_call'
GROUP BY model;

vs Alternatives

Dimension traqo Opik (self-hosted) Langfuse (self-hosted)
Infrastructure None (filesystem) Docker + ClickHouse + MySQL Docker + Postgres
Setup pip install traqo Docker compose + config Docker compose + config
Monthly cost $0 $50-200 $50-200
Data format JSONL (portable) ClickHouse tables Postgres tables
Query method grep / DuckDB / AI SQL + UI SQL + UI
Dependencies Zero Many Many

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.1.0.tar.gz (94.1 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.1.0-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for traqo-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6ca900b685064180a796fde76d16eee99fee6287d6f64de1ab13f13a15a74373
MD5 dba2058145bc93d710af797f0ea9035d
BLAKE2b-256 f3eec98a17c4f51d869d60879042eeaf6b9ed0fbf8b3655058896728bb64078c

See more details on using hashes here.

Provenance

The following attestation bundles were made for traqo-0.1.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.1.0-py3-none-any.whl.

File metadata

  • Download URL: traqo-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.8 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1621c85d15bbd95ee052c7c8f96c932f3d2c80650d88f089a6e256074da29a6
MD5 7f0e56917590c0863547d01d3be67d9e
BLAKE2b-256 c0021920098e7d146811270cfced009e5eff6b30d5cee2bfca74936eda84b101

See more details on using hashes here.

Provenance

The following attestation bundles were made for traqo-0.1.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