Skip to content

Latest commit

 

History

History
531 lines (432 loc) · 13.9 KB

File metadata and controls

531 lines (432 loc) · 13.9 KB
layout default
title n8n AI Tutorial - Chapter 7: Custom AI Tools
nav_order 7
has_children false
parent n8n AI Tutorial

Chapter 7: Building Custom AI Tools and Integrations

Welcome to Chapter 7: Building Custom AI Tools and Integrations. In this part of n8n AI Tutorial: Workflow Automation with AI, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.

Extend n8n's capabilities with custom AI tools, integrations, and specialized functions.

Custom Tool Types in n8n

flowchart TD
    CUSTOM[Custom AI Tools] --> HTTP[HTTP Request Tool\ncall any REST API]
    CUSTOM --> CODE[Code Tool\nrun JavaScript/Python]
    CUSTOM --> WF[n8n Workflow Tool\nexpose workflow as tool]
    CUSTOM --> MCP[MCP Tool Node\nModel Context Protocol servers]
    HTTP --> AGENT[AI Agent Node]
    CODE --> AGENT
    WF --> AGENT
    MCP --> AGENT
Loading

Custom Tool Development

HTTP Request Tools

{
  "parameters": {
    "name": "custom_api_tool",
    "description": "Call custom API for specialized processing",
    "parameters": {
      "endpoint": {
        "type": "string",
        "description": "API endpoint to call"
      },
      "data": {
        "type": "object",
        "description": "Request payload"
      }
    }
  },
  "name": "Custom API Tool",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "={{ $parameter.endpoint }}",
    "method": "POST",
    "bodyContentType": "json",
    "bodyParameters": {
      "parameters": [
        {
          "name": "payload",
          "value": "={{ $parameter.data }}"
        }
      ]
    }
  }
}

Database Integration Tools

{
  "parameters": {
    "name": "knowledge_search",
    "description": "Search internal knowledge base",
    "parameters": {
      "query": {
        "type": "string",
        "description": "Search query"
      },
      "limit": {
        "type": "number",
        "description": "Maximum results"
      }
    }
  },
  "name": "Knowledge Base Tool",
  "type": "n8n-nodes-base.postgres",
  "parameters": {
    "operation": "select",
    "query": "SELECT * FROM knowledge WHERE text ILIKE '%' || {{ $parameter.query }} || '%' LIMIT {{ $parameter.limit || 5 }}"
  },
  "credentials": {
    "postgres": "knowledge-db"
  }
}

JavaScript Function Tools

Custom Processing Functions

// Custom text analysis tool
const text = $input.item.json.input_text;

const analysis = {
  word_count: text.split(/\s+/).length,
  character_count: text.length,
  sentence_count: text.split(/[.!?]+/).length - 1,
  readability_score: calculateReadability(text),
  language: detectLanguage(text),
  sentiment: analyzeSentiment(text),
  keywords: extractKeywords(text, 5)
};

function calculateReadability(text) {
  // Simplified readability calculation
  const words = text.split(/\s+/).length;
  const sentences = text.split(/[.!?]+/).length;
  const avgWordsPerSentence = words / sentences;
  return Math.max(0, Math.min(100, 206.835 - 1.015 * avgWordsPerSentence));
}

function detectLanguage(text) {
  // Simple language detection
  if (text.includes('the ') && text.includes(' and ')) return 'en';
  if (text.includes('el ') && text.includes(' y ')) return 'es';
  if (text.includes('le ') && text.includes(' et ')) return 'fr';
  return 'unknown';
}

function analyzeSentiment(text) {
  const positiveWords = ['good', 'great', 'excellent', 'amazing', 'wonderful'];
  const negativeWords = ['bad', 'terrible', 'awful', 'horrible', 'poor'];

  const lowerText = text.toLowerCase();
  const positiveCount = positiveWords.filter(word => lowerText.includes(word)).length;
  const negativeCount = negativeWords.filter(word => lowerText.includes(word)).length;

  if (positiveCount > negativeCount) return 'positive';
  if (negativeCount > positiveCount) return 'negative';
  return 'neutral';
}

