createComponentGenerator
import { createComponentGenerator } from '@rudra-js/core';Call createComponentGenerator once on your server to get a generator. Its generate method turns one page view into a block for RudraComponent to render.
createComponentGenerator(options?: ComponentGeneratorOptions) => ComponentGeneratorReference
createComponentGenerator(options?)
Parameters
optionsoptional: Settings for the generator. Each one has a default, so you can leave this out.provideroptional: The model to call, such ascreateAnthropicProvideror your ownComponentProvider. Defaults tonull, which calls no model and bills nothing.cacheoptional: Where the model's answers are kept between requests. Defaults tocreateMemorySpecCache(). PasscreateNullSpecCache()to keep nothing.modelTimeoutMsoptional: How long the model gets to answer, in milliseconds. Defaults to1500. Past that, core cancels the call and gives you the block core builds without a model.cacheTimeoutMsoptional: How long a cache read gets, in milliseconds. Defaults to50. Past that, the request carries on as if the cache were empty.generationoptional: Defaults to'cohort'. Shoppers in the same cohort share the layout and copy the model wrote. Core fills in each shopper's own products. With'per-shopper', the model writes a block for every shopper and picks the products too.rankoptional: How core orders the products.'signals', the default, orders them by the shopper's signals.'given'keeps the order you sent. It applies in'cohort'mode and to the block core builds without a model. In'per-shopper'mode the model picks the order.onEventoptional: Called once each time the generator builds a block, with a report on how it went. If your function throws, core ignores the error.key: The cache key this request used. It isnullwhen the generator has no provider, and forgenerateDeterministic. A store withdeletecan drop one bad entry by this key.source:'llm'when the model answered this request.'cache'when an earlier answer came from the cache.'fallback'when core built the block without the model.elapsedMs: How long the request took, in milliseconds.calledModel:truewhen this request started a model call. A request that joined a call already running for the same key getsfalse.violationsoptional: What core took out of the model's block before serving it. Each one is a string, such as'empty-block:grid'. They can name products the shopper bought, disliked or has in the basket, so treat logs of them as personal data.usageoptional: The token counts the provider reported. Requests that joined a running call carry the sameusage, so add it up only wherecalledModelistrue.degradedReasonoptional: Why core built the block without the model. It is set whensourceis'fallback'.'no-provider': The generator has no provider.'provider-error': The provider threw.'timeout': The model took longer thanmodelTimeoutMs.'invalid-generation': The model's answer didn't fit the schema.'unusable-on-serve': Core's checks left no headline or no product to show.'requested': You calledgenerateDeterministic.
erroroptional: What was thrown, whendegradedReasonis'provider-error'or'timeout'.cacheoptional: How the cache read went:'hit','miss','error'or'timeout'. It is missing when no cache was read, as with no provider.
Returns
A ComponentGenerator object with the two methods below.
Caveats
- Create one generator and reuse it for every request. Its default cache and its list of model calls in progress live inside it. A new generator starts with neither.
generatenever rejects because of the model. When the model times out, fails or gives an answer core can't use, you get the block core builds without a model.- Input that fails
parseTrackingInputmakesgeneratereject andgenerateDeterministicthrow. Neither reports an event.
generator.generate(input)
Call generate with one page view to get the block for it.
Parameters
input: The page view: the shopper, the page, the products you're willing to show and the shopper's signals. Pass whatparseTrackingInputreturned, or the raw object, whichgenerateparses for you.
Returns
A promise of the ComponentSpec to pass to RudraComponent as spec.
Caveats
- With a provider, a request that isn't in the cache waits for the model, for up to
modelTimeoutMs. - Requests for the same cache key that arrive while a model call is running share that call.
generator.generateDeterministic(input)
Call generateDeterministic to get the block core builds without a model, even when the generator has a provider.
Parameters
input: The same page viewgeneratetakes.
Returns
The ComponentSpec itself. You don't need to await it.
Caveats
- It never calls the model or reads the cache.
- Its event has
source: 'fallback'anddegradedReason: 'requested'. - It still uses the shopper's
signalsto pick and order products. For a shopper who objects to profiling, see Running it in a regulated shop.
Usage
Creating one generator for your server
Create the generator at the top of a module. Call it from every place that renders a block, so they all share one cache.
import { createComponentGenerator, parseTrackingInput, type Product } from '@rudra-js/core';
const generator = createComponentGenerator();
export async function recommendationsFor(shopperId: string, sku: string, catalog: Product[]) {
const input = parseTrackingInput({
user: { id: shopperId },
context: { surface: 'pdp', currentSku: sku },
candidates: catalog,
});
const spec = await generator.generate(input);
return { spec, products: input.candidates };
}Render the result with RudraComponent.
Turning the model on
Pass a provider, and the model writes the layout, the headings and the copy. Core still checks the block against your catalog before it reaches the page.
import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator } from '@rudra-js/core';
const provider = createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! });
export const generator = createComponentGenerator({ provider });The provider takes the API key as an option, so it can come from wherever you keep secrets.
Give a slower model more time with modelTimeoutMs.
Seeing whether the model answered
Read source in onEvent to see where each block came from.
import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator } from '@rudra-js/core';
export const generator = createComponentGenerator({
provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
onEvent(event) {
switch (event.source) {
case 'llm':
console.log(`The model answered in ${event.elapsedMs}ms`);
break;
case 'cache':
console.log('An earlier answer came from the cache');
break;
case 'fallback':
console.warn('Built without the model:', event.degradedReason, event.error);
break;
}
},
});The rendered block carries the same source in its data-rudra-source attribute, so you can also check the HTML.
To track this over time, see Watching it in production.
Troubleshooting
Every block looks the same and source is 'fallback'
Log event.degradedReason. It names the cause.
'timeout' means the model took longer than modelTimeoutMs. Raise it, or pick a faster model.
'provider-error' means the provider threw. The error is in event.error. With Anthropic, look it up under source is always fallback. A 400 that asks for a workspace has its own entry.
'unusable-on-serve' means core's checks left the model's block with no headline or no product. Log event.violations to see what they took out.
'invalid-generation' means the provider returned a spec that doesn't fit the schema. A provider you wrote should throw instead, as ComponentProvider shows, so event.error says what was wrong.
'no-provider' means the generator has no provider. Pass one, as in Turning the model on.
Each request calls the model again
You're creating a new generator for each render. Unless you pass a cache, every generator makes its own, so a new one starts empty.
Create it once at the top of a module and reuse it.
import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator, type TrackingInputDraft } from '@rudra-js/core';
const provider = createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! });
// wrong
export async function slowRecommendations(input: TrackingInputDraft) {
const generator = createComponentGenerator({ provider });
return generator.generate(input);
}
// right
const generator = createComponentGenerator({ provider });
export async function recommendations(input: TrackingInputDraft) {
return generator.generate(input);
}