@juspay/neurolink 7.24.1 → 7.26.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/core/baseProvider.d.ts +13 -0
- package/dist/core/baseProvider.js +121 -1
- package/dist/core/evaluationProviders.d.ts +26 -0
- package/dist/core/evaluationProviders.js +160 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/lib/core/baseProvider.d.ts +13 -0
- package/dist/lib/core/baseProvider.js +121 -1
- package/dist/lib/core/evaluationProviders.d.ts +26 -0
- package/dist/lib/core/evaluationProviders.js +160 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/middleware/builtin/analytics.d.ts +16 -0
- package/dist/lib/middleware/builtin/analytics.js +130 -0
- package/dist/lib/middleware/factory.d.ts +54 -0
- package/dist/lib/middleware/factory.js +289 -0
- package/dist/lib/middleware/index.d.ts +58 -0
- package/dist/lib/middleware/index.js +67 -0
- package/dist/lib/middleware/registry.d.ts +78 -0
- package/dist/lib/middleware/registry.js +283 -0
- package/dist/lib/middleware/types.d.ts +144 -0
- package/dist/lib/middleware/types.js +1 -0
- package/dist/middleware/builtin/analytics.d.ts +16 -0
- package/dist/middleware/builtin/analytics.js +130 -0
- package/dist/middleware/factory.d.ts +54 -0
- package/dist/middleware/factory.js +289 -0
- package/dist/middleware/index.d.ts +58 -0
- package/dist/middleware/index.js +67 -0
- package/dist/middleware/registry.d.ts +78 -0
- package/dist/middleware/registry.js +283 -0
- package/dist/middleware/types.d.ts +144 -0
- package/dist/middleware/types.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [7.26.0](https://github.com/juspay/neurolink/compare/v7.25.0...v7.26.0) (2025-08-21)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(core):** implement provider performance metrics and optimization system ([caa68e7](https://github.com/juspay/neurolink/commit/caa68e7fd44d5a15dd48605063b093279b6f82ae))
|
|
6
|
+
|
|
7
|
+
## [7.25.0](https://github.com/juspay/neurolink/compare/v7.24.1...v7.25.0) (2025-08-21)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **(middleware):** add custom middleware development guide ([ffd0343](https://github.com/juspay/neurolink/commit/ffd0343a589b267a5b8349a06cdfe2664a942e4c))
|
|
12
|
+
|
|
1
13
|
## [7.24.1](https://github.com/juspay/neurolink/compare/v7.24.0...v7.24.1) (2025-08-21)
|
|
2
14
|
|
|
3
15
|
## [7.24.0](https://github.com/juspay/neurolink/compare/v7.23.0...v7.24.0) (2025-08-20)
|
|
@@ -63,6 +63,19 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
63
63
|
* Returns the Vercel AI SDK model instance for this provider
|
|
64
64
|
*/
|
|
65
65
|
protected abstract getAISDKModel(): LanguageModelV1 | Promise<LanguageModelV1>;
|
|
66
|
+
/**
|
|
67
|
+
* Get AI SDK model with middleware applied
|
|
68
|
+
* This method wraps the base model with any configured middleware
|
|
69
|
+
*/
|
|
70
|
+
protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModelV1>;
|
|
71
|
+
/**
|
|
72
|
+
* Extract middleware options from generation options
|
|
73
|
+
*/
|
|
74
|
+
private extractMiddlewareOptions;
|
|
75
|
+
/**
|
|
76
|
+
* Determine if middleware should be skipped for this request
|
|
77
|
+
*/
|
|
78
|
+
private shouldSkipMiddleware;
|
|
66
79
|
/**
|
|
67
80
|
* Get all available tools - direct tools are ALWAYS available
|
|
68
81
|
* MCP tools are added when available (without blocking)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
2
3
|
import { logger } from "../utils/logger.js";
|
|
3
4
|
import { DEFAULT_MAX_STEPS, STEP_LIMITS } from "../core/constants.js";
|
|
4
5
|
import { directAgentTools } from "../agent/directTools.js";
|
|
@@ -8,6 +9,8 @@ import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
|
|
|
8
9
|
import { buildMessagesArray } from "../utils/messageBuilder.js";
|
|
9
10
|
import { getKeysAsString, getKeyCount } from "../utils/transformationUtils.js";
|
|
10
11
|
import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../utils/parameterValidation.js";
|
|
12
|
+
import { recordProviderPerformanceFromMetrics, getPerformanceOptimizedProvider, } from "./evaluationProviders.js";
|
|
13
|
+
import { modelConfig } from "./modelConfiguration.js";
|
|
11
14
|
/**
|
|
12
15
|
* Abstract base class for all AI providers
|
|
13
16
|
* Tools are integrated as first-class citizens - always available by default
|
|
@@ -181,6 +184,46 @@ export class BaseProvider {
|
|
|
181
184
|
temperature: options.temperature,
|
|
182
185
|
maxTokens: options.maxTokens || 8192,
|
|
183
186
|
});
|
|
187
|
+
const responseTime = Date.now() - startTime;
|
|
188
|
+
try {
|
|
189
|
+
// Calculate actual cost based on token usage and provider configuration
|
|
190
|
+
const calculateActualCost = () => {
|
|
191
|
+
try {
|
|
192
|
+
const costInfo = modelConfig.getCostInfo(this.providerName, this.modelName);
|
|
193
|
+
if (!costInfo) {
|
|
194
|
+
return 0; // No cost info available
|
|
195
|
+
}
|
|
196
|
+
const promptTokens = result.usage?.promptTokens || 0;
|
|
197
|
+
const completionTokens = result.usage?.completionTokens || 0;
|
|
198
|
+
// Calculate cost per 1K tokens
|
|
199
|
+
const inputCost = (promptTokens / 1000) * costInfo.input;
|
|
200
|
+
const outputCost = (completionTokens / 1000) * costInfo.output;
|
|
201
|
+
return inputCost + outputCost;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
logger.debug(`Cost calculation failed for ${this.providerName}:`, error);
|
|
205
|
+
return 0; // Fallback to 0 on any error
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const actualCost = calculateActualCost();
|
|
209
|
+
recordProviderPerformanceFromMetrics(this.providerName, {
|
|
210
|
+
responseTime,
|
|
211
|
+
tokensGenerated: result.usage?.totalTokens || 0,
|
|
212
|
+
cost: actualCost,
|
|
213
|
+
success: true,
|
|
214
|
+
});
|
|
215
|
+
// Show what the system learned (updated to include cost)
|
|
216
|
+
const optimizedProvider = getPerformanceOptimizedProvider("speed");
|
|
217
|
+
logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
|
|
218
|
+
responseTime: `${responseTime}ms`,
|
|
219
|
+
tokens: result.usage?.totalTokens || 0,
|
|
220
|
+
estimatedCost: `$${actualCost.toFixed(6)}`,
|
|
221
|
+
recommendedSpeedProvider: optimizedProvider?.provider || "none",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (perfError) {
|
|
225
|
+
logger.warn("⚠️ Performance recording failed:", perfError);
|
|
226
|
+
}
|
|
184
227
|
// Extract tool names from tool calls for tracking
|
|
185
228
|
// AI SDK puts tool calls in steps array for multi-step generation
|
|
186
229
|
const toolsUsed = [];
|
|
@@ -357,6 +400,83 @@ export class BaseProvider {
|
|
|
357
400
|
evaluation: result.evaluation,
|
|
358
401
|
};
|
|
359
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Get AI SDK model with middleware applied
|
|
405
|
+
* This method wraps the base model with any configured middleware
|
|
406
|
+
*/
|
|
407
|
+
async getAISDKModelWithMiddleware(options = {}) {
|
|
408
|
+
// Get the base model
|
|
409
|
+
const baseModel = await this.getAISDKModel();
|
|
410
|
+
// Check if middleware should be applied
|
|
411
|
+
const middlewareOptions = this.extractMiddlewareOptions(options);
|
|
412
|
+
if (!middlewareOptions || this.shouldSkipMiddleware(options)) {
|
|
413
|
+
return baseModel;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
// Create middleware context
|
|
417
|
+
const context = MiddlewareFactory.createContext(this.providerName, this.modelName, options, {
|
|
418
|
+
sessionId: this.sessionId,
|
|
419
|
+
userId: this.userId,
|
|
420
|
+
});
|
|
421
|
+
// Apply middleware to the model
|
|
422
|
+
const wrappedModel = MiddlewareFactory.applyMiddleware(baseModel, context, middlewareOptions);
|
|
423
|
+
logger.debug(`Applied middleware to ${this.providerName} model`, {
|
|
424
|
+
provider: this.providerName,
|
|
425
|
+
model: this.modelName,
|
|
426
|
+
hasMiddleware: true,
|
|
427
|
+
});
|
|
428
|
+
return wrappedModel;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
logger.warn(`Failed to apply middleware to ${this.providerName}, using base model`, {
|
|
432
|
+
error: error instanceof Error ? error.message : String(error),
|
|
433
|
+
});
|
|
434
|
+
// Return base model on middleware failure to maintain functionality
|
|
435
|
+
return baseModel;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Extract middleware options from generation options
|
|
440
|
+
*/
|
|
441
|
+
extractMiddlewareOptions(options) {
|
|
442
|
+
// Check for middleware configuration in options
|
|
443
|
+
const optionsRecord = options;
|
|
444
|
+
const middlewareConfig = optionsRecord.middlewareConfig;
|
|
445
|
+
const enabledMiddleware = optionsRecord.enabledMiddleware;
|
|
446
|
+
const disabledMiddleware = optionsRecord.disabledMiddleware;
|
|
447
|
+
const preset = optionsRecord.middlewarePreset;
|
|
448
|
+
// If no middleware configuration is present, return null
|
|
449
|
+
if (!middlewareConfig &&
|
|
450
|
+
!enabledMiddleware &&
|
|
451
|
+
!disabledMiddleware &&
|
|
452
|
+
!preset) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
middlewareConfig,
|
|
457
|
+
enabledMiddleware,
|
|
458
|
+
disabledMiddleware,
|
|
459
|
+
preset,
|
|
460
|
+
global: {
|
|
461
|
+
collectStats: true,
|
|
462
|
+
continueOnError: true,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Determine if middleware should be skipped for this request
|
|
468
|
+
*/
|
|
469
|
+
shouldSkipMiddleware(options) {
|
|
470
|
+
// Skip middleware if explicitly disabled
|
|
471
|
+
if (options.disableMiddleware === true) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
// Skip middleware for tool-disabled requests to avoid conflicts
|
|
475
|
+
if (options.disableTools === true) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
360
480
|
// ===================
|
|
361
481
|
// TOOL MANAGEMENT
|
|
362
482
|
// ===================
|
|
@@ -428,7 +548,7 @@ export class BaseProvider {
|
|
|
428
548
|
timestamp: new Date().toISOString(),
|
|
429
549
|
params: params,
|
|
430
550
|
// Keep it simple - just indicate an error occurred
|
|
431
|
-
message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}
|
|
551
|
+
message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
432
552
|
};
|
|
433
553
|
}
|
|
434
554
|
},
|
|
@@ -32,3 +32,29 @@ export declare function isProviderAvailable(providerName: string): boolean;
|
|
|
32
32
|
* Get the best available provider based on preference
|
|
33
33
|
*/
|
|
34
34
|
export declare function getBestAvailableProvider(preferCheap?: boolean): ProviderModelConfig | null;
|
|
35
|
+
/**
|
|
36
|
+
Record actual provider performance for optimization
|
|
37
|
+
*/
|
|
38
|
+
export declare function recordProviderPerformanceFromMetrics(providerName: string, metrics: {
|
|
39
|
+
responseTime: number;
|
|
40
|
+
tokensGenerated: number;
|
|
41
|
+
cost: number;
|
|
42
|
+
success: boolean;
|
|
43
|
+
}): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get performance-optimized provider based on real metrics
|
|
46
|
+
*/
|
|
47
|
+
export declare function getPerformanceOptimizedProvider(priority?: "speed" | "cost" | "reliability"): ProviderModelConfig | null;
|
|
48
|
+
export declare function getProviderPerformanceAnalytics(): Record<string, {
|
|
49
|
+
avgResponseTime: number;
|
|
50
|
+
successRate: number;
|
|
51
|
+
tokenThroughput: number;
|
|
52
|
+
costEfficiency: number;
|
|
53
|
+
recommendation: string;
|
|
54
|
+
sampleCount: number;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Reset performance metrics for a provider or all providers.
|
|
58
|
+
* @param providerName - (Optional) The name of the provider to reset. If omitted, resets all providers.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resetProviderMetrics(providerName?: string): void;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { modelConfig } from "./modelConfiguration.js";
|
|
2
|
+
const PERFORMANCE_THRESHOLDS = {
|
|
3
|
+
EXCELLENT_SUCCESS_RATE: 0.95,
|
|
4
|
+
EXCELLENT_RESPONSE_TIME_MS: 2000,
|
|
5
|
+
GOOD_SUCCESS_RATE: 0.9,
|
|
6
|
+
FAIR_SUCCESS_RATE: 0.8,
|
|
7
|
+
};
|
|
8
|
+
const providerMetrics = new Map();
|
|
2
9
|
/**
|
|
3
10
|
* Convert new configuration format to legacy format for backwards compatibility
|
|
4
11
|
*/
|
|
@@ -101,3 +108,156 @@ export function getBestAvailableProvider(preferCheap = true) {
|
|
|
101
108
|
const sortedProviders = sortProvidersByPreference(availableProviders, preferCheap);
|
|
102
109
|
return sortedProviders[0];
|
|
103
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
Record actual provider performance for optimization
|
|
113
|
+
*/
|
|
114
|
+
export function recordProviderPerformanceFromMetrics(providerName, metrics) {
|
|
115
|
+
const existing = providerMetrics.get(providerName) || {
|
|
116
|
+
responseTime: [],
|
|
117
|
+
successRate: 0,
|
|
118
|
+
tokenThroughput: 0,
|
|
119
|
+
costEfficiency: 0,
|
|
120
|
+
lastUpdated: new Date(),
|
|
121
|
+
sampleCount: 0,
|
|
122
|
+
};
|
|
123
|
+
// Keep rolling window of last 50 response times
|
|
124
|
+
existing.responseTime.push(metrics.responseTime);
|
|
125
|
+
if (existing.responseTime.length > 50) {
|
|
126
|
+
existing.responseTime.shift();
|
|
127
|
+
}
|
|
128
|
+
// Update success rate
|
|
129
|
+
const totalSamples = existing.sampleCount + 1;
|
|
130
|
+
existing.successRate =
|
|
131
|
+
(existing.successRate * existing.sampleCount + (metrics.success ? 1 : 0)) /
|
|
132
|
+
totalSamples;
|
|
133
|
+
// Update throughput (tokens per second)
|
|
134
|
+
if (metrics.responseTime > 0) {
|
|
135
|
+
const tokensPerSecond = metrics.tokensGenerated / (metrics.responseTime / 1000);
|
|
136
|
+
existing.tokenThroughput =
|
|
137
|
+
(existing.tokenThroughput * existing.sampleCount + tokensPerSecond) /
|
|
138
|
+
totalSamples;
|
|
139
|
+
}
|
|
140
|
+
// If responseTime is 0, skip updating tokenThroughput for this sample
|
|
141
|
+
// but still update other metrics
|
|
142
|
+
// Update cost efficiency (tokens per dollar)
|
|
143
|
+
if (metrics.cost > 0) {
|
|
144
|
+
const tokensPerDollar = metrics.tokensGenerated / metrics.cost;
|
|
145
|
+
existing.costEfficiency =
|
|
146
|
+
(existing.costEfficiency * existing.sampleCount + tokensPerDollar) /
|
|
147
|
+
totalSamples;
|
|
148
|
+
}
|
|
149
|
+
existing.sampleCount = totalSamples;
|
|
150
|
+
existing.lastUpdated = new Date();
|
|
151
|
+
providerMetrics.set(providerName, existing);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get performance-optimized provider based on real metrics
|
|
155
|
+
*/
|
|
156
|
+
export function getPerformanceOptimizedProvider(priority = "speed") {
|
|
157
|
+
const availableProviders = getAvailableProviders();
|
|
158
|
+
if (availableProviders.length === 0) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// Score providers based on real performance data
|
|
162
|
+
const scoredProviders = availableProviders.map((provider) => {
|
|
163
|
+
const metrics = providerMetrics.get(provider.provider);
|
|
164
|
+
if (!metrics || metrics.sampleCount < 3) {
|
|
165
|
+
// Fall back to static performance ratings for providers without data
|
|
166
|
+
return {
|
|
167
|
+
provider,
|
|
168
|
+
score: getStaticPerformanceScore(provider, priority),
|
|
169
|
+
metrics: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
let score = 0;
|
|
173
|
+
switch (priority) {
|
|
174
|
+
case "speed": {
|
|
175
|
+
const avgResponseTime = metrics.responseTime.reduce((a, b) => a + b, 0) /
|
|
176
|
+
metrics.responseTime.length;
|
|
177
|
+
score =
|
|
178
|
+
metrics.tokenThroughput * 0.6 +
|
|
179
|
+
(5000 / Math.max(avgResponseTime, 100)) * 0.4;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "cost": {
|
|
183
|
+
score = metrics.costEfficiency * 0.7 + metrics.successRate * 0.3;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "reliability": {
|
|
187
|
+
score = metrics.successRate * 0.8 + (metrics.sampleCount / 100) * 0.2;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { provider, score, metrics };
|
|
192
|
+
});
|
|
193
|
+
// Sort by score and return best
|
|
194
|
+
scoredProviders.sort((a, b) => b.score - a.score);
|
|
195
|
+
return scoredProviders[0].provider;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Helper function for providers without performance data
|
|
199
|
+
*/
|
|
200
|
+
function getStaticPerformanceScore(provider, priority) {
|
|
201
|
+
switch (priority) {
|
|
202
|
+
case "speed": {
|
|
203
|
+
const speedScore = provider.performance?.speed || 1;
|
|
204
|
+
return speedScore;
|
|
205
|
+
}
|
|
206
|
+
case "cost": {
|
|
207
|
+
const costScore = provider.performance?.cost || 1;
|
|
208
|
+
return costScore;
|
|
209
|
+
}
|
|
210
|
+
case "reliability": {
|
|
211
|
+
const qualityScore = provider.performance?.quality || 1;
|
|
212
|
+
return qualityScore;
|
|
213
|
+
}
|
|
214
|
+
default: {
|
|
215
|
+
throw new Error(`Invalid priority: "${priority}". Must be one of: speed, cost, reliability`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function getProviderPerformanceAnalytics() {
|
|
220
|
+
const analytics = {};
|
|
221
|
+
for (const [providerName, metrics] of providerMetrics.entries()) {
|
|
222
|
+
if (metrics.sampleCount === 0) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const avgResponseTime = metrics.responseTime.reduce((a, b) => a + b, 0) /
|
|
226
|
+
metrics.responseTime.length;
|
|
227
|
+
let recommendation = "";
|
|
228
|
+
if (metrics.successRate > PERFORMANCE_THRESHOLDS.EXCELLENT_SUCCESS_RATE &&
|
|
229
|
+
avgResponseTime < PERFORMANCE_THRESHOLDS.EXCELLENT_RESPONSE_TIME_MS) {
|
|
230
|
+
recommendation = "Excellent - Recommended for production";
|
|
231
|
+
}
|
|
232
|
+
else if (metrics.successRate > PERFORMANCE_THRESHOLDS.GOOD_SUCCESS_RATE) {
|
|
233
|
+
recommendation = "Good - Suitable for most tasks";
|
|
234
|
+
}
|
|
235
|
+
else if (metrics.successRate > PERFORMANCE_THRESHOLDS.FAIR_SUCCESS_RATE) {
|
|
236
|
+
recommendation = "Fair - Monitor closely";
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
recommendation = "Poor - Consider alternative providers";
|
|
240
|
+
}
|
|
241
|
+
analytics[providerName] = {
|
|
242
|
+
avgResponseTime,
|
|
243
|
+
successRate: metrics.successRate,
|
|
244
|
+
tokenThroughput: metrics.tokenThroughput,
|
|
245
|
+
costEfficiency: metrics.costEfficiency,
|
|
246
|
+
recommendation,
|
|
247
|
+
sampleCount: metrics.sampleCount,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return analytics;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Reset performance metrics for a provider or all providers.
|
|
254
|
+
* @param providerName - (Optional) The name of the provider to reset. If omitted, resets all providers.
|
|
255
|
+
*/
|
|
256
|
+
export function resetProviderMetrics(providerName) {
|
|
257
|
+
if (providerName) {
|
|
258
|
+
providerMetrics.delete(providerName);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
providerMetrics.clear();
|
|
262
|
+
}
|
|
263
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export { getBestProvider, getAvailableProviders, isValidProvider, } from "./util
|
|
|
18
18
|
export { NeuroLink } from "./neurolink.js";
|
|
19
19
|
export type { ProviderStatus, MCPStatus } from "./neurolink.js";
|
|
20
20
|
export type { MCPServerInfo } from "./types/mcpTypes.js";
|
|
21
|
+
export type { NeuroLinkMiddleware, MiddlewareContext, MiddlewareFactoryOptions, } from "./middleware/types.js";
|
|
22
|
+
export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
|
|
23
|
+
export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
|
|
21
24
|
export declare const VERSION = "1.0.0";
|
|
22
25
|
/**
|
|
23
26
|
* Quick start factory function
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export { BedrockModels, OpenAIModels, VertexModels, DEFAULT_PROVIDER_CONFIGS, }
|
|
|
16
16
|
export { getBestProvider, getAvailableProviders, isValidProvider, } from "./utils/providerUtils.js";
|
|
17
17
|
// Main NeuroLink wrapper class and diagnostic types
|
|
18
18
|
export { NeuroLink } from "./neurolink.js";
|
|
19
|
+
export { MiddlewareRegistry, MiddlewareFactory } from "./middleware/index.js";
|
|
20
|
+
export { createAnalyticsMiddleware } from "./middleware/builtin/analytics.js";
|
|
19
21
|
// Version
|
|
20
22
|
export const VERSION = "1.0.0";
|
|
21
23
|
/**
|
|
@@ -63,6 +63,19 @@ export declare abstract class BaseProvider implements AIProvider {
|
|
|
63
63
|
* Returns the Vercel AI SDK model instance for this provider
|
|
64
64
|
*/
|
|
65
65
|
protected abstract getAISDKModel(): LanguageModelV1 | Promise<LanguageModelV1>;
|
|
66
|
+
/**
|
|
67
|
+
* Get AI SDK model with middleware applied
|
|
68
|
+
* This method wraps the base model with any configured middleware
|
|
69
|
+
*/
|
|
70
|
+
protected getAISDKModelWithMiddleware(options?: TextGenerationOptions | StreamOptions): Promise<LanguageModelV1>;
|
|
71
|
+
/**
|
|
72
|
+
* Extract middleware options from generation options
|
|
73
|
+
*/
|
|
74
|
+
private extractMiddlewareOptions;
|
|
75
|
+
/**
|
|
76
|
+
* Determine if middleware should be skipped for this request
|
|
77
|
+
*/
|
|
78
|
+
private shouldSkipMiddleware;
|
|
66
79
|
/**
|
|
67
80
|
* Get all available tools - direct tools are ALWAYS available
|
|
68
81
|
* MCP tools are added when available (without blocking)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
2
3
|
import { logger } from "../utils/logger.js";
|
|
3
4
|
import { DEFAULT_MAX_STEPS, STEP_LIMITS } from "../core/constants.js";
|
|
4
5
|
import { directAgentTools } from "../agent/directTools.js";
|
|
@@ -8,6 +9,8 @@ import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
|
|
|
8
9
|
import { buildMessagesArray } from "../utils/messageBuilder.js";
|
|
9
10
|
import { getKeysAsString, getKeyCount } from "../utils/transformationUtils.js";
|
|
10
11
|
import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../utils/parameterValidation.js";
|
|
12
|
+
import { recordProviderPerformanceFromMetrics, getPerformanceOptimizedProvider, } from "./evaluationProviders.js";
|
|
13
|
+
import { modelConfig } from "./modelConfiguration.js";
|
|
11
14
|
/**
|
|
12
15
|
* Abstract base class for all AI providers
|
|
13
16
|
* Tools are integrated as first-class citizens - always available by default
|
|
@@ -181,6 +184,46 @@ export class BaseProvider {
|
|
|
181
184
|
temperature: options.temperature,
|
|
182
185
|
maxTokens: options.maxTokens || 8192,
|
|
183
186
|
});
|
|
187
|
+
const responseTime = Date.now() - startTime;
|
|
188
|
+
try {
|
|
189
|
+
// Calculate actual cost based on token usage and provider configuration
|
|
190
|
+
const calculateActualCost = () => {
|
|
191
|
+
try {
|
|
192
|
+
const costInfo = modelConfig.getCostInfo(this.providerName, this.modelName);
|
|
193
|
+
if (!costInfo) {
|
|
194
|
+
return 0; // No cost info available
|
|
195
|
+
}
|
|
196
|
+
const promptTokens = result.usage?.promptTokens || 0;
|
|
197
|
+
const completionTokens = result.usage?.completionTokens || 0;
|
|
198
|
+
// Calculate cost per 1K tokens
|
|
199
|
+
const inputCost = (promptTokens / 1000) * costInfo.input;
|
|
200
|
+
const outputCost = (completionTokens / 1000) * costInfo.output;
|
|
201
|
+
return inputCost + outputCost;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
logger.debug(`Cost calculation failed for ${this.providerName}:`, error);
|
|
205
|
+
return 0; // Fallback to 0 on any error
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const actualCost = calculateActualCost();
|
|
209
|
+
recordProviderPerformanceFromMetrics(this.providerName, {
|
|
210
|
+
responseTime,
|
|
211
|
+
tokensGenerated: result.usage?.totalTokens || 0,
|
|
212
|
+
cost: actualCost,
|
|
213
|
+
success: true,
|
|
214
|
+
});
|
|
215
|
+
// Show what the system learned (updated to include cost)
|
|
216
|
+
const optimizedProvider = getPerformanceOptimizedProvider("speed");
|
|
217
|
+
logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
|
|
218
|
+
responseTime: `${responseTime}ms`,
|
|
219
|
+
tokens: result.usage?.totalTokens || 0,
|
|
220
|
+
estimatedCost: `$${actualCost.toFixed(6)}`,
|
|
221
|
+
recommendedSpeedProvider: optimizedProvider?.provider || "none",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch (perfError) {
|
|
225
|
+
logger.warn("⚠️ Performance recording failed:", perfError);
|
|
226
|
+
}
|
|
184
227
|
// Extract tool names from tool calls for tracking
|
|
185
228
|
// AI SDK puts tool calls in steps array for multi-step generation
|
|
186
229
|
const toolsUsed = [];
|
|
@@ -357,6 +400,83 @@ export class BaseProvider {
|
|
|
357
400
|
evaluation: result.evaluation,
|
|
358
401
|
};
|
|
359
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Get AI SDK model with middleware applied
|
|
405
|
+
* This method wraps the base model with any configured middleware
|
|
406
|
+
*/
|
|
407
|
+
async getAISDKModelWithMiddleware(options = {}) {
|
|
408
|
+
// Get the base model
|
|
409
|
+
const baseModel = await this.getAISDKModel();
|
|
410
|
+
// Check if middleware should be applied
|
|
411
|
+
const middlewareOptions = this.extractMiddlewareOptions(options);
|
|
412
|
+
if (!middlewareOptions || this.shouldSkipMiddleware(options)) {
|
|
413
|
+
return baseModel;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
// Create middleware context
|
|
417
|
+
const context = MiddlewareFactory.createContext(this.providerName, this.modelName, options, {
|
|
418
|
+
sessionId: this.sessionId,
|
|
419
|
+
userId: this.userId,
|
|
420
|
+
});
|
|
421
|
+
// Apply middleware to the model
|
|
422
|
+
const wrappedModel = MiddlewareFactory.applyMiddleware(baseModel, context, middlewareOptions);
|
|
423
|
+
logger.debug(`Applied middleware to ${this.providerName} model`, {
|
|
424
|
+
provider: this.providerName,
|
|
425
|
+
model: this.modelName,
|
|
426
|
+
hasMiddleware: true,
|
|
427
|
+
});
|
|
428
|
+
return wrappedModel;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
logger.warn(`Failed to apply middleware to ${this.providerName}, using base model`, {
|
|
432
|
+
error: error instanceof Error ? error.message : String(error),
|
|
433
|
+
});
|
|
434
|
+
// Return base model on middleware failure to maintain functionality
|
|
435
|
+
return baseModel;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Extract middleware options from generation options
|
|
440
|
+
*/
|
|
441
|
+
extractMiddlewareOptions(options) {
|
|
442
|
+
// Check for middleware configuration in options
|
|
443
|
+
const optionsRecord = options;
|
|
444
|
+
const middlewareConfig = optionsRecord.middlewareConfig;
|
|
445
|
+
const enabledMiddleware = optionsRecord.enabledMiddleware;
|
|
446
|
+
const disabledMiddleware = optionsRecord.disabledMiddleware;
|
|
447
|
+
const preset = optionsRecord.middlewarePreset;
|
|
448
|
+
// If no middleware configuration is present, return null
|
|
449
|
+
if (!middlewareConfig &&
|
|
450
|
+
!enabledMiddleware &&
|
|
451
|
+
!disabledMiddleware &&
|
|
452
|
+
!preset) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
middlewareConfig,
|
|
457
|
+
enabledMiddleware,
|
|
458
|
+
disabledMiddleware,
|
|
459
|
+
preset,
|
|
460
|
+
global: {
|
|
461
|
+
collectStats: true,
|
|
462
|
+
continueOnError: true,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Determine if middleware should be skipped for this request
|
|
468
|
+
*/
|
|
469
|
+
shouldSkipMiddleware(options) {
|
|
470
|
+
// Skip middleware if explicitly disabled
|
|
471
|
+
if (options.disableMiddleware === true) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
// Skip middleware for tool-disabled requests to avoid conflicts
|
|
475
|
+
if (options.disableTools === true) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
360
480
|
// ===================
|
|
361
481
|
// TOOL MANAGEMENT
|
|
362
482
|
// ===================
|
|
@@ -428,7 +548,7 @@ export class BaseProvider {
|
|
|
428
548
|
timestamp: new Date().toISOString(),
|
|
429
549
|
params: params,
|
|
430
550
|
// Keep it simple - just indicate an error occurred
|
|
431
|
-
message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}
|
|
551
|
+
message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
432
552
|
};
|
|
433
553
|
}
|
|
434
554
|
},
|
|
@@ -32,3 +32,29 @@ export declare function isProviderAvailable(providerName: string): boolean;
|
|
|
32
32
|
* Get the best available provider based on preference
|
|
33
33
|
*/
|
|
34
34
|
export declare function getBestAvailableProvider(preferCheap?: boolean): ProviderModelConfig | null;
|
|
35
|
+
/**
|
|
36
|
+
Record actual provider performance for optimization
|
|
37
|
+
*/
|
|
38
|
+
export declare function recordProviderPerformanceFromMetrics(providerName: string, metrics: {
|
|
39
|
+
responseTime: number;
|
|
40
|
+
tokensGenerated: number;
|
|
41
|
+
cost: number;
|
|
42
|
+
success: boolean;
|
|
43
|
+
}): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get performance-optimized provider based on real metrics
|
|
46
|
+
*/
|
|
47
|
+
export declare function getPerformanceOptimizedProvider(priority?: "speed" | "cost" | "reliability"): ProviderModelConfig | null;
|
|
48
|
+
export declare function getProviderPerformanceAnalytics(): Record<string, {
|
|
49
|
+
avgResponseTime: number;
|
|
50
|
+
successRate: number;
|
|
51
|
+
tokenThroughput: number;
|
|
52
|
+
costEfficiency: number;
|
|
53
|
+
recommendation: string;
|
|
54
|
+
sampleCount: number;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Reset performance metrics for a provider or all providers.
|
|
58
|
+
* @param providerName - (Optional) The name of the provider to reset. If omitted, resets all providers.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resetProviderMetrics(providerName?: string): void;
|