function extractKeywords(text, limit) {
  const words = text.toLowerCase().match(/\b\w{4,}\b/g) || [];
  const wordCount = {};

  words.forEach(word => {
    wordCount[word] = (wordCount[word] || 0) + 1;
  });

  return Object.entries(wordCount)
    .sort(([,a], [,b]) => b - a)
    .slice(0, limit)
    .map(([word, count]) => ({ word, count }));
}

return [{
  json: {
    analysis: analysis,
    processed_at: new Date().toISOString()
  }
}];

AI-Enhanced Tools

// Tool that combines local processing with AI
const data = $input.item.json.raw_data;

// Step 1: Local preprocessing
const cleaned = data.trim().toLowerCase();

// Step 2: AI analysis (this would call an AI model)
const aiInsights = $node.openAi.default.sendMessage({
  model: 'gpt-3.5-turbo',
  messages: [{
    role: 'user',
    content: `Analyze this data and provide insights: ${cleaned}`
  }],
  max_tokens: 200
});

// Step 3: Combine results
return [{
  json: {
    original_data: data,
    processed_data: cleaned,
    ai_insights: aiInsights.choices[0].message.content,
    processing_steps: ['cleaning', 'ai_analysis'],
    confidence_score: 0.85
  }
}];

Custom Node Development

Basic Custom Node Template

// custom-node.ts
import { IExecuteFunctions } from 'n8n-workflow';

export class CustomAINode {
  async execute(this: IExecuteFunctions) {
    const items = this.getInputData();

    for (let i = 0; i < items.length; i++) {
      const input = items[i].json;

      // Custom logic here
      const result = {
        processed: true,
        input_length: JSON.stringify(input).length,
        timestamp: new Date().toISOString()
      };

      // Return processed data
      this.addOutputData(i, [{ json: result }]);
    }

    return this.prepareOutputData(items);
  }
}

Advanced Tool Orchestration

Tool Chain Pattern

{
  "nodes": [
    {
      "parameters": {
        "functionCode": "
// Step 1: Validate input
const query = $input.item.json.query;
if (!query || query.length < 3) {
  return [{ json: { error: 'Query too short', valid: false } }];
}

// Step 2: Preprocessing
const processedQuery = query.trim().toLowerCase();

// Step 3: Route to appropriate tool
const tool = processedQuery.includes('calculate') ? 'calculator' :
            processedQuery.includes('search') ? 'search' :
            'general_ai';

return [{
  json: {
    original_query: query,
    processed_query: processedQuery,
    selected_tool: tool,
    confidence: 0.9
  }
}];
"
      },
      "name": "Query Router",
      "type": "n8n-nodes-base.function"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.selected_tool }}",
              "operation": "equal",
              "value2": "calculator"
            }
          ]
        }
      },
      "name": "Calculator Route",
      "type": "n8n-nodes-base.if"
    },
    {
      "parameters": {
        "functionCode": "
// Calculator implementation
const expression = $input.item.json.processed_query;
const result = eval(expression.replace(/[^0-9+\-*/().\s]/g, ''));

return [{
  json: {
    tool: 'calculator',
    result: result,
    expression: expression
  }
}];
"
      },
      "name": "Calculator Tool",
      "type": "n8n-nodes-base.function"
    }
  ]
}

Integration Patterns

Webhook-Based Tools

{
  "parameters": {
    "httpMethod": "POST",
    "path": "ai-tool",
    "responseMode": "responseNode",
    "options": {}
  },
  "name": "AI Tool Webhook",
  "type": "n8n-nodes-base.webhook"
}

API-Based Integrations

// Custom API integration
const apiKey = $credentials.customApi.apiKey;
const endpoint = $input.item.json.endpoint;

const response = await $node.httpRequest.default.sendRequest({
  method: 'POST',
  url: endpoint,
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify($input.item.json.payload)
});

return [{
  json: {
    api_response: response,
    status_code: response.statusCode,
    processing_time: Date.now() - $input.item.json.start_time
  }
}];

Performance Optimization

Caching Custom Tools

// Tool with built-in caching
const cache = $workflow.expression.get('tool_cache') || {};
const cacheKey = JSON.stringify($input.item.json);

if (cache[cacheKey]) {
  return [cache[cacheKey]];
}

