Usage Tracking
Usage Tracking
Section titled “Usage Tracking”The wippy/usage module records LLM token consumption and provides aggregate queries grouped by time interval, model, or user. It binds to the wippy.llm:usage_tracker contract, so any code that calls through the LLM module automatically produces usage records.
Add the module to your project:
wippy add wippy/usagewippy installDeclare the dependency and point the target_db requirement at the database where usage records should live:
version: "1.0"namespace: app
entries: - name: app_db kind: db.sql.sqlite path: ./data/app.db
- name: dep.usage kind: ns.dependency component: wippy/usage version: "*"
- name: target_db kind: registry.entry meta: wippy.usage.target_db: app:app_dbWhen the application starts, wippy/migration runs the module’s 01_create_token_usage_table migration, which creates the token_usage table along with indexes on user_id, context_id, model_id, and timestamp.
Schema
Section titled “Schema”token_usage├── usage_id text primary key (uuid v7)├── user_id text not null├── context_id text├── model_id text not null├── prompt_tokens integer├── completion_tokens integer├── thinking_tokens integer default 0├── cache_read_tokens integer default 0├── cache_write_tokens integer default 0├── timestamp timestamp└── meta text (JSON)Automatic Tracking
Section titled “Automatic Tracking”wippy/llm resolves the wippy.llm:usage_tracker contract before each generation. wippy/usage binds its implementation as the default:
contracts: - contract: wippy.llm:usage_tracker default: true methods: track_usage: wippy.usage:usage_trackerEvery successful LLM call invokes track_usage with the model id, token counts, and an optional context_id. The user_id is taken from the active security actor; calls outside of a user context are recorded as "system".
Tracker API
Section titled “Tracker API”Import the tracker directly when you need to record usage outside of the LLM flow:
imports: usage_tracker: wippy.usage:usage_trackerlocal tracker = require("usage_tracker")
local usage_id, err = tracker.track_usage( "openai:gpt-4o", prompt_tokens, completion_tokens, thinking_tokens, cache_read_tokens, cache_write_tokens, { context_id = "chat-42", metadata = { feature = "summary" } })| Parameter | Type | Description |
|---|---|---|
model_id | string | Canonical model id |
prompt_tokens | number | Input tokens |
completion_tokens | number | Output tokens |
thinking_tokens | number | Reasoning tokens (0 when not reported) |
cache_read_tokens | number | Prompt-cache hits |
cache_write_tokens | number | Prompt-cache writes |
options.context_id | string | Free-form tag; falls back to ctx.get("context_id") |
options.timestamp | number | Unix timestamp; defaults to now (UTC) |
options.metadata | table | Arbitrary JSON metadata stored alongside the record |
Returns usage_id or nil, err.
Repository API
Section titled “Repository API”wippy.usage:token_usage_repo offers aggregate queries:
imports: usage: wippy.usage:token_usage_repolocal usage = require("usage")
local summary = usage.get_summary(start_unix, end_unix)local by_time = usage.get_usage_by_time(start_unix, end_unix, usage.INTERVAL.DAY)local by_model = usage.get_usage_by_model(start_unix, end_unix)local by_user = usage.get_usage_by_user(start_unix, end_unix)Functions
Section titled “Functions”| Function | Returns |
|---|---|
get_summary(start, end) | Totals across the range: prompt/completion/thinking/cache tokens, request count, total_tokens (prompt + completion + thinking) |
get_usage_by_time(start, end, interval) | Array of buckets, one per interval; missing buckets return zeroes |
get_usage_by_model(start, end) | Per-model totals, ordered by total_tokens descending |
get_usage_by_user(start, end) | Per-user totals, ordered by total_tokens descending |
create(user_id, model_id, prompt, completion, options) | Low-level insert used by the tracker |
Intervals
Section titled “Intervals”usage.INTERVAL.HOUR -- "hour"usage.INTERVAL.DAY -- "day"usage.INTERVAL.WEEK -- "week"usage.INTERVAL.MONTH -- "month"get_usage_by_time aligns buckets to the configured interval. On PostgreSQL it uses generate_series with interval arithmetic; on SQLite it uses a recursive CTE over UNIX timestamps. total_tokens in each bucket excludes cache tokens.
Time Ranges
Section titled “Time Ranges”Both the tracker and the repository accept UNIX timestamps at the public API boundary. Internally the repository converts to RFC3339 strings for storage and querying. Pass os.time() or time.now():unix() values, not formatted strings.
Metadata and Context
Section titled “Metadata and Context”The meta column stores a free-form JSON blob. Use it to correlate records with application events:
tracker.track_usage(model_id, prompt, completion, 0, 0, 0, { context_id = "chat-42", metadata = { session_id = "s-7", route = "/api/summarise", agent_id = "writer", },})context_id is a top-level column and can be indexed; metadata is stored as text and is intended for display, not filtering.
See Also
Section titled “See Also”- LLM - LLM generation and the
usage_trackercontract - Migrations - Migration runner that creates the schema
- Framework Overview - Framework module usage