n4nAI

Vercel AI SDK token usage tracking across providers

Learn how to implement Vercel AI SDK token usage tracking across multiple LLM providers with step-by-step code for accurate metering and cost attribution.

n4n Team3 min read572 words

Audio narration

Coming soon — every post will get a voice note here.

Tracking spend in production LLM apps gets messy the moment you route requests to more than one model. This guide shows how to build reliable Vercel AI SDK token usage tracking across providers so you can attribute every prompt and completion token to the right user, feature, and vendor.

Step 1: Install and configure the Vercel AI SDK for multi-provider access

Start with the core ai package and the provider bindings you actually use. The SDK normalizes responses, but each provider needs its own factory.

npm install ai @ai-sdk/openai @ai-sdk/anthropic

Configure the clients from environment variables. Keep keys server-side; never ship them to the browser.

import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

export const models = {
  'gpt-4o': openai('gpt-4o'),
  'claude-3-5-sonnet': anthropic('claude-3-5-sonnet-20240620'),
};

Step 2: Extract token usage from non-streaming calls

The generateText function returns a usage object with normalized fields. This is the simplest place to start Vercel AI SDK token usage tracking.

import { generateText } from 'ai';

export async function runPrompt(model: string, prompt: string) {
  const { text, usage } = await generateText({
    model: models[model],
    prompt,
  });

  return {
    text,
    usage: {
      promptTokens: usage.promptTokens,
      completionTokens: usage.completionTokens,
      totalTokens: usage.totalTokens,
    },
  };
}

The SDK converts provider-specific counts (e.g., Anthropic’s input_tokens) into promptTokens and completionTokens. Trust those fields; don’t parse raw responses.

Step 3: Capture usage from streamed responses

Production chat UIs stream. streamText exposes a usage promise and an onFinish callback. Use onFinish to record tokens without blocking the first token.

import { streamText } from 'ai';

export async function streamPrompt(model: string, prompt: string, onToken: (t: string) => void) {
  const result = streamText({
    model: models[model],
    prompt,
    onFinish: ({ usage }) => {
      // fire-and-forget persistence
      recordUsage(model, usage);
    },
  });

  for await (const delta of result.textStream) {
    onToken(delta);
  }

  // alternatively await result.usage if you need it in the caller
  const finalUsage = await result.usage;
  return finalUsage;
}

If you need the total before responding to the client, await result.usage after consuming the stream.

Step 4: Normalize and tag usage with request context

Raw token counts are useless without attribution. Wrap the SDK calls in a function that injects userId, feature, and model into your ledger.

type UsageEvent = {
  userId: string;
  feature: string;
  model: string;
  promptTokens: number;
  completionTokens: number;
  totalTokens: number;
  ts: Date;
};

const events: UsageEvent[] = [];

export function recordUsage(
  userId: string,
  feature: string,
  model: string,
  usage: { promptTokens: number; completionTokens: number; totalTokens: number }
) {
  events.push({
    userId,
    feature,
    model,
    promptTokens: usage.promptTokens,
    completionTokens: usage.completionTokens,
    totalTokens: usage.totalTokens,
    ts: new Date(),
  });
}

Call recordUsage from your API route after generateText or inside onFinish for streams. This gives you Vercel AI SDK token usage tracking tied to business context.

Step 5: Route through a single gateway to reduce provider skew

Maintaining separate provider bindings multiplies edge cases: differing headers, error shapes, and usage fields. If you route through a gateway such as n4n.ai, the single OpenAI-compatible endpoint fronts 240+ models and returns per-token usage metering, so your SDK code treats every call identically. The gateway also honors client routing directives and forwards provider cache-control hints, which keeps cache hits accounted for correctly.

Point the OpenAI factory at the gateway instead of the vendor:

const gateway = createOpenAI({
  baseURL: process.env.N4N_BASE_URL ?? 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY!,
});

export const models = {
  'gpt-4o': gateway('openai/gpt-4o'),
  'claude-3-5-sonnet': gateway('anthropic/claude-3-5-sonnet'),
};

Your recordUsage logic doesn’t change. The usage object is still normalized by the Vercel AI SDK on top of the gateway’s metering.

Step 6: Persist token counts to your own ledger

In-memory arrays are fine for tests, not for production. Write to Postgres (or equivalent) with a minimal schema:

CREATE TABLE token_usage (
  id SERIAL PRIMARY KEY,
  user_id TEXT NOT NULL,
  feature TEXT NOT NULL,
  model TEXT NOT NULL,
  prompt_tokens INT NOT NULL,
  completion_tokens INT NOT NULL,
  total_tokens INT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Insert from your recordUsage function using a pooled client:

import { pool } from './db';

export async function recordUsageDb(
  userId: string,
  feature: string,
  model: string,
  usage: { promptTokens: number; completionTokens: number; totalTokens: number }
) {
  await pool.query(
    `INSERT INTO token_usage (user_id, feature, model, prompt_tokens, completion_tokens, total_tokens)
     VALUES ($1, $2, $3, $4, $5, $6)`,
    [userId, feature, model, usage.promptTokens, usage.completionTokens, usage.totalTokens]
  );
}

Swap the in-memory events.push for recordUsageDb in your routes.

Step 7: Verify end-to-end with a cross-provider script

Write a small Node script that hits two models, records usage, and prints the aggregate. Success means the summed tokens match the sum of individual call usages and the model column differs per provider.

import { runPrompt } from './lib';
import { recordUsageDb } from './lib/db';

async function main() {
  const a = await runPrompt('gpt-4o', 'Say hello in 5 words.');
  await recordUsageDb('test-user', 'onboarding', 'gpt-4o', a.usage);

  const b = await runPrompt('claude-3-5-sonnet', 'Say hello in 5 words.');
  await recordUsageDb('test-user', 'onboarding', 'claude-3-5-sonnet', b.usage);

  const total = a.usage.totalTokens + b.usage.totalTokens;

  console.log({
    gpt: a.usage,
    claude: b.usage,
    combined: total,
  });
}

main();

Run it with tsx script.ts. Verify success by checking the console output shows non-zero promptTokens and completionTokens for both models, and by querying SELECT model, sum(total_tokens) FROM token_usage GROUP BY model;—you should see two rows.

Step 8: Handle edge cases in Vercel AI SDK token usage tracking

A few things will bite you in production:

  • Cache hits: Anthropic and OpenAI return lower prompt token counts when a prompt prefix is cached. The SDK surfaces the same promptTokens field; don’t assume it equals your input length.
  • Stream interruptions: If the client disconnects, onFinish may not fire. Use a timeout or middleware that records partial usage from await result.usage in a finally block.
  • Model aliases: Gateway model IDs like openai/gpt-4o differ from vendor-native IDs. Store the ID you passed to the SDK, not the resolved vendor ID, to keep queries stable.

Implement these and your Vercel AI SDK token usage tracking will survive real traffic.

Tagsvercel-ai-sdktoken-usagetrackingmulti-provider

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vercel ai sdk deep dive posts →