mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-08 06:13:07 +00:00
fix: improve node type suggestions for all test cases
- Enhanced substring matching for short search terms (http, sheet) - Boosted pattern match scores for short searches (45 points) - Added name similarity boost for substring matches - Fixed cross-package suggestions (nodes-base.openai → nodes-langchain.openAi) - Increased confidence for deprecated package prefixes to 95% - Added debug and test summary scripts All 16 test cases now pass with 100% accuracy: ✅ Case variations (HttpRequest, Webhook, etc.) - 95% confidence ✅ Missing prefixes (slack, googleSheets, etc.) - 90% confidence ✅ Common typos (htpRequest, webook, etc.) - 80% confidence ✅ Short partials (http, sheet) - 52-60% confidence ✅ Cross-package (nodes-base.openai) - 90% confidence ✅ Deprecated prefixes (n8n-nodes-base) - 95% confidence
This commit is contained in:
77
src/scripts/debug-http-search.ts
Normal file
77
src/scripts/debug-http-search.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
import { createDatabaseAdapter } from '../database/database-adapter';
|
||||||
|
import { NodeRepository } from '../database/node-repository';
|
||||||
|
import { NodeSimilarityService } from '../services/node-similarity-service';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
async function debugHttpSearch() {
|
||||||
|
const dbPath = path.join(process.cwd(), 'data/nodes.db');
|
||||||
|
const db = await createDatabaseAdapter(dbPath);
|
||||||
|
const repository = new NodeRepository(db);
|
||||||
|
const service = new NodeSimilarityService(repository);
|
||||||
|
|
||||||
|
console.log('Testing "http" search...\n');
|
||||||
|
|
||||||
|
// Check if httpRequest exists
|
||||||
|
const httpNode = repository.getNode('nodes-base.httpRequest');
|
||||||
|
console.log('HTTP Request node exists:', httpNode ? 'Yes' : 'No');
|
||||||
|
if (httpNode) {
|
||||||
|
console.log(' Display name:', httpNode.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the search with internal details
|
||||||
|
const suggestions = await service.findSimilarNodes('http', 5);
|
||||||
|
console.log('\nSuggestions for "http":', suggestions.length);
|
||||||
|
suggestions.forEach(s => {
|
||||||
|
console.log(` - ${s.nodeType} (${Math.round(s.confidence * 100)}%)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually calculate score for httpRequest
|
||||||
|
console.log('\nManual score calculation for httpRequest:');
|
||||||
|
const testNode = {
|
||||||
|
nodeType: 'nodes-base.httpRequest',
|
||||||
|
displayName: 'HTTP Request',
|
||||||
|
category: 'Core Nodes'
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanInvalid = 'http';
|
||||||
|
const cleanValid = 'nodesbasehttprequest';
|
||||||
|
const displayNameClean = 'httprequest';
|
||||||
|
|
||||||
|
// Check substring
|
||||||
|
const hasSubstring = cleanValid.includes(cleanInvalid) || displayNameClean.includes(cleanInvalid);
|
||||||
|
console.log(` Substring match: ${hasSubstring}`);
|
||||||
|
|
||||||
|
// This should give us pattern match score
|
||||||
|
const patternScore = hasSubstring ? 35 : 0; // Using 35 for short searches
|
||||||
|
console.log(` Pattern score: ${patternScore}`);
|
||||||
|
|
||||||
|
// Name similarity would be low
|
||||||
|
console.log(` Total score would need to be >= 50 to appear`);
|
||||||
|
|
||||||
|
// Get all nodes and check which ones contain 'http'
|
||||||
|
const allNodes = repository.getAllNodes();
|
||||||
|
const httpNodes = allNodes.filter(n =>
|
||||||
|
n.nodeType.toLowerCase().includes('http') ||
|
||||||
|
(n.displayName && n.displayName.toLowerCase().includes('http'))
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('\n\nNodes containing "http" in name:');
|
||||||
|
httpNodes.slice(0, 5).forEach(n => {
|
||||||
|
console.log(` - ${n.nodeType} (${n.displayName})`);
|
||||||
|
|
||||||
|
// Calculate score for this node
|
||||||
|
const normalizedSearch = 'http';
|
||||||
|
const normalizedType = n.nodeType.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
const normalizedDisplay = (n.displayName || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
|
||||||
|
const containsInType = normalizedType.includes(normalizedSearch);
|
||||||
|
const containsInDisplay = normalizedDisplay.includes(normalizedSearch);
|
||||||
|
|
||||||
|
console.log(` Type check: "${normalizedType}" includes "${normalizedSearch}" = ${containsInType}`);
|
||||||
|
console.log(` Display check: "${normalizedDisplay}" includes "${normalizedSearch}" = ${containsInDisplay}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debugHttpSearch().catch(console.error);
|
||||||
81
src/scripts/test-summary.ts
Normal file
81
src/scripts/test-summary.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
import { createDatabaseAdapter } from '../database/database-adapter';
|
||||||
|
import { NodeRepository } from '../database/node-repository';
|
||||||
|
import { NodeSimilarityService } from '../services/node-similarity-service';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
async function testSummary() {
|
||||||
|
const dbPath = path.join(process.cwd(), 'data/nodes.db');
|
||||||
|
const db = await createDatabaseAdapter(dbPath);
|
||||||
|
const repository = new NodeRepository(db);
|
||||||
|
const similarityService = new NodeSimilarityService(repository);
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{ invalid: 'HttpRequest', expected: 'nodes-base.httpRequest' },
|
||||||
|
{ invalid: 'HTTPRequest', expected: 'nodes-base.httpRequest' },
|
||||||
|
{ invalid: 'Webhook', expected: 'nodes-base.webhook' },
|
||||||
|
{ invalid: 'WebHook', expected: 'nodes-base.webhook' },
|
||||||
|
{ invalid: 'slack', expected: 'nodes-base.slack' },
|
||||||
|
{ invalid: 'googleSheets', expected: 'nodes-base.googleSheets' },
|
||||||
|
{ invalid: 'telegram', expected: 'nodes-base.telegram' },
|
||||||
|
{ invalid: 'htpRequest', expected: 'nodes-base.httpRequest' },
|
||||||
|
{ invalid: 'webook', expected: 'nodes-base.webhook' },
|
||||||
|
{ invalid: 'slak', expected: 'nodes-base.slack' },
|
||||||
|
{ invalid: 'http', expected: 'nodes-base.httpRequest' },
|
||||||
|
{ invalid: 'sheet', expected: 'nodes-base.googleSheets' },
|
||||||
|
{ invalid: 'nodes-base.openai', expected: 'nodes-langchain.openAi' },
|
||||||
|
{ invalid: 'n8n-nodes-base.httpRequest', expected: 'nodes-base.httpRequest' },
|
||||||
|
{ invalid: 'foobar', expected: null },
|
||||||
|
{ invalid: 'xyz123', expected: null },
|
||||||
|
];
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
console.log('Test Results Summary:');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
const suggestions = await similarityService.findSimilarNodes(testCase.invalid, 3);
|
||||||
|
|
||||||
|
let result = '❌';
|
||||||
|
let status = 'FAILED';
|
||||||
|
|
||||||
|
if (testCase.expected === null) {
|
||||||
|
// Should have no suggestions
|
||||||
|
if (suggestions.length === 0) {
|
||||||
|
result = '✅';
|
||||||
|
status = 'PASSED';
|
||||||
|
passed++;
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Should have the expected suggestion
|
||||||
|
const found = suggestions.some(s => s.nodeType === testCase.expected);
|
||||||
|
if (found) {
|
||||||
|
const suggestion = suggestions.find(s => s.nodeType === testCase.expected);
|
||||||
|
const isAutoFixable = suggestion && suggestion.confidence >= 0.9;
|
||||||
|
result = '✅';
|
||||||
|
status = isAutoFixable ? 'PASSED (auto-fixable)' : 'PASSED';
|
||||||
|
passed++;
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${result} "${testCase.invalid}" → ${testCase.expected || 'no suggestions'}: ${status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log(`\nTotal: ${passed}/${testCases.length} tests passed`);
|
||||||
|
|
||||||
|
if (failed === 0) {
|
||||||
|
console.log('🎉 All tests passed!');
|
||||||
|
} else {
|
||||||
|
console.log(`⚠️ ${failed} tests failed`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testSummary().catch(console.error);
|
||||||
@@ -62,8 +62,8 @@ export class NodeSimilarityService {
|
|||||||
|
|
||||||
// Old versions or deprecated names
|
// Old versions or deprecated names
|
||||||
patterns.set('deprecated', [
|
patterns.set('deprecated', [
|
||||||
{ pattern: /^n8n-nodes-base\./i, suggestion: '', confidence: 0.85, reason: 'Full package name used instead of short form' },
|
{ pattern: /^n8n-nodes-base\./i, suggestion: '', confidence: 0.95, reason: 'Full package name used instead of short form' },
|
||||||
{ pattern: /^@n8n\/n8n-nodes-langchain\./i, suggestion: '', confidence: 0.85, reason: 'Full package name used instead of short form' },
|
{ pattern: /^@n8n\/n8n-nodes-langchain\./i, suggestion: '', confidence: 0.95, reason: 'Full package name used instead of short form' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Common typos
|
// Common typos
|
||||||
@@ -78,6 +78,7 @@ export class NodeSimilarityService {
|
|||||||
// AI/LangChain specific
|
// AI/LangChain specific
|
||||||
patterns.set('ai_nodes', [
|
patterns.set('ai_nodes', [
|
||||||
{ pattern: /^openai$/i, suggestion: 'nodes-langchain.openAi', confidence: 0.85, reason: 'AI node - incorrect package' },
|
{ pattern: /^openai$/i, suggestion: 'nodes-langchain.openAi', confidence: 0.85, reason: 'AI node - incorrect package' },
|
||||||
|
{ pattern: /^nodes-base\.openai$/i, suggestion: 'nodes-langchain.openAi', confidence: 0.9, reason: 'Wrong package - OpenAI is in LangChain package' },
|
||||||
{ pattern: /^chatOpenAI$/i, suggestion: 'nodes-langchain.lmChatOpenAi', confidence: 0.85, reason: 'LangChain node naming convention' },
|
{ pattern: /^chatOpenAI$/i, suggestion: 'nodes-langchain.lmChatOpenAi', confidence: 0.85, reason: 'LangChain node naming convention' },
|
||||||
{ pattern: /^vectorStore$/i, suggestion: 'nodes-langchain.vectorStoreInMemory', confidence: 0.7, reason: 'Generic vector store reference' },
|
{ pattern: /^vectorStore$/i, suggestion: 'nodes-langchain.vectorStoreInMemory', confidence: 0.7, reason: 'Generic vector store reference' },
|
||||||
]);
|
]);
|
||||||
@@ -186,12 +187,20 @@ export class NodeSimilarityService {
|
|||||||
const cleanValid = this.normalizeNodeType(node.nodeType);
|
const cleanValid = this.normalizeNodeType(node.nodeType);
|
||||||
const displayNameClean = this.normalizeNodeType(node.displayName);
|
const displayNameClean = this.normalizeNodeType(node.displayName);
|
||||||
|
|
||||||
|
// Special handling for very short search terms (e.g., "http", "sheet")
|
||||||
|
const isShortSearch = invalidType.length <= 5;
|
||||||
|
|
||||||
// Name similarity (40% weight)
|
// Name similarity (40% weight)
|
||||||
const nameSimilarity = Math.max(
|
let nameSimilarity = Math.max(
|
||||||
this.getStringSimilarity(cleanInvalid, cleanValid),
|
this.getStringSimilarity(cleanInvalid, cleanValid),
|
||||||
this.getStringSimilarity(cleanInvalid, displayNameClean)
|
this.getStringSimilarity(cleanInvalid, displayNameClean)
|
||||||
) * 40;
|
) * 40;
|
||||||
|
|
||||||
|
// For short searches that are substrings, give a small name similarity boost
|
||||||
|
if (isShortSearch && (cleanValid.includes(cleanInvalid) || displayNameClean.includes(cleanInvalid))) {
|
||||||
|
nameSimilarity = Math.max(nameSimilarity, 10);
|
||||||
|
}
|
||||||
|
|
||||||
// Category match (20% weight)
|
// Category match (20% weight)
|
||||||
let categoryMatch = 0;
|
let categoryMatch = 0;
|
||||||
if (node.category) {
|
if (node.category) {
|
||||||
@@ -215,7 +224,9 @@ export class NodeSimilarityService {
|
|||||||
|
|
||||||
// Check if it's a substring match
|
// Check if it's a substring match
|
||||||
if (cleanValid.includes(cleanInvalid) || displayNameClean.includes(cleanInvalid)) {
|
if (cleanValid.includes(cleanInvalid) || displayNameClean.includes(cleanInvalid)) {
|
||||||
patternMatch = 25;
|
// Boost score significantly for short searches that are exact substring matches
|
||||||
|
// Short searches need more boost to reach the 50 threshold
|
||||||
|
patternMatch = isShortSearch ? 45 : 25;
|
||||||
} else if (this.getEditDistance(cleanInvalid, cleanValid) <= 2) {
|
} else if (this.getEditDistance(cleanInvalid, cleanValid) <= 2) {
|
||||||
// Small edit distance indicates likely typo
|
// Small edit distance indicates likely typo
|
||||||
patternMatch = 20;
|
patternMatch = 20;
|
||||||
@@ -223,6 +234,11 @@ export class NodeSimilarityService {
|
|||||||
patternMatch = 18;
|
patternMatch = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For very short searches, also check if the search term appears at the start
|
||||||
|
if (isShortSearch && (cleanValid.startsWith(cleanInvalid) || displayNameClean.startsWith(cleanInvalid))) {
|
||||||
|
patternMatch = Math.max(patternMatch, 40);
|
||||||
|
}
|
||||||
|
|
||||||
const totalScore = nameSimilarity + categoryMatch + packageMatch + patternMatch;
|
const totalScore = nameSimilarity + categoryMatch + packageMatch + patternMatch;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user