VernLLMVernLLM
Core Features

Usage Tracking

Hook into every call's usage data

usage-tracking-setup.ts
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:

token-usage-interface.ts
interface TokenUsage {
  promptTokens: number;
  completionTokens: number;
  totalTokens: number;
  reasoningTokens?: number;
  requestId: string;
  model: string;
  provider?: string;
}
  • requestId is whatever you passed to call({ 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.
  • model reflects the model that actually served the request, including a per call model override, not just the instance's default. This is important if you bill different models at different rates.
  • provider is the configured VernLLMOptions.name instance 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.
  • reasoningTokens is a subset of completionTokens, never added on top of it. Present when the provider reports a separate figure for internal reasoning. See the budgetTokens row 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:

usage-provider-information.ts
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, onUsage never fires. If a failed attempt's response included usage data, that spend is reported through onUsageFailure instead, not lost.
  • With caching, a cache hit in cachedCall does not run the underlying LLM call, so onUsage does not fire for cached results. It only fires when a provider response is actually received.

Reporting usage on failure

usage-failure-setup.ts
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

usage-cost-estimation.ts
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)}`);
  },
});

On this page