Skip to main content

Async LLM/embedding token usage tracking with multi-backend support (Redis, Postgres/Supabase, MongoDB)

Project description

Token Usage Metrics

A lightweight, async-first Python package for tracking LLM and embedding token usage with multi-backend support (Redis, PostgreSQL/Supabase, MongoDB). Built for production with lifetime retention, project-based deletion, comprehensive aggregations, and non-blocking async operations.

Features

โœจ Multi-Backend Support: Redis, PostgreSQL, Supabase, and MongoDB backends with unified API ๐Ÿ“Š Rich Aggregations: Daily summaries, project/type grouping, and time-series for dashboards ๐Ÿš€ Async-First: Non-blocking operations with background flushing and circuit breakers ๐Ÿ’พ Lifetime Retention: No enforced TTL; explicit project-based deletion with date ranges ๐Ÿ” Flexible Queries: Raw event fetching with filters and cursor-based pagination ๐Ÿ›ก๏ธ Production-Ready: Structured logging, retry logic, graceful fallbacks, and health checks

Installation

# Install with Redis support (recommended for getting started)
uv add token-usage-metrics[redis]

# Or with all backends
uv add token-usage-metrics[all]

# Individual backends
uv add token-usage-metrics[postgres]  # PostgreSQL
uv add token-usage-metrics[mongo]     # MongoDB

Quick Start

Simple 3-line example:

import asyncio
from token_usage_metrics import TokenUsageClient

async def main():
    client = await TokenUsageClient.init("redis://localhost:6379/0")
    await client.log("my_app", "chat", input_tokens=100, output_tokens=50)

    events, _ = await client.query(project="my_app")
    print(f"Found {len(events)} events")
    await client.aclose()

asyncio.run(main())

With more features:

from datetime import datetime, timedelta, timezone

async def main():
    client = await TokenUsageClient.init("redis://localhost:6379/0")

    # Log usage
    await client.log(
        "chatbot", "chat",
        input_tokens=100, output_tokens=50,
        metadata={"model": "gpt-4"}
    )

    # Query events
    events, _ = await client.query(project="chatbot")

    # Get daily aggregates
    daily = await client.aggregate(
        group_by="day",
        time_from=datetime.now(timezone.utc) - timedelta(days=7)
    )

    await client.aclose()

Supabase example

settings = Settings(
    backend="supabase",
    supabase_dsn="postgresql://postgres:service_role_key@db.supabase.co:5432/postgres"
)

Configuration

Configure via environment variables (prefix: TUM_) or Settings object:

from token_usage_metrics import Settings

settings = Settings(
    # Backend selection
    backend="redis",  # redis | postgres | supabase | mongodb

    # Redis settings
    redis_url="redis://localhost:6379/0",
    redis_pool_size=10,

    # Postgres settings
    postgres_dsn="postgresql://user:pass@localhost:5432/token_usage",

    # Supabase settings
    supabase_dsn="postgresql://postgres:service_role_key@db.supabase.co:5432/postgres",

    # MongoDB settings
    mongodb_url="mongodb://localhost:27017",
    mongodb_database="token_usage",

    # Queue/buffering
    buffer_size=1000,
    flush_interval=1.0,  # seconds
    flush_batch_size=200,

    # Resilience
    max_retries=3,
    circuit_breaker_threshold=5,
)

Or via .env:

TUM_BACKEND=redis
TUM_REDIS_URL=redis://localhost:6379/0
TUM_BUFFER_SIZE=1000
TUM_FLUSH_INTERVAL=1.0

Backend Setup

Redis

# Docker
docker run -d -p 6379:6379 redis:7-alpine

# Or use managed Redis (AWS ElastiCache, Redis Cloud, etc.)

Schema: Hash-per-event + day-partitioned ZSETs + daily aggregate hashes. Optimized for fast writes and efficient date-range queries.

PostgreSQL

# Docker
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=token_usage postgres:16-alpine

# Tables auto-created on first connection

Schema: usage_events table + daily_aggregates table with indexes on (project, timestamp), (type, timestamp).

Supabase

Supabase exposes the same Postgres-compatible usage_events and daily_aggregates schema. Provide the Postgres connection string (including the service role key) via supabase_dsn so the client can connect securely.

# Example Supabase credentials
export TUM_BACKEND=supabase
export TUM_SUPABASE_DSN="postgresql://postgres:service_role_key@db.supabase.co:5432/postgres"

MongoDB

# Docker
docker run -d -p 27017:27017 mongo:7

# Collections auto-created with indexes

Schema: usage_events collection + daily_aggregates collection with compound indexes.

API Reference

Logging Events

# Single event (async, non-blocking)
await client.log(event)

# Multiple events
await client.log_many([event1, event2, event3])

