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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b48dfc9b1c0cab2cfa4c316873488848d242bc1c7b5463d4897860f028ef3bf
|
|
| MD5 |
fbe51410905902d0c6bf2f1fbaeae151
|
|
| BLAKE2b-256 |
ffe44dff90dd3d4cccf36ce0fd08d95e480dbaa6bf1b88aa62a89fb8b06f058c
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
token_usage_metrics-0.1.1.tar.gz -
Subject digest:
7b48dfc9b1c0cab2cfa4c316873488848d242bc1c7b5463d4897860f028ef3bf - Sigstore transparency entry: 707444813
- Sigstore integration time:
-
Permalink:
lazzyms/token_usage_metrics@e2ba44c47883626b7d6944bd05228fb94c4a3bdb -
Branch / Tag:
refs/heads/master - Owner: https://github.com/lazzyms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@e2ba44c47883626b7d6944bd05228fb94c4a3bdb -
Trigger Event:
push
-
Statement type:
File details
Details for the file token_usage_metrics-0.1.1-py3-none-any.whl.
File metadata
- Download URL: token_usage_metrics-0.1.1-py3-none-any.whl
- Upload date:
- Size: 58.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ab5e47b8c3a4bde73fd382469420027903daefe1b27de4c171123313dc4265f
|
|
| MD5 |
eab99e7127faa757f8aa2aa60be4b733
|
|
| BLAKE2b-256 |
33b42bc1d7966feb954e95b9990293ec2e78f59110593451fd714133d4e982ac
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
token_usage_metrics-0.1.1-py3-none-any.whl -
Subject digest:
9ab5e47b8c3a4bde73fd382469420027903daefe1b27de4c171123313dc4265f - Sigstore transparency entry: 707444815
- Sigstore integration time:
-
Permalink:
lazzyms/token_usage_metrics@e2ba44c47883626b7d6944bd05228fb94c4a3bdb -
Branch / Tag:
refs/heads/master - Owner: https://github.com/lazzyms
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@e2ba44c47883626b7d6944bd05228fb94c4a3bdb -
Trigger Event:
push
-
Statement type: