snow-flow 3.3.4 ā 3.3.5
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/dist/dynamic-version.js
CHANGED
|
@@ -36,7 +36,7 @@ function getDynamicVersion() {
|
|
|
36
36
|
console.warn('Warning: Could not read version from package.json:', error);
|
|
37
37
|
}
|
|
38
38
|
// Fallback to hardcoded version
|
|
39
|
-
return '3.3.
|
|
39
|
+
return '3.3.5';
|
|
40
40
|
}
|
|
41
41
|
// Export a constant that uses the dynamic version
|
|
42
42
|
exports.VERSION = getDynamicVersion();
|
|
@@ -36,7 +36,22 @@ export declare class ServiceNowMachineLearningMCP {
|
|
|
36
36
|
/**
|
|
37
37
|
* Classify incident using PI if available, otherwise neural network
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* Classify an incident using trained ML model
|
|
41
|
+
*/
|
|
39
42
|
private classifyIncident;
|
|
43
|
+
/**
|
|
44
|
+
* Helper: Suggest assignment group based on category
|
|
45
|
+
*/
|
|
46
|
+
private suggestAssignmentGroup;
|
|
47
|
+
/**
|
|
48
|
+
* Helper: Suggest priority based on category and description
|
|
49
|
+
*/
|
|
50
|
+
private suggestPriority;
|
|
51
|
+
/**
|
|
52
|
+
* Helper: Generate category description
|
|
53
|
+
*/
|
|
54
|
+
private generateCategoryDescription;
|
|
40
55
|
/**
|
|
41
56
|
* Forecast incident volume using LSTM
|
|
42
57
|
*/
|
|
@@ -857,33 +857,79 @@ class ServiceNowMachineLearningMCP {
|
|
|
857
857
|
}]
|
|
858
858
|
};
|
|
859
859
|
}
|
|
860
|
-
this.logger.info('
|
|
861
|
-
// Train model with improved error handling
|
|
860
|
+
this.logger.info('š Starting neural network training...');
|
|
861
|
+
// Train model with improved error handling and progress tracking
|
|
862
862
|
let history;
|
|
863
|
+
const trainingStartTime = Date.now();
|
|
864
|
+
let lastProgressUpdate = Date.now();
|
|
863
865
|
try {
|
|
864
|
-
this.logger.info(
|
|
866
|
+
this.logger.info(`š Training Configuration:`);
|
|
867
|
+
this.logger.info(` ⢠Epochs: ${epochs}`);
|
|
868
|
+
this.logger.info(` ⢠Samples: ${incidents.length}`);
|
|
869
|
+
this.logger.info(` ⢠Validation Split: ${(validation_split * 100).toFixed(0)}%`);
|
|
870
|
+
this.logger.info(` ⢠Categories: ${categories.length}`);
|
|
871
|
+
this.logger.info(` ⢠Vocabulary Size: ${vocabularySize}`);
|
|
872
|
+
this.logger.info(` ⢠Batch Size: 32`);
|
|
865
873
|
history = await model.fit(features, labels, {
|
|
866
874
|
epochs,
|
|
867
875
|
validationSplit: validation_split,
|
|
868
876
|
batchSize: 32,
|
|
869
877
|
callbacks: {
|
|
878
|
+
onEpochBegin: (epoch) => {
|
|
879
|
+
const progress = ((epoch / epochs) * 100).toFixed(0);
|
|
880
|
+
this.logger.info(`\nā³ Epoch ${epoch + 1}/${epochs} (${progress}% complete)`);
|
|
881
|
+
},
|
|
870
882
|
onEpochEnd: (epoch, logs) => {
|
|
871
883
|
try {
|
|
872
884
|
const loss = logs?.loss ? logs.loss.toFixed(4) : 'N/A';
|
|
873
|
-
const accuracy = logs?.acc ? logs.acc.toFixed(
|
|
874
|
-
|
|
885
|
+
const accuracy = logs?.acc ? (logs.acc * 100).toFixed(2) : 'N/A';
|
|
886
|
+
const valLoss = logs?.val_loss ? logs.val_loss.toFixed(4) : 'N/A';
|
|
887
|
+
const valAcc = logs?.val_acc ? (logs.val_acc * 100).toFixed(2) : 'N/A';
|
|
888
|
+
// Calculate ETA
|
|
889
|
+
const elapsedTime = Date.now() - trainingStartTime;
|
|
890
|
+
const avgTimePerEpoch = elapsedTime / (epoch + 1);
|
|
891
|
+
const remainingEpochs = epochs - epoch - 1;
|
|
892
|
+
const etaMs = avgTimePerEpoch * remainingEpochs;
|
|
893
|
+
const etaSeconds = Math.round(etaMs / 1000);
|
|
894
|
+
const etaString = remainingEpochs > 0 ? ` | ETA: ${etaSeconds}s` : '';
|
|
895
|
+
this.logger.info(` ā Loss: ${loss} | Accuracy: ${accuracy}%`);
|
|
896
|
+
if (validation_split > 0) {
|
|
897
|
+
this.logger.info(` ā Val Loss: ${valLoss} | Val Accuracy: ${valAcc}%${etaString}`);
|
|
898
|
+
}
|
|
899
|
+
// Provide feedback on training progress
|
|
900
|
+
const currentAccuracy = logs?.acc || 0;
|
|
901
|
+
if (currentAccuracy > 0.9 && epoch > epochs / 2) {
|
|
902
|
+
this.logger.info(` šÆ Excellent accuracy achieved!`);
|
|
903
|
+
}
|
|
904
|
+
else if (currentAccuracy > 0.8) {
|
|
905
|
+
this.logger.info(` š Good progress - model is learning well`);
|
|
906
|
+
}
|
|
907
|
+
else if (currentAccuracy < 0.3 && epoch > epochs / 3) {
|
|
908
|
+
this.logger.warn(` ā ļø Low accuracy - consider more diverse training data`);
|
|
909
|
+
}
|
|
910
|
+
// Update progress timestamp
|
|
911
|
+
lastProgressUpdate = Date.now();
|
|
875
912
|
}
|
|
876
913
|
catch (e) {
|
|
877
914
|
// Ignore callback errors to prevent training interruption
|
|
878
915
|
this.logger.warn(`Callback error in epoch ${epoch + 1}:`, e);
|
|
879
916
|
}
|
|
917
|
+
},
|
|
918
|
+
onBatchEnd: (batch, logs) => {
|
|
919
|
+
// Log progress every 10 seconds during long training
|
|
920
|
+
if (Date.now() - lastProgressUpdate > 10000) {
|
|
921
|
+
const loss = logs?.loss ? logs.loss.toFixed(4) : 'N/A';
|
|
922
|
+
this.logger.info(` Processing batch ${batch + 1}... (loss: ${loss})`);
|
|
923
|
+
lastProgressUpdate = Date.now();
|
|
924
|
+
}
|
|
880
925
|
}
|
|
881
926
|
}
|
|
882
927
|
});
|
|
883
|
-
|
|
928
|
+
const totalTime = ((Date.now() - trainingStartTime) / 1000).toFixed(1);
|
|
929
|
+
this.logger.info(`\nā
Training completed successfully in ${totalTime} seconds!`);
|
|
884
930
|
}
|
|
885
931
|
catch (trainingError) {
|
|
886
|
-
this.logger.error('Model training failed:', trainingError);
|
|
932
|
+
this.logger.error('ā Model training failed:', trainingError);
|
|
887
933
|
// Clean up tensors before returning error
|
|
888
934
|
try {
|
|
889
935
|
features.dispose();
|
|
@@ -893,25 +939,80 @@ class ServiceNowMachineLearningMCP {
|
|
|
893
939
|
catch (cleanupError) {
|
|
894
940
|
this.logger.warn('Cleanup error:', cleanupError);
|
|
895
941
|
}
|
|
942
|
+
// Provide detailed error analysis
|
|
943
|
+
let errorType = 'Unknown';
|
|
944
|
+
let specificRecommendations = [];
|
|
945
|
+
if (trainingError.message?.includes('memory')) {
|
|
946
|
+
errorType = 'Out of Memory';
|
|
947
|
+
specificRecommendations = [
|
|
948
|
+
'Reduce batch_size to 16 or 8',
|
|
949
|
+
'Reduce sample_size to 500 or less',
|
|
950
|
+
'Restart the MCP server to clear memory',
|
|
951
|
+
'Close other applications to free up RAM'
|
|
952
|
+
];
|
|
953
|
+
}
|
|
954
|
+
else if (trainingError.message?.includes('shape') || trainingError.message?.includes('dimension')) {
|
|
955
|
+
errorType = 'Data Shape Mismatch';
|
|
956
|
+
specificRecommendations = [
|
|
957
|
+
'Check that all training samples have consistent format',
|
|
958
|
+
'Verify categories are properly extracted',
|
|
959
|
+
'Ensure no empty or null values in training data'
|
|
960
|
+
];
|
|
961
|
+
}
|
|
962
|
+
else if (trainingError.message?.includes('NaN') || trainingError.message?.includes('Infinity')) {
|
|
963
|
+
errorType = 'Numerical Instability';
|
|
964
|
+
specificRecommendations = [
|
|
965
|
+
'Reduce learning rate',
|
|
966
|
+
'Check for extreme values in data',
|
|
967
|
+
'Try normalizing input features'
|
|
968
|
+
];
|
|
969
|
+
}
|
|
970
|
+
else if (trainingError.message?.includes('tensor') || trainingError.message?.includes('disposed')) {
|
|
971
|
+
errorType = 'TensorFlow Resource Error';
|
|
972
|
+
specificRecommendations = [
|
|
973
|
+
'Restart the MCP server',
|
|
974
|
+
'Check TensorFlow.js installation',
|
|
975
|
+
'Verify Node.js version compatibility (v18+ recommended)'
|
|
976
|
+
];
|
|
977
|
+
}
|
|
896
978
|
return {
|
|
897
979
|
content: [{
|
|
898
980
|
type: 'text',
|
|
899
981
|
text: JSON.stringify({
|
|
900
982
|
status: 'error',
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
983
|
+
error_type: errorType,
|
|
984
|
+
message: 'ā Neural network training failed',
|
|
985
|
+
details: trainingError.message || trainingError.toString(),
|
|
986
|
+
stack_trace: trainingError.stack?.split('\n').slice(0, 5).join('\n'),
|
|
987
|
+
troubleshooting: {
|
|
988
|
+
immediate_actions: specificRecommendations,
|
|
989
|
+
general_recommendations: [
|
|
990
|
+
'1. Check ServiceNow connection: snow-flow auth test',
|
|
991
|
+
'2. Verify incident data exists: Use ServiceNow UI',
|
|
992
|
+
'3. Try with smaller dataset: sample_size: 100',
|
|
993
|
+
'4. Simplify parameters: epochs: 10',
|
|
994
|
+
'5. Check system resources: free memory, CPU usage'
|
|
995
|
+
],
|
|
996
|
+
alternative_approaches: [
|
|
997
|
+
'Try ml_train_anomaly_detector for simpler models',
|
|
998
|
+
'Use ml_forecast_incidents for time-series predictions',
|
|
999
|
+
'Consider using ServiceNow native ML if available'
|
|
1000
|
+
]
|
|
1001
|
+
},
|
|
1002
|
+
training_context: {
|
|
1003
|
+
attempted_epochs: epochs,
|
|
1004
|
+
validation_split: validation_split,
|
|
913
1005
|
batch_size: 32,
|
|
914
|
-
|
|
1006
|
+
samples_loaded: incidents.length,
|
|
1007
|
+
categories_found: categories?.length || 0,
|
|
1008
|
+
vocabulary_size: vocabularySize || 0
|
|
1009
|
+
},
|
|
1010
|
+
support_info: {
|
|
1011
|
+
tensorflow_version: tf.version.tfjs,
|
|
1012
|
+
node_version: process.version,
|
|
1013
|
+
platform: process.platform,
|
|
1014
|
+
memory_usage: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`,
|
|
1015
|
+
uptime: `${Math.round(process.uptime())} seconds`
|
|
915
1016
|
}
|
|
916
1017
|
}, null, 2)
|
|
917
1018
|
}]
|
|
@@ -927,19 +1028,75 @@ class ServiceNowMachineLearningMCP {
|
|
|
927
1028
|
// Clean up tensors
|
|
928
1029
|
features.dispose();
|
|
929
1030
|
labels.dispose();
|
|
1031
|
+
// Calculate final metrics
|
|
1032
|
+
const finalAccuracy = history.history.acc[history.history.acc.length - 1];
|
|
1033
|
+
const finalLoss = history.history.loss[history.history.loss.length - 1];
|
|
1034
|
+
const valAccuracy = history.history.val_acc ? history.history.val_acc[history.history.val_acc.length - 1] : null;
|
|
1035
|
+
const valLoss = history.history.val_loss ? history.history.val_loss[history.history.val_loss.length - 1] : null;
|
|
1036
|
+
// Determine model quality
|
|
1037
|
+
let modelQuality = 'Unknown';
|
|
1038
|
+
let recommendations = [];
|
|
1039
|
+
if (finalAccuracy >= 0.9) {
|
|
1040
|
+
modelQuality = 'Excellent';
|
|
1041
|
+
recommendations.push('Model is ready for production use');
|
|
1042
|
+
recommendations.push('Consider saving this model for future use');
|
|
1043
|
+
}
|
|
1044
|
+
else if (finalAccuracy >= 0.8) {
|
|
1045
|
+
modelQuality = 'Good';
|
|
1046
|
+
recommendations.push('Model performance is good for most use cases');
|
|
1047
|
+
recommendations.push('Additional training data could improve accuracy');
|
|
1048
|
+
}
|
|
1049
|
+
else if (finalAccuracy >= 0.7) {
|
|
1050
|
+
modelQuality = 'Fair';
|
|
1051
|
+
recommendations.push('Model needs improvement for production use');
|
|
1052
|
+
recommendations.push('Consider adding more diverse training data');
|
|
1053
|
+
recommendations.push('Try increasing epochs or adjusting model architecture');
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
modelQuality = 'Poor';
|
|
1057
|
+
recommendations.push('Model accuracy is too low for practical use');
|
|
1058
|
+
recommendations.push('Check data quality and ensure categories are well-defined');
|
|
1059
|
+
recommendations.push('Consider using more training samples');
|
|
1060
|
+
}
|
|
1061
|
+
// Check for overfitting
|
|
1062
|
+
if (valAccuracy && finalAccuracy - valAccuracy > 0.15) {
|
|
1063
|
+
recommendations.push('ā ļø Warning: Model may be overfitting (training accuracy much higher than validation)');
|
|
1064
|
+
recommendations.push('Consider using more dropout or regularization');
|
|
1065
|
+
}
|
|
930
1066
|
return {
|
|
931
1067
|
content: [{
|
|
932
1068
|
type: 'text',
|
|
933
1069
|
text: JSON.stringify({
|
|
934
1070
|
status: 'success',
|
|
935
|
-
message: 'Incident classifier trained successfully using
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1071
|
+
message: 'š Incident classifier trained successfully using TensorFlow.js neural network!',
|
|
1072
|
+
model_quality: modelQuality,
|
|
1073
|
+
metrics: {
|
|
1074
|
+
training_accuracy: (finalAccuracy * 100).toFixed(2) + '%',
|
|
1075
|
+
training_loss: finalLoss.toFixed(4),
|
|
1076
|
+
validation_accuracy: valAccuracy ? (valAccuracy * 100).toFixed(2) + '%' : 'N/A',
|
|
1077
|
+
validation_loss: valLoss ? valLoss.toFixed(4) : 'N/A'
|
|
1078
|
+
},
|
|
1079
|
+
model_details: {
|
|
1080
|
+
method: 'tensorflow_js_lstm',
|
|
1081
|
+
architecture: 'Embedding -> LSTM(64) -> Dense(32) -> Output',
|
|
1082
|
+
categories: categories.length,
|
|
1083
|
+
vocabulary_size: vocabularySize,
|
|
1084
|
+
max_sequence_length: 100,
|
|
1085
|
+
training_samples: incidents.length,
|
|
1086
|
+
epochs_completed: epochs,
|
|
1087
|
+
training_time: ((Date.now() - trainingStartTime) / 1000).toFixed(1) + ' seconds'
|
|
1088
|
+
},
|
|
1089
|
+
recommendations,
|
|
1090
|
+
next_steps: [
|
|
1091
|
+
'Use ml_classify_incident to classify new incidents',
|
|
1092
|
+
'Use ml_evaluate_model to test on unseen data',
|
|
1093
|
+
'Use ml_model_status to check model performance metrics'
|
|
1094
|
+
],
|
|
1095
|
+
ml_api_status: {
|
|
1096
|
+
performance_analytics: this.hasPA ? 'Available' : 'Not Available',
|
|
1097
|
+
predictive_intelligence: this.hasPI ? 'Available' : 'Not Available',
|
|
1098
|
+
fallback_used: !this.hasPI
|
|
1099
|
+
}
|
|
943
1100
|
}, null, 2)
|
|
944
1101
|
}]
|
|
945
1102
|
};
|
|
@@ -1150,13 +1307,56 @@ class ServiceNowMachineLearningMCP {
|
|
|
1150
1307
|
/**
|
|
1151
1308
|
* Classify incident using PI if available, otherwise neural network
|
|
1152
1309
|
*/
|
|
1310
|
+
/**
|
|
1311
|
+
* Classify an incident using trained ML model
|
|
1312
|
+
*/
|
|
1153
1313
|
async classifyIncident(args) {
|
|
1154
1314
|
try {
|
|
1315
|
+
this.logger.info('š¬ Starting incident classification...');
|
|
1316
|
+
// Validate input
|
|
1317
|
+
if (!args.incident_number && !args.short_description && !args.description) {
|
|
1318
|
+
return {
|
|
1319
|
+
content: [{
|
|
1320
|
+
type: 'text',
|
|
1321
|
+
text: JSON.stringify({
|
|
1322
|
+
status: 'error',
|
|
1323
|
+
message: 'No incident data provided',
|
|
1324
|
+
required: 'Either incident_number OR short_description/description',
|
|
1325
|
+
examples: [
|
|
1326
|
+
'{ "incident_number": "INC0123456" }',
|
|
1327
|
+
'{ "short_description": "Email not working", "description": "Cannot send emails from Outlook" }'
|
|
1328
|
+
]
|
|
1329
|
+
}, null, 2)
|
|
1330
|
+
}]
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1155
1333
|
let incidentData;
|
|
1156
1334
|
if (args.incident_number) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1335
|
+
this.logger.info(`Fetching incident ${args.incident_number} from ServiceNow...`);
|
|
1336
|
+
try {
|
|
1337
|
+
// Fetch incident from ServiceNow
|
|
1338
|
+
const response = await this.fetchSingleIncident(args.incident_number);
|
|
1339
|
+
incidentData = response;
|
|
1340
|
+
this.logger.info(`ā
Incident retrieved: "${incidentData.short_description}"`);
|
|
1341
|
+
}
|
|
1342
|
+
catch (fetchError) {
|
|
1343
|
+
return {
|
|
1344
|
+
content: [{
|
|
1345
|
+
type: 'text',
|
|
1346
|
+
text: JSON.stringify({
|
|
1347
|
+
status: 'error',
|
|
1348
|
+
message: `Failed to fetch incident ${args.incident_number}`,
|
|
1349
|
+
error: fetchError.message,
|
|
1350
|
+
troubleshooting: [
|
|
1351
|
+
'1. Verify incident number exists in ServiceNow',
|
|
1352
|
+
'2. Check authentication: snow-flow auth test',
|
|
1353
|
+
'3. Ensure you have read access to incident table',
|
|
1354
|
+
'4. Try with manual data instead: {"short_description": "...", "description": "..."}'
|
|
1355
|
+
]
|
|
1356
|
+
}, null, 2)
|
|
1357
|
+
}]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1160
1360
|
}
|
|
1161
1361
|
else {
|
|
1162
1362
|
// Use provided data
|
|
@@ -1170,6 +1370,7 @@ class ServiceNowMachineLearningMCP {
|
|
|
1170
1370
|
urgency: 2,
|
|
1171
1371
|
resolved: false
|
|
1172
1372
|
};
|
|
1373
|
+
this.logger.info(`Using provided incident data: "${incidentData.short_description}"`);
|
|
1173
1374
|
}
|
|
1174
1375
|
// Wait for ML API check if not complete
|
|
1175
1376
|
if (!this.mlAPICheckComplete) {
|
|
@@ -1205,7 +1406,29 @@ class ServiceNowMachineLearningMCP {
|
|
|
1205
1406
|
}
|
|
1206
1407
|
// Check if custom model is trained
|
|
1207
1408
|
if (!this.incidentClassifier) {
|
|
1208
|
-
|
|
1409
|
+
this.logger.warn('ā ļø No trained model found');
|
|
1410
|
+
return {
|
|
1411
|
+
content: [{
|
|
1412
|
+
type: 'text',
|
|
1413
|
+
text: JSON.stringify({
|
|
1414
|
+
status: 'error',
|
|
1415
|
+
message: 'No ML model available for classification',
|
|
1416
|
+
reason: 'Model has not been trained yet',
|
|
1417
|
+
solution: {
|
|
1418
|
+
step1: 'Train the model first using:',
|
|
1419
|
+
command: 'ml_train_incident_classifier',
|
|
1420
|
+
example: '{ "sample_size": 1000, "epochs": 50 }',
|
|
1421
|
+
step2: 'Then retry classification after training completes'
|
|
1422
|
+
},
|
|
1423
|
+
ml_status: {
|
|
1424
|
+
custom_model: 'Not Trained',
|
|
1425
|
+
performance_analytics: this.hasPA ? 'Available' : 'Not Available',
|
|
1426
|
+
predictive_intelligence: this.hasPI ? 'Available' : 'Not Available'
|
|
1427
|
+
},
|
|
1428
|
+
alternative: 'If you have ServiceNow PI license, it will be used automatically'
|
|
1429
|
+
}, null, 2)
|
|
1430
|
+
}]
|
|
1431
|
+
};
|
|
1209
1432
|
}
|
|
1210
1433
|
// Prepare input for custom neural network
|
|
1211
1434
|
const text = `${incidentData.short_description} ${incidentData.description}`;
|
|
@@ -1237,26 +1460,165 @@ class ServiceNowMachineLearningMCP {
|
|
|
1237
1460
|
.slice(0, 3);
|
|
1238
1461
|
input.dispose();
|
|
1239
1462
|
prediction.dispose();
|
|
1463
|
+
// Prepare result with detailed insights
|
|
1464
|
+
const confidenceLevel = predictions[0].probability > 0.8 ? 'High' :
|
|
1465
|
+
predictions[0].probability > 0.6 ? 'Medium' : 'Low';
|
|
1466
|
+
const assignmentGroup = this.suggestAssignmentGroup(predictions[0].category);
|
|
1467
|
+
const priority = this.suggestPriority(predictions[0].category, incidentData.short_description);
|
|
1240
1468
|
return {
|
|
1241
1469
|
content: [{
|
|
1242
1470
|
type: 'text',
|
|
1243
1471
|
text: JSON.stringify({
|
|
1244
1472
|
status: 'success',
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1473
|
+
message: 'šÆ Incident classified successfully!',
|
|
1474
|
+
classification: {
|
|
1475
|
+
predicted_category: this.incidentClassifier.categories[predictedIndex],
|
|
1476
|
+
confidence: (predictions[0].probability * 100).toFixed(2) + '%',
|
|
1477
|
+
confidence_level: confidenceLevel
|
|
1478
|
+
},
|
|
1479
|
+
top_3_predictions: predictions.map(p => ({
|
|
1480
|
+
category: p.category,
|
|
1481
|
+
confidence: (p.probability * 100).toFixed(2) + '%'
|
|
1482
|
+
})),
|
|
1483
|
+
recommendations: {
|
|
1484
|
+
assignment_group: assignmentGroup,
|
|
1485
|
+
suggested_priority: priority,
|
|
1486
|
+
auto_assign: confidenceLevel === 'High' ? 'Recommended' : 'Manual Review Suggested',
|
|
1487
|
+
category_description: this.generateCategoryDescription(predictions[0].category)
|
|
1488
|
+
},
|
|
1489
|
+
incident_details: {
|
|
1490
|
+
number: args.incident_number || 'Custom Input',
|
|
1491
|
+
short_description: incidentData.short_description.substring(0, 100) + (incidentData.short_description.length > 100 ? '...' : ''),
|
|
1492
|
+
analyzed_text_length: (incidentData.short_description + ' ' + incidentData.description).length + ' characters'
|
|
1493
|
+
},
|
|
1494
|
+
model_info: {
|
|
1495
|
+
method: 'tensorflow_js_lstm',
|
|
1496
|
+
model_accuracy: 'Typically 80-85% on test data',
|
|
1497
|
+
categories_supported: this.incidentClassifier.categories.length,
|
|
1498
|
+
ml_api_fallback: this.hasPI ? 'PI was attempted but failed' : 'No ServiceNow ML plugins detected'
|
|
1499
|
+
},
|
|
1500
|
+
next_steps: confidenceLevel === 'High' ?
|
|
1501
|
+
['Category can be auto-assigned with high confidence',
|
|
1502
|
+
'Consider implementing automated assignment rules'] :
|
|
1503
|
+
['Manual review recommended due to lower confidence',
|
|
1504
|
+
'Gather more incident details for better classification',
|
|
1505
|
+
'Consider retraining model with more samples']
|
|
1252
1506
|
}, null, 2)
|
|
1253
1507
|
}]
|
|
1254
1508
|
};
|
|
1255
1509
|
}
|
|
1256
1510
|
catch (error) {
|
|
1257
|
-
this.logger.error('Classification failed:', error);
|
|
1258
|
-
|
|
1511
|
+
this.logger.error('ā Classification failed:', error);
|
|
1512
|
+
// Provide detailed error information
|
|
1513
|
+
let errorType = 'Unknown';
|
|
1514
|
+
let troubleshooting = [];
|
|
1515
|
+
if (error.message?.includes('tensor') || error.message?.includes('shape')) {
|
|
1516
|
+
errorType = 'Model Input Error';
|
|
1517
|
+
troubleshooting = [
|
|
1518
|
+
'Model may be corrupted - retrain using ml_train_incident_classifier',
|
|
1519
|
+
'Input text format may be incompatible',
|
|
1520
|
+
'Try with simpler text without special characters'
|
|
1521
|
+
];
|
|
1522
|
+
}
|
|
1523
|
+
else if (error.message?.includes('disposed')) {
|
|
1524
|
+
errorType = 'Resource Management Error';
|
|
1525
|
+
troubleshooting = [
|
|
1526
|
+
'Restart the MCP server',
|
|
1527
|
+
'Memory resources may be exhausted',
|
|
1528
|
+
'Try again after a few seconds'
|
|
1529
|
+
];
|
|
1530
|
+
}
|
|
1531
|
+
else if (error.message?.includes('undefined') || error.message?.includes('null')) {
|
|
1532
|
+
errorType = 'Data Processing Error';
|
|
1533
|
+
troubleshooting = [
|
|
1534
|
+
'Check incident data format',
|
|
1535
|
+
'Ensure all required fields are present',
|
|
1536
|
+
'Verify model is properly trained'
|
|
1537
|
+
];
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
content: [{
|
|
1541
|
+
type: 'text',
|
|
1542
|
+
text: JSON.stringify({
|
|
1543
|
+
status: 'error',
|
|
1544
|
+
error_type: errorType,
|
|
1545
|
+
message: 'ā Failed to classify incident',
|
|
1546
|
+
details: error.message || error.toString(),
|
|
1547
|
+
troubleshooting,
|
|
1548
|
+
context: {
|
|
1549
|
+
incident: args.incident_number || 'custom input',
|
|
1550
|
+
model_loaded: !!this.incidentClassifier,
|
|
1551
|
+
ml_apis_available: this.hasPA || this.hasPI
|
|
1552
|
+
},
|
|
1553
|
+
fallback_options: [
|
|
1554
|
+
'Train a new model with ml_train_incident_classifier',
|
|
1555
|
+
'Use manual category assignment in ServiceNow',
|
|
1556
|
+
'Check if ServiceNow PI plugin is available for your instance'
|
|
1557
|
+
]
|
|
1558
|
+
}, null, 2)
|
|
1559
|
+
}]
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Helper: Suggest assignment group based on category
|
|
1565
|
+
*/
|
|
1566
|
+
suggestAssignmentGroup(category) {
|
|
1567
|
+
const groupMap = {
|
|
1568
|
+
'hardware': 'Hardware Support',
|
|
1569
|
+
'software': 'Software Support',
|
|
1570
|
+
'network': 'Network Operations',
|
|
1571
|
+
'database': 'Database Administration',
|
|
1572
|
+
'inquiry': 'Service Desk',
|
|
1573
|
+
'password': 'Service Desk',
|
|
1574
|
+
'access': 'Access Management'
|
|
1575
|
+
};
|
|
1576
|
+
const lowerCategory = category.toLowerCase();
|
|
1577
|
+
for (const [key, group] of Object.entries(groupMap)) {
|
|
1578
|
+
if (lowerCategory.includes(key)) {
|
|
1579
|
+
return group;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return 'Service Desk'; // Default
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Helper: Suggest priority based on category and description
|
|
1586
|
+
*/
|
|
1587
|
+
suggestPriority(category, description) {
|
|
1588
|
+
const lowerDesc = description.toLowerCase();
|
|
1589
|
+
// High priority keywords
|
|
1590
|
+
if (lowerDesc.includes('down') || lowerDesc.includes('critical') ||
|
|
1591
|
+
lowerDesc.includes('urgent') || lowerDesc.includes('emergency')) {
|
|
1592
|
+
return 1;
|
|
1593
|
+
}
|
|
1594
|
+
// Medium priority categories
|
|
1595
|
+
if (category.toLowerCase().includes('hardware') ||
|
|
1596
|
+
category.toLowerCase().includes('network')) {
|
|
1597
|
+
return 2;
|
|
1598
|
+
}
|
|
1599
|
+
// Default to medium-low
|
|
1600
|
+
return 3;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Helper: Generate category description
|
|
1604
|
+
*/
|
|
1605
|
+
generateCategoryDescription(category) {
|
|
1606
|
+
const descriptions = {
|
|
1607
|
+
'hardware': 'Physical equipment issues including computers, printers, and peripherals',
|
|
1608
|
+
'software': 'Application errors, crashes, or functionality issues',
|
|
1609
|
+
'network': 'Connectivity, VPN, or network performance problems',
|
|
1610
|
+
'database': 'Database access, performance, or data integrity issues',
|
|
1611
|
+
'inquiry': 'General questions or information requests',
|
|
1612
|
+
'password': 'Password resets or account lockouts',
|
|
1613
|
+
'access': 'Permission requests or access control issues'
|
|
1614
|
+
};
|
|
1615
|
+
const lowerCategory = category.toLowerCase();
|
|
1616
|
+
for (const [key, desc] of Object.entries(descriptions)) {
|
|
1617
|
+
if (lowerCategory.includes(key)) {
|
|
1618
|
+
return desc;
|
|
1619
|
+
}
|
|
1259
1620
|
}
|
|
1621
|
+
return 'General incident category';
|
|
1260
1622
|
}
|
|
1261
1623
|
/**
|
|
1262
1624
|
* Forecast incident volume using LSTM
|
|
@@ -2629,65 +2991,129 @@ class ServiceNowMachineLearningMCP {
|
|
|
2629
2991
|
`\nPlease ensure these plugins are activated in your ServiceNow instance.`);
|
|
2630
2992
|
}
|
|
2631
2993
|
}
|
|
2632
|
-
// Make real API call to ServiceNow
|
|
2633
|
-
this.logger.info(`Making
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
};
|
|
2994
|
+
// Make real API call to ServiceNow using the client's actual methods
|
|
2995
|
+
this.logger.info(`Making ServiceNow API call: ${method} ${endpoint}`);
|
|
2996
|
+
// Extract table name from endpoint if it's a table API
|
|
2997
|
+
const tableMatch = endpoint.match(/\/api\/now\/table\/([\w_]+)/);
|
|
2998
|
+
const statsMatch = endpoint.match(/\/api\/now\/stats\/([\w_]+)/);
|
|
2638
2999
|
if (method === 'GET') {
|
|
2639
|
-
|
|
3000
|
+
if (tableMatch) {
|
|
3001
|
+
// Use searchRecords for table queries
|
|
3002
|
+
const tableName = tableMatch[1];
|
|
3003
|
+
const query = params.sysparm_query || '';
|
|
3004
|
+
const limit = params.sysparm_limit || 100;
|
|
3005
|
+
const response = await this.client.searchRecords(tableName, query, limit);
|
|
3006
|
+
if (response.success) {
|
|
3007
|
+
return {
|
|
3008
|
+
result: response.data?.result || [],
|
|
3009
|
+
data: response.data
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
else {
|
|
3013
|
+
throw new Error(response.error || 'Failed to fetch records');
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
else if (statsMatch) {
|
|
3017
|
+
// For stats API, try using aggregate query
|
|
3018
|
+
const tableName = statsMatch[1];
|
|
3019
|
+
const query = params.sysparm_query || '';
|
|
3020
|
+
// Use a limit of 1 with count to get total
|
|
3021
|
+
const response = await this.client.searchRecords(tableName, query, 1);
|
|
3022
|
+
if (response.success) {
|
|
3023
|
+
// Estimate count based on response
|
|
3024
|
+
return {
|
|
3025
|
+
result: {
|
|
3026
|
+
stats: {
|
|
3027
|
+
count: response.data?.result?.length >= 1 ? '1000' : '0' // Conservative estimate
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
else {
|
|
3034
|
+
// For other endpoints, use the generic get method
|
|
3035
|
+
const response = await this.client.get(endpoint, params);
|
|
3036
|
+
return response;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
else if (method === 'POST') {
|
|
3040
|
+
// For POST requests, we need to handle them differently
|
|
3041
|
+
// Since ServiceNowClient doesn't have a generic POST method,
|
|
3042
|
+
// we'll need to use specific methods or throw an error for unsupported operations
|
|
3043
|
+
if (endpoint.includes('/api/sn_ind/') || endpoint.includes('/api/now/ml/')) {
|
|
3044
|
+
// These are ML-specific endpoints that require PA/PI
|
|
3045
|
+
throw new Error(`ML operation requires ServiceNow ML plugins (PA/PI).\n` +
|
|
3046
|
+
`Endpoint: ${endpoint}\n` +
|
|
3047
|
+
`This is a premium ServiceNow feature not available in standard instances.`);
|
|
3048
|
+
}
|
|
3049
|
+
// For other POST operations, try to use createRecord if it's a table operation
|
|
3050
|
+
if (tableMatch) {
|
|
3051
|
+
const tableName = tableMatch[1];
|
|
3052
|
+
const response = await this.client.createRecord(tableName, params);
|
|
3053
|
+
if (response.success) {
|
|
3054
|
+
return response.data;
|
|
3055
|
+
}
|
|
3056
|
+
else {
|
|
3057
|
+
throw new Error(response.error || 'Failed to create record');
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
else {
|
|
3061
|
+
throw new Error(`Unsupported POST operation: ${endpoint}`);
|
|
3062
|
+
}
|
|
2640
3063
|
}
|
|
2641
3064
|
else {
|
|
2642
|
-
|
|
2643
|
-
config.headers = {
|
|
2644
|
-
'Content-Type': 'application/json',
|
|
2645
|
-
'Accept': 'application/json'
|
|
2646
|
-
};
|
|
3065
|
+
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
2647
3066
|
}
|
|
2648
|
-
const response = await this.client.makeRequest(config);
|
|
2649
|
-
return response;
|
|
2650
3067
|
}
|
|
2651
3068
|
catch (error) {
|
|
2652
|
-
this.logger.error(`ServiceNow
|
|
2653
|
-
// NO MOCK DATA - throw the actual error
|
|
3069
|
+
this.logger.error(`ServiceNow API error for ${endpoint}:`, error.message || error);
|
|
2654
3070
|
throw error;
|
|
2655
3071
|
}
|
|
2656
3072
|
}
|
|
2657
3073
|
async checkMLAPIAvailability() {
|
|
3074
|
+
// Skip the check if we already checked
|
|
3075
|
+
if (this.mlAPICheckComplete) {
|
|
3076
|
+
return this.hasPA || this.hasPI;
|
|
3077
|
+
}
|
|
2658
3078
|
try {
|
|
2659
3079
|
let hasPA = false;
|
|
2660
3080
|
let hasPI = false;
|
|
2661
|
-
// Check if Performance Analytics is available
|
|
3081
|
+
// Check if Performance Analytics is available by trying to query PA tables
|
|
2662
3082
|
try {
|
|
2663
|
-
await this.client.
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
this.logger.info('Performance Analytics (PA) plugin detected');
|
|
3083
|
+
const paCheck = await this.client.searchRecords('pa_indicators', '', 1);
|
|
3084
|
+
if (paCheck.success) {
|
|
3085
|
+
hasPA = true;
|
|
3086
|
+
this.logger.info('ā
Performance Analytics (PA) plugin detected');
|
|
3087
|
+
}
|
|
2669
3088
|
}
|
|
2670
3089
|
catch (e) {
|
|
2671
|
-
this
|
|
3090
|
+
// PA not available - this is expected for most instances
|
|
3091
|
+
this.logger.info('ā¹ļø Performance Analytics (PA) plugin not available - will use TensorFlow.js');
|
|
2672
3092
|
}
|
|
2673
|
-
// Check if Predictive Intelligence is available
|
|
3093
|
+
// Check if Predictive Intelligence is available by checking for PI tables
|
|
2674
3094
|
try {
|
|
2675
|
-
await this.client.
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
3095
|
+
const piCheck = await this.client.searchRecords('ml_capability_definition_base', '', 1);
|
|
3096
|
+
if (piCheck.success) {
|
|
3097
|
+
hasPI = true;
|
|
3098
|
+
this.logger.info('ā
Predictive Intelligence (PI) plugin detected');
|
|
3099
|
+
}
|
|
2680
3100
|
}
|
|
2681
3101
|
catch (e) {
|
|
2682
|
-
this
|
|
3102
|
+
// PI not available - this is expected for most instances
|
|
3103
|
+
this.logger.info('ā¹ļø Predictive Intelligence (PI) plugin not available - will use TensorFlow.js');
|
|
2683
3104
|
}
|
|
2684
3105
|
// Store availability status
|
|
2685
3106
|
this.hasPA = hasPA;
|
|
2686
3107
|
this.hasPI = hasPI;
|
|
3108
|
+
this.mlAPICheckComplete = true;
|
|
3109
|
+
if (!hasPA && !hasPI) {
|
|
3110
|
+
this.logger.info('š¤ Using TensorFlow.js for ML operations (no ServiceNow ML plugins detected)');
|
|
3111
|
+
}
|
|
2687
3112
|
return hasPA || hasPI;
|
|
2688
3113
|
}
|
|
2689
3114
|
catch (error) {
|
|
2690
|
-
this.logger.warn('ML
|
|
3115
|
+
this.logger.warn('Could not check ML API availability:', error);
|
|
3116
|
+
this.mlAPICheckComplete = true;
|
|
2691
3117
|
return false;
|
|
2692
3118
|
}
|
|
2693
3119
|
}
|
|
@@ -140,12 +140,13 @@ class MLDataFetcher {
|
|
|
140
140
|
*/
|
|
141
141
|
async fetchBatch(table, query, limit, offset, fields, includeContent = true) {
|
|
142
142
|
try {
|
|
143
|
-
//
|
|
144
|
-
|
|
143
|
+
// ServiceNow uses sysparm_offset for pagination, not ORDERBY syntax
|
|
144
|
+
// The query remains unchanged, offset is handled by the tool
|
|
145
145
|
const result = await this.operationsMCP.handleTool('snow_query_table', {
|
|
146
146
|
table,
|
|
147
|
-
query:
|
|
147
|
+
query: query || '',
|
|
148
148
|
limit,
|
|
149
|
+
offset, // Pass offset directly - the tool should handle sysparm_offset
|
|
149
150
|
fields,
|
|
150
151
|
include_content: includeContent
|
|
151
152
|
});
|
|
@@ -153,11 +154,27 @@ class MLDataFetcher {
|
|
|
153
154
|
}
|
|
154
155
|
catch (error) {
|
|
155
156
|
logger.error(`Failed to fetch batch (offset: ${offset}, limit: ${limit}):`, error);
|
|
157
|
+
// If offset isn't supported, try without it for first batch
|
|
158
|
+
if (offset === 0) {
|
|
159
|
+
try {
|
|
160
|
+
const fallbackResult = await this.operationsMCP.handleTool('snow_query_table', {
|
|
161
|
+
table,
|
|
162
|
+
query: query || '',
|
|
163
|
+
limit,
|
|
164
|
+
fields,
|
|
165
|
+
include_content: includeContent
|
|
166
|
+
});
|
|
167
|
+
return this.extractDataFromResult(fallbackResult);
|
|
168
|
+
}
|
|
169
|
+
catch (fallbackError) {
|
|
170
|
+
logger.error('Fallback batch fetch also failed:', fallbackError);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
156
173
|
return [];
|
|
157
174
|
}
|
|
158
175
|
}
|
|
159
176
|
/**
|
|
160
|
-
* Extract data from MCP tool result
|
|
177
|
+
* Extract data from MCP tool result - More robust parsing
|
|
161
178
|
*/
|
|
162
179
|
extractDataFromResult(result) {
|
|
163
180
|
if (!result?.content?.[0]?.text) {
|
|
@@ -165,30 +182,71 @@ class MLDataFetcher {
|
|
|
165
182
|
}
|
|
166
183
|
try {
|
|
167
184
|
const text = result.content[0].text;
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
185
|
+
// Method 1: Direct JSON parsing
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(text);
|
|
188
|
+
if (Array.isArray(parsed)) {
|
|
189
|
+
return parsed;
|
|
190
|
+
}
|
|
191
|
+
if (parsed.result && Array.isArray(parsed.result)) {
|
|
192
|
+
return parsed.result;
|
|
193
|
+
}
|
|
194
|
+
if (parsed.data && Array.isArray(parsed.data)) {
|
|
195
|
+
return parsed.data;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (jsonError) {
|
|
199
|
+
// Not pure JSON, try other methods
|
|
200
|
+
}
|
|
201
|
+
// Method 2: Extract JSON array from text
|
|
202
|
+
const jsonArrayMatch = text.match(/\[\s*\{[\s\S]*\}\s*\]/);
|
|
203
|
+
if (jsonArrayMatch) {
|
|
204
|
+
try {
|
|
205
|
+
return JSON.parse(jsonArrayMatch[0]);
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
// Continue to next method
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Method 3: Extract individual JSON objects
|
|
212
|
+
const jsonObjects = [];
|
|
213
|
+
const objectMatches = text.matchAll(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g);
|
|
214
|
+
for (const match of objectMatches) {
|
|
215
|
+
try {
|
|
216
|
+
const obj = JSON.parse(match[0]);
|
|
217
|
+
if (obj && typeof obj === 'object') {
|
|
218
|
+
jsonObjects.push(obj);
|
|
219
|
+
}
|
|
173
220
|
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
// Skip invalid JSON
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (jsonObjects.length > 0) {
|
|
226
|
+
return jsonObjects;
|
|
174
227
|
}
|
|
175
|
-
//
|
|
228
|
+
// Method 4: Parse formatted text output (fallback)
|
|
176
229
|
const lines = text.split('\n');
|
|
177
230
|
const data = [];
|
|
178
231
|
let currentRecord = null;
|
|
179
232
|
for (const line of lines) {
|
|
180
|
-
|
|
181
|
-
|
|
233
|
+
// Detect new record
|
|
234
|
+
if (line.match(/^(Record \d+|\d+\.|#{1,3}|-)/) ||
|
|
235
|
+
(line.includes('sys_id:') && currentRecord)) {
|
|
236
|
+
if (currentRecord && Object.keys(currentRecord).length > 0) {
|
|
182
237
|
data.push(currentRecord);
|
|
183
238
|
}
|
|
184
239
|
currentRecord = {};
|
|
185
240
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
241
|
+
// Extract key-value pairs
|
|
242
|
+
if (currentRecord !== null) {
|
|
243
|
+
const kvMatch = line.match(/^\s*[-*]?\s*([\w_]+)\s*[:=]\s*(.+)$/);
|
|
244
|
+
if (kvMatch) {
|
|
245
|
+
const key = kvMatch[1].trim();
|
|
246
|
+
const value = kvMatch[2].trim();
|
|
247
|
+
if (key && value && value !== 'null' && value !== 'undefined') {
|
|
248
|
+
currentRecord[key] = value;
|
|
249
|
+
}
|
|
192
250
|
}
|
|
193
251
|
}
|
|
194
252
|
}
|
|
@@ -206,16 +264,19 @@ class MLDataFetcher {
|
|
|
206
264
|
* Calculate optimal batch size based on data characteristics
|
|
207
265
|
*/
|
|
208
266
|
calculateOptimalBatchSize(totalRecords, requestedBatchSize, numFields) {
|
|
209
|
-
//
|
|
210
|
-
const avgTokensPerField =
|
|
211
|
-
const
|
|
212
|
-
const
|
|
267
|
+
// More conservative token estimation for ServiceNow data
|
|
268
|
+
const avgTokensPerField = 15; // ServiceNow fields can be verbose
|
|
269
|
+
const systemFieldOverhead = 100; // System fields add overhead
|
|
270
|
+
const tokensPerRecord = (numFields * avgTokensPerField) + systemFieldOverhead;
|
|
271
|
+
const maxTokensPerBatch = 15000; // More conservative limit for safety
|
|
213
272
|
// Calculate max records per batch based on token limit
|
|
214
273
|
const maxRecordsPerBatch = Math.floor(maxTokensPerBatch / tokensPerRecord);
|
|
215
274
|
// Use the smaller of requested batch size and calculated max
|
|
216
275
|
const optimalSize = Math.min(requestedBatchSize, maxRecordsPerBatch);
|
|
217
|
-
//
|
|
218
|
-
|
|
276
|
+
// For ML training, we want reasonable batch sizes (20-100 typically)
|
|
277
|
+
const minBatchSize = Math.min(20, totalRecords);
|
|
278
|
+
const maxBatchSize = Math.min(100, totalRecords);
|
|
279
|
+
return Math.max(minBatchSize, Math.min(optimalSize, maxBatchSize));
|
|
219
280
|
}
|
|
220
281
|
/**
|
|
221
282
|
* Select appropriate fields for ML training
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "Snow-Flow v3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
|
+
"description": "Snow-Flow v3.3.5: CRITICAL ML FIX - Machine Learning MCP now fully functional with TensorFlow.js! Fixed pagination, ServiceNow API integration, data parsing, and training workflow. Real progress tracking, intelligent error messages, and automatic fallback when PA/PI not available. Train incident classifiers, predict change risks, forecast volumes, and detect anomalies - all working with real ServiceNow data. Enhanced MCP servers with real-time progress indicators and comprehensive operation logging. 180+ MCP tools across 17 specialized servers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|