# Force flush pending events
flushed_count = await client.flush(timeout=5.0)

Fetching Raw Events

from token_usage_metrics import UsageFilter

filters = UsageFilter(
    project_name="my_app",
    request_type="chat",
    time_from=datetime(...),
    time_to=datetime(...),
    limit=100,
    cursor=None  # For pagination
)

events, next_cursor = await client.fetch_raw(filters)

# Pagination
if next_cursor:
    more_events, cursor = await client.fetch_raw(
        UsageFilter(cursor=next_cursor, limit=100)
    )

Aggregations & Summaries

from token_usage_metrics import AggregateSpec, AggregateMetric

# Daily time-series (for graphs)
spec = AggregateSpec(
    metrics={
        AggregateMetric.SUM_TOTAL,
        AggregateMetric.COUNT_REQUESTS,
        AggregateMetric.AVG_TOTAL_PER_REQUEST
    }
)

daily_buckets = await client.summary_by_day(spec, filters)

# Grouped by project
project_summaries = await client.summary_by_project(spec, filters)
for summary in project_summaries:
    print(f"{summary.group_keys['project_name']}: {summary.metrics}")

# Grouped by request type
type_summaries = await client.summary_by_request_type(spec, filters)

Deleting Project Data

from token_usage_metrics import DeleteOptions

# Simulate deletion (dry-run)
options = DeleteOptions(
    project_name="my_app",
    time_from=datetime(...),  # Optional
    time_to=datetime(...),    # Optional
    include_aggregates=True,
    simulate=True
)

result = await client.delete_project(options)
print(f"Would delete {result.events_deleted} events and {result.aggregates_deleted} aggregates")

# Actual deletion
options.simulate = False
result = await client.delete_project(options)

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     TokenUsageClient                        โ”‚
โ”‚  (Async API: log, fetch_raw, summary_*, delete_project)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚ AsyncEventQueue โ”‚  (Background flusher, circuit breaker)
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚   Backend Interface   โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚            โ”‚            โ”‚            โ”‚
โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Redis  โ”‚  โ”‚Postgres โ”‚  โ”‚Supabase โ”‚  โ”‚ MongoDB โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  • Async Queue: Buffers events in memory, flushes batches periodically
  • Circuit Breaker: Auto-recovery when backend is unhealthy
  • Retry Logic: Exponential backoff with jitter for transient errors
  • Lifetime Retention: No enforced TTL (configurable per-backend if needed)

Testing

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=token_usage_metrics

# Run specific test
uv run pytest tests/test_redis_backend.py -v

Development

# Install dev dependencies
uv add --dev ruff mypy pytest pytest-asyncio fakeredis

# Lint and format
uv run ruff check .
uv run ruff format .

# Type checking
uv run mypy token_usage_metrics

Performance

  • Redis: ~10k writes/sec (pipelined batches), ~5k reads/sec (optimized ZSETs)
  • Postgres: ~2k writes/sec (bulk inserts), ~10k reads/sec (indexed queries)
  • MongoDB: ~5k writes/sec (batched inserts), ~8k reads/sec (indexed scans)

Benchmarks on single-instance deployments. Scale horizontally for higher throughput.

License

MIT

Contributing

Contributions welcome! Please open an issue or PR on GitHub.


Built with โค๏ธ by lazzyms

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

token_usage_metrics-0.1.1.tar.gz (123.1 kB view details)

Uploaded Source

Built Distribution

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

token_usage_metrics-0.1.1-py3-none-any.whl (58.8 kB view details)

Uploaded Python 3

File details

Details for the file token_usage_metrics-0.1.1.tar.gz.

File metadata

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

File hashes

Hashes for token_usage_metrics-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7b48dfc9b1c0cab2cfa4c316873488848d242bc1c7b5463d4897860f028ef3bf
MD5 fbe51410905902d0c6bf2f1fbaeae151
BLAKE2b-256 ffe44dff90dd3d4cccf36ce0fd08d95e480dbaa6bf1b88aa62a89fb8b06f058c

See more details on using hashes here.

Provenance

The following attestation bundles were made for token_usage_metrics-0.1.1.tar.gz:

Publisher: python-publish.yml on lazzyms/token_usage_metrics

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

File details

Details for the file token_usage_metrics-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for token_usage_metrics-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9ab5e47b8c3a4bde73fd382469420027903daefe1b27de4c171123313dc4265f
MD5 eab99e7127faa757f8aa2aa60be4b733
BLAKE2b-256 33b42bc1d7966feb954e95b9990293ec2e78f59110593451fd714133d4e982ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for token_usage_metrics-0.1.1-py3-none-any.whl:

Publisher: python-publish.yml on lazzyms/token_usage_metrics

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