Router
Classify input and select a model route before generation starts.
router() selects one route before a model call starts. It is the right primitive when request shape, user tier, region, or an operator override should choose the model.
import { router, type RouteArgs } from "@use-crux/core/routing";
interface RoutingContext {
tier: "free" | "pro" | "enterprise";
betaOptIn?: boolean;
}
export const supportRouter = router({
id: "support-router",
classify: ({
input,
context,
}: RouteArgs<RoutingContext, { question: string }>) => {
if (context.betaOptIn || context.tier === "enterprise") return "deep";
return input.question.length > 2000 ? "balanced" : "fast";
},
routes: {
fast: { model: gpt5nano, maxTokens: 800 },
balanced: sonnet,
deep: { model: opus, reasoning: { effort: "high" } },
default: sonnet,
},
});Routing context is typed from the RouteArgs annotation. It is supplied at the call site with routing: and is not part of the prompt input.
const result = await generate(supportPrompt, {
model: supportRouter,
input: { question },
routing: { tier: account.tier, betaOptIn: account.flags.beta },
});Forced Routes
Use route: for tests, previews, admin controls, or deterministic comparisons.
await generate(supportPrompt, {
model: supportRouter,
input: { question },
routing: { tier: "pro" },
route: "deep",
});route: is typed from the router route keys. Forced routes skip classify and mark the receipt step with forced: true.
Default Route
Always include default. If classify returns an unknown key, Crux runs default and records usedDefaultRoute: true in the receipt.
result.routing?.trace;
// [{ kind: 'router', id: 'support-router', classifiedAs: 'legacy', route: 'default', usedDefaultRoute: true, forced: false }]The Project Index emits routing.router_missing_default when a router omits default.
Call Profiles
A route can be a raw model, another routing wrapper, or a call profile:
routes: {
cheap: { model: gpt5nano, temperature: 0, maxTokens: 600 },
careful: fallback([sonnet, opus]),
default: sonnet,
}Profile settings become defaults for the selected concrete attempt. Explicit call-site settings still win.
Avoid
- Do not omit
default. - Do not make
classifyexpensive unless the savings justify the extra work. - Do not put routing context schemas on the router. Validate at transport boundaries; use
RouteArgsfor app-internal routing types. - Use
cascade()when you must inspect a completed result before choosing the next model.