Usage Tracking
Hook into every call's usage data
const llm = new VernLLM({
client: fromOpenAI(openai),
model: 'gpt-4o',
onUsage: (usage) => {
billing.record(usage);
},
});onUsage fires after every successful call, with the following shape:
interface TokenUsage {
promptTokens: number;
completionTokens: number;
totalTokens: number;
reasoningTokens?: number;
requestId: string;
model: string;
provider?: string;
}requestIdis whatever you passed tocall({ requestId: '...' }), or an auto generated UUID if you didn't. It is included in usage callbacks and internal logging, making it useful for correlating a usage record back to the originating request, joining usage data with application logs, tracing a request across services, or implementing your own billing reconciliation or deduplication logic.modelreflects the model that actually served the request, including a per callmodeloverride, not just the instance's default. This is important if you bill different models at different rates.provideris the configuredVernLLMOptions.nameinstance label, default'primary', assigned by VernLLM to usage records. It is a label for the instance that produced the usage data, not a signal of request routing or provider selection.reasoningTokensis a subset ofcompletionTokens, never added on top of it. Present when the provider reports a separate figure for internal reasoning. See thebudgetTokensrow in the Call Params reference for how to set a limit and per provider notes on when this field is populated.
The same instance label is included in usage callbacks for both successful and failed responses:
const llm = new VernLLM({
client: fromOpenAI(openai),
model: 'gpt-4o',
onUsage: ({ requestId, model, provider, totalTokens }) => {
billing.record({
requestId,
model,
provider,
totalTokens,
});
},
onUsageFailure: ({ requestId, model, provider, totalTokens }, error) => {
billing.recordFailedRequest({
requestId,
model,
provider,
totalTokens,
error: error.type,
});
},
});provider is the configured instance label, while model describes the model that actually
served the request. Do not assume that the instance's default model or provider label is
necessarily the one represented by a particular usage record when per call overrides are involved.
When it does and doesn't fire
onUsage fires only after the call fully succeeds, meaning the raw completion, JSON parsing, and
schema validation all passed. If the onUsage callback itself throws, that failure is logged and
swallowed rather than propagated. It cannot fail or retrigger retries on an otherwise successful
call. If you need to gate a call before it is dispatched, such as enforcing a quota, see Usage
Metering.
onUsage only fires when the provider's response actually includes usage data. Not every adapter
surfaces usage identically, so check your provider's response shape if usage isn't showing up.
- On success, once per completed call, after retries have finished. It does not fire once per retry attempt.
- On failure,
onUsagenever fires. If a failed attempt's response included usage data, that spend is reported throughonUsageFailureinstead, not lost. - With caching, a cache hit in
cachedCalldoes not run the underlying LLM call, soonUsagedoes not fire for cached results. It only fires when a provider response is actually received.
Reporting usage on failure
const llm = new VernLLM({
client: fromOpenAI(openai),
model: 'gpt-4o',
onUsage: (usage) => billing.record(usage),
onUsageFailure: (usage, error) => {
billing.record(usage);
const tokens = usage.totalTokens || usage.promptTokens + usage.completionTokens;
console.warn(
`[${usage.provider ?? 'unknown'}] ${usage.model} failed after spending ${tokens} tokens: ${error.type}`,
);
},
});onUsageFailure fires when a provider response arrives, and usage data was present on it, but
VernLLM's own post processing then fails. This includes parsing, schema validation, or any other
error raised after the response landed.
It exists because onUsage only fires on full success. Without onUsageFailure, tokens spent on an
attempt that then failed would go unreported even though the provider already charged for them.
onUsageFailure fires once per failed attempt with extractable usage, not once per logical
call(). A call that fails once and then succeeds on retry fires onUsageFailure for the failed
attempt and onUsage for the successful one, both carrying their own real usage and provider
information when available.
For non streaming calls, onUsageFailure never fires for transport failures such as a timeout,
network error, or non retryable status, or when the call was aborted. In those cases there is no
reliable usage record associated with the failed response.
For stream: true, this is not guaranteed. A stream can deliver a usage chunk and then fail later,
such as an idle timeout while waiting for the final close. In that case onUsageFailure does
fire. See
Usage metering and tracking while streaming.
If the onUsageFailure callback itself throws, that failure is logged and swallowed, just like
onUsage. Usage callback failures do not replace the original call error.
Example: per request cost estimation
const PRICE_PER_1K_TOKENS = {
prompt: 0.005,
completion: 0.015,
};
const llm = new VernLLM({
client: fromOpenAI(openai),
model: 'gpt-4o',
onUsage: ({ promptTokens, completionTokens, requestId, model, provider }) => {
const cost =
(promptTokens / 1000) * PRICE_PER_1K_TOKENS.prompt +
(completionTokens / 1000) * PRICE_PER_1K_TOKENS.completion;
console.info(`[${requestId}] ${provider ?? 'unknown'} / ${model} cost: $${cost.toFixed(4)}`);
},
});