UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

125 lines (124 loc) • 6.15 kB
/** * ClassifierRouter — the single "classify → pick model + tools → run" engine. * * Given a request snapshot it (1) classifies difficulty (heuristic or a cheap * LLM), (2) selects the best provider/model from the host-declared base pool — * cheaper/faster for easy tiers, more capable for hard tiers — enriching pool * members with real cost/quality metadata from the model registry, and * (3) narrows the tool set via per-difficulty directives or classifier hints. * * Pure of provider imports (mirrors ModelPool): the LLM caller is injected. * The only data dependency is the read-only `ModelResolver` registry lookup, * used purely for metadata enrichment and never for provider construction. */ import type { ClassifierRouterConfig, ClassifierRouterDecision, ClassifierRouterDeps, ClassifierRouterInput, ClassifierRouterPoolMember } from "../types/index.js"; export declare class ClassifierRouter { private readonly config; private readonly deps; private readonly metaCache; /** * The effective pool: what the host declared, plus catalogue-derived members * when `catalog.enabled`. Mutable so a long-lived host can widen or narrow * it without rebuilding the router — the constructor-frozen pool was the one * thing preventing a runtime model catalogue. */ private pool; /** Registry index for candidate enrichment; built once, on first use. */ private registryIndex?; constructor(config: ClassifierRouterConfig, deps?: ClassifierRouterDeps); /** * Merge the declared pool with the catalogue. Declared members win on a * duplicate `provider/model`, because a host that spelled a member out has * said something about it (a region, a description, an explicit tier) that * the registry does not know. */ private static composePool; /** * Replace the routable pool at runtime. Returns the new size. * * Clears the metadata cache, since a member's declared cost/quality is * cached per `provider::model` and a replacement pool may declare different * values for the same pair. */ setPool(members: ClassifierRouterPoolMember[]): number; /** The pool currently routed over, declared plus catalogue. */ getPool(): ClassifierRouterPoolMember[]; /** Rebuild the catalogue half of the pool (e.g. after credentials change). */ refreshCatalog(): number; /** * Classify the request and produce a combined model + tool decision, or * `null` when nothing should change. Never throws (fails open). */ route(input: ClassifierRouterInput): Promise<ClassifierRouterDecision | null>; /** * Resolve "auto" (the default) to a concrete strategy. Jev is preferred the * moment a decision provider is configured; without one the behaviour is * exactly what it was before Jev existed. */ private resolveStrategy; /** Run the chosen strategy; every one falls back to heuristic on failure. */ private classify; /** * Build LLM-facing model descriptors + an id→member map so the classifier can * pick a model directly. Works for ANY pool, registry-backed or not. */ private buildCandidates; /** * Resolve the ranked model list for a decision. Prefers the LLM's direct pick * (`selectedModelId`); otherwise falls back to difficulty-based tierMap / * metadata scoring. */ private selectModels; /** * Whether a directly-picked model clears the bar that applies to it. * * The bars are asymmetric because the two mistakes do not cost the same, and * which one a pick risks depends on what the difficulty tier would otherwise * have chosen. Picking something COSTLIER than the tier's own choice risks * spending more than necessary, so it clears the low upgrade bar. Picking * something CHEAPER risks handing the task to a model that cannot do it, so * it must clear the high downgrade bar. Agreeing with the tier needs no bar * at all. * * A strategy that reports no confidence for its pick — the LLM classifier — * is honoured exactly as before. Its self-reported numbers are not * calibrated, so a bar over them would be arithmetic on noise, and imposing * one would silently change the behaviour of a shipped, unrelated strategy. */ private pickClearsBar; /** The difficulty-based ranking, independent of any direct pick. */ private rankForDifficulty; /** * Whether a member's context window can hold the estimated request. Unknown * windows pass — the registry does not know every model, and starving the * pool on missing metadata is worse than an occasional retry. */ private fitsRequest; /** Drop members too small for the request, unless that empties the pool. */ private withRoomFor; /** Candidate members for a difficulty: explicit tierMap or eligible pool. */ private candidatesFor; /** Drop members that cannot satisfy the required capabilities (lenient). */ private filterByCapabilities; /** Rank candidates best-first for the difficulty's strategy. */ private rank; /** * Turn the classifier's context reading into a compaction threshold. * * Returns undefined — meaning "leave the 0.8 default alone" — whenever the * feature is off, no scope was read, or the scope is the widest one. The * mapping can only ever LOWER the threshold, which is enforced in * `contextScopeToThreshold` rather than here, because the consequence of * getting it wrong in the other direction is not recoverable: a * `context_window` error puts the model into ModelPool's 10-year cooldown. */ private selectContextBudget; /** Tool narrowing: per-difficulty directive, then classifier hints. */ private selectTools; /** * Resolve cost/quality/capabilities for a member. Declared values win; * gaps are filled from the model registry (by model name/alias) when known. * Results are cached per (provider/model) for the router's lifetime. */ private metaFor; }