SpecCache
import { SpecCache } from '@rudra-js/core';SpecCache lets you keep the model's answers in a store of your own, such as Redis, so all your servers share them. Write an object with these methods and pass it to createComponentGenerator as cache.
export interface SpecCache { get(key: string): Promise<CachedSpec | undefined>; set(key: string, cached: CachedSpec): Promise<void>; delete?(key: string): Promise<void>; }Reference
SpecCache
Properties
get: Called with the request'skeybefore core asks the model. Resolve with the entrysetstored under it, orundefinedwhen there's none.set: Called after the model answers, with akeyand the entry to store under it. The entry holds:spec: The model's answer, before core's checks. Core checks it each time it's served from the cache.generatedAt: When the model answered, as aDate.now()timestamp.
deleteoptional: Removes the entry underkey. Core never calls it. Call it yourself to drop one bad answer, by thekeythatonEventreported for it. The next request asks the model again.
Returns
Each method returns a promise. get resolves to the entry or undefined. set and delete resolve once the store is done.
Caveats
- Entries can hold shopper data, so give them an expiry. In
'per-shopper'mode the model writes each answer from one shopper's signals, and its text can repeat their searches. Nothing maps a shopper to their keys, so you can't delete one shopper's entries on request. - Core checks what
getreturns. An entry without the shape above counts as a miss. - A
getthat throws, or takes longer thancacheTimeoutMs, counts as a miss too. The page still renders. - Core doesn't wait for
set, and it ignores an error from it. Log failed writes insidesetif you want to see them.
Usage
Sharing one cache across servers
Wrap your Redis client in the three methods. Every server that creates its generator with this cache reads the answers the others stored.
import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator, type CachedSpec, type SpecCache } from '@rudra-js/core';
interface RedisClient {
get(key: string): Promise<string | null>;
set(key: string, value: string, options: { EX: number }): Promise<string | null>;
del(key: string): Promise<number>;
}
const entries = new Map<string, string>();
const redis: RedisClient = {
async get(key) {
return entries.get(key) ?? null;
},
async set(key, value) {
entries.set(key, value);
return 'OK';
},
async del(key) {
return entries.delete(key) ? 1 : 0;
},
};
export const redisSpecCache: SpecCache = {
async get(key) {
const value = await redis.get(`rudra:${key}`);
return value === null ? undefined : (JSON.parse(value) as CachedSpec);
},
async set(key, cached) {
await redis.set(`rudra:${key}`, JSON.stringify(cached), { EX: 60 });
},
async delete(key) {
await redis.del(`rudra:${key}`);
},
};
export const generator = createComponentGenerator({
provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
cache: redisSpecCache,
});The redis object here keeps entries in a Map, so the sample runs on its own. Swap in your own Redis client.
EX: 60 expires each entry after a minute, the same as the in-memory default.
The rudra: prefix keeps these keys apart from the rest of your data.
Troubleshooting
Every event says cache: 'timeout' or cache: 'error'
'timeout' means get took longer than cacheTimeoutMs, which defaults to 50 milliseconds. Time a get against your store, then set cacheTimeoutMs above it.
'error' means get threw. Check that the store is up and your client is connected.