@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
TypeScript
/**
* 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;
}