Use with Anthropic
Record Anthropic prompt-caching tokens (cache_read / cache_creation) and cost in your traces.
Use with Anthropic
Anthropic prompt caching lets you reuse a large, stable prompt prefix across requests at a reduced rate. When caching is active, the Anthropic API returns two extra usage fields:
cache_creation_input_tokens— tokens written to the cache (billed ~1.25× base input)cache_read_input_tokens— tokens served from the cache (billed ~0.1× base input)
The SDK records both into your trace so the platform can compute cost correctly, applying the cache multipliers automatically. There are two ways to do it: auto-instrumentation (zero tracing code) or the manual tracing API (you map usage yourself).
The examples on this page are Node.js / TypeScript. Token capture works for both
auto-instrumented and manually-traced calls; the usageDetails keys below are the same
regardless of language.
Enable prompt caching
Caching is a property of the Anthropic request, not the SDK. Mark a large, stable block with
cache_control. The block must be big enough to cache (≥1024 tokens for Sonnet).
const message = await client.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 256,
system: [
{ type: 'text', text: 'You are a concise assistant.' },
// This large, stable block is cached and reused across calls:
{ type: 'text', text: LARGE_CONTEXT, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: 'In one sentence, what are you?' }],
});The first call with a given prefix writes the cache (cache_creation_input_tokens > 0); a
later call with the identical prefix reads it (cache_read_input_tokens > 0).
Auto-instrumentation (recommended)
With auto-instrumentation, the SDK reads the cache fields off the Anthropic response
automatically — you write no tracing code. The captured span (anthropic.chat) carries
gen_ai.usage.cache_read_input_tokens and gen_ai.usage.cache_creation_input_tokens, and cost
is computed server-side.
There are two ways to enable it.
Option A — --import flag
Preload the instrumentation so it patches @anthropic-ai/sdk before your code imports it.
// cache-auto.mjs
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const LARGE_CONTEXT = 'You are a helpful assistant.\n' + 'Reference material. '.repeat(1200);
const res = await client.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 256,
system: [{ type: 'text', text: LARGE_CONTEXT, cache_control: { type: 'ephemeral' } }],
messages: [{ role: 'user', content: 'In one sentence, what are you?' }],
});
console.log('usage:', res.usage);Run it with the instrument preload:
node --import @browserstack/ai-sdk/instrument cache-auto.mjsOption B — Observe.init() (no flag)
Configure instrumentation in code. The catch is the ordering rule below: Observe.init() must
run before @anthropic-ai/sdk loads, so the Anthropic import must be dynamic (after init).
// cache-auto-observe.mjs
import { Observe } from '@browserstack/ai-sdk';
// 1. Initialize instrumentation FIRST.
await Observe.init({
publicKey: process.env.AISDK_PUBLIC_KEY,
secretKey: process.env.AISDK_SECRET_KEY,
});
// 2. Let async patching finish before loading any provider SDK.
await new Promise((r) => setTimeout(r, 3000));
// 3. NOW import Anthropic — DYNAMIC, after init, so it is patched.
const Anthropic = (await import('@anthropic-ai/sdk')).default;
const client = new Anthropic();
const LARGE_CONTEXT = 'You are a helpful assistant.\n' + 'Reference material. '.repeat(1200);
const res = await client.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 256,
system: [{ type: 'text', text: LARGE_CONTEXT, cache_control: { type: 'ephemeral' } }],
messages: [{ role: 'user', content: 'In one sentence, what are you?' }],
});
console.log('usage:', res.usage);node cache-auto-observe.mjsThe ordering rule: instrumentation must patch @anthropic-ai/sdk before that module
is loaded. If Anthropic loads first, it loads unpatched, no spans are produced, and nothing
reaches the dashboard — even though the LLM call and caching themselves work. A static
top-level import Anthropic from '@anthropic-ai/sdk' placed above a later Observe.init()
traces nothing; use the --import flag or the dynamic-import pattern in Option B.
Manual tracing
Use the manual API when you want full control over span names, or the call isn't
auto-instrumented. Map the Anthropic usage object onto the SDK's usageDetails keys — these
are what the platform reads for cost.
import { AISDK } from '@browserstack/ai-sdk';
import Anthropic from '@anthropic-ai/sdk';
const client = new AISDK({
publicKey: process.env.AISDK_PUBLIC_KEY,
secretKey: process.env.AISDK_SECRET_KEY,
});
const anthropic = new Anthropic();
const trace = client.trace({ name: 'cache-demo', tags: ['anthropic'] });
const generation = trace.generation({
name: 'claude-call',
model: 'claude-sonnet-4-5',
input: { prompt: 'Summarize the document.' },
});
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 128,
system: [
{ type: 'text', text: 'You are a concise assistant.' },
{ type: 'text', text: LARGE_CONTEXT, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: 'Summarize the document.' }],
});
generation.end({
output: message.content.map((c) => (c.type === 'text' ? c.text : '')).join(''),
// Map real usage -> usageDetails. cache_* keys let the platform price cache hits correctly.
usageDetails: {
input: message.usage.input_tokens,
output: message.usage.output_tokens,
cache_read_input_tokens: message.usage.cache_read_input_tokens ?? 0,
cache_creation_input_tokens: message.usage.cache_creation_input_tokens ?? 0,
},
});
await client.flushAsync();
await client.shutdownAsync();The basic usage: { input, output, total } field on generation.end() cannot represent cache
tokens. Use usageDetails (shown above) whenever you need the cache_read_input_tokens /
cache_creation_input_tokens breakdown.
Overriding cost
By default the platform computes cost from model + the token breakdown, applying the cache
multipliers. To override it with your own figures, pass costDetails (manual API only):
generation.end({
output: '…',
usageDetails: {
input: 12,
output: 87,
cache_read_input_tokens: 4096,
cache_creation_input_tokens: 1024,
},
costDetails: { input: 0.0031, output: 0.0044, total: 0.0075 },
});usageDetails reference
| Key | Meaning | Relative price |
|---|---|---|
input | uncached input tokens | 1× |
output | output tokens | output rate |
cache_read_input_tokens | tokens served from cache | ~0.1× input |
cache_creation_input_tokens | tokens written to cache | ~1.25× input |
Node.js environment notes
These apply to running the standalone examples above:
--env-filerequires Node 20.6+. On Node 18, load env vars withnode -r dotenv/config <file>orimport 'dotenv/config'at the top of the script instead.--importworks on Node 18.18+. Use it (not-r) for the Option A preload.- Do not substitute
-r @browserstack/ai-sdk/instrumentfor--importin ESM scripts. The CommonJS-rpreload cannot patch an ESMimport Anthropicin time, so the SDK loads unpatched and exports zero spans even though caching works. Use--import. - Harmless
Module @anthropic-ai/sdk has been loaded before instrumentation-anthropic …warnings may still print under--import. As long as the span exports, they can be ignored.
See also
- Auto-Instrumentation — full provider list and setup
- Manual Tracing — traces, spans, generations, and scores