Lightweight LLM cost tracking for OpenAI, Anthropic, and Google AI
Project description
Tokpulse - Python SDK
Lightweight, non-blocking LLM cost tracking for OpenAI, Anthropic, and Google AI.
Installation
pip install tokpulse
# With provider-specific extras
pip install tokpulse[openai]
pip install tokpulse[anthropic]
pip install tokpulse[google]
pip install tokpulse[all]
Quick Start
from tokpulse import TokpulseTracker
from openai import OpenAI
# Initialize tracker
tracker = TokpulseTracker(
api_key="lct_your_api_key",
project="my-app",
environment="production",
)
# Wrap your OpenAI client
client = tracker.openai(OpenAI())
# Use as normal - tracking is automatic
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
# Streaming works too
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
# Flush before exit (important for serverless)
tracker.flush()
Provider Support
OpenAI
from openai import OpenAI
client = tracker.openai(OpenAI())
response = client.chat.completions.create(...)
Anthropic
from anthropic import Anthropic
client = tracker.anthropic(Anthropic())
message = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
Google AI
import google.generativeai as genai
genai.configure(api_key="...")
model = tracker.google(genai.GenerativeModel("gemini-2.5-flash"))
response = model.generate_content("Hello!")
Configuration
tracker = TokpulseTracker(
api_key="lct_xxx", # Required
api_url="https://...", # API endpoint (default: api.tokpulse.io)
allow_insecure_http=False, # Set True only for http://localhost in local dev
batch_size=10, # Trigger an early flush when this many events are queued
flush_interval_seconds=5, # Auto-flush interval (background thread)
max_queue_size=1000, # Max queued events; excess events are silently dropped
project="my-app", # Tag all events
environment="production", # Tag with environment
debug=False, # Enable logging
on_error=my_handler, # Error callback: fn(error: Exception, event: UsageEvent)
timeout_seconds=5, # Request timeout
max_retries=3, # Retry attempts
)
api_url must use https:// by default. Plain HTTP is only allowed for localhost (http://localhost, http://127.0.0.1, http://[::1]) when allow_insecure_http=True is explicitly set.
Context Manager
with TokpulseTracker(api_key="...") as tracker:
client = tracker.openai(OpenAI())
response = client.chat.completions.create(...)
# Auto-flush on exit
Manual Tracking
tracker.track(
provider="openai",
model="gpt-4o",
input_tokens=100,
output_tokens=50,
cached_input_tokens=20,
)
track() validates inputs and raises ValueError when provider/model are empty or token counts are negative/non-integer.
Flush And Close
# Drain currently queued events
tracker.flush()
# Close: stop background workers, flush queue, and attempt pending retries once
tracker.close()
Retry backoff is scheduled in a background retry worker (non-blocking for the flush worker).
Serverless / Lambda
Always flush before the function exits:
def handler(event, context):
response = client.chat.completions.create(...)
tracker.flush() # Important!
return {"statusCode": 200, "body": response.choices[0].message.content}
Streaming Notes
Streaming usage is tracked when streams are fully consumed and also when iteration stops early (for example, break).
Async Clients
Full async wrappers are provided for OpenAI and Anthropic. Use tracker.openai_async() and tracker.anthropic_async() to get an async-native wrapper, then call await tracker.aflush() to drain the queue without blocking the event loop.
import asyncio
from openai import AsyncOpenAI
from anthropic import AsyncAnthropic
from tokpulse import TokpulseTracker
tracker = TokpulseTracker(api_key="lct_your_api_key")
# Async OpenAI
async def run_openai():
client = tracker.openai_async(AsyncOpenAI())
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
await tracker.aflush()
# Async Anthropic
async def run_anthropic():
client = tracker.anthropic_async(AsyncAnthropic())
message = await client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
await tracker.aflush()
# Close without blocking the event loop
async def shutdown():
await tracker.aclose()
Streaming works the same way — iterate with async for and usage is captured in the generator's finally block.
License
MIT
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 tokpulse-0.1.0.tar.gz.
File metadata
- Download URL: tokpulse-0.1.0.tar.gz
- Upload date:
- Size: 26.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5704192f3edccb33f5b3d795081b457c4f86be092f32c2c0549942fe45cbdfb9
|
|
| MD5 |
f9f1f6d678f06c6245b5df4f70c0c3d7
|
|
| BLAKE2b-256 |
a3f8571cc8178fec0500ff2c15ee23fe615b9cec6e6f95910dff3c283efc592b
|
File details
Details for the file tokpulse-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tokpulse-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd580ae32ce95f3388138f6e251b6277b09c2d10409fa4df8769fa8da46a40cf
|
|
| MD5 |
8a65bf79f6964518ed4073f21c27d4a6
|
|
| BLAKE2b-256 |
aeccfa8dfbe71167728932cdb4458404ee7ec0290c56cec5465f549efe07c206
|