std.usage
std.usage is explicit model usage accounting: a Usage struct you add up
across calls, and a Pricing struct that turns those counts into a cost estimate.
Rates are always passed in by the caller — nothing is hardcoded.
from std.usage import Usage, Pricing, zero, estimate_costWhy it exists
Section titled “Why it exists”Tracking tokens and cost across a pipeline is otherwise done by threading a
(result, usage) tuple through every function, or by a heavyweight metadata
tracker (SymbolicAI’s MetadataTracker was ~470 lines). std.usage gives you a
small, addable value instead — and because Pricing is a parameter, the same
accounting works for any provider’s rate card.
Usage — an accumulating tally
Section titled “Usage — an accumulating tally”Usage records prompt, completion, reasoning, and cached tokens, plus call and
token totals and a running cost_estimate. Start from zero() and combine tallies
with add, which returns a new summed Usage:
from std.usage import Usage, zero
total = zero()total = total.add(call_one_usage)total = total.add(call_two_usage)print(total.total_tokens, total.total_calls)Pricing — caller-supplied rates
Section titled “Pricing — caller-supplied rates”Pricing holds per-unit rates: input, cached_input, output, and per-calls.
estimate_cost(u, p) prices a Usage against a Pricing, charging cached input
tokens at the cached rate and everything else at its rate:
from std.usage import Pricing, estimate_cost
rates = Pricing(input=0.00000015, cached_input=0.000000075, output=0.0000006, calls=0.0)cost = estimate_cost(total, rates)print(cost) # dollars, given these ratesAmbient metering vs. explicit accounting
Section titled “Ambient metering vs. explicit accounting”std.usage is the explicit path — you hold and pass the Usage values
yourself. For ambient, automatic accounting, Sema has a language construct:
with meter as u: summary = summarize(article) # any model.invoke inside is countedprint(u.total_tokens)The with meter as u: scope (spec §3.6) tallies every model call in its body
without any tuple threading, and with budget(tokens=…, calls=…) as b: adds a
hard spend cap that aborts when exceeded. Use the ambient scopes for enforcement
and convenience; use std.usage when you need to hold, merge, or serialize
tallies explicitly.
See the full field and function reference in the generated std.usage API reference.