agentic-qe 2.6.1 → 2.6.3
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/agents/qe-code-intelligence.md +88 -1
- package/CHANGELOG.md +134 -0
- package/README.md +222 -159
- package/dist/agents/BaseAgent.d.ts +19 -0
- package/dist/agents/BaseAgent.d.ts.map +1 -1
- package/dist/agents/BaseAgent.js +41 -1
- package/dist/agents/BaseAgent.js.map +1 -1
- package/dist/agents/CodeIntelligenceAgent.d.ts +18 -1
- package/dist/agents/CodeIntelligenceAgent.d.ts.map +1 -1
- package/dist/agents/CodeIntelligenceAgent.js +96 -1
- package/dist/agents/CodeIntelligenceAgent.js.map +1 -1
- package/dist/agents/CoverageAnalyzerAgent.d.ts +8 -0
- package/dist/agents/CoverageAnalyzerAgent.d.ts.map +1 -1
- package/dist/agents/CoverageAnalyzerAgent.js +65 -1
- package/dist/agents/CoverageAnalyzerAgent.js.map +1 -1
- package/dist/agents/TestGeneratorAgent.d.ts +2 -2
- package/dist/agents/TestGeneratorAgent.d.ts.map +1 -1
- package/dist/agents/TestGeneratorAgent.js +16 -6
- package/dist/agents/TestGeneratorAgent.js.map +1 -1
- package/dist/agents/adapters/AgentLLMAdapter.d.ts +127 -0
- package/dist/agents/adapters/AgentLLMAdapter.d.ts.map +1 -0
- package/dist/agents/adapters/AgentLLMAdapter.js +366 -0
- package/dist/agents/adapters/AgentLLMAdapter.js.map +1 -0
- package/dist/agents/adapters/index.d.ts +1 -0
- package/dist/agents/adapters/index.d.ts.map +1 -1
- package/dist/agents/adapters/index.js +5 -1
- package/dist/agents/adapters/index.js.map +1 -1
- package/dist/agents/interfaces/IAgentLLM.d.ts +257 -0
- package/dist/agents/interfaces/IAgentLLM.d.ts.map +1 -0
- package/dist/agents/interfaces/IAgentLLM.js +39 -0
- package/dist/agents/interfaces/IAgentLLM.js.map +1 -0
- package/dist/agents/interfaces/index.d.ts +10 -0
- package/dist/agents/interfaces/index.d.ts.map +1 -0
- package/dist/agents/interfaces/index.js +14 -0
- package/dist/agents/interfaces/index.js.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts +18 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -1
- package/dist/agents/n8n/N8nBaseAgent.js +80 -0
- package/dist/agents/n8n/N8nBaseAgent.js.map +1 -1
- package/dist/cli/commands/knowledge-graph.d.ts +30 -0
- package/dist/cli/commands/knowledge-graph.d.ts.map +1 -1
- package/dist/cli/commands/knowledge-graph.js +206 -4
- package/dist/cli/commands/knowledge-graph.js.map +1 -1
- package/dist/cli/commands/providers.d.ts +50 -0
- package/dist/cli/commands/providers.d.ts.map +1 -0
- package/dist/cli/commands/providers.js +403 -0
- package/dist/cli/commands/providers.js.map +1 -0
- package/dist/cli/index.js +214 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/code-intelligence/indexing/FileWatcher.d.ts.map +1 -1
- package/dist/code-intelligence/indexing/FileWatcher.js +11 -8
- package/dist/code-intelligence/indexing/FileWatcher.js.map +1 -1
- package/dist/code-intelligence/inference/ComponentBoundaryAnalyzer.d.ts +75 -0
- package/dist/code-intelligence/inference/ComponentBoundaryAnalyzer.d.ts.map +1 -0
- package/dist/code-intelligence/inference/ComponentBoundaryAnalyzer.js +400 -0
- package/dist/code-intelligence/inference/ComponentBoundaryAnalyzer.js.map +1 -0
- package/dist/code-intelligence/inference/ExternalSystemDetector.d.ts +31 -0
- package/dist/code-intelligence/inference/ExternalSystemDetector.d.ts.map +1 -0
- package/dist/code-intelligence/inference/ExternalSystemDetector.js +523 -0
- package/dist/code-intelligence/inference/ExternalSystemDetector.js.map +1 -0
- package/dist/code-intelligence/inference/ProjectMetadataAnalyzer.d.ts +78 -0
- package/dist/code-intelligence/inference/ProjectMetadataAnalyzer.d.ts.map +1 -0
- package/dist/code-intelligence/inference/ProjectMetadataAnalyzer.js +491 -0
- package/dist/code-intelligence/inference/ProjectMetadataAnalyzer.js.map +1 -0
- package/dist/code-intelligence/inference/index.d.ts +36 -0
- package/dist/code-intelligence/inference/index.d.ts.map +1 -0
- package/dist/code-intelligence/inference/index.js +65 -0
- package/dist/code-intelligence/inference/index.js.map +1 -0
- package/dist/code-intelligence/inference/types.d.ts +196 -0
- package/dist/code-intelligence/inference/types.d.ts.map +1 -0
- package/dist/code-intelligence/inference/types.js +9 -0
- package/dist/code-intelligence/inference/types.js.map +1 -0
- package/dist/code-intelligence/visualization/C4ComponentDiagramBuilder.d.ts +75 -0
- package/dist/code-intelligence/visualization/C4ComponentDiagramBuilder.d.ts.map +1 -0
- package/dist/code-intelligence/visualization/C4ComponentDiagramBuilder.js +267 -0
- package/dist/code-intelligence/visualization/C4ComponentDiagramBuilder.js.map +1 -0
- package/dist/code-intelligence/visualization/C4ContainerDiagramBuilder.d.ts +138 -0
- package/dist/code-intelligence/visualization/C4ContainerDiagramBuilder.d.ts.map +1 -0
- package/dist/code-intelligence/visualization/C4ContainerDiagramBuilder.js +343 -0
- package/dist/code-intelligence/visualization/C4ContainerDiagramBuilder.js.map +1 -0
- package/dist/code-intelligence/visualization/C4ContextDiagramBuilder.d.ts +67 -0
- package/dist/code-intelligence/visualization/C4ContextDiagramBuilder.d.ts.map +1 -0
- package/dist/code-intelligence/visualization/C4ContextDiagramBuilder.js +152 -0
- package/dist/code-intelligence/visualization/C4ContextDiagramBuilder.js.map +1 -0
- package/dist/code-intelligence/visualization/MermaidGenerator.d.ts +79 -0
- package/dist/code-intelligence/visualization/MermaidGenerator.d.ts.map +1 -1
- package/dist/code-intelligence/visualization/MermaidGenerator.js +143 -0
- package/dist/code-intelligence/visualization/MermaidGenerator.js.map +1 -1
- package/dist/config/ConfigLoader.d.ts +86 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +450 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/ProviderConfig.d.ts +153 -0
- package/dist/config/ProviderConfig.d.ts.map +1 -0
- package/dist/config/ProviderConfig.js +155 -0
- package/dist/config/ProviderConfig.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +45 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/mcp/handlers/integration/integration-test-orchestrate.d.ts.map +1 -1
- package/dist/mcp/handlers/integration/integration-test-orchestrate.js +6 -9
- package/dist/mcp/handlers/integration/integration-test-orchestrate.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/memory/HNSWPatternStore.d.ts.map +1 -1
- package/dist/memory/HNSWPatternStore.js +23 -0
- package/dist/memory/HNSWPatternStore.js.map +1 -1
- package/dist/memory/RuVectorPatternStore.d.ts +5 -0
- package/dist/memory/RuVectorPatternStore.d.ts.map +1 -1
- package/dist/memory/RuVectorPatternStore.js +11 -0
- package/dist/memory/RuVectorPatternStore.js.map +1 -1
- package/dist/providers/CostOptimizationStrategies.d.ts +297 -0
- package/dist/providers/CostOptimizationStrategies.d.ts.map +1 -0
- package/dist/providers/CostOptimizationStrategies.js +831 -0
- package/dist/providers/CostOptimizationStrategies.js.map +1 -0
- package/dist/providers/HybridRouter.d.ts +142 -5
- package/dist/providers/HybridRouter.d.ts.map +1 -1
- package/dist/providers/HybridRouter.js +472 -6
- package/dist/providers/HybridRouter.js.map +1 -1
- package/dist/providers/HybridRouterComplexityIntegration.d.ts +169 -0
- package/dist/providers/HybridRouterComplexityIntegration.d.ts.map +1 -0
- package/dist/providers/HybridRouterComplexityIntegration.js +319 -0
- package/dist/providers/HybridRouterComplexityIntegration.js.map +1 -0
- package/dist/providers/HybridRouterModelSelection.d.ts +106 -0
- package/dist/providers/HybridRouterModelSelection.d.ts.map +1 -0
- package/dist/providers/HybridRouterModelSelection.js +420 -0
- package/dist/providers/HybridRouterModelSelection.js.map +1 -0
- package/dist/providers/LLMProviderFactory.d.ts +23 -9
- package/dist/providers/LLMProviderFactory.d.ts.map +1 -1
- package/dist/providers/LLMProviderFactory.js +54 -11
- package/dist/providers/LLMProviderFactory.js.map +1 -1
- package/dist/providers/OllamaProvider.d.ts +122 -0
- package/dist/providers/OllamaProvider.d.ts.map +1 -0
- package/dist/providers/OllamaProvider.js +425 -0
- package/dist/providers/OllamaProvider.js.map +1 -0
- package/dist/providers/index.d.ts +6 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +17 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/routing/ComplexityClassifier.d.ts +266 -0
- package/dist/routing/ComplexityClassifier.d.ts.map +1 -0
- package/dist/routing/ComplexityClassifier.js +567 -0
- package/dist/routing/ComplexityClassifier.js.map +1 -0
- package/dist/routing/ModelCapabilityRegistry.d.ts +98 -0
- package/dist/routing/ModelCapabilityRegistry.d.ts.map +1 -0
- package/dist/routing/ModelCapabilityRegistry.js +216 -0
- package/dist/routing/ModelCapabilityRegistry.js.map +1 -0
- package/dist/routing/index.d.ts +13 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +24 -0
- package/dist/routing/index.js.map +1 -0
- package/docs/reference/model-capability-registry.md +402 -0
- package/docs/reference/provider-config-schema.md +608 -0
- package/package.json +20 -4
|
@@ -24,6 +24,8 @@ const ClaudeProvider_1 = require("./ClaudeProvider");
|
|
|
24
24
|
const RuvllmProvider_1 = require("./RuvllmProvider");
|
|
25
25
|
const RuVectorClient_1 = require("./RuVectorClient");
|
|
26
26
|
const Logger_1 = require("../utils/Logger");
|
|
27
|
+
const ComplexityClassifier_1 = require("../routing/ComplexityClassifier");
|
|
28
|
+
const CostOptimizationStrategies_1 = require("./CostOptimizationStrategies");
|
|
27
29
|
/**
|
|
28
30
|
* Request priority levels
|
|
29
31
|
*/
|
|
@@ -100,7 +102,12 @@ class HybridRouter {
|
|
|
100
102
|
},
|
|
101
103
|
claude: config.claude,
|
|
102
104
|
ruvllm: config.ruvllm,
|
|
103
|
-
ruvector: config.ruvector
|
|
105
|
+
ruvector: config.ruvector,
|
|
106
|
+
// Phase 2 integration config
|
|
107
|
+
complexityClassifier: config.complexityClassifier,
|
|
108
|
+
costOptimization: config.costOptimization,
|
|
109
|
+
useMLClassifier: config.useMLClassifier ?? true,
|
|
110
|
+
useCostOptimization: config.useCostOptimization ?? true
|
|
104
111
|
};
|
|
105
112
|
this.circuitBreakers = new Map();
|
|
106
113
|
this.routingHistory = [];
|
|
@@ -111,6 +118,9 @@ class HybridRouter {
|
|
|
111
118
|
this.cloudRequestCount = 0;
|
|
112
119
|
this.cacheHitCount = 0;
|
|
113
120
|
this.cacheMissCount = 0;
|
|
121
|
+
this.costHistory = [];
|
|
122
|
+
this.trackingStartDate = new Date();
|
|
123
|
+
this.compressionStats = { totalSaved: 0, totalOriginal: 0, count: 0 };
|
|
114
124
|
}
|
|
115
125
|
/**
|
|
116
126
|
* Initialize the hybrid router and its providers
|
|
@@ -184,11 +194,49 @@ class HybridRouter {
|
|
|
184
194
|
if (!this.localProvider && !this.cloudProvider) {
|
|
185
195
|
throw new ILLMProvider_1.LLMProviderError('Failed to initialize any providers', 'hybrid-router', 'INIT_ERROR', false);
|
|
186
196
|
}
|
|
197
|
+
// Initialize ML-based complexity classifier (Phase 2.1.2)
|
|
198
|
+
if (this.config.useMLClassifier) {
|
|
199
|
+
try {
|
|
200
|
+
this.complexityClassifier = new ComplexityClassifier_1.ComplexityClassifier(this.config.complexityClassifier ?? {
|
|
201
|
+
enableLearning: true,
|
|
202
|
+
learningRate: 0.05
|
|
203
|
+
});
|
|
204
|
+
this.logger.info('ML ComplexityClassifier initialized', {
|
|
205
|
+
enableLearning: this.config.complexityClassifier?.enableLearning ?? true
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
this.logger.warn('Failed to initialize ComplexityClassifier, using heuristics', {
|
|
210
|
+
error: error.message
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Initialize cost optimization manager (Phase 2.3.2)
|
|
215
|
+
if (this.config.useCostOptimization) {
|
|
216
|
+
try {
|
|
217
|
+
this.costOptimizationManager = new CostOptimizationStrategies_1.CostOptimizationManager(this.config.costOptimization ?? {
|
|
218
|
+
enableCompression: true,
|
|
219
|
+
enableBatching: true,
|
|
220
|
+
enableSmartCaching: true
|
|
221
|
+
});
|
|
222
|
+
this.logger.info('CostOptimizationManager initialized', {
|
|
223
|
+
compression: this.config.costOptimization?.enableCompression ?? true,
|
|
224
|
+
batching: this.config.costOptimization?.enableBatching ?? true
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
this.logger.warn('Failed to initialize CostOptimizationManager', {
|
|
229
|
+
error: error.message
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
187
233
|
this.isInitialized = true;
|
|
188
234
|
this.logger.info('HybridRouter initialized', {
|
|
189
235
|
hasLocal: !!this.localProvider,
|
|
190
236
|
hasCloud: !!this.cloudProvider,
|
|
191
237
|
hasRuVectorCache: !!this.ruVectorClient,
|
|
238
|
+
hasMLClassifier: !!this.complexityClassifier,
|
|
239
|
+
hasCostOptimization: !!this.costOptimizationManager,
|
|
192
240
|
strategy: this.config.defaultStrategy
|
|
193
241
|
});
|
|
194
242
|
}
|
|
@@ -208,7 +256,47 @@ class HybridRouter {
|
|
|
208
256
|
const startTime = Date.now();
|
|
209
257
|
const priority = options.priority ?? RequestPriority.NORMAL;
|
|
210
258
|
const strategy = options.routingStrategy ?? this.config.defaultStrategy;
|
|
211
|
-
|
|
259
|
+
// Apply cost optimization: prompt compression (Phase 2.3.2)
|
|
260
|
+
let optimizedOptions = options;
|
|
261
|
+
let compressionResult;
|
|
262
|
+
if (this.costOptimizationManager && this.config.useCostOptimization) {
|
|
263
|
+
try {
|
|
264
|
+
const originalContent = this.extractQueryFromOptions(options) || '';
|
|
265
|
+
const compressor = this.costOptimizationManager.getCompressor();
|
|
266
|
+
compressionResult = compressor.compress(originalContent);
|
|
267
|
+
if (compressionResult.tokensSaved > 0) {
|
|
268
|
+
// Track compression stats for benchmarking
|
|
269
|
+
// Estimate original tokens from ratio: ratio = tokensSaved / originalTokens
|
|
270
|
+
// So originalTokens ≈ tokensSaved / ratio (if ratio > 0)
|
|
271
|
+
const estimatedOriginalTokens = compressionResult.ratio > 0
|
|
272
|
+
? Math.round(compressionResult.tokensSaved / compressionResult.ratio)
|
|
273
|
+
: compressionResult.original.split(/\s+/).length;
|
|
274
|
+
this.compressionStats.totalOriginal += estimatedOriginalTokens;
|
|
275
|
+
this.compressionStats.totalSaved += compressionResult.tokensSaved;
|
|
276
|
+
this.compressionStats.count++;
|
|
277
|
+
// Apply compressed content to options
|
|
278
|
+
optimizedOptions = this.applyCompressedContent(options, compressionResult.compressed);
|
|
279
|
+
if (this.config.debug) {
|
|
280
|
+
this.logger.debug('Prompt compressed', {
|
|
281
|
+
tokensSaved: compressionResult.tokensSaved,
|
|
282
|
+
ratio: compressionResult.ratio,
|
|
283
|
+
techniques: compressionResult.techniques
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
this.logger.warn('Prompt compression failed, using original', {
|
|
290
|
+
error: error.message
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const complexity = this.analyzeComplexity(optimizedOptions);
|
|
295
|
+
// Check budget before proceeding
|
|
296
|
+
const estimatedCost = strategy === RoutingStrategy.COST_OPTIMIZED ? 0 : 0.01;
|
|
297
|
+
if (!this.checkBudget(estimatedCost)) {
|
|
298
|
+
throw new ILLMProvider_1.LLMProviderError('Request rejected: budget limit exceeded', 'hybrid-router', 'BUDGET_EXCEEDED', false);
|
|
299
|
+
}
|
|
212
300
|
// Phase 0.5: Try RuVector cache first (sub-ms pattern matching)
|
|
213
301
|
if (this.ruVectorClient && this.shouldUseCache(options, complexity)) {
|
|
214
302
|
try {
|
|
@@ -427,6 +515,8 @@ class HybridRouter {
|
|
|
427
515
|
// Track cost
|
|
428
516
|
const actualCost = this.calculateCost(response, decision.provider);
|
|
429
517
|
this.totalCost += actualCost;
|
|
518
|
+
// Track detailed cost information
|
|
519
|
+
this.trackCostByProvider(decision.providerName, actualCost, response.model, decision.complexity, response.usage.input_tokens + response.usage.output_tokens);
|
|
430
520
|
// Record outcome for learning
|
|
431
521
|
if (this.config.enableLearning) {
|
|
432
522
|
this.recordOutcome({
|
|
@@ -658,15 +748,55 @@ class HybridRouter {
|
|
|
658
748
|
* Get cost savings report including RuVector cache savings
|
|
659
749
|
*/
|
|
660
750
|
getCostSavingsReport() {
|
|
661
|
-
|
|
751
|
+
return this.getDetailedCostReport();
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Get detailed cost report with optional date filtering
|
|
755
|
+
*/
|
|
756
|
+
getDetailedCostReport(startDate, endDate) {
|
|
757
|
+
const start = startDate || this.trackingStartDate;
|
|
758
|
+
const end = endDate || new Date();
|
|
759
|
+
// Filter cost history by date range
|
|
760
|
+
const filteredHistory = this.costHistory.filter(entry => entry.timestamp >= start && entry.timestamp <= end);
|
|
761
|
+
// Calculate cost by provider
|
|
762
|
+
const costByProvider = {};
|
|
763
|
+
const costByTaskType = {};
|
|
764
|
+
const costByModel = {};
|
|
765
|
+
const taskTypeCounts = {};
|
|
766
|
+
for (const entry of filteredHistory) {
|
|
767
|
+
// By provider
|
|
768
|
+
costByProvider[entry.provider] = (costByProvider[entry.provider] || 0) + entry.cost;
|
|
769
|
+
// By task type
|
|
770
|
+
costByTaskType[entry.taskType] = (costByTaskType[entry.taskType] || 0) + entry.cost;
|
|
771
|
+
// By model
|
|
772
|
+
costByModel[entry.model] = (costByModel[entry.model] || 0) + entry.cost;
|
|
773
|
+
// Task type counts for top costly tasks
|
|
774
|
+
if (!taskTypeCounts[entry.taskType]) {
|
|
775
|
+
taskTypeCounts[entry.taskType] = { cost: 0, count: 0 };
|
|
776
|
+
}
|
|
777
|
+
taskTypeCounts[entry.taskType].cost += entry.cost;
|
|
778
|
+
taskTypeCounts[entry.taskType].count++;
|
|
779
|
+
}
|
|
780
|
+
// Calculate top costly tasks
|
|
781
|
+
const topCostlyTasks = Object.entries(taskTypeCounts)
|
|
782
|
+
.map(([taskType, data]) => ({
|
|
783
|
+
taskType,
|
|
784
|
+
cost: data.cost,
|
|
785
|
+
count: data.count
|
|
786
|
+
}))
|
|
787
|
+
.sort((a, b) => b.cost - a.cost)
|
|
788
|
+
.slice(0, 10);
|
|
789
|
+
// Calculate totals and estimates
|
|
662
790
|
const cloudCostPerRequest = 0.01; // Rough estimate
|
|
663
791
|
const estimatedCloudCost = this.requestCount * cloudCostPerRequest;
|
|
664
792
|
const savings = estimatedCloudCost - this.totalCost;
|
|
665
793
|
const savingsPercentage = estimatedCloudCost > 0
|
|
666
794
|
? (savings / estimatedCloudCost) * 100
|
|
667
795
|
: 0;
|
|
668
|
-
// Cache hits are essentially free
|
|
669
796
|
const cacheSavings = this.cacheHitCount * cloudCostPerRequest;
|
|
797
|
+
const averageCostPerRequest = this.requestCount > 0 ? this.totalCost / this.requestCount : 0;
|
|
798
|
+
// Project monthly cost based on current usage rate
|
|
799
|
+
const monthlyCostProjection = this.projectMonthlyCost();
|
|
670
800
|
return {
|
|
671
801
|
totalRequests: this.requestCount,
|
|
672
802
|
localRequests: this.localRequestCount,
|
|
@@ -676,8 +806,190 @@ class HybridRouter {
|
|
|
676
806
|
savings,
|
|
677
807
|
savingsPercentage,
|
|
678
808
|
cacheHits: this.cacheHitCount,
|
|
679
|
-
cacheSavings
|
|
809
|
+
cacheSavings,
|
|
810
|
+
costByProvider,
|
|
811
|
+
costByTaskType,
|
|
812
|
+
costByModel,
|
|
813
|
+
averageCostPerRequest,
|
|
814
|
+
topCostlyTasks,
|
|
815
|
+
monthlyCostProjection,
|
|
816
|
+
periodStart: start,
|
|
817
|
+
periodEnd: end
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get top costly operations
|
|
822
|
+
*/
|
|
823
|
+
getTopCostlyOperations(limit = 10) {
|
|
824
|
+
const taskTypeCosts = {};
|
|
825
|
+
for (const entry of this.costHistory) {
|
|
826
|
+
taskTypeCosts[entry.taskType] = (taskTypeCosts[entry.taskType] || 0) + entry.cost;
|
|
827
|
+
}
|
|
828
|
+
return Object.entries(taskTypeCosts)
|
|
829
|
+
.map(([taskType, cost]) => ({ taskType, cost }))
|
|
830
|
+
.sort((a, b) => b.cost - a.cost)
|
|
831
|
+
.slice(0, limit);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Project monthly cost based on current usage rate
|
|
835
|
+
*/
|
|
836
|
+
projectMonthlyCost() {
|
|
837
|
+
if (this.costHistory.length === 0) {
|
|
838
|
+
return 0;
|
|
839
|
+
}
|
|
840
|
+
const now = new Date();
|
|
841
|
+
const timeElapsed = now.getTime() - this.trackingStartDate.getTime();
|
|
842
|
+
const daysElapsed = timeElapsed / (1000 * 60 * 60 * 24);
|
|
843
|
+
if (daysElapsed < 0.1) {
|
|
844
|
+
// Less than 2.4 hours, not enough data
|
|
845
|
+
return this.totalCost * 30; // Rough estimate
|
|
846
|
+
}
|
|
847
|
+
// Calculate daily average and project to 30 days
|
|
848
|
+
const dailyAverage = this.totalCost / daysElapsed;
|
|
849
|
+
return dailyAverage * 30;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Set budget configuration
|
|
853
|
+
*/
|
|
854
|
+
setBudget(config) {
|
|
855
|
+
this.budgetConfig = config;
|
|
856
|
+
this.logger.info('Budget configuration updated', {
|
|
857
|
+
monthlyBudget: config.monthlyBudget,
|
|
858
|
+
dailyBudget: config.dailyBudget,
|
|
859
|
+
alertThreshold: config.alertThreshold,
|
|
860
|
+
enforceLimit: config.enforceLimit
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get current budget status
|
|
865
|
+
*/
|
|
866
|
+
getBudgetStatus() {
|
|
867
|
+
if (!this.budgetConfig) {
|
|
868
|
+
return {
|
|
869
|
+
dailySpent: this.totalCost,
|
|
870
|
+
dailyRemaining: Infinity,
|
|
871
|
+
monthlySpent: this.totalCost,
|
|
872
|
+
monthlyRemaining: Infinity,
|
|
873
|
+
utilizationPercentage: 0,
|
|
874
|
+
isOverBudget: false,
|
|
875
|
+
alertTriggered: false
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
const now = new Date();
|
|
879
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
880
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
881
|
+
// Calculate daily spending
|
|
882
|
+
const dailyHistory = this.costHistory.filter(entry => entry.timestamp >= today);
|
|
883
|
+
const dailySpent = dailyHistory.reduce((sum, entry) => sum + entry.cost, 0);
|
|
884
|
+
// Calculate monthly spending
|
|
885
|
+
const monthlyHistory = this.costHistory.filter(entry => entry.timestamp >= monthStart);
|
|
886
|
+
const monthlySpent = monthlyHistory.reduce((sum, entry) => sum + entry.cost, 0);
|
|
887
|
+
const dailyBudget = this.budgetConfig.dailyBudget ?? Infinity;
|
|
888
|
+
const monthlyBudget = this.budgetConfig.monthlyBudget ?? Infinity;
|
|
889
|
+
const dailyRemaining = Math.max(0, dailyBudget - dailySpent);
|
|
890
|
+
const monthlyRemaining = Math.max(0, monthlyBudget - monthlySpent);
|
|
891
|
+
// Calculate utilization based on the most restrictive budget
|
|
892
|
+
let utilizationPercentage = 0;
|
|
893
|
+
if (monthlyBudget !== Infinity) {
|
|
894
|
+
utilizationPercentage = (monthlySpent / monthlyBudget) * 100;
|
|
895
|
+
}
|
|
896
|
+
else if (dailyBudget !== Infinity) {
|
|
897
|
+
utilizationPercentage = (dailySpent / dailyBudget) * 100;
|
|
898
|
+
}
|
|
899
|
+
const isOverBudget = dailySpent > dailyBudget || monthlySpent > monthlyBudget;
|
|
900
|
+
const alertTriggered = utilizationPercentage >= (this.budgetConfig.alertThreshold * 100);
|
|
901
|
+
if (alertTriggered && !isOverBudget) {
|
|
902
|
+
this.logger.warn('Budget alert threshold reached', {
|
|
903
|
+
utilizationPercentage,
|
|
904
|
+
threshold: this.budgetConfig.alertThreshold * 100,
|
|
905
|
+
dailySpent,
|
|
906
|
+
monthlySpent
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
if (isOverBudget) {
|
|
910
|
+
this.logger.error('Budget limit exceeded', {
|
|
911
|
+
dailySpent,
|
|
912
|
+
dailyBudget,
|
|
913
|
+
monthlySpent,
|
|
914
|
+
monthlyBudget
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
dailySpent,
|
|
919
|
+
dailyRemaining,
|
|
920
|
+
monthlySpent,
|
|
921
|
+
monthlyRemaining,
|
|
922
|
+
utilizationPercentage,
|
|
923
|
+
isOverBudget,
|
|
924
|
+
alertTriggered
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Check if budget allows this request
|
|
929
|
+
*/
|
|
930
|
+
checkBudget(estimatedCost) {
|
|
931
|
+
if (!this.budgetConfig || !this.budgetConfig.enforceLimit) {
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
const status = this.getBudgetStatus();
|
|
935
|
+
if (status.isOverBudget) {
|
|
936
|
+
this.logger.error('Request rejected: budget exceeded', {
|
|
937
|
+
estimatedCost,
|
|
938
|
+
dailySpent: status.dailySpent,
|
|
939
|
+
monthlySpent: status.monthlySpent
|
|
940
|
+
});
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
// Check if this request would exceed budget
|
|
944
|
+
if (this.budgetConfig.dailyBudget !== undefined) {
|
|
945
|
+
if (status.dailySpent + estimatedCost > this.budgetConfig.dailyBudget) {
|
|
946
|
+
this.logger.error('Request rejected: would exceed daily budget', {
|
|
947
|
+
estimatedCost,
|
|
948
|
+
dailySpent: status.dailySpent,
|
|
949
|
+
dailyBudget: this.budgetConfig.dailyBudget
|
|
950
|
+
});
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (this.budgetConfig.monthlyBudget !== undefined) {
|
|
955
|
+
if (status.monthlySpent + estimatedCost > this.budgetConfig.monthlyBudget) {
|
|
956
|
+
this.logger.error('Request rejected: would exceed monthly budget', {
|
|
957
|
+
estimatedCost,
|
|
958
|
+
monthlySpent: status.monthlySpent,
|
|
959
|
+
monthlyBudget: this.budgetConfig.monthlyBudget
|
|
960
|
+
});
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Track cost by provider
|
|
968
|
+
*/
|
|
969
|
+
trackCostByProvider(provider, cost, model, taskType, tokens) {
|
|
970
|
+
const entry = {
|
|
971
|
+
timestamp: new Date(),
|
|
972
|
+
provider,
|
|
973
|
+
model,
|
|
974
|
+
taskType,
|
|
975
|
+
cost,
|
|
976
|
+
tokens
|
|
680
977
|
};
|
|
978
|
+
this.costHistory.push(entry);
|
|
979
|
+
// Keep only last 10,000 entries to prevent unbounded growth
|
|
980
|
+
if (this.costHistory.length > 10000) {
|
|
981
|
+
this.costHistory.shift();
|
|
982
|
+
}
|
|
983
|
+
if (this.config.debug) {
|
|
984
|
+
this.logger.debug('Cost tracked', {
|
|
985
|
+
provider,
|
|
986
|
+
model,
|
|
987
|
+
taskType,
|
|
988
|
+
cost,
|
|
989
|
+
tokens,
|
|
990
|
+
historySize: this.costHistory.length
|
|
991
|
+
});
|
|
992
|
+
}
|
|
681
993
|
}
|
|
682
994
|
/**
|
|
683
995
|
* Get routing statistics including cache metrics
|
|
@@ -872,9 +1184,36 @@ class HybridRouter {
|
|
|
872
1184
|
return options;
|
|
873
1185
|
}
|
|
874
1186
|
/**
|
|
875
|
-
* Analyze task complexity
|
|
1187
|
+
* Analyze task complexity using ML classifier or heuristics fallback
|
|
876
1188
|
*/
|
|
877
1189
|
analyzeComplexity(options) {
|
|
1190
|
+
// Use ML-based classifier if available (Phase 2.1.2)
|
|
1191
|
+
if (this.complexityClassifier && this.config.useMLClassifier) {
|
|
1192
|
+
try {
|
|
1193
|
+
const mlComplexity = this.complexityClassifier.classifyTask(options);
|
|
1194
|
+
const confidence = this.complexityClassifier.getClassificationConfidence();
|
|
1195
|
+
if (this.config.debug) {
|
|
1196
|
+
this.logger.debug('ML complexity classification', {
|
|
1197
|
+
complexity: mlComplexity,
|
|
1198
|
+
confidence: confidence.toFixed(3)
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
// Map ComplexityClassifier output to TaskComplexity enum
|
|
1202
|
+
return mlComplexity;
|
|
1203
|
+
}
|
|
1204
|
+
catch (error) {
|
|
1205
|
+
this.logger.warn('ML classifier failed, using heuristics', {
|
|
1206
|
+
error: error.message
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// Fallback to heuristics
|
|
1211
|
+
return this.analyzeComplexityHeuristics(options);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Heuristic-based complexity analysis (fallback)
|
|
1215
|
+
*/
|
|
1216
|
+
analyzeComplexityHeuristics(options) {
|
|
878
1217
|
const maxTokens = options.maxTokens || 0;
|
|
879
1218
|
const messageCount = options.messages.length;
|
|
880
1219
|
const totalContent = options.messages
|
|
@@ -911,6 +1250,27 @@ class HybridRouter {
|
|
|
911
1250
|
return TaskComplexity.MODERATE;
|
|
912
1251
|
return TaskComplexity.SIMPLE;
|
|
913
1252
|
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Apply compressed content to options
|
|
1255
|
+
*/
|
|
1256
|
+
applyCompressedContent(options, compressed) {
|
|
1257
|
+
if (!options.messages || options.messages.length === 0) {
|
|
1258
|
+
return options;
|
|
1259
|
+
}
|
|
1260
|
+
// Clone options to avoid mutation
|
|
1261
|
+
const newOptions = { ...options, messages: [...options.messages] };
|
|
1262
|
+
// Find and replace last user message with compressed version
|
|
1263
|
+
for (let i = newOptions.messages.length - 1; i >= 0; i--) {
|
|
1264
|
+
if (newOptions.messages[i].role === 'user') {
|
|
1265
|
+
newOptions.messages[i] = {
|
|
1266
|
+
...newOptions.messages[i],
|
|
1267
|
+
content: compressed
|
|
1268
|
+
};
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return newOptions;
|
|
1273
|
+
}
|
|
914
1274
|
/**
|
|
915
1275
|
* Check for privacy-sensitive data
|
|
916
1276
|
*/
|
|
@@ -1061,6 +1421,112 @@ class HybridRouter {
|
|
|
1061
1421
|
throw new ILLMProvider_1.LLMProviderError('HybridRouter not initialized. Call initialize() first.', 'hybrid-router', 'NOT_INITIALIZED', false);
|
|
1062
1422
|
}
|
|
1063
1423
|
}
|
|
1424
|
+
// ============================================================
|
|
1425
|
+
// Phase 2 Integration: Public Methods for Benchmarking & Stats
|
|
1426
|
+
// ============================================================
|
|
1427
|
+
/**
|
|
1428
|
+
* Get compression statistics for benchmarking (Phase 2.3.2)
|
|
1429
|
+
*
|
|
1430
|
+
* Returns actual measured compression savings, not estimated values.
|
|
1431
|
+
*/
|
|
1432
|
+
getCompressionStats() {
|
|
1433
|
+
const avgSavings = this.compressionStats.totalOriginal > 0
|
|
1434
|
+
? (this.compressionStats.totalSaved / this.compressionStats.totalOriginal) * 100
|
|
1435
|
+
: 0;
|
|
1436
|
+
return {
|
|
1437
|
+
totalRequests: this.compressionStats.count,
|
|
1438
|
+
totalOriginalTokens: this.compressionStats.totalOriginal,
|
|
1439
|
+
totalSavedTokens: this.compressionStats.totalSaved,
|
|
1440
|
+
averageSavingsPercent: avgSavings,
|
|
1441
|
+
compressionEnabled: !!this.costOptimizationManager && !!this.config.useCostOptimization
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Get ML classifier statistics (Phase 2.1.2)
|
|
1446
|
+
*
|
|
1447
|
+
* Returns classifier performance metrics if ML classifier is enabled.
|
|
1448
|
+
*/
|
|
1449
|
+
getMLClassifierStats() {
|
|
1450
|
+
if (!this.complexityClassifier) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
try {
|
|
1454
|
+
const stats = this.complexityClassifier.getStatistics();
|
|
1455
|
+
return {
|
|
1456
|
+
enabled: true,
|
|
1457
|
+
totalClassifications: stats.totalClassifications,
|
|
1458
|
+
averageConfidence: stats.averageConfidence,
|
|
1459
|
+
successRate: stats.successRate,
|
|
1460
|
+
complexityDistribution: stats.complexityDistribution
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
catch {
|
|
1464
|
+
return {
|
|
1465
|
+
enabled: true,
|
|
1466
|
+
totalClassifications: 0,
|
|
1467
|
+
averageConfidence: 0,
|
|
1468
|
+
successRate: 0,
|
|
1469
|
+
complexityDistribution: {
|
|
1470
|
+
[TaskComplexity.SIMPLE]: 0,
|
|
1471
|
+
[TaskComplexity.MODERATE]: 0,
|
|
1472
|
+
[TaskComplexity.COMPLEX]: 0,
|
|
1473
|
+
[TaskComplexity.VERY_COMPLEX]: 0
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Train classifier from routing outcome (Phase 2.1.2)
|
|
1480
|
+
*
|
|
1481
|
+
* Allows manual training feedback for the complexity classifier.
|
|
1482
|
+
*/
|
|
1483
|
+
trainClassifierFromOutcome(entry) {
|
|
1484
|
+
if (!this.complexityClassifier) {
|
|
1485
|
+
this.logger.warn('Cannot train: ML classifier not enabled');
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
this.complexityClassifier.recordOutcome(entry);
|
|
1489
|
+
if (this.config.debug) {
|
|
1490
|
+
this.logger.debug('Classifier trained from outcome', {
|
|
1491
|
+
complexity: entry.selectedComplexity,
|
|
1492
|
+
success: entry.actualOutcome.success
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Get feature weights from the ML classifier (Phase 2.1.2)
|
|
1498
|
+
*/
|
|
1499
|
+
getClassifierWeights() {
|
|
1500
|
+
if (!this.complexityClassifier) {
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
const weights = this.complexityClassifier.getWeights();
|
|
1504
|
+
// Convert typed object to generic Record
|
|
1505
|
+
return { ...weights };
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Get complexity thresholds from the ML classifier (Phase 2.1.2)
|
|
1509
|
+
*/
|
|
1510
|
+
getComplexityThresholds() {
|
|
1511
|
+
if (!this.complexityClassifier) {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
const thresholds = this.complexityClassifier.getThresholds();
|
|
1515
|
+
// Convert typed object to generic Record
|
|
1516
|
+
return { ...thresholds };
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Check if ML classifier is enabled and active
|
|
1520
|
+
*/
|
|
1521
|
+
isMLClassifierEnabled() {
|
|
1522
|
+
return !!this.complexityClassifier && !!this.config.useMLClassifier;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Check if cost optimization is enabled and active
|
|
1526
|
+
*/
|
|
1527
|
+
isCostOptimizationEnabled() {
|
|
1528
|
+
return !!this.costOptimizationManager && !!this.config.useCostOptimization;
|
|
1529
|
+
}
|
|
1064
1530
|
}
|
|
1065
1531
|
exports.HybridRouter = HybridRouter;
|
|
1066
1532
|
//# sourceMappingURL=HybridRouter.js.map
|