@juspay/neurolink 9.79.2 → 9.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +351 -347
- package/dist/cli/factories/commandFactory.js +61 -0
- package/dist/cli/utils/classifierRouterFlags.d.ts +19 -0
- package/dist/cli/utils/classifierRouterFlags.js +111 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/neurolink.d.ts +14 -0
- package/dist/lib/neurolink.js +131 -0
- package/dist/lib/providers/googleNativeGemini3.d.ts +26 -0
- package/dist/lib/providers/googleNativeGemini3.js +48 -0
- package/dist/lib/providers/googleVertex.d.ts +16 -0
- package/dist/lib/providers/googleVertex.js +200 -24
- package/dist/lib/routing/classifierRouter.d.ts +52 -0
- package/dist/lib/routing/classifierRouter.js +269 -0
- package/dist/lib/routing/classifierStrategies.d.ts +23 -0
- package/dist/lib/routing/classifierStrategies.js +156 -0
- package/dist/lib/routing/index.d.ts +2 -0
- package/dist/lib/routing/index.js +2 -0
- package/dist/lib/session/globalSessionState.d.ts +10 -1
- package/dist/lib/session/globalSessionState.js +18 -0
- package/dist/lib/types/classifierRouter.d.ts +193 -0
- package/dist/lib/types/classifierRouter.js +17 -0
- package/dist/lib/types/cli.d.ts +27 -3
- package/dist/lib/types/config.d.ts +10 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/neurolink.d.ts +14 -0
- package/dist/neurolink.js +131 -0
- package/dist/providers/googleNativeGemini3.d.ts +26 -0
- package/dist/providers/googleNativeGemini3.js +48 -0
- package/dist/providers/googleVertex.d.ts +16 -0
- package/dist/providers/googleVertex.js +200 -24
- package/dist/routing/classifierRouter.d.ts +52 -0
- package/dist/routing/classifierRouter.js +268 -0
- package/dist/routing/classifierStrategies.d.ts +23 -0
- package/dist/routing/classifierStrategies.js +155 -0
- package/dist/routing/index.d.ts +2 -0
- package/dist/routing/index.js +2 -0
- package/dist/session/globalSessionState.d.ts +10 -1
- package/dist/session/globalSessionState.js +18 -0
- package/dist/types/classifierRouter.d.ts +193 -0
- package/dist/types/classifierRouter.js +16 -0
- package/dist/types/cli.d.ts +27 -3
- package/dist/types/config.d.ts +10 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +2 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification strategies for the ClassifierRouter.
|
|
3
|
+
*
|
|
4
|
+
* - `classifyHeuristic` — zero-cost, no LLM. Generalizes the existing binary
|
|
5
|
+
* task classifier scorer (fast vs reasoning) into five difficulty tiers.
|
|
6
|
+
* - `classifyLlm` — runs a cheap "classifier model" via an injected generate
|
|
7
|
+
* function, asking for a schema-constrained difficulty verdict. Falls back to
|
|
8
|
+
* the heuristic on any failure.
|
|
9
|
+
*/
|
|
10
|
+
import type { ClassifierCandidate, ClassifierDecision, ClassifierDifficulty, ClassifierGenerateFn, ClassifierModelRef, ClassifierRouterInput } from "../types/index.js";
|
|
11
|
+
/** Difficulty tiers, ordered easiest → hardest. */
|
|
12
|
+
export declare const CLASSIFIER_DIFFICULTIES: ClassifierDifficulty[];
|
|
13
|
+
/**
|
|
14
|
+
* Heuristic classifier — maps the binary fast/reasoning scores plus prompt
|
|
15
|
+
* length into one of five difficulty tiers. Deterministic and dependency-free.
|
|
16
|
+
*/
|
|
17
|
+
export declare function classifyHeuristic(input: ClassifierRouterInput): ClassifierDecision;
|
|
18
|
+
/**
|
|
19
|
+
* LLM classifier — asks a cheap model for a schema-constrained verdict.
|
|
20
|
+
* Falls back to the heuristic if the model is unavailable, times out, or
|
|
21
|
+
* returns output that does not match the schema.
|
|
22
|
+
*/
|
|
23
|
+
export declare function classifyLlm(input: ClassifierRouterInput, generate: ClassifierGenerateFn, classifierModel?: ClassifierModelRef, timeoutMs?: number, candidates?: ClassifierCandidate[]): Promise<ClassifierDecision>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification strategies for the ClassifierRouter.
|
|
3
|
+
*
|
|
4
|
+
* - `classifyHeuristic` — zero-cost, no LLM. Generalizes the existing binary
|
|
5
|
+
* task classifier scorer (fast vs reasoning) into five difficulty tiers.
|
|
6
|
+
* - `classifyLlm` — runs a cheap "classifier model" via an injected generate
|
|
7
|
+
* function, asking for a schema-constrained difficulty verdict. Falls back to
|
|
8
|
+
* the heuristic on any failure.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { analyzePrompt, calculateConfidence, } from "../utils/taskClassificationUtils.js";
|
|
12
|
+
import { withTimeout } from "../utils/async/index.js";
|
|
13
|
+
/** Difficulty tiers, ordered easiest → hardest. */
|
|
14
|
+
export const CLASSIFIER_DIFFICULTIES = [
|
|
15
|
+
"trivial",
|
|
16
|
+
"simple",
|
|
17
|
+
"moderate",
|
|
18
|
+
"hard",
|
|
19
|
+
"expert",
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Add capability tags implied by the request shape (vision/tools) to a set the
|
|
23
|
+
* classifier already produced.
|
|
24
|
+
*/
|
|
25
|
+
function withRequestCapabilities(input, base) {
|
|
26
|
+
const caps = new Set(base ?? []);
|
|
27
|
+
if (input.requiresVision) {
|
|
28
|
+
caps.add("vision");
|
|
29
|
+
}
|
|
30
|
+
if (input.hasTools) {
|
|
31
|
+
caps.add("tools");
|
|
32
|
+
}
|
|
33
|
+
return caps.size > 0 ? Array.from(caps) : undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Heuristic classifier — maps the binary fast/reasoning scores plus prompt
|
|
37
|
+
* length into one of five difficulty tiers. Deterministic and dependency-free.
|
|
38
|
+
*/
|
|
39
|
+
export function classifyHeuristic(input) {
|
|
40
|
+
const prompt = input.prompt ?? "";
|
|
41
|
+
const { fastScore, reasoningScore, reasons } = analyzePrompt(prompt);
|
|
42
|
+
const net = reasoningScore - fastScore;
|
|
43
|
+
const len = prompt.trim().length;
|
|
44
|
+
let difficulty;
|
|
45
|
+
if (fastScore === 0 && reasoningScore === 0) {
|
|
46
|
+
// No signal — fall back to length as a weak proxy.
|
|
47
|
+
difficulty = len < 80 ? "simple" : len < 400 ? "moderate" : "hard";
|
|
48
|
+
}
|
|
49
|
+
else if (net <= -2) {
|
|
50
|
+
difficulty = "trivial";
|
|
51
|
+
}
|
|
52
|
+
else if (net <= 0) {
|
|
53
|
+
difficulty = "simple";
|
|
54
|
+
}
|
|
55
|
+
else if (net <= 3) {
|
|
56
|
+
difficulty = "moderate";
|
|
57
|
+
}
|
|
58
|
+
else if (net <= 7) {
|
|
59
|
+
difficulty = "hard";
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
difficulty = "expert";
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
difficulty,
|
|
66
|
+
confidence: calculateConfidence(fastScore, reasoningScore),
|
|
67
|
+
requiredCapabilities: withRequestCapabilities(input),
|
|
68
|
+
reason: `heuristic: net=${net}, len=${len}${reasons.length ? ` (${reasons.slice(0, 3).join("; ")})` : ""}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Schema the LLM classifier is forced to answer with. */
|
|
72
|
+
const classifierOutputSchema = z.object({
|
|
73
|
+
difficulty: z.enum(["trivial", "simple", "moderate", "hard", "expert"]),
|
|
74
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
75
|
+
requiredCapabilities: z.array(z.string()).optional(),
|
|
76
|
+
suggestedTools: z.array(z.string()).optional(),
|
|
77
|
+
selectedModelId: z.string().optional(),
|
|
78
|
+
reason: z.string().optional(),
|
|
79
|
+
});
|
|
80
|
+
const CLASSIFIER_SYSTEM_PROMPT = [
|
|
81
|
+
"You are a routing classifier inside an AI gateway.",
|
|
82
|
+
"Classify the user's task by difficulty into EXACTLY one of:",
|
|
83
|
+
"trivial, simple, moderate, hard, expert.",
|
|
84
|
+
"Judge by reasoning depth, number of steps, domain expertise required, and ambiguity.",
|
|
85
|
+
"Greetings/lookups/one-liners are trivial/simple; multi-step analysis, design,",
|
|
86
|
+
"or expert-domain work is hard/expert.",
|
|
87
|
+
'Also list required model capabilities (e.g. "vision", "tools", "reasoning")',
|
|
88
|
+
"and, only if obvious, the names of tools the task needs.",
|
|
89
|
+
"If a list of available models is provided, also set selectedModelId to the",
|
|
90
|
+
"single best model id for this task.",
|
|
91
|
+
"Respond ONLY via the structured schema.",
|
|
92
|
+
].join(" ");
|
|
93
|
+
/**
|
|
94
|
+
* LLM classifier — asks a cheap model for a schema-constrained verdict.
|
|
95
|
+
* Falls back to the heuristic if the model is unavailable, times out, or
|
|
96
|
+
* returns output that does not match the schema.
|
|
97
|
+
*/
|
|
98
|
+
export async function classifyLlm(input, generate, classifierModel, timeoutMs, candidates) {
|
|
99
|
+
const lines = [
|
|
100
|
+
"Task to classify:",
|
|
101
|
+
'"""',
|
|
102
|
+
(input.prompt ?? "").slice(0, 4000),
|
|
103
|
+
'"""',
|
|
104
|
+
`hasTools=${!!input.hasTools} requiresVision=${!!input.requiresVision}`,
|
|
105
|
+
];
|
|
106
|
+
if (candidates && candidates.length > 0) {
|
|
107
|
+
lines.push("", "Available models — pick the single best `id` for THIS task:");
|
|
108
|
+
for (const c of candidates) {
|
|
109
|
+
const bits = [c.provider + (c.model ? `/${c.model}` : "")];
|
|
110
|
+
if (c.description) {
|
|
111
|
+
bits.push(c.description);
|
|
112
|
+
}
|
|
113
|
+
if (c.tiers && c.tiers.length > 0) {
|
|
114
|
+
bits.push(`tiers: ${c.tiers.join("/")}`);
|
|
115
|
+
}
|
|
116
|
+
if (c.capabilities && c.capabilities.length > 0) {
|
|
117
|
+
bits.push(`caps: ${c.capabilities.join(",")}`);
|
|
118
|
+
}
|
|
119
|
+
lines.push(`- id="${c.id}": ${bits.join(" — ")}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push("", "Set selectedModelId to the chosen id (omit if unsure).");
|
|
122
|
+
}
|
|
123
|
+
// `timeout` lets the provider abort its own request; withTimeout adds a hard
|
|
124
|
+
// wall-clock ceiling so a stalled classifier call can never block the turn.
|
|
125
|
+
// A TimeoutError propagates to ClassifierRouter.classify(), which falls back
|
|
126
|
+
// to the heuristic (fail-open).
|
|
127
|
+
const hardTimeoutMs = timeoutMs ?? 8000;
|
|
128
|
+
const result = await withTimeout(generate({
|
|
129
|
+
input: { text: lines.join("\n") },
|
|
130
|
+
systemPrompt: CLASSIFIER_SYSTEM_PROMPT,
|
|
131
|
+
provider: classifierModel?.provider,
|
|
132
|
+
model: classifierModel?.model,
|
|
133
|
+
region: classifierModel?.region,
|
|
134
|
+
temperature: classifierModel?.temperature ?? 0,
|
|
135
|
+
disableTools: true,
|
|
136
|
+
schema: classifierOutputSchema,
|
|
137
|
+
timeout: hardTimeoutMs,
|
|
138
|
+
// Marker consumed by NeuroLink.applyClassifierRouting to prevent the
|
|
139
|
+
// classifier's own generate() call from recursively re-routing.
|
|
140
|
+
context: { __classifierRouted: true },
|
|
141
|
+
}), hardTimeoutMs, `Classifier LLM call exceeded ${hardTimeoutMs}ms`);
|
|
142
|
+
const parsed = classifierOutputSchema.safeParse(result?.structuredData);
|
|
143
|
+
if (!parsed.success) {
|
|
144
|
+
return classifyHeuristic(input);
|
|
145
|
+
}
|
|
146
|
+
const d = parsed.data;
|
|
147
|
+
return {
|
|
148
|
+
difficulty: d.difficulty,
|
|
149
|
+
confidence: d.confidence ?? 0.7,
|
|
150
|
+
requiredCapabilities: withRequestCapabilities(input, d.requiredCapabilities),
|
|
151
|
+
suggestedTools: d.suggestedTools,
|
|
152
|
+
selectedModelId: d.selectedModelId,
|
|
153
|
+
reason: d.reason ?? "llm classifier",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=classifierStrategies.js.map
|
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { classifyProviderError, ModelPool } from "./modelPool.js";
|
|
7
7
|
export { createDefaultRequestRouter } from "./requestRouter.js";
|
|
8
|
+
export { ClassifierRouter } from "./classifierRouter.js";
|
|
9
|
+
export { classifyHeuristic, classifyLlm } from "./classifierStrategies.js";
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { classifyProviderError, ModelPool } from "./modelPool.js";
|
|
7
7
|
export { createDefaultRequestRouter } from "./requestRouter.js";
|
|
8
|
+
export { ClassifierRouter } from "./classifierRouter.js";
|
|
9
|
+
export { classifyHeuristic, classifyLlm } from "./classifierStrategies.js";
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NeuroLink } from "../neurolink.js";
|
|
2
|
-
import type { ConversationMemoryConfig, LoopSessionState, SessionVariableValue, ToolRoutingConfig } from "../types/index.js";
|
|
2
|
+
import type { ClassifierRouterConfig, ConversationMemoryConfig, LoopSessionState, SessionVariableValue, ToolRoutingConfig } from "../types/index.js";
|
|
3
3
|
export declare class GlobalSessionManager {
|
|
4
4
|
private static instance;
|
|
5
5
|
private loopSession;
|
|
6
6
|
/** Optional tool-routing config set by CLI handlers before SDK construction. */
|
|
7
7
|
private _toolRoutingConfig;
|
|
8
|
+
/** Optional classifier-router config set by CLI handlers before SDK construction. */
|
|
9
|
+
private _classifierRouterConfig;
|
|
8
10
|
static getInstance(): GlobalSessionManager;
|
|
9
11
|
setLoopSession(config?: ConversationMemoryConfig): string;
|
|
10
12
|
/**
|
|
@@ -42,6 +44,13 @@ export declare class GlobalSessionManager {
|
|
|
42
44
|
* already exists).
|
|
43
45
|
*/
|
|
44
46
|
setToolRoutingConfig(config: ToolRoutingConfig): void;
|
|
47
|
+
/**
|
|
48
|
+
* Store a classifier-router config to be injected at SDK construction time.
|
|
49
|
+
* Call this BEFORE `getOrCreateNeuroLink()` inside a command handler.
|
|
50
|
+
* When a loop session is already active the config is ignored (the instance
|
|
51
|
+
* already exists).
|
|
52
|
+
*/
|
|
53
|
+
setClassifierRouterConfig(config: ClassifierRouterConfig): void;
|
|
45
54
|
getOrCreateNeuroLink(): NeuroLink;
|
|
46
55
|
getCurrentSessionId(): string | undefined;
|
|
47
56
|
setSessionVariable(key: string, value: SessionVariableValue): void;
|
|
@@ -33,6 +33,8 @@ export class GlobalSessionManager {
|
|
|
33
33
|
loopSession = null;
|
|
34
34
|
/** Optional tool-routing config set by CLI handlers before SDK construction. */
|
|
35
35
|
_toolRoutingConfig = undefined;
|
|
36
|
+
/** Optional classifier-router config set by CLI handlers before SDK construction. */
|
|
37
|
+
_classifierRouterConfig = undefined;
|
|
36
38
|
static getInstance() {
|
|
37
39
|
if (!GlobalSessionManager.instance) {
|
|
38
40
|
GlobalSessionManager.instance = new GlobalSessionManager();
|
|
@@ -141,6 +143,18 @@ export class GlobalSessionManager {
|
|
|
141
143
|
}
|
|
142
144
|
this._toolRoutingConfig = config;
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Store a classifier-router config to be injected at SDK construction time.
|
|
148
|
+
* Call this BEFORE `getOrCreateNeuroLink()` inside a command handler.
|
|
149
|
+
* When a loop session is already active the config is ignored (the instance
|
|
150
|
+
* already exists).
|
|
151
|
+
*/
|
|
152
|
+
setClassifierRouterConfig(config) {
|
|
153
|
+
if (this.hasActiveSession()) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this._classifierRouterConfig = config;
|
|
157
|
+
}
|
|
144
158
|
getOrCreateNeuroLink() {
|
|
145
159
|
const session = this.getLoopSession();
|
|
146
160
|
if (session) {
|
|
@@ -160,6 +174,10 @@ export class GlobalSessionManager {
|
|
|
160
174
|
options.toolRouting = this._toolRoutingConfig;
|
|
161
175
|
this._toolRoutingConfig = undefined;
|
|
162
176
|
}
|
|
177
|
+
if (this._classifierRouterConfig) {
|
|
178
|
+
options.classifierRouter = this._classifierRouterConfig;
|
|
179
|
+
this._classifierRouterConfig = undefined;
|
|
180
|
+
}
|
|
163
181
|
return new NeuroLink(Object.keys(options).length ? options : undefined);
|
|
164
182
|
}
|
|
165
183
|
getCurrentSessionId() {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClassifierRouter types — generic "classify → pick model + tools → run".
|
|
3
|
+
*
|
|
4
|
+
* A ClassifierRouter inspects an incoming request, classifies it by difficulty
|
|
5
|
+
* (and optional required capabilities / suggested tools), then selects a
|
|
6
|
+
* provider/model from a host-declared "available base" pool — routing harder
|
|
7
|
+
* tasks to more capable models and easier tasks to cheaper/faster ones — and
|
|
8
|
+
* optionally narrows the tool set for that request.
|
|
9
|
+
*
|
|
10
|
+
* It is entirely opt-in (constructor config, `enabled: false` by default) and
|
|
11
|
+
* fails open: any classifier or selection error leaves the call unrouted.
|
|
12
|
+
*
|
|
13
|
+
* Type names are domain-prefixed `Classifier*` to stay globally unique across
|
|
14
|
+
* `src/lib/types/` (see CLAUDE.md rule 9).
|
|
15
|
+
*/
|
|
16
|
+
/** Coarse difficulty buckets the classifier maps a request into. */
|
|
17
|
+
export type ClassifierDifficulty = "trivial" | "simple" | "moderate" | "hard" | "expert";
|
|
18
|
+
/** Which classification strategy to run. */
|
|
19
|
+
export type ClassifierStrategyKind = "heuristic" | "llm";
|
|
20
|
+
/**
|
|
21
|
+
* The classifier's verdict for a single request. Strategy-agnostic: produced
|
|
22
|
+
* by both the heuristic and the LLM classifier.
|
|
23
|
+
*/
|
|
24
|
+
export type ClassifierDecision = {
|
|
25
|
+
/** The classified difficulty bucket. */
|
|
26
|
+
difficulty: ClassifierDifficulty;
|
|
27
|
+
/** Confidence in the classification (0–1). */
|
|
28
|
+
confidence: number;
|
|
29
|
+
/** Capability tags the request needs (e.g. "vision", "tools", "reasoning"). */
|
|
30
|
+
requiredCapabilities?: string[];
|
|
31
|
+
/** Tool names the classifier thinks the task needs (allowlist hint). */
|
|
32
|
+
suggestedTools?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* When the LLM classifier picks a model directly, the chosen candidate id
|
|
35
|
+
* (matches a `ClassifierCandidate.id`). Ignored by the heuristic classifier.
|
|
36
|
+
*/
|
|
37
|
+
selectedModelId?: string;
|
|
38
|
+
/** Human-readable explanation, emitted at debug level. */
|
|
39
|
+
reason?: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Lightweight model descriptor handed to the LLM classifier so it can select a
|
|
43
|
+
* model directly from the pool by `id` — the generic path for custom models.
|
|
44
|
+
*/
|
|
45
|
+
export type ClassifierCandidate = {
|
|
46
|
+
id: string;
|
|
47
|
+
provider: string;
|
|
48
|
+
model?: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
tiers?: ClassifierDifficulty[];
|
|
51
|
+
capabilities?: string[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* One candidate (provider, model, region) in the available base pool, with
|
|
55
|
+
* optional routing metadata. When `cost`/`quality`/`capabilities` are omitted,
|
|
56
|
+
* the router enriches them from the model registry (by `model` name/alias).
|
|
57
|
+
*/
|
|
58
|
+
export type ClassifierRouterPoolMember = {
|
|
59
|
+
provider: string;
|
|
60
|
+
model?: string;
|
|
61
|
+
region?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Stable id the LLM classifier references when selecting a model directly.
|
|
64
|
+
* Defaults to `${provider}/${model}` (or just `provider`) when omitted.
|
|
65
|
+
*/
|
|
66
|
+
id?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Plain-English description of when to use this model (e.g. "cheap & fast,
|
|
69
|
+
* for simple Q&A" / "powerful reasoning model for complex analysis"). Drives
|
|
70
|
+
* LLM-based model selection — the only metadata needed for custom models that
|
|
71
|
+
* are NOT in the registry (LiteLLM, OpenAI-compatible, self-hosted, …).
|
|
72
|
+
*/
|
|
73
|
+
description?: string;
|
|
74
|
+
/** Difficulty tiers this member is eligible for. Omit = eligible for all. */
|
|
75
|
+
tiers?: ClassifierDifficulty[];
|
|
76
|
+
/** Relative cost (lower = cheaper). Preferred for easy tiers. */
|
|
77
|
+
cost?: number;
|
|
78
|
+
/** Relative quality/capability (higher = more capable). Preferred for hard tiers. */
|
|
79
|
+
quality?: number;
|
|
80
|
+
/** Capability tags this member supports (e.g. "vision", "tools"). */
|
|
81
|
+
capabilities?: string[];
|
|
82
|
+
/** Tiebreak weight when scores are equal. Default: 1. */
|
|
83
|
+
weight?: number;
|
|
84
|
+
};
|
|
85
|
+
/** Per-difficulty tool policy applied to the request. */
|
|
86
|
+
export type ClassifierToolDirective = {
|
|
87
|
+
/** Allowlist of tool names to keep (maps to `options.toolFilter`). */
|
|
88
|
+
toolFilter?: string[];
|
|
89
|
+
/** Denylist of tool names to drop (appended to `options.excludeTools`). */
|
|
90
|
+
excludeTools?: string[];
|
|
91
|
+
};
|
|
92
|
+
/** Provider/model the LLM classifier strategy itself runs on. */
|
|
93
|
+
export type ClassifierModelRef = {
|
|
94
|
+
provider?: string;
|
|
95
|
+
model?: string;
|
|
96
|
+
region?: string;
|
|
97
|
+
temperature?: number;
|
|
98
|
+
};
|
|
99
|
+
/** Constructor-level configuration for the classifier router. */
|
|
100
|
+
export type ClassifierRouterConfig = {
|
|
101
|
+
/** Master switch. When false/absent, the router is never built. */
|
|
102
|
+
enabled: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Classification strategy. Default: "heuristic" (no LLM, zero added latency).
|
|
105
|
+
* "llm" runs a cheap classifier model (see `classifierModel`).
|
|
106
|
+
*/
|
|
107
|
+
classifier?: ClassifierStrategyKind;
|
|
108
|
+
/** Model used by the "llm" strategy. Defaults to provider/model auto. */
|
|
109
|
+
classifierModel?: ClassifierModelRef;
|
|
110
|
+
/** The available base pool the router selects a model from. */
|
|
111
|
+
pool: ClassifierRouterPoolMember[];
|
|
112
|
+
/**
|
|
113
|
+
* Explicit difficulty → members map. When a difficulty has entries here they
|
|
114
|
+
* take precedence over metadata scoring of `pool`.
|
|
115
|
+
*/
|
|
116
|
+
tierMap?: Partial<Record<ClassifierDifficulty, ClassifierRouterPoolMember[]>>;
|
|
117
|
+
/** Per-difficulty tool directives applied to the request. */
|
|
118
|
+
toolDirectives?: Partial<Record<ClassifierDifficulty, ClassifierToolDirective>>;
|
|
119
|
+
/** Hard timeout (ms) for the LLM classifier call. Default: 8000. */
|
|
120
|
+
timeoutMs?: number;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The router's combined decision: a provider/model/region override plus an
|
|
124
|
+
* optional tool narrowing. Any undefined field means "keep what the caller
|
|
125
|
+
* already configured". Returning `null` from the router is a valid no-op.
|
|
126
|
+
*/
|
|
127
|
+
export type ClassifierRouterDecision = {
|
|
128
|
+
provider?: string;
|
|
129
|
+
model?: string;
|
|
130
|
+
region?: string;
|
|
131
|
+
/** Allowlist applied to `options.toolFilter`. */
|
|
132
|
+
toolFilter?: string[];
|
|
133
|
+
/** Denylist appended to `options.excludeTools`. */
|
|
134
|
+
excludeTools?: string[];
|
|
135
|
+
/** The difficulty this decision was made for (debug/telemetry). */
|
|
136
|
+
difficulty?: ClassifierDifficulty;
|
|
137
|
+
/** Remaining ranked candidates, best-first, for downstream failover. */
|
|
138
|
+
modelFallbacks?: ClassifierRouterPoolMember[];
|
|
139
|
+
/** Human-readable explanation, emitted at debug level. */
|
|
140
|
+
reason?: string;
|
|
141
|
+
};
|
|
142
|
+
/** Lightweight request snapshot handed to the router. */
|
|
143
|
+
export type ClassifierRouterInput = {
|
|
144
|
+
prompt: string;
|
|
145
|
+
estimatedInputTokens?: number;
|
|
146
|
+
hasTools?: boolean;
|
|
147
|
+
requiresVision?: boolean;
|
|
148
|
+
thinkingLevel?: string;
|
|
149
|
+
sessionId?: string;
|
|
150
|
+
};
|
|
151
|
+
/** Enriched per-model metadata used while ranking pool members. */
|
|
152
|
+
export type ClassifierModelMeta = {
|
|
153
|
+
cost?: number;
|
|
154
|
+
quality?: number;
|
|
155
|
+
capabilities?: string[];
|
|
156
|
+
};
|
|
157
|
+
/** Minimal options accepted by the injected LLM-classifier `generate` fn. */
|
|
158
|
+
export type ClassifierGenerateOptions = {
|
|
159
|
+
input: {
|
|
160
|
+
text: string;
|
|
161
|
+
};
|
|
162
|
+
systemPrompt?: string;
|
|
163
|
+
provider?: string;
|
|
164
|
+
model?: string;
|
|
165
|
+
region?: string;
|
|
166
|
+
temperature?: number;
|
|
167
|
+
maxTokens?: number;
|
|
168
|
+
disableTools?: boolean;
|
|
169
|
+
schema?: unknown;
|
|
170
|
+
timeout?: number | string;
|
|
171
|
+
context?: Record<string, unknown>;
|
|
172
|
+
};
|
|
173
|
+
/** Minimal result shape the LLM classifier reads back. */
|
|
174
|
+
export type ClassifierGenerateResult = {
|
|
175
|
+
content?: string;
|
|
176
|
+
structuredData?: unknown;
|
|
177
|
+
};
|
|
178
|
+
/** Injected LLM caller — typically a bound `NeuroLink.generate`. */
|
|
179
|
+
export type ClassifierGenerateFn = (options: ClassifierGenerateOptions) => Promise<ClassifierGenerateResult>;
|
|
180
|
+
/** Minimal logger surface the router uses (debug/warn). */
|
|
181
|
+
export type ClassifierLogger = {
|
|
182
|
+
debug: (message: string, meta?: unknown) => void;
|
|
183
|
+
warn: (message: string, meta?: unknown) => void;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Injected dependencies — keep `ClassifierRouter` provider-import-free and
|
|
187
|
+
* unit-testable (mirrors the `toolRouting` generateFn-injection pattern).
|
|
188
|
+
*/
|
|
189
|
+
export type ClassifierRouterDeps = {
|
|
190
|
+
/** LLM caller for the "llm" strategy. Omit to disable LLM classification. */
|
|
191
|
+
generate?: ClassifierGenerateFn;
|
|
192
|
+
logger?: ClassifierLogger;
|
|
193
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClassifierRouter types — generic "classify → pick model + tools → run".
|
|
3
|
+
*
|
|
4
|
+
* A ClassifierRouter inspects an incoming request, classifies it by difficulty
|
|
5
|
+
* (and optional required capabilities / suggested tools), then selects a
|
|
6
|
+
* provider/model from a host-declared "available base" pool — routing harder
|
|
7
|
+
* tasks to more capable models and easier tasks to cheaper/faster ones — and
|
|
8
|
+
* optionally narrows the tool set for that request.
|
|
9
|
+
*
|
|
10
|
+
* It is entirely opt-in (constructor config, `enabled: false` by default) and
|
|
11
|
+
* fails open: any classifier or selection error leaves the call unrouted.
|
|
12
|
+
*
|
|
13
|
+
* Type names are domain-prefixed `Classifier*` to stay globally unique across
|
|
14
|
+
* `src/lib/types/` (see CLAUDE.md rule 9).
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=classifierRouter.js.map
|
package/dist/lib/types/cli.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export type BaseCommandArgs = {
|
|
|
45
45
|
/**
|
|
46
46
|
* Generate command arguments
|
|
47
47
|
*/
|
|
48
|
-
export type GenerateCommandArgs = BaseCommandArgs & CliToolRoutingFlags & {
|
|
48
|
+
export type GenerateCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassifierRouterFlags & {
|
|
49
49
|
/** Input text or prompt */
|
|
50
50
|
input?: string;
|
|
51
51
|
/** AI provider to use */
|
|
@@ -110,7 +110,7 @@ export type GenerateCommandArgs = BaseCommandArgs & CliToolRoutingFlags & {
|
|
|
110
110
|
/**
|
|
111
111
|
* Stream command arguments
|
|
112
112
|
*/
|
|
113
|
-
export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & {
|
|
113
|
+
export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassifierRouterFlags & {
|
|
114
114
|
/** Input text or prompt */
|
|
115
115
|
input?: string;
|
|
116
116
|
/** AI provider to use */
|
|
@@ -137,7 +137,7 @@ export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & {
|
|
|
137
137
|
/**
|
|
138
138
|
* Batch command arguments
|
|
139
139
|
*/
|
|
140
|
-
export type BatchCommandArgs = BaseCommandArgs & CliToolRoutingFlags & {
|
|
140
|
+
export type BatchCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassifierRouterFlags & {
|
|
141
141
|
/** Input file path */
|
|
142
142
|
file?: string;
|
|
143
143
|
/** AI provider to use */
|
|
@@ -1526,3 +1526,27 @@ export type CliToolRoutingFlags = {
|
|
|
1526
1526
|
*/
|
|
1527
1527
|
toolRoutingServers?: string;
|
|
1528
1528
|
};
|
|
1529
|
+
/**
|
|
1530
|
+
* CLI flags for the classifier router (`--classifier-*`). Builds a
|
|
1531
|
+
* ClassifierRouterConfig that is injected at SDK construction time.
|
|
1532
|
+
*/
|
|
1533
|
+
export type CliClassifierRouterFlags = {
|
|
1534
|
+
/** Master enable switch (--classifier-router). */
|
|
1535
|
+
classifierRouter?: boolean;
|
|
1536
|
+
/** Strategy: "heuristic" (default) or "llm" (--classifier-strategy). */
|
|
1537
|
+
classifierStrategy?: string;
|
|
1538
|
+
/** LLM-classifier provider override (--classifier-model-provider). */
|
|
1539
|
+
classifierModelProvider?: string;
|
|
1540
|
+
/** LLM-classifier model override (--classifier-model-name). */
|
|
1541
|
+
classifierModelName?: string;
|
|
1542
|
+
/** LLM-classifier region override (--classifier-model-region). */
|
|
1543
|
+
classifierModelRegion?: string;
|
|
1544
|
+
/**
|
|
1545
|
+
* Path to a JSON file OR inline JSON array of pool members
|
|
1546
|
+
* (--classifier-pool). Each entry: { provider, model?, region?, description?,
|
|
1547
|
+
* tiers?, cost?, quality?, capabilities?, id? }.
|
|
1548
|
+
*/
|
|
1549
|
+
classifierPool?: string;
|
|
1550
|
+
/** LLM-classifier hard timeout in ms (--classifier-timeout). */
|
|
1551
|
+
classifierTimeout?: number;
|
|
1552
|
+
};
|
|
@@ -11,6 +11,7 @@ import type { AuthProvider, AuthProviderType, AuthProviderConfig, Auth0Config, C
|
|
|
11
11
|
import type { NeurolinkCredentials } from "./providers.js";
|
|
12
12
|
import type { ModelPoolConfig } from "./modelPool.js";
|
|
13
13
|
import type { RequestRouter } from "./requestRouter.js";
|
|
14
|
+
import type { ClassifierRouterConfig } from "./classifierRouter.js";
|
|
14
15
|
/**
|
|
15
16
|
* Main NeuroLink configuration type
|
|
16
17
|
*/
|
|
@@ -104,6 +105,15 @@ export type NeurolinkConstructorConfig = {
|
|
|
104
105
|
* error proceeds unrouted.
|
|
105
106
|
*/
|
|
106
107
|
requestRouter?: RequestRouter;
|
|
108
|
+
/**
|
|
109
|
+
* Pre-call classifier router: classifies each request by difficulty and
|
|
110
|
+
* selects a provider/model from a configured "available base" pool — routing
|
|
111
|
+
* harder tasks to more capable models and easier tasks to cheaper/faster
|
|
112
|
+
* ones — and optionally narrows the tool set. Opt-in (`enabled: false` by
|
|
113
|
+
* default) and fails open. Skipped when a `modelPool` is configured or the
|
|
114
|
+
* caller pinned both `provider` and `model`. See {@link ClassifierRouterConfig}.
|
|
115
|
+
*/
|
|
116
|
+
classifierRouter?: ClassifierRouterConfig;
|
|
107
117
|
};
|
|
108
118
|
/**
|
|
109
119
|
* Configuration for MCP enhancement modules wired into generate()/stream() paths.
|
package/dist/lib/types/index.js
CHANGED
|
@@ -80,4 +80,6 @@ export * from "./safeFetch.js";
|
|
|
80
80
|
export * from "./modelPool.js";
|
|
81
81
|
// RequestRouter — pluggable pre-call provider/model selection (M9.x+)
|
|
82
82
|
export * from "./requestRouter.js";
|
|
83
|
+
// ClassifierRouter — classify → pick model + tools from a base pool (M9.x+)
|
|
84
|
+
export * from "./classifierRouter.js";
|
|
83
85
|
//# sourceMappingURL=index.js.map
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export declare class NeuroLink {
|
|
|
112
112
|
private readonly fallbackConfig;
|
|
113
113
|
private readonly modelPool;
|
|
114
114
|
private readonly requestRouter;
|
|
115
|
+
private readonly classifierRouter;
|
|
115
116
|
/**
|
|
116
117
|
* Merge instance-level credentials with per-call credentials.
|
|
117
118
|
*
|
|
@@ -634,6 +635,19 @@ export declare class NeuroLink {
|
|
|
634
635
|
* @param thinkingLevel — optional thinking level string from the call options.
|
|
635
636
|
*/
|
|
636
637
|
private applyRequestRouter;
|
|
638
|
+
/**
|
|
639
|
+
* Applies the host-configured `classifierRouter` to `options` in place.
|
|
640
|
+
*
|
|
641
|
+
* Classifies the request by difficulty and selects a provider/model from the
|
|
642
|
+
* configured base pool (cheaper/faster for easy tasks, more capable for hard
|
|
643
|
+
* ones), and optionally narrows the tool set via `toolFilter`/`excludeTools`.
|
|
644
|
+
*
|
|
645
|
+
* Skipped when: no router is configured; the inbound call is the classifier's
|
|
646
|
+
* own generate() (marked `__classifierRouted`); the caller pinned BOTH
|
|
647
|
+
* provider and model; or a ModelPool is configured (the pool owns selection).
|
|
648
|
+
* Fails open — any error leaves options unchanged.
|
|
649
|
+
*/
|
|
650
|
+
private applyClassifierRouting;
|
|
637
651
|
private prepareGenerateAugmentations;
|
|
638
652
|
private buildGenerateTextOptions;
|
|
639
653
|
private finalizeGenerateRequestResult;
|