// Process normally
const result = performExpensiveOperation($input.item.json);

// Cache result
cache[cacheKey] = result;
$workflow.expression.set('tool_cache', cache);

return [result];

Batch Processing

{
  "parameters": {
    "batchSize": 10,
    "options": {
      "merge": false
    }
  },
  "name": "Batch Tool Processor",
  "type": "n8n-nodes-base.splitInBatches"
}

Monitoring and Debugging

Tool Performance Tracking

// Track tool usage and performance
const toolMetrics = $workflow.expression.get('tool_metrics') || {};

const toolName = $input.item.json.tool_name;
const startTime = $input.item.json.start_time || Date.now();
const endTime = Date.now();

if (!toolMetrics[toolName]) {
  toolMetrics[toolName] = {
    calls: 0,
    total_time: 0,
    avg_time: 0,
    errors: 0
  };
}

toolMetrics[toolName].calls += 1;
toolMetrics[toolName].total_time += (endTime - startTime);
toolMetrics[toolName].avg_time = toolMetrics[toolName].total_time / toolMetrics[toolName].calls;

if ($input.item.json.error) {
  toolMetrics[toolName].errors += 1;
}

$workflow.expression.set('tool_metrics', toolMetrics);

return [{
  json: {
    metrics: toolMetrics[toolName],
    tool_name: toolName
  }
}];

Security Considerations

Input Validation

// Secure input validation
const input = $input.item.json;

const MAX_LENGTH = 10000;
const ALLOWED_CHARS = /^[a-zA-Z0-9\s.,!?-]+$/;

if (typeof input !== 'object' || !input.text) {
  throw new Error('Invalid input format');
}

if (input.text.length > MAX_LENGTH) {
  throw new Error('Input too long');
}

if (!ALLOWED_CHARS.test(input.text)) {
  throw new Error('Invalid characters in input');
}

// Sanitize input
const sanitized = input.text.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');

return [{
  json: {
    sanitized_input: sanitized,
    validation_passed: true
  }
}];

Rate Limiting

{
  "parameters": {
    "mode": "queue",
    "batchSize": 1,
    "concurrency": 1,
    "options": {
      "reset": false
    }
  },
  "name": "Rate Limiter",
  "type": "n8n-nodes-base.splitInBatches"
}

Best Practices

  1. Modular Design: Build reusable tools with clear interfaces
  2. Error Handling: Implement comprehensive error handling and recovery
  3. Documentation: Document tool parameters and expected outputs
  4. Testing: Thoroughly test custom tools before production use
  5. Security: Validate inputs and implement appropriate security measures
  6. Performance: Monitor and optimize tool performance
  7. Versioning: Maintain version control for custom tools
  8. Monitoring: Track tool usage and success rates

Custom tools extend n8n's capabilities infinitely. The final chapter covers production deployment and scaling.

What Problem Does This Solve?

Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for text, json, input so behavior stays predictable as complexity grows.

In practical terms, this chapter helps you avoid three common failures:

  • coupling core logic too tightly to one implementation path
  • missing the handoff boundaries between setup, execution, and validation
  • shipping changes without clear rollback or observability strategy

After working through this chapter, you should be able to reason about Chapter 7: Building Custom AI Tools and Integrations as an operating subsystem inside n8n AI Tutorial: Workflow Automation with AI, with explicit contracts for inputs, state transitions, and outputs.

Use the implementation notes around parameters, item, name as your checklist when adapting these patterns to your own repository.

How it Works Under the Hood

Under the hood, Chapter 7: Building Custom AI Tools and Integrations usually follows a repeatable control path:

  1. Context bootstrap: initialize runtime config and prerequisites for text.
  2. Input normalization: shape incoming data so json receives stable contracts.
  3. Core execution: run the main logic branch and propagate intermediate state through input.
  4. Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
  5. Output composition: return canonical result payloads for downstream consumers.
  6. Operational telemetry: emit logs/metrics needed for debugging and performance tuning.

When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.

Source Walkthrough

Key source files in n8n-io/n8n:

Suggested trace: the ToolWorkflow node's supplyData() creates a LangChain DynamicTool that invokes another workflow via WorkflowRunner.run() when called.

Chapter Connections