@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
|
@@ -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/lib/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/lib/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
|
/**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NeuroLinkMiddleware } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create analytics middleware for tracking AI model usage
|
|
4
|
+
* Collects metrics on token usage, response times, and model performance
|
|
5
|
+
*/
|
|
6
|
+
export declare function createAnalyticsMiddleware(): NeuroLinkMiddleware;
|
|
7
|
+
/**
|
|
8
|
+
* Get collected metrics from analytics middleware
|
|
9
|
+
* Note: This is a utility function for accessing metrics
|
|
10
|
+
*/
|
|
11
|
+
export declare function getAnalyticsMetrics(): Map<string, Record<string, unknown>>;
|
|
12
|
+
/**
|
|
13
|
+
* Clear collected metrics from analytics middleware
|
|
14
|
+
* Note: This is a utility function for clearing metrics
|
|
15
|
+
*/
|
|
16
|
+
export declare function clearAnalyticsMetrics(): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { logger } from "../../utils/logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create analytics middleware for tracking AI model usage
|
|
4
|
+
* Collects metrics on token usage, response times, and model performance
|
|
5
|
+
*/
|
|
6
|
+
export function createAnalyticsMiddleware() {
|
|
7
|
+
const requestMetrics = new Map();
|
|
8
|
+
const metadata = {
|
|
9
|
+
id: "analytics",
|
|
10
|
+
name: "Analytics Tracking",
|
|
11
|
+
description: "Tracks token usage, response times, and model performance metrics",
|
|
12
|
+
priority: 100, // High priority to ensure analytics are captured
|
|
13
|
+
defaultEnabled: true,
|
|
14
|
+
};
|
|
15
|
+
const middleware = {
|
|
16
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
17
|
+
const requestId = `analytics-${Date.now()}`;
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
logger.debug(`[AnalyticsMiddleware] Starting request tracking`, {
|
|
20
|
+
requestId,
|
|
21
|
+
hasPrompt: !!params.prompt,
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
// Execute the generation
|
|
25
|
+
const result = await doGenerate();
|
|
26
|
+
// Calculate metrics
|
|
27
|
+
const responseTime = Date.now() - startTime;
|
|
28
|
+
const analytics = {
|
|
29
|
+
requestId,
|
|
30
|
+
responseTime,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
usage: {
|
|
33
|
+
inputTokens: result.usage?.promptTokens || 0,
|
|
34
|
+
outputTokens: result.usage?.completionTokens || 0,
|
|
35
|
+
totalTokens: (result.usage?.promptTokens || 0) +
|
|
36
|
+
(result.usage?.completionTokens || 0),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
// Store metrics for potential retrieval
|
|
40
|
+
requestMetrics.set(requestId, analytics);
|
|
41
|
+
logger.debug(`[AnalyticsMiddleware] Request completed`, analytics);
|
|
42
|
+
// Add analytics to the result
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
const updatedResult = { ...result };
|
|
45
|
+
if (!updatedResult.experimental_providerMetadata) {
|
|
46
|
+
updatedResult.experimental_providerMetadata = {};
|
|
47
|
+
}
|
|
48
|
+
if (!updatedResult.experimental_providerMetadata.neurolink) {
|
|
49
|
+
updatedResult.experimental_providerMetadata.neurolink = {};
|
|
50
|
+
}
|
|
51
|
+
updatedResult.experimental_providerMetadata.neurolink.analytics =
|
|
52
|
+
analytics;
|
|
53
|
+
return updatedResult;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const responseTime = Date.now() - startTime;
|
|
57
|
+
logger.error(`[AnalyticsMiddleware] Request failed`, {
|
|
58
|
+
requestId,
|
|
59
|
+
responseTime,
|
|
60
|
+
error: error instanceof Error ? error.message : String(error),
|
|
61
|
+
});
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
wrapStream: async ({ doStream, params }) => {
|
|
66
|
+
const requestId = `analytics-stream-${Date.now()}`;
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
logger.debug(`[AnalyticsMiddleware] Starting stream tracking`, {
|
|
69
|
+
requestId,
|
|
70
|
+
hasPrompt: !!params.prompt,
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const result = await doStream();
|
|
74
|
+
const streamAnalytics = {
|
|
75
|
+
requestId,
|
|
76
|
+
startTime,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
streamingMode: true,
|
|
79
|
+
};
|
|
80
|
+
requestMetrics.set(requestId, streamAnalytics);
|
|
81
|
+
// The 'result' is a stream, so we can't directly modify it.
|
|
82
|
+
// Analytics for streams are typically handled after the stream is consumed.
|
|
83
|
+
// For this middleware, we'll log the start and rely on other mechanisms
|
|
84
|
+
// to capture the end-to-end stream metrics if needed.
|
|
85
|
+
// We will pass a new property in the `rawResponse`
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
const updatedResult = { ...result };
|
|
88
|
+
if (!updatedResult.rawResponse) {
|
|
89
|
+
updatedResult.rawResponse = {};
|
|
90
|
+
}
|
|
91
|
+
if (!updatedResult.rawResponse.neurolink) {
|
|
92
|
+
updatedResult.rawResponse.neurolink = {};
|
|
93
|
+
}
|
|
94
|
+
updatedResult.rawResponse.neurolink.analytics = streamAnalytics;
|
|
95
|
+
return updatedResult;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const responseTime = Date.now() - startTime;
|
|
99
|
+
logger.error(`[AnalyticsMiddleware] Stream failed`, {
|
|
100
|
+
requestId,
|
|
101
|
+
responseTime,
|
|
102
|
+
error: error instanceof Error ? error.message : String(error),
|
|
103
|
+
});
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// Return the NeuroLinkMiddleware with metadata
|
|
109
|
+
return {
|
|
110
|
+
...middleware,
|
|
111
|
+
metadata,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get collected metrics from analytics middleware
|
|
116
|
+
* Note: This is a utility function for accessing metrics
|
|
117
|
+
*/
|
|
118
|
+
export function getAnalyticsMetrics() {
|
|
119
|
+
// This would need to be implemented with a global registry
|
|
120
|
+
// For now, return empty map
|
|
121
|
+
return new Map();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Clear collected metrics from analytics middleware
|
|
125
|
+
* Note: This is a utility function for clearing metrics
|
|
126
|
+
*/
|
|
127
|
+
export function clearAnalyticsMetrics() {
|
|
128
|
+
// This would need to be implemented with a global registry
|
|
129
|
+
// For now, do nothing
|
|
130
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { LanguageModelV1 } from "ai";
|
|
2
|
+
import type { MiddlewareContext, MiddlewareConfig, MiddlewareFactoryOptions, MiddlewareChainStats } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Middleware factory for creating and applying middleware chains
|
|
5
|
+
*/
|
|
6
|
+
export declare class MiddlewareFactory {
|
|
7
|
+
/**
|
|
8
|
+
* Apply middleware to a language model
|
|
9
|
+
*/
|
|
10
|
+
static applyMiddleware(model: LanguageModelV1, context: MiddlewareContext, options?: MiddlewareFactoryOptions): LanguageModelV1;
|
|
11
|
+
/**
|
|
12
|
+
* Build middleware configuration from factory options
|
|
13
|
+
*/
|
|
14
|
+
private static buildMiddlewareConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Get preset configuration
|
|
17
|
+
*/
|
|
18
|
+
private static getPresetConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Get built-in preset configurations
|
|
21
|
+
*/
|
|
22
|
+
private static getBuiltInPresets;
|
|
23
|
+
/**
|
|
24
|
+
* Create middleware context from provider and options
|
|
25
|
+
*/
|
|
26
|
+
static createContext(provider: string, model: string, options?: Record<string, unknown>, session?: {
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
userId?: string;
|
|
29
|
+
}): MiddlewareContext;
|
|
30
|
+
/**
|
|
31
|
+
* Validate middleware configuration
|
|
32
|
+
*/
|
|
33
|
+
static validateConfig(config: Record<string, MiddlewareConfig>): {
|
|
34
|
+
isValid: boolean;
|
|
35
|
+
errors: string[];
|
|
36
|
+
warnings: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Get available presets
|
|
40
|
+
*/
|
|
41
|
+
static getAvailablePresets(): Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
middleware: string[];
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Get middleware chain statistics
|
|
48
|
+
*/
|
|
49
|
+
static getChainStats(context: MiddlewareContext, config: Record<string, MiddlewareConfig>): MiddlewareChainStats;
|
|
50
|
+
/**
|
|
51
|
+
* Create a middleware-enabled model factory function
|
|
52
|
+
*/
|
|
53
|
+
static createModelFactory(baseModelFactory: () => Promise<LanguageModelV1>, defaultOptions?: MiddlewareFactoryOptions): (context: MiddlewareContext, options?: MiddlewareFactoryOptions) => Promise<LanguageModelV1>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { wrapLanguageModel } from "ai";
|
|
2
|
+
import { middlewareRegistry } from "./registry.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* Middleware factory for creating and applying middleware chains
|
|
6
|
+
*/
|
|
7
|
+
export class MiddlewareFactory {
|
|
8
|
+
/**
|
|
9
|
+
* Apply middleware to a language model
|
|
10
|
+
*/
|
|
11
|
+
static applyMiddleware(model, context, options = {}) {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
try {
|
|
14
|
+
// Build middleware configuration
|
|
15
|
+
const middlewareConfig = this.buildMiddlewareConfig(options);
|
|
16
|
+
// Build middleware chain
|
|
17
|
+
const middlewareChain = middlewareRegistry.buildChain(context, middlewareConfig);
|
|
18
|
+
if (middlewareChain.length === 0) {
|
|
19
|
+
logger.debug("No middleware to apply", { provider: context.provider });
|
|
20
|
+
return model;
|
|
21
|
+
}
|
|
22
|
+
logger.debug(`Applying ${middlewareChain.length} middleware to model`, {
|
|
23
|
+
provider: context.provider,
|
|
24
|
+
model: context.model,
|
|
25
|
+
middlewareCount: middlewareChain.length,
|
|
26
|
+
});
|
|
27
|
+
// Apply middleware using AI SDK's wrapLanguageModel
|
|
28
|
+
// Cast to the expected AI SDK middleware type
|
|
29
|
+
const wrappedModel = wrapLanguageModel({
|
|
30
|
+
model,
|
|
31
|
+
middleware: middlewareChain,
|
|
32
|
+
});
|
|
33
|
+
const processingTime = Date.now() - startTime;
|
|
34
|
+
logger.debug("Middleware applied successfully", {
|
|
35
|
+
provider: context.provider,
|
|
36
|
+
middlewareCount: middlewareChain.length,
|
|
37
|
+
processingTime,
|
|
38
|
+
});
|
|
39
|
+
return wrappedModel;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.error("Failed to apply middleware", {
|
|
43
|
+
provider: context.provider,
|
|
44
|
+
error: error instanceof Error ? error.message : String(error),
|
|
45
|
+
});
|
|
46
|
+
// Return original model on error to maintain functionality
|
|
47
|
+
return model;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build middleware configuration from factory options
|
|
52
|
+
*/
|
|
53
|
+
static buildMiddlewareConfig(options) {
|
|
54
|
+
const config = {};
|
|
55
|
+
// Start with all registered middleware
|
|
56
|
+
const allMiddleware = middlewareRegistry.list();
|
|
57
|
+
for (const middleware of allMiddleware) {
|
|
58
|
+
// Default configuration
|
|
59
|
+
config[middleware.metadata.id] = {
|
|
60
|
+
enabled: middleware.metadata.defaultEnabled || false,
|
|
61
|
+
config: {},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Apply preset configuration if specified
|
|
65
|
+
if (options.preset) {
|
|
66
|
+
const presetConfig = this.getPresetConfig(options.preset);
|
|
67
|
+
if (presetConfig) {
|
|
68
|
+
Object.assign(config, presetConfig);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Apply explicit middleware configurations
|
|
72
|
+
if (options.middlewareConfig) {
|
|
73
|
+
for (const [middlewareId, middlewareConfig] of Object.entries(options.middlewareConfig)) {
|
|
74
|
+
config[middlewareId] = {
|
|
75
|
+
...config[middlewareId],
|
|
76
|
+
...middlewareConfig,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Apply enabled middleware list
|
|
81
|
+
if (options.enabledMiddleware) {
|
|
82
|
+
for (const middlewareId of options.enabledMiddleware) {
|
|
83
|
+
if (config[middlewareId]) {
|
|
84
|
+
config[middlewareId].enabled = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Apply disabled middleware list
|
|
89
|
+
if (options.disabledMiddleware) {
|
|
90
|
+
for (const middlewareId of options.disabledMiddleware) {
|
|
91
|
+
if (config[middlewareId]) {
|
|
92
|
+
config[middlewareId].enabled = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return config;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get preset configuration
|
|
100
|
+
*/
|
|
101
|
+
static getPresetConfig(presetName) {
|
|
102
|
+
const presets = this.getBuiltInPresets();
|
|
103
|
+
return presets[presetName] || null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get built-in preset configurations
|
|
107
|
+
*/
|
|
108
|
+
static getBuiltInPresets() {
|
|
109
|
+
return {
|
|
110
|
+
// Development preset - logging and basic analytics
|
|
111
|
+
development: {
|
|
112
|
+
logging: { enabled: true },
|
|
113
|
+
analytics: { enabled: true },
|
|
114
|
+
},
|
|
115
|
+
// Production preset - analytics, caching, rate limiting
|
|
116
|
+
production: {
|
|
117
|
+
analytics: { enabled: true },
|
|
118
|
+
caching: { enabled: true },
|
|
119
|
+
rateLimit: { enabled: true },
|
|
120
|
+
retry: { enabled: true },
|
|
121
|
+
},
|
|
122
|
+
// Security preset - guardrails and content filtering
|
|
123
|
+
security: {
|
|
124
|
+
guardrails: { enabled: true },
|
|
125
|
+
logging: { enabled: true },
|
|
126
|
+
rateLimit: { enabled: true },
|
|
127
|
+
},
|
|
128
|
+
// Performance preset - caching and optimization
|
|
129
|
+
performance: {
|
|
130
|
+
caching: { enabled: true },
|
|
131
|
+
retry: { enabled: true },
|
|
132
|
+
timeout: { enabled: true },
|
|
133
|
+
},
|
|
134
|
+
// Enterprise preset - all middleware enabled
|
|
135
|
+
enterprise: {
|
|
136
|
+
analytics: { enabled: true },
|
|
137
|
+
guardrails: { enabled: true },
|
|
138
|
+
logging: { enabled: true },
|
|
139
|
+
caching: { enabled: true },
|
|
140
|
+
rateLimit: { enabled: true },
|
|
141
|
+
retry: { enabled: true },
|
|
142
|
+
timeout: { enabled: true },
|
|
143
|
+
},
|
|
144
|
+
// Minimal preset - only essential middleware
|
|
145
|
+
minimal: {
|
|
146
|
+
analytics: { enabled: true },
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create middleware context from provider and options
|
|
152
|
+
*/
|
|
153
|
+
static createContext(provider, model, options = {}, session) {
|
|
154
|
+
return {
|
|
155
|
+
provider,
|
|
156
|
+
model,
|
|
157
|
+
options,
|
|
158
|
+
session,
|
|
159
|
+
metadata: {
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
requestId: `${provider}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validate middleware configuration
|
|
167
|
+
*/
|
|
168
|
+
static validateConfig(config) {
|
|
169
|
+
const errors = [];
|
|
170
|
+
const warnings = [];
|
|
171
|
+
for (const [middlewareId, middlewareConfig] of Object.entries(config)) {
|
|
172
|
+
// Check if middleware is registered
|
|
173
|
+
if (!middlewareRegistry.has(middlewareId)) {
|
|
174
|
+
errors.push(`Middleware '${middlewareId}' is not registered`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// Validate configuration structure
|
|
178
|
+
if (middlewareConfig.enabled !== undefined &&
|
|
179
|
+
typeof middlewareConfig.enabled !== "boolean") {
|
|
180
|
+
errors.push(`Middleware '${middlewareId}' enabled property must be boolean`);
|
|
181
|
+
}
|
|
182
|
+
if (middlewareConfig.config &&
|
|
183
|
+
typeof middlewareConfig.config !== "object") {
|
|
184
|
+
errors.push(`Middleware '${middlewareId}' config property must be an object`);
|
|
185
|
+
}
|
|
186
|
+
// Check for potential conflicts
|
|
187
|
+
if (middlewareConfig.conditions?.providers &&
|
|
188
|
+
middlewareConfig.conditions.providers.length === 0) {
|
|
189
|
+
warnings.push(`Middleware '${middlewareId}' has empty providers condition`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
isValid: errors.length === 0,
|
|
194
|
+
errors,
|
|
195
|
+
warnings,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get available presets
|
|
200
|
+
*/
|
|
201
|
+
static getAvailablePresets() {
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
name: "development",
|
|
205
|
+
description: "Logging and basic analytics for development",
|
|
206
|
+
middleware: ["logging", "analytics"],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "production",
|
|
210
|
+
description: "Optimized for production with caching and rate limiting",
|
|
211
|
+
middleware: ["analytics", "caching", "rateLimit", "retry"],
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "security",
|
|
215
|
+
description: "Enhanced security with guardrails and monitoring",
|
|
216
|
+
middleware: ["guardrails", "logging", "rateLimit"],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "performance",
|
|
220
|
+
description: "Optimized for performance with caching and retries",
|
|
221
|
+
middleware: ["caching", "retry", "timeout"],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "enterprise",
|
|
225
|
+
description: "Full enterprise feature set with all middleware",
|
|
226
|
+
middleware: [
|
|
227
|
+
"analytics",
|
|
228
|
+
"guardrails",
|
|
229
|
+
"logging",
|
|
230
|
+
"caching",
|
|
231
|
+
"rateLimit",
|
|
232
|
+
"retry",
|
|
233
|
+
"timeout",
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "minimal",
|
|
238
|
+
description: "Minimal overhead with only essential features",
|
|
239
|
+
middleware: ["analytics"],
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get middleware chain statistics
|
|
245
|
+
*/
|
|
246
|
+
static getChainStats(context, config) {
|
|
247
|
+
const chain = middlewareRegistry.buildChain(context, config);
|
|
248
|
+
const stats = middlewareRegistry.getAggregatedStats();
|
|
249
|
+
const results = {};
|
|
250
|
+
let totalExecutionTime = 0;
|
|
251
|
+
let appliedMiddleware = 0;
|
|
252
|
+
for (const [middlewareId, middlewareStats] of Object.entries(stats)) {
|
|
253
|
+
if (config[middlewareId]?.enabled) {
|
|
254
|
+
results[middlewareId] = {
|
|
255
|
+
applied: true,
|
|
256
|
+
executionTime: middlewareStats.averageExecutionTime,
|
|
257
|
+
};
|
|
258
|
+
totalExecutionTime += middlewareStats.averageExecutionTime;
|
|
259
|
+
appliedMiddleware++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
totalMiddleware: chain.length,
|
|
264
|
+
appliedMiddleware,
|
|
265
|
+
totalExecutionTime,
|
|
266
|
+
results,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a middleware-enabled model factory function
|
|
271
|
+
*/
|
|
272
|
+
static createModelFactory(baseModelFactory, defaultOptions = {}) {
|
|
273
|
+
return async (context, options = {}) => {
|
|
274
|
+
// Get base model
|
|
275
|
+
const baseModel = await baseModelFactory();
|
|
276
|
+
// Merge options
|
|
277
|
+
const _mergedOptions = {
|
|
278
|
+
...defaultOptions,
|
|
279
|
+
...options,
|
|
280
|
+
middlewareConfig: {
|
|
281
|
+
...defaultOptions.middlewareConfig,
|
|
282
|
+
...options.middlewareConfig,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
// Apply middleware
|
|
286
|
+
return this.applyMiddleware(baseModel, context, _mergedOptions);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|