ai-functions
Version:
Core AI primitives for building intelligent applications
89 lines • 4.01 kB
JavaScript
/**
* wrapForV3 — convenience composer for the v3 cascade-walker / evaluator-panel
* use case.
*
* Composes `cacheMiddleware`, `budgetMiddleware`, and `traceMiddleware` in a
* single `wrapLanguageModel` call so callers in services-as-software (round
* 15+ swap) can replace their existing post-hoc duck-typing in
* `cost-estimate.ts` with a single wrap call:
*
* ```ts
* import { wrapForV3, BudgetTracker, getEvalLogStore } from 'ai-functions'
*
* const tracker = new BudgetTracker({ maxCost: 1.0 })
* const store = getEvalLogStore()
*
* const wrapped = wrapForV3(openai('gpt-4o'), {
* cache: { store: 'disk', ttlMs: 86_400_000 },
* budget: { tracker },
* trace: { emit: (e) => store.record({ ...e, costUsd: e.costUsd ?? 0 }) },
* })
* ```
*
* **Composition order:**
* 1. **cache** — first so cache hits skip budget+trace network cost. The
* cached payload's usage still flows downstream so budget records the
* cost on hits AND misses.
* 2. **budget** — second so it sees the wrapped result regardless of
* cache hit/miss; tracker.recordUsage fires either way.
* 3. **trace** — last so the event sees the final outcome (post-cache,
* post-budget). Errors from `emit` are swallowed.
*
* AI SDK 6 ordering semantics (per the wrapLanguageModel JSDoc): "the first
* middleware will transform the input first, and the last middleware will
* be wrapped directly around the model." So when we hand the array
* `[cache, budget, trace]`, the runtime call order is
* `cache → budget → trace → model` on the way in, and the reverse on the
* way out. Cache sees the call first; if it has a hit, downstream layers
* never run their `wrapGenerate` body for that call. Budget and trace each
* get their own post-completion hook on the result the layer below them
* returned — so on a cache hit, neither budget nor trace runs (because
* cache short-circuits via `return cached`). To get budget + trace on
* cache hits, the budgetMiddleware/traceMiddleware would need to wrap the
* cache middleware (i.e. install AFTER it in the chain). Per the spec
* above, "later in the array = closer to the model" → install order
* matters: callers who want cache-hit cost recording should pass
* `[budget, trace, cache]`. We use `[cache, budget, trace]` as the default
* because the eval-fixture use case wants the 5x verify-time win and is
* happy to skip the cost record on the hit path (the original miss already
* recorded it).
*
* @packageDocumentation
*/
import { wrapLanguageModel } from 'ai';
import { cacheMiddleware } from './middleware/cache.js';
import { budgetMiddleware } from './middleware/budget.js';
import { traceMiddleware } from './middleware/trace.js';
// ============================================================================
// Composer
// ============================================================================
/**
* Wrap a {@link LanguageModel} with the v3-cascade middleware stack
* (cache → budget → trace, in install order).
*
* Returns the wrapped model with the same shape as the input; downstream
* `generateText` / `generateObject` / `streamText` calls treat it as a
* regular model.
*
* @see WrapForV3Options for partial-config behaviour
*/
export function wrapForV3(model, opts = {}) {
const middleware = [];
if (opts.cache)
middleware.push(cacheMiddleware(opts.cache));
if (opts.budget)
middleware.push(budgetMiddleware(opts.budget));
if (opts.trace)
middleware.push(traceMiddleware(opts.trace));
if (middleware.length === 0)
return model;
// wrapLanguageModel currently accepts LanguageModelV3 — at the AI SDK 6
// surface, `LanguageModel` is the wider union. The runtime is V3
// everywhere in the published providers; the cast is the same one the
// wrapLanguageModel cookbook uses.
return wrapLanguageModel({
model: model,
middleware,
});
}
//# sourceMappingURL=wrap-for-v3.js.map