Watching it in production
generate never fails because of the model. So a provider that's been down for a week only shows up as plainer pages. To find out, watch onEvent.
Every block the generator builds reports one event. onEvent lists what an event holds.
Recording the events
import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator, type GenerationEvent } from '@rudra-js/core';
export const totals = { events: 0, fallbacks: 0, cacheReads: 0, cacheHits: 0 };
export const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
function record(event: GenerationEvent) {
if (event.degradedReason === 'requested') return;
totals.events += 1;
if (event.source === 'fallback') {
totals.fallbacks += 1;
console.warn('built without the model:', event.degradedReason, event.error);
}
if (event.cache) {
totals.cacheReads += 1;
if (event.cache === 'hit') totals.cacheHits += 1;
}
if (event.calledModel && event.usage) {
tokens.input += event.usage.inputTokens ?? 0;
tokens.output += event.usage.outputTokens ?? 0;
tokens.cacheRead += event.usage.cacheReadTokens ?? 0;
tokens.cacheWrite += event.usage.cacheWriteTokens ?? 0;
}
}
export const generator = createComponentGenerator({
provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
onEvent: record,
});Send these numbers to whatever you already use for metrics. The sample adds them up in memory to keep it short. It skips your own calls to generateDeterministic, which report degradedReason: 'requested'. Those are no sign of a problem.
How often the model answers
Watch the share of events with source: 'fallback', which is totals.fallbacks over totals.events. Alert when it climbs.
degradedReason and error say what went wrong. Every block looks the same and source is 'fallback' goes through each reason.
The cache hit rate
The hit rate is the events with cache: 'hit' over the events that have a cache field at all. In the sample, that's totals.cacheHits over totals.cacheReads.
A store that's down shows up here as cache: 'error', and a slow one as cache: 'timeout'. Without this, you'd only notice it as a bigger bill. Every event says cache: 'timeout' or cache: 'error' has the fix.
What you spend
Add up usage over the events where calledModel is true, as the sample does.
The total comes out lower than your bill. Sometimes the model answers, but the Anthropic adapter can't use the reply. The call was still billed. That happens when the reply stops at maxTokens, is a refusal, has no tool call, or doesn't fit the schema. Those events arrive as 'provider-error', with no usage.