claude-flow 2.0.0-alpha.66 → 2.0.0-alpha.68
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/.claude/cache/agent-pool.json +33 -0
- package/.claude/cache/memory-optimization.json +19 -0
- package/.claude/cache/neural-optimization.json +25 -0
- package/.claude/cache/optimized-hooks.json +19 -0
- package/.claude/cache/parallel-processing.json +26 -0
- package/.claude/optimized-settings.json +270 -0
- package/.claude/settings-backup.json +186 -0
- package/.claude/settings-enhanced.json +278 -0
- package/.claude/settings-fixed.json +186 -0
- package/.claude/settings.json +105 -8
- package/CHANGELOG.md +38 -0
- package/bin/claude-flow +1 -1
- package/dist/cli/simple-commands/hive-mind.js +1 -1
- package/dist/cli/simple-commands/hive-mind.js.map +1 -1
- package/dist/cli/simple-commands/hooks.js +6 -4
- package/dist/cli/simple-commands/hooks.js.map +1 -1
- package/dist/providers/anthropic-provider.d.ts +27 -0
- package/dist/providers/anthropic-provider.d.ts.map +1 -0
- package/dist/providers/anthropic-provider.js +247 -0
- package/dist/providers/anthropic-provider.js.map +1 -0
- package/dist/providers/base-provider.d.ts +134 -0
- package/dist/providers/base-provider.d.ts.map +1 -0
- package/dist/providers/base-provider.js +407 -0
- package/dist/providers/base-provider.js.map +1 -0
- package/dist/providers/cohere-provider.d.ts +28 -0
- package/dist/providers/cohere-provider.d.ts.map +1 -0
- package/dist/providers/cohere-provider.js +407 -0
- package/dist/providers/cohere-provider.js.map +1 -0
- package/dist/providers/google-provider.d.ts +23 -0
- package/dist/providers/google-provider.d.ts.map +1 -0
- package/dist/providers/google-provider.js +362 -0
- package/dist/providers/google-provider.js.map +1 -0
- package/dist/providers/index.d.ts +14 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama-provider.d.ts +23 -0
- package/dist/providers/ollama-provider.d.ts.map +1 -0
- package/dist/providers/ollama-provider.js +374 -0
- package/dist/providers/ollama-provider.js.map +1 -0
- package/dist/providers/openai-provider.d.ts +23 -0
- package/dist/providers/openai-provider.d.ts.map +1 -0
- package/dist/providers/openai-provider.js +349 -0
- package/dist/providers/openai-provider.js.map +1 -0
- package/dist/providers/provider-manager.d.ts +139 -0
- package/dist/providers/provider-manager.d.ts.map +1 -0
- package/dist/providers/provider-manager.js +513 -0
- package/dist/providers/provider-manager.js.map +1 -0
- package/dist/providers/types.d.ts +356 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +61 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/utils.d.ts +37 -0
- package/dist/providers/utils.d.ts.map +1 -0
- package/dist/providers/utils.js +322 -0
- package/dist/providers/utils.js.map +1 -0
- package/dist/services/agentic-flow-hooks/hook-manager.d.ts +70 -0
- package/dist/services/agentic-flow-hooks/hook-manager.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/hook-manager.js +512 -0
- package/dist/services/agentic-flow-hooks/hook-manager.js.map +1 -0
- package/dist/services/agentic-flow-hooks/index.d.ts +36 -0
- package/dist/services/agentic-flow-hooks/index.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/index.js +325 -0
- package/dist/services/agentic-flow-hooks/index.js.map +1 -0
- package/dist/services/agentic-flow-hooks/llm-hooks.d.ts +33 -0
- package/dist/services/agentic-flow-hooks/llm-hooks.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/llm-hooks.js +415 -0
- package/dist/services/agentic-flow-hooks/llm-hooks.js.map +1 -0
- package/dist/services/agentic-flow-hooks/memory-hooks.d.ts +45 -0
- package/dist/services/agentic-flow-hooks/memory-hooks.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/memory-hooks.js +532 -0
- package/dist/services/agentic-flow-hooks/memory-hooks.js.map +1 -0
- package/dist/services/agentic-flow-hooks/neural-hooks.d.ts +39 -0
- package/dist/services/agentic-flow-hooks/neural-hooks.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/neural-hooks.js +561 -0
- package/dist/services/agentic-flow-hooks/neural-hooks.js.map +1 -0
- package/dist/services/agentic-flow-hooks/performance-hooks.d.ts +33 -0
- package/dist/services/agentic-flow-hooks/performance-hooks.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/performance-hooks.js +621 -0
- package/dist/services/agentic-flow-hooks/performance-hooks.js.map +1 -0
- package/dist/services/agentic-flow-hooks/types.d.ts +379 -0
- package/dist/services/agentic-flow-hooks/types.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/types.js +8 -0
- package/dist/services/agentic-flow-hooks/types.js.map +1 -0
- package/dist/services/agentic-flow-hooks/workflow-hooks.d.ts +39 -0
- package/dist/services/agentic-flow-hooks/workflow-hooks.d.ts.map +1 -0
- package/dist/services/agentic-flow-hooks/workflow-hooks.js +742 -0
- package/dist/services/agentic-flow-hooks/workflow-hooks.js.map +1 -0
- package/package.json +1 -1
- package/scripts/optimize-performance.js +400 -0
- package/scripts/performance-monitor.js +263 -0
- package/src/cli/help-text.js +1 -1
- package/src/cli/simple-cli.js +1 -1
- package/src/cli/simple-commands/hive-mind.js +1 -1
- package/src/providers/anthropic-provider.ts +282 -0
- package/src/providers/base-provider.ts +560 -0
- package/src/providers/cohere-provider.ts +521 -0
- package/src/providers/google-provider.ts +477 -0
- package/src/providers/index.ts +21 -0
- package/src/providers/ollama-provider.ts +489 -0
- package/src/providers/openai-provider.ts +476 -0
- package/src/providers/provider-manager.ts +654 -0
- package/src/providers/types.ts +531 -0
- package/src/providers/utils.ts +376 -0
- package/src/services/agentic-flow-hooks/hook-manager.ts +701 -0
- package/src/services/agentic-flow-hooks/index.ts +386 -0
- package/src/services/agentic-flow-hooks/llm-hooks.ts +557 -0
- package/src/services/agentic-flow-hooks/memory-hooks.ts +710 -0
- package/src/services/agentic-flow-hooks/neural-hooks.ts +758 -0
- package/src/services/agentic-flow-hooks/performance-hooks.ts +827 -0
- package/src/services/agentic-flow-hooks/types.ts +503 -0
- package/src/services/agentic-flow-hooks/workflow-hooks.ts +1026 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Manager - Central orchestration for multi-LLM providers
|
|
3
|
+
* Handles provider selection, fallback, load balancing, and cost optimization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { ILogger } from '../core/logger.js';
|
|
8
|
+
import { ConfigManager } from '../config/config-manager.js';
|
|
9
|
+
import {
|
|
10
|
+
ILLMProvider,
|
|
11
|
+
LLMProvider,
|
|
12
|
+
LLMProviderConfig,
|
|
13
|
+
LLMRequest,
|
|
14
|
+
LLMResponse,
|
|
15
|
+
LLMStreamEvent,
|
|
16
|
+
LLMModel,
|
|
17
|
+
FallbackStrategy,
|
|
18
|
+
FallbackRule,
|
|
19
|
+
LoadBalancer,
|
|
20
|
+
ProviderMetrics,
|
|
21
|
+
CostOptimizer,
|
|
22
|
+
CostConstraints,
|
|
23
|
+
OptimizationResult,
|
|
24
|
+
RateLimiter,
|
|
25
|
+
ProviderMonitor,
|
|
26
|
+
CacheConfig,
|
|
27
|
+
LLMProviderError,
|
|
28
|
+
RateLimitError,
|
|
29
|
+
isRateLimitError,
|
|
30
|
+
} from './types.js';
|
|
31
|
+
|
|
32
|
+
// Import providers
|
|
33
|
+
import { AnthropicProvider } from './anthropic-provider.js';
|
|
34
|
+
import { OpenAIProvider } from './openai-provider.js';
|
|
35
|
+
import { GoogleProvider } from './google-provider.js';
|
|
36
|
+
import { CohereProvider } from './cohere-provider.js';
|
|
37
|
+
import { OllamaProvider } from './ollama-provider.js';
|
|
38
|
+
|
|
39
|
+
export interface ProviderManagerConfig {
|
|
40
|
+
providers: Record<LLMProvider, LLMProviderConfig>;
|
|
41
|
+
defaultProvider: LLMProvider;
|
|
42
|
+
fallbackStrategy?: FallbackStrategy;
|
|
43
|
+
loadBalancing?: {
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
strategy: 'round-robin' | 'least-loaded' | 'latency-based' | 'cost-based';
|
|
46
|
+
};
|
|
47
|
+
costOptimization?: {
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
maxCostPerRequest?: number;
|
|
50
|
+
preferredProviders?: LLMProvider[];
|
|
51
|
+
};
|
|
52
|
+
caching?: CacheConfig;
|
|
53
|
+
monitoring?: {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
metricsInterval: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ProviderManager extends EventEmitter {
|
|
60
|
+
private providers: Map<LLMProvider, ILLMProvider> = new Map();
|
|
61
|
+
private logger: ILogger;
|
|
62
|
+
private config: ProviderManagerConfig;
|
|
63
|
+
private requestCount: Map<LLMProvider, number> = new Map();
|
|
64
|
+
private lastUsed: Map<LLMProvider, Date> = new Map();
|
|
65
|
+
private providerMetrics: Map<LLMProvider, ProviderMetrics[]> = new Map();
|
|
66
|
+
private cache: Map<string, { response: LLMResponse; timestamp: Date }> = new Map();
|
|
67
|
+
private currentProviderIndex = 0;
|
|
68
|
+
|
|
69
|
+
constructor(logger: ILogger, configManager: ConfigManager, config: ProviderManagerConfig) {
|
|
70
|
+
super();
|
|
71
|
+
this.logger = logger;
|
|
72
|
+
this.config = config;
|
|
73
|
+
|
|
74
|
+
// Initialize providers
|
|
75
|
+
this.initializeProviders();
|
|
76
|
+
|
|
77
|
+
// Start monitoring if enabled
|
|
78
|
+
if (config.monitoring?.enabled) {
|
|
79
|
+
this.startMonitoring();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialize all configured providers
|
|
85
|
+
*/
|
|
86
|
+
private async initializeProviders(): Promise<void> {
|
|
87
|
+
for (const [providerName, providerConfig] of Object.entries(this.config.providers)) {
|
|
88
|
+
try {
|
|
89
|
+
const provider = await this.createProvider(providerName as LLMProvider, providerConfig);
|
|
90
|
+
if (provider) {
|
|
91
|
+
this.providers.set(providerName as LLMProvider, provider);
|
|
92
|
+
this.requestCount.set(providerName as LLMProvider, 0);
|
|
93
|
+
this.logger.info(`Initialized ${providerName} provider`);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.logger.error(`Failed to initialize ${providerName} provider`, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (this.providers.size === 0) {
|
|
101
|
+
throw new Error('No providers could be initialized');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a provider instance
|
|
107
|
+
*/
|
|
108
|
+
private async createProvider(name: LLMProvider, config: LLMProviderConfig): Promise<ILLMProvider | null> {
|
|
109
|
+
const providerOptions = {
|
|
110
|
+
logger: this.logger,
|
|
111
|
+
config,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
let provider: ILLMProvider;
|
|
116
|
+
|
|
117
|
+
switch (name) {
|
|
118
|
+
case 'anthropic':
|
|
119
|
+
provider = new AnthropicProvider(providerOptions);
|
|
120
|
+
break;
|
|
121
|
+
case 'openai':
|
|
122
|
+
provider = new OpenAIProvider(providerOptions);
|
|
123
|
+
break;
|
|
124
|
+
case 'google':
|
|
125
|
+
provider = new GoogleProvider(providerOptions);
|
|
126
|
+
break;
|
|
127
|
+
case 'cohere':
|
|
128
|
+
provider = new CohereProvider(providerOptions);
|
|
129
|
+
break;
|
|
130
|
+
case 'ollama':
|
|
131
|
+
provider = new OllamaProvider(providerOptions);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
this.logger.warn(`Unknown provider: ${name}`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await provider.initialize();
|
|
139
|
+
|
|
140
|
+
// Set up event listeners
|
|
141
|
+
provider.on('response', (data) => this.handleProviderResponse(name, data));
|
|
142
|
+
provider.on('error', (error) => this.handleProviderError(name, error));
|
|
143
|
+
provider.on('health_check', (result) => this.handleHealthCheck(name, result));
|
|
144
|
+
|
|
145
|
+
return provider;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
this.logger.error(`Failed to create ${name} provider`, error);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Complete a request using the appropriate provider
|
|
154
|
+
*/
|
|
155
|
+
async complete(request: LLMRequest): Promise<LLMResponse> {
|
|
156
|
+
// Check cache first
|
|
157
|
+
if (this.config.caching?.enabled) {
|
|
158
|
+
const cached = this.checkCache(request);
|
|
159
|
+
if (cached) {
|
|
160
|
+
this.logger.debug('Returning cached response');
|
|
161
|
+
return cached;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Select provider based on strategy
|
|
166
|
+
const provider = await this.selectProvider(request);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const response = await provider.complete(request);
|
|
170
|
+
|
|
171
|
+
// Cache successful response
|
|
172
|
+
if (this.config.caching?.enabled) {
|
|
173
|
+
this.cacheResponse(request, response);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Update metrics
|
|
177
|
+
this.updateProviderMetrics(provider.name, {
|
|
178
|
+
success: true,
|
|
179
|
+
latency: response.latency || 0,
|
|
180
|
+
cost: response.cost?.totalCost || 0,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return response;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Handle error and potentially fallback
|
|
186
|
+
return this.handleRequestError(error, request, provider);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Stream complete a request
|
|
192
|
+
*/
|
|
193
|
+
async *streamComplete(request: LLMRequest): AsyncIterable<LLMStreamEvent> {
|
|
194
|
+
const provider = await this.selectProvider(request);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
yield* provider.streamComplete(request);
|
|
198
|
+
|
|
199
|
+
// Update metrics
|
|
200
|
+
this.updateProviderMetrics(provider.name, {
|
|
201
|
+
success: true,
|
|
202
|
+
latency: 0, // Will be updated by stream events
|
|
203
|
+
cost: 0, // Will be updated by stream events
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
// Handle error and potentially fallback
|
|
207
|
+
const fallbackProvider = await this.getFallbackProvider(error, provider);
|
|
208
|
+
if (fallbackProvider) {
|
|
209
|
+
this.logger.info(`Falling back to ${fallbackProvider.name} provider`);
|
|
210
|
+
yield* fallbackProvider.streamComplete(request);
|
|
211
|
+
} else {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Select the best provider for a request
|
|
219
|
+
*/
|
|
220
|
+
private async selectProvider(request: LLMRequest): Promise<ILLMProvider> {
|
|
221
|
+
// If specific provider requested
|
|
222
|
+
if (request.providerOptions?.preferredProvider) {
|
|
223
|
+
const provider = this.providers.get(request.providerOptions.preferredProvider);
|
|
224
|
+
if (provider && this.isProviderAvailable(provider)) {
|
|
225
|
+
return provider;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Cost optimization
|
|
230
|
+
if (this.config.costOptimization?.enabled && request.costConstraints) {
|
|
231
|
+
const optimized = await this.selectOptimalProvider(request);
|
|
232
|
+
if (optimized) {
|
|
233
|
+
return optimized;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Load balancing
|
|
238
|
+
if (this.config.loadBalancing?.enabled) {
|
|
239
|
+
return this.selectLoadBalancedProvider();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Default provider
|
|
243
|
+
const defaultProvider = this.providers.get(this.config.defaultProvider);
|
|
244
|
+
if (defaultProvider && this.isProviderAvailable(defaultProvider)) {
|
|
245
|
+
return defaultProvider;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// First available provider
|
|
249
|
+
for (const provider of this.providers.values()) {
|
|
250
|
+
if (this.isProviderAvailable(provider)) {
|
|
251
|
+
return provider;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
throw new Error('No available providers');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Select provider based on cost optimization
|
|
260
|
+
*/
|
|
261
|
+
private async selectOptimalProvider(request: LLMRequest): Promise<ILLMProvider | null> {
|
|
262
|
+
let bestProvider: ILLMProvider | null = null;
|
|
263
|
+
let bestCost = Infinity;
|
|
264
|
+
|
|
265
|
+
for (const provider of this.providers.values()) {
|
|
266
|
+
if (!this.isProviderAvailable(provider)) continue;
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const estimate = await provider.estimateCost(request);
|
|
270
|
+
|
|
271
|
+
if (estimate.estimatedCost.total < bestCost &&
|
|
272
|
+
(!request.costConstraints?.maxCostPerRequest ||
|
|
273
|
+
estimate.estimatedCost.total <= request.costConstraints.maxCostPerRequest)) {
|
|
274
|
+
bestCost = estimate.estimatedCost.total;
|
|
275
|
+
bestProvider = provider;
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.logger.warn(`Failed to estimate cost for ${provider.name}`, error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return bestProvider;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Select provider using load balancing
|
|
287
|
+
*/
|
|
288
|
+
private selectLoadBalancedProvider(): ILLMProvider {
|
|
289
|
+
const availableProviders = Array.from(this.providers.values()).filter(p =>
|
|
290
|
+
this.isProviderAvailable(p)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (availableProviders.length === 0) {
|
|
294
|
+
throw new Error('No available providers');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
switch (this.config.loadBalancing?.strategy) {
|
|
298
|
+
case 'round-robin':
|
|
299
|
+
return this.roundRobinSelect(availableProviders);
|
|
300
|
+
|
|
301
|
+
case 'least-loaded':
|
|
302
|
+
return this.leastLoadedSelect(availableProviders);
|
|
303
|
+
|
|
304
|
+
case 'latency-based':
|
|
305
|
+
return this.latencyBasedSelect(availableProviders);
|
|
306
|
+
|
|
307
|
+
case 'cost-based':
|
|
308
|
+
return this.costBasedSelect(availableProviders);
|
|
309
|
+
|
|
310
|
+
default:
|
|
311
|
+
return availableProviders[0];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Round-robin provider selection
|
|
317
|
+
*/
|
|
318
|
+
private roundRobinSelect(providers: ILLMProvider[]): ILLMProvider {
|
|
319
|
+
const provider = providers[this.currentProviderIndex % providers.length];
|
|
320
|
+
this.currentProviderIndex++;
|
|
321
|
+
return provider;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Select least loaded provider
|
|
326
|
+
*/
|
|
327
|
+
private leastLoadedSelect(providers: ILLMProvider[]): ILLMProvider {
|
|
328
|
+
let minLoad = Infinity;
|
|
329
|
+
let selectedProvider = providers[0];
|
|
330
|
+
|
|
331
|
+
for (const provider of providers) {
|
|
332
|
+
const status = provider.getStatus();
|
|
333
|
+
if (status.currentLoad < minLoad) {
|
|
334
|
+
minLoad = status.currentLoad;
|
|
335
|
+
selectedProvider = provider;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return selectedProvider;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Select provider with lowest latency
|
|
344
|
+
*/
|
|
345
|
+
private latencyBasedSelect(providers: ILLMProvider[]): ILLMProvider {
|
|
346
|
+
let minLatency = Infinity;
|
|
347
|
+
let selectedProvider = providers[0];
|
|
348
|
+
|
|
349
|
+
for (const provider of providers) {
|
|
350
|
+
const metrics = this.providerMetrics.get(provider.name);
|
|
351
|
+
if (metrics && metrics.length > 0) {
|
|
352
|
+
const avgLatency = metrics.reduce((sum, m) => sum + m.latency, 0) / metrics.length;
|
|
353
|
+
if (avgLatency < minLatency) {
|
|
354
|
+
minLatency = avgLatency;
|
|
355
|
+
selectedProvider = provider;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return selectedProvider;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Select provider with lowest cost
|
|
365
|
+
*/
|
|
366
|
+
private costBasedSelect(providers: ILLMProvider[]): ILLMProvider {
|
|
367
|
+
let minCost = Infinity;
|
|
368
|
+
let selectedProvider = providers[0];
|
|
369
|
+
|
|
370
|
+
for (const provider of providers) {
|
|
371
|
+
const metrics = this.providerMetrics.get(provider.name);
|
|
372
|
+
if (metrics && metrics.length > 0) {
|
|
373
|
+
const avgCost = metrics.reduce((sum, m) => sum + m.cost, 0) / metrics.length;
|
|
374
|
+
if (avgCost < minCost) {
|
|
375
|
+
minCost = avgCost;
|
|
376
|
+
selectedProvider = provider;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return selectedProvider;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check if provider is available
|
|
386
|
+
*/
|
|
387
|
+
private isProviderAvailable(provider: ILLMProvider): boolean {
|
|
388
|
+
const status = provider.getStatus();
|
|
389
|
+
return status.available;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Handle request error with fallback
|
|
394
|
+
*/
|
|
395
|
+
private async handleRequestError(
|
|
396
|
+
error: unknown,
|
|
397
|
+
request: LLMRequest,
|
|
398
|
+
failedProvider: ILLMProvider
|
|
399
|
+
): Promise<LLMResponse> {
|
|
400
|
+
this.logger.error(`Provider ${failedProvider.name} failed`, error);
|
|
401
|
+
|
|
402
|
+
// Update metrics
|
|
403
|
+
this.updateProviderMetrics(failedProvider.name, {
|
|
404
|
+
success: false,
|
|
405
|
+
latency: 0,
|
|
406
|
+
cost: 0,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Try fallback
|
|
410
|
+
const fallbackProvider = await this.getFallbackProvider(error, failedProvider);
|
|
411
|
+
if (fallbackProvider) {
|
|
412
|
+
this.logger.info(`Falling back to ${fallbackProvider.name} provider`);
|
|
413
|
+
return fallbackProvider.complete(request);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get fallback provider based on error
|
|
421
|
+
*/
|
|
422
|
+
private async getFallbackProvider(
|
|
423
|
+
error: unknown,
|
|
424
|
+
failedProvider: ILLMProvider
|
|
425
|
+
): Promise<ILLMProvider | null> {
|
|
426
|
+
if (!this.config.fallbackStrategy?.enabled) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const errorCondition = this.getErrorCondition(error);
|
|
431
|
+
const fallbackRule = this.config.fallbackStrategy.rules.find(rule =>
|
|
432
|
+
rule.condition === errorCondition
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
if (!fallbackRule) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Find first available fallback provider
|
|
440
|
+
for (const providerName of fallbackRule.fallbackProviders) {
|
|
441
|
+
const provider = this.providers.get(providerName);
|
|
442
|
+
if (provider && provider !== failedProvider && this.isProviderAvailable(provider)) {
|
|
443
|
+
return provider;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Determine error condition for fallback
|
|
452
|
+
*/
|
|
453
|
+
private getErrorCondition(error: unknown): FallbackRule['condition'] {
|
|
454
|
+
if (isRateLimitError(error)) {
|
|
455
|
+
return 'rate_limit';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (error instanceof LLMProviderError) {
|
|
459
|
+
if (error.statusCode === 503) {
|
|
460
|
+
return 'unavailable';
|
|
461
|
+
}
|
|
462
|
+
if (error.code === 'TIMEOUT') {
|
|
463
|
+
return 'timeout';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return 'error';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Cache management
|
|
472
|
+
*/
|
|
473
|
+
private checkCache(request: LLMRequest): LLMResponse | null {
|
|
474
|
+
const cacheKey = this.generateCacheKey(request);
|
|
475
|
+
const cached = this.cache.get(cacheKey);
|
|
476
|
+
|
|
477
|
+
if (cached) {
|
|
478
|
+
const age = Date.now() - cached.timestamp.getTime();
|
|
479
|
+
if (age < (this.config.caching?.ttl || 3600) * 1000) {
|
|
480
|
+
return cached.response;
|
|
481
|
+
}
|
|
482
|
+
// Remove expired entry
|
|
483
|
+
this.cache.delete(cacheKey);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private cacheResponse(request: LLMRequest, response: LLMResponse): void {
|
|
490
|
+
const cacheKey = this.generateCacheKey(request);
|
|
491
|
+
this.cache.set(cacheKey, {
|
|
492
|
+
response,
|
|
493
|
+
timestamp: new Date(),
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Cleanup old cache entries
|
|
497
|
+
if (this.cache.size > 1000) {
|
|
498
|
+
const oldestKey = this.cache.keys().next().value;
|
|
499
|
+
this.cache.delete(oldestKey);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private generateCacheKey(request: LLMRequest): string {
|
|
504
|
+
return JSON.stringify({
|
|
505
|
+
model: request.model,
|
|
506
|
+
messages: request.messages,
|
|
507
|
+
temperature: request.temperature,
|
|
508
|
+
maxTokens: request.maxTokens,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Update provider metrics
|
|
514
|
+
*/
|
|
515
|
+
private updateProviderMetrics(
|
|
516
|
+
provider: LLMProvider,
|
|
517
|
+
metrics: { success: boolean; latency: number; cost: number }
|
|
518
|
+
): void {
|
|
519
|
+
const count = this.requestCount.get(provider) || 0;
|
|
520
|
+
this.requestCount.set(provider, count + 1);
|
|
521
|
+
this.lastUsed.set(provider, new Date());
|
|
522
|
+
|
|
523
|
+
const providerMetricsList = this.providerMetrics.get(provider) || [];
|
|
524
|
+
const errorRate = metrics.success ? 0 : 1;
|
|
525
|
+
const successRate = metrics.success ? 1 : 0;
|
|
526
|
+
|
|
527
|
+
providerMetricsList.push({
|
|
528
|
+
provider,
|
|
529
|
+
timestamp: new Date(),
|
|
530
|
+
latency: metrics.latency,
|
|
531
|
+
errorRate,
|
|
532
|
+
successRate,
|
|
533
|
+
load: this.providers.get(provider)?.getStatus().currentLoad || 0,
|
|
534
|
+
cost: metrics.cost,
|
|
535
|
+
availability: this.providers.get(provider)?.getStatus().available ? 1 : 0,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Keep only recent metrics (last 100)
|
|
539
|
+
if (providerMetricsList.length > 100) {
|
|
540
|
+
providerMetricsList.shift();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.providerMetrics.set(provider, providerMetricsList);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Event handlers
|
|
548
|
+
*/
|
|
549
|
+
private handleProviderResponse(provider: LLMProvider, data: any): void {
|
|
550
|
+
this.emit('provider_response', { provider, ...data });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private handleProviderError(provider: LLMProvider, error: any): void {
|
|
554
|
+
this.emit('provider_error', { provider, error });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private handleHealthCheck(provider: LLMProvider, result: any): void {
|
|
558
|
+
this.emit('health_check', { provider, result });
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Start monitoring
|
|
563
|
+
*/
|
|
564
|
+
private startMonitoring(): void {
|
|
565
|
+
setInterval(() => {
|
|
566
|
+
this.emitMetrics();
|
|
567
|
+
}, this.config.monitoring?.metricsInterval || 60000);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Emit aggregated metrics
|
|
572
|
+
*/
|
|
573
|
+
private emitMetrics(): void {
|
|
574
|
+
const metrics = {
|
|
575
|
+
providers: {} as Record<LLMProvider, any>,
|
|
576
|
+
totalRequests: 0,
|
|
577
|
+
totalCost: 0,
|
|
578
|
+
averageLatency: 0,
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
for (const [provider, count] of this.requestCount.entries()) {
|
|
582
|
+
const providerMetricsList = this.providerMetrics.get(provider) || [];
|
|
583
|
+
const avgLatency = providerMetricsList.length > 0
|
|
584
|
+
? providerMetricsList.reduce((sum, m) => sum + m.latency, 0) / providerMetricsList.length
|
|
585
|
+
: 0;
|
|
586
|
+
const totalCost = providerMetricsList.reduce((sum, m) => sum + m.cost, 0);
|
|
587
|
+
|
|
588
|
+
metrics.providers[provider] = {
|
|
589
|
+
requests: count,
|
|
590
|
+
averageLatency: avgLatency,
|
|
591
|
+
totalCost,
|
|
592
|
+
lastUsed: this.lastUsed.get(provider),
|
|
593
|
+
available: this.providers.get(provider)?.getStatus().available,
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
metrics.totalRequests += count;
|
|
597
|
+
metrics.totalCost += totalCost;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (metrics.totalRequests > 0) {
|
|
601
|
+
let totalLatency = 0;
|
|
602
|
+
let latencyCount = 0;
|
|
603
|
+
|
|
604
|
+
for (const providerMetricsList of this.providerMetrics.values()) {
|
|
605
|
+
for (const metric of providerMetricsList) {
|
|
606
|
+
totalLatency += metric.latency;
|
|
607
|
+
latencyCount++;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
metrics.averageLatency = latencyCount > 0 ? totalLatency / latencyCount : 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
this.emit('metrics', metrics);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Get available providers
|
|
619
|
+
*/
|
|
620
|
+
getAvailableProviders(): LLMProvider[] {
|
|
621
|
+
return Array.from(this.providers.keys()).filter(name => {
|
|
622
|
+
const provider = this.providers.get(name);
|
|
623
|
+
return provider && this.isProviderAvailable(provider);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Get provider by name
|
|
629
|
+
*/
|
|
630
|
+
getProvider(name: LLMProvider): ILLMProvider | undefined {
|
|
631
|
+
return this.providers.get(name);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Get all providers
|
|
636
|
+
*/
|
|
637
|
+
getAllProviders(): Map<LLMProvider, ILLMProvider> {
|
|
638
|
+
return new Map(this.providers);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Clean up resources
|
|
643
|
+
*/
|
|
644
|
+
destroy(): void {
|
|
645
|
+
for (const provider of this.providers.values()) {
|
|
646
|
+
provider.destroy();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
this.providers.clear();
|
|
650
|
+
this.cache.clear();
|
|
651
|
+
this.providerMetrics.clear();
|
|
652
|
+
this.removeAllListeners();
|
|
653
|
+
}
|
|
654
|
+
}
|