🤖

自動化ワークフローの作成を自動化したい

に公開

はじめに

こんにちは、BTMの坂本です!

効率の悪い作業を見つけた時、自動化して効率よくしたい!って思うことありますよね。
でもいざやろうとしてみると
「API呼んで...」
「分岐作って...」
「エラー処理して...」
「うーん、なんか自動化するまでに手でやってること多いな...」
自動化するために手作業が発生する、こんなもどかしい経験あるんじゃないでしょうか?

今回はn8nを使って自動化ワークフローの作成を自動化するための方法を紹介していきたいと思います!

n8nとは?

ノードと呼ばれるものをつなげることでAIを組み込んだ自動化ワークフローが作成できるツールです。
詳細は公式サイトをご覧ください。
https://n8n.io/

開発アーキテクチャ

n8nはオープンソースなのでローカルでツールを使用することもできます。
今回はWSL上でDockerを使用してn8nをself-hostingしています。

self-hostingの方法はここでは割愛します。
詳しくは以下をご覧ください。
https://docs.n8n.io/hosting/installation/docker/#using-with-postgresql

ワークフロー概要

今回作成したワークフロー自動生成のワークフローの全体像です。

全体で15個のノードから構成され、以下の5つのフェーズに分かれています:

  1. 受付フェーズ: Webhook
  2. ノードタイプ読み込みフェーズ: Prepare Request → Load Available Nodes from File → Format Nodes for AI
  3. AI生成フェーズ: AI Agent → Structured Output Parser
  4. 正規化・検証フェーズ: Add Config → Normalize Nodes → Validate Node Types
  5. 登録フェーズ: Build Workflow Payload → Ensure OAuth Credentials → Call Create Workflow API
  6. レスポンスフェーズ Format API Result

Phase 1: 受付フェーズ

1-1. Webhook - User Request

役割: Webhookによるユーザーリクエストの受付

ノードタイプ: n8n-nodes-base.webhook

設定:

  • Path: /webhook/workflow-request
  • Method: POST
  • Response Mode: respondWith: "lastNode" (最終ノードの結果を返却)

リクエスト例(Gmailからメール取得してSlackへ通知):

curl -X POST http://localhost:5678/webhook/workflow-request \
  -H "Content-Type: application/json" \
  -d '{
    "userRequest": "Create a workflow to fetch Gmail and send to Slack"
  }'

出力データ構造:

{
  "headers": {
    "content-type": "application/json",
    "user-agent": "curl/7.81.0",
    ...
  },
  "params": {},
  "query": {},
  "body": {
    "userRequest": "Create a workflow to fetch Gmail and send to Slack"
  }
}

ポイント
ユーザーから「どんなワークフローを作成したいのか」のリクエストを受け付けます。


Phase 2: ノードタイプ読み込みフェーズ

2-1. Prepare Request

役割: Webhookデータを整形し、必要な設定情報を追加

ノードタイプ: n8n-nodes-base.code

実装コード:

// Webhookから受け取ったuserRequestを抽出
const userRequest = $json.body.userRequest;

if (!userRequest) {
  throw new Error('userRequest is required in the request body');
}

// n8n API接続情報を環境変数から取得
const baseUrl = 'http://localhost:5678';
const apiKey = process.env.N8N_API_KEY || '<your-api-key>';

console.log('\n=== Prepare Request ===');
console.log('User request:', userRequest);
console.log('Base URL:', baseUrl);
console.log('API Key:', apiKey ? '***' + apiKey.slice(-10) : 'NOT SET');

// 次のノードに渡すデータを構築
return [{
  json: {
    userRequest: userRequest,
    baseUrl: baseUrl,
    apiKey: apiKey
  }
}];

出力データ例:

{
  "userRequest": "Create a workflow to fetch Gmail and send to Slack",
  "baseUrl": "http://localhost:5678",
  "apiKey": "<your-api-key>"
}

ポイント
後続のノードで使用するbaseUrlとapiKeyをここで準備しておきます


2-2. Load Available Nodes from File

役割: 利用可能なn8nノードタイプの読み込み

ノードタイプ: n8n-nodes-base.executeCommand

実行コマンド:

cat /data/workflow-creation/available-node-types.txt

ファイル内容 (available-node-types.txt):

n8n-nodes-base.Brandfetch
n8n-nodes-base.BrandfetchTool
n8n-nodes-base.actionNetwork
n8n-nodes-base.github
n8n-nodes-base.gmail
n8n-nodes-base.slack
n8n-nodes-base.code
n8n-nodes-base.httpRequest
...

出力データ例:

{
  "stdout": "n8n-nodes-base.Brandfetch\nn8n-nodes-base.github\nn8n-nodes-base.gmail\nn8n-nodes-base.slack\n...",
  "stderr": "",
  "exitCode": 0
}

ポイント
n8nで利用可能なノードタイプを取得して利用することで、ワークフロー作成API実行時に動くワークフローを生成できるようにサポートできます。

この情報がない場合、AIが実際には利用できないノードタイプを設定してしまうことが多くありました。

特にAIが利用できる情報量が少ない場面だと、ガードレールとして効果が大きい手法だと感じました。

もちろん生成後のバリデーションチェックにも使えます。

利用可能なノードタイプ一覧の取得方法:

curl -s http://localhost:5678/types/nodes.json | node -e "
const data = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
[...new Set(data.map(n => n.name))].sort().forEach(name => console.log(name));
" > available-node-types.txt

2-3. Format Nodes for AI

役割: ファイルから読み込んだノードタイプをAIが理解しやすい形式に整形

ノードタイプ: n8n-nodes-base.code

実装コード (重要部分のみ抜粋):

// Execute Commandノードからファイル内容を取得
const fileContent = $json.stdout;

if (!fileContent) {
  throw new Error('No file content available. Please ensure "Load Available Nodes from File" node executed successfully.');
}

// 行ごとに分割してノードタイプの配列を作成
const nodeTypes = fileContent
  .trim()
  .split('\n')
  .map(line => line.trim())
  .filter(line => line.length > 0);

console.log('\n=== Format Nodes for AI - File Read ===');
console.log('Node types count:', nodeTypes.length);
console.log('First 10 types:', nodeTypes.slice(0, 10).join(', '));

if (nodeTypes.length === 0) {
  console.error('ERROR: No node types available!');
  throw new Error('No node types loaded from file - file may be empty');
}

// 前のノードからuserRequest等を取得
const prepareData = $('Prepare Request').first().json;
const userRequest = prepareData.userRequest;
const baseUrl = prepareData.baseUrl;
const apiKey = prepareData.apiKey;

// ノードタイプリストをカンマ区切りで整形
const availableNodesList = nodeTypes.join(', ');

// AIへのインプット準備
const chatInput = `USER REQUEST: ${userRequest}

AVAILABLE NODE TYPES IN THIS N8N INSTANCE (${nodeTypes.length} types):
${availableNodesList}

🚨🚨🚨 CRITICAL RULES - YOU MUST FOLLOW THESE EXACTLY 🚨🚨🚨

1. COPY node types EXACTLY from the list above
2. NEVER abbreviate or modify the node type string
3. EVERY node type MUST start with "n8n-nodes-base."
4. NEVER use just the display name (e.g., "Code", "Slack")

CORRECT EXAMPLES:
✅ "type": "n8n-nodes-base.manualTrigger"  (CORRECT - full string)
✅ "type": "n8n-nodes-base.code"           (CORRECT - full string)
✅ "type": "n8n-nodes-base.httpRequest"    (CORRECT - full string)
✅ "type": "n8n-nodes-base.slack"          (CORRECT - full string)
✅ "type": "n8n-nodes-base.gmail"          (CORRECT - full string)

WRONG EXAMPLES:
❌ "type": "manualTrigger"   (WRONG - missing prefix)
❌ "type": "code"            (WRONG - missing prefix)
❌ "type": "httpRequest"     (WRONG - missing prefix)
❌ "type": "Manual Trigger"  (WRONG - display name)
❌ "type": "Code"            (WRONG - display name)
`;

console.log('Chat input length:', chatInput.length);
console.log('=== Format Nodes for AI - END ===\n');

return [{
  json: {
    chatInput: chatInput,
    userRequest: userRequest,
    baseUrl: baseUrl,
    apiKey: apiKey,
    availableNodeTypes: nodeTypes  // 配列として保持(後続のバリデーションで使用)
  }
}];

出力データ例:

{
  "chatInput": "USER REQUEST: Create a workflow to fetch Gmail and send to Slack\n\nAVAILABLE NODE TYPES IN THIS N8N INSTANCE:\nn8n-nodes-base.Brandfetch, n8n-nodes-base.github, ...\n\n🚨🚨🚨 CRITICAL RULES...",
  "userRequest": "Create a workflow to fetch Gmail and send to Slack",
  "baseUrl": "http://localhost:5678",
  "apiKey": "eyJhbG...",
  "availableNodeTypes": [
    "n8n-nodes-base.Brandfetch",
    "n8n-nodes-base.github",
    "n8n-nodes-base.gmail",
    ...
  ]
}

ポイント
AIへ利用可能なノードタイプを使用するようにプロンプトを構築します。

正しい例と間違った例を両方提示することで、AIが正しく生成できるようにサポートします。

事前に利用可能なノードタイプを提示しているので存在しないノードタイプを生成しないようにできるだけ頑張ります。


Phase 3: AI生成フェーズ

3-1. AI Agent

役割: 生成AIを使用して、自然言語リクエストからワークフロー定義を生成

ノードタイプ: @n8n/n8n-nodes-langchain.agent

設定:

  • Model: Anthropic Chat Model (Claude Sonnet 4.5 - claude-sonnet-4-5-20250929)
  • Temperature: 0.3(一貫性重視)
  • Max Tokens: 4096
  • Output Parser: Structured Output Parser(JSON Schema強制)
  • Tools: Think Tool(思考プロセスを記録)

System Promptの主要部分:

You are an n8n workflow generation expert with deep knowledge of the n8n automation platform.

TASK:
Generate a complete, executable n8n workflow based on the user's natural language request.

CRITICAL REQUIREMENTS:

1. NODE TYPES:
   - Use ONLY node types from the provided AVAILABLE NODE TYPES list
   - Every node type MUST start with "n8n-nodes-base."
   - Copy node type strings EXACTLY - do not abbreviate or modify
   - NEVER use display names (e.g., "Gmail", "Code", "Slack")

2. NODE STRUCTURE:
   Each node must have:
   - type: Exact node type from available list
   - name: Human-readable display name
   - parameters: Node-specific configuration
   - credentials: (for OAuth nodes) Authentication configuration
   - typeVersion: Node type version (usually 1 or 2)

3. OAUTH NODES:
   For nodes requiring OAuth (Gmail, Slack, GitHub, Google Sheets, etc.):
   - Include credentials field with appropriate credential type
   - Example for Gmail:
     "credentials": {
       "gmailOAuth2": {}
     }

4. CONNECTIONS:
   - Nodes should be connected in a logical flow
   - The workflow will auto-connect nodes sequentially
   - First node should typically be a trigger (manualTrigger, webhook, etc.)

OUTPUT FORMAT (JSON):
{
  "workflowName": "string",
  "description": "string",
  "nodes": [
    {
      "type": "n8n-nodes-base.xxx",
      "name": "Node Name",
      "parameters": {...},
      "credentials": {...},  // For OAuth nodes only
      "typeVersion": 1
    }
  ]
}

EXAMPLES:
...

AI生成例:

{
  "workflowName": "Gmail to Slack Notification",
  "description": "Fetches the latest Gmail message and sends it to Slack",
  "nodes": [
    {
      "type": "n8n-nodes-base.manualTrigger",
      "name": "Manual Trigger",
      "parameters": {},
      "typeVersion": 1
    },
    {
      "type": "n8n-nodes-base.gmail",
      "name": "Get Gmail Messages",
      "parameters": {
        "operation": "getAll",
        "returnAll": false,
        "limit": 1
      },
      "credentials": {
        "gmailOAuth2": {}
      },
      "typeVersion": 2
    },
    {
      "type": "n8n-nodes-base.code",
      "name": "Format Message",
      "parameters": {
        "jsCode": "return [{ json: { text: `New email: ${$json.subject}` } }];"
      },
      "typeVersion": 2
    },
    {
      "type": "n8n-nodes-base.slack",
      "name": "Send to Slack",
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": "#general",
        "text": "={{ $json.text }}"
      },
      "credentials": {
        "slackOAuth2Api": {}
      },
      "typeVersion": 2
    }
  ]
}

ポイント

「Structured Output Parser」でAIの出力をJSON Schema準拠にします。

これで指定のJSONでのレスポンスを強制してパースエラーを防ぎ、後続の処理を安定化できます。


3-2. Structured Output Parser

役割: AIの生成結果をJSON Schemaに基づいて厳密に構造化

ノードタイプ: @n8n/n8n-nodes-langchain.outputParserStructured

JSON Schema定義:

{
  "type": "object",
  "properties": {
    "workflowName": {
      "type": "string",
      "description": "A concise name for the workflow (e.g., 'Gmail to Slack Notification')"
    },
    "description": {
      "type": "string",
      "description": "A brief description of what the workflow does"
    },
    "nodes": {
      "type": "array",
      "description": "Array of n8n nodes that make up the workflow",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "The exact node type from the available list (e.g., n8n-nodes-base.manualTrigger)"
          },
          "name": {
            "type": "string",
            "description": "Display name for the node (e.g., 'Get Gmail Messages')"
          },
          "parameters": {
            "type": "object",
            "description": "Node-specific parameters (varies by node type)"
          },
          "credentials": {
            "type": "object",
            "description": "Authentication credentials for OAuth nodes (e.g., {gmailOAuth2: {}})"
          },
          "typeVersion": {
            "type": "number",
            "description": "Node type version (usually 1 or 2)"
          }
        },
        "required": ["type", "name"]
      }
    }
  },
  "required": ["workflowName", "description", "nodes"]
}

ポイント

ワークフロー作成APIに必要なリクエストのパラメータ形式になるようになるべく整えて出力するように調整します。

GmailやSlackなどは認証情報のパラメータも必要だったりとノードタイプによって必要なパラメータが違うので、構造が崩れないように注意します。一番重要な部分かもしれません。


Phase 4: 正規化・検証フェーズ

4-1. Add Config

役割: AIが返したworkflowNamedescriptionnodes[]に対して、リクエスト時に収集したbaseUrlapiKey、さらに利用可能なノードタイプ一覧の合流

主要処理:

  • Structured Output Parser(Phase 3)からのJSONを受け取り、欠落している説明文やワークフロー名をデフォルト値で補完
  • Format Nodes for AIで生成したavailableNodeTypes配列を保持し、バリデーション工程に受け渡し
  • すべてのノード配列をnodesフィールドにまとめ、環境情報とともに単一のJSONに格納

この時点で「AI生成物 + 実行環境設定」を1つのデータ構造に集約できるため、以降のNormalize/Validateでは余計な参照を行わずに済みます。

ポイント
各フローで作成した内容を合流させることで、後続ノードが一貫した設定情報を参照できるようにします。


4-2. Normalize Nodes

役割: AIが生成したノードをn8n API仕様に準拠する形式に正規化

ノードタイプ: n8n-nodes-base.code

実装コード (重要部分のみ抜粋):

const nodes = $json.nodes || [];
const workflowName = $json.workflowName;
const description = $json.description;
const baseUrl = $json.baseUrl;
const apiKey = $json.apiKey;

console.log('\n=== Normalize AI Generated Nodes ===');
console.log('Input nodes:', nodes.length);

// OAuth対応ノードのクレデンシャルマッピング
const CREDENTIAL_MAPPING = {
  'n8n-nodes-base.github': 'githubOAuth2Api',
  'n8n-nodes-base.slack': 'slackOAuth2Api',
  'n8n-nodes-base.gmail': 'gmailOAuth2',
  'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api',
  'n8n-nodes-base.googleDrive': 'googleDriveOAuth2Api',
  'n8n-nodes-base.googleCalendar': 'googleCalendarOAuth2Api',
  'n8n-nodes-base.googleDocs': 'googleDocsOAuth2Api',
  'n8n-nodes-base.notion': 'notionOAuth2Api',
  'n8n-nodes-base.microsoftTeams': 'microsoftTeamsOAuth2Api'
};

const normalizedNodes = nodes.map((node, index) => {
  const normalized = {
    type: node.type,
    name: node.name,
    parameters: node.parameters || {},
    typeVersion: node.typeVersion || 1,
    position: [index * 200, 100]
  };

  // AIがテンプレート構文を生成した場合は削除
  // 例: "{{ $credentials.gmailOAuth2.id }}" → 削除
  if (node.credentials && JSON.stringify(node.credentials).includes('{{ $credentials')) {
    console.log(`⚠️  Removing template credentials from ${node.name}`);
    // credentialsフィールドを削除(次のステップで空構造を追加)
  } else if (node.credentials) {
    // テンプレートでなければそのまま使用
    normalized.credentials = node.credentials;
  }

  // OAuth対応ノードに空のcredentials構造を自動追加
  const credentialType = CREDENTIAL_MAPPING[node.type];
  if (credentialType && !normalized.credentials) {
    normalized.credentials = {
      [credentialType]: {}
    };
    console.log(`✅ Added empty credentials for ${node.name} (${credentialType})`);
  }

  return normalized;
});

console.log('Normalized nodes:', normalizedNodes.length);
console.log('=== Normalize Nodes - END ===\n');

return [{
  json: {
    workflowName: workflowName,
    description: description,
    baseUrl: baseUrl,
    apiKey: apiKey,
    nodes: normalizedNodes
  }
}];

主要処理:

  1. テンプレート構文の自動削除:

    • AIが {{ $credentials.gmailOAuth2.id }} を生成してしまう問題に対処
    • テンプレート構文は実行時エラーの原因になるため削除
  2. OAuth対応ノードの自動検出:

    • ノードタイプから必要な認証タイプを判定
    • CREDENTIAL_MAPPINGで9種類のOAuthノードをサポート
  3. 空のcredentials構造追加:

    • n8nエディタで認識される形式で追加
    • ユーザーは既存の認証情報を選択するだけでOK

ポイント

認証情報を空にしてノードを作成しておくことで、ワークフロー作成後にユーザーが認証情報を設定するだけですぐに利用できるようにしました。

利用できない形式でノードが作成されてしまうことが多く、認証情報まわりの処理が厚くなっています。


4-3. Validate Node Types

役割: 生成されたノードタイプが利用可能なリストに存在するか検証

ノードタイプ: n8n-nodes-base.code

実装コード (重要部分のみ抜粋):

// Format Nodes for AIから availableNodeTypes を取得(ファイルから読み込まれたもの)
const previousData = $('Format Nodes for AI').first().json;
const availableNodeTypes = previousData.availableNodeTypes;

if (!availableNodeTypes || !Array.isArray(availableNodeTypes)) {
  throw new Error('Available node types not found. Please ensure "Format Nodes for AI" node executed successfully.');
}

console.log('\n=== Validate Node Types (from file) ===');
console.log('Available node types loaded from file:', availableNodeTypes.length);

// Set(集合)による高速検索
const availableTypesSet = new Set(availableNodeTypes);

const nodes = $json.nodes || [];
const validationResults = [];
const invalidNodes = [];

console.log('Total nodes to validate:', nodes.length);

// 各ノードを検証
nodes.forEach((node, index) => {
  const nodeType = node.type;
  const isValid = availableTypesSet.has(nodeType);  // O(1)の高速検索

  validationResults.push({
    index,
    name: node.name,
    type: nodeType,
    isValid
  });

  if (!isValid) {
    invalidNodes.push({
      index,
      name: node.name,
      type: nodeType,
      suggestion: `検証エラー: ノードタイプ "${nodeType}" はnodes.jsonに存在しません`
    });
    console.log(`❌ Invalid node [${index}]: ${node.name} (${nodeType})`);
  } else {
    console.log(`✅ Valid node [${index}]: ${node.name} (${nodeType})`);
  }
});

const validCount = validationResults.filter(r => r.isValid).length;
const invalidCount = invalidNodes.length;

console.log('\n=== Validation Summary ===');
console.log(`Valid nodes: ${validCount}/${nodes.length}`);
console.log(`Invalid nodes: ${invalidCount}/${nodes.length}`);

if (invalidCount > 0) {
  console.log('\n❌ VALIDATION FAILED');
  console.log('Invalid nodes:', JSON.stringify(invalidNodes, null, 2));
} else {
  console.log('\n✅ ALL NODES VALID');
}

return [{
  json: {
    workflowName: $json.workflowName,
    description: $json.description,
    baseUrl: $json.baseUrl,
    apiKey: $json.apiKey,
    nodes: $json.nodes,
    validation: {
      totalNodes: nodes.length,
      validNodes: validCount,
      invalidNodes: invalidCount,
      isValid: invalidCount === 0,
      invalidNodeDetails: invalidNodes,
      validationResults: validationResults
    }
  }
}];

ポイント

取得していたavailable-node-typesから利用できるノードと検証してバリデーションチェックします。生成前にルールとして提示していたので、基本的に問題なく通過します。


Phase 5: 登録フェーズ

5-1. Build Workflow Payload

役割: n8n API形式のワークフローペイロードを構築

ノードタイプ: n8n-nodes-base.code

実装コード (重要部分のみ抜粋):

const nodes = $json.nodes;
const workflowName = $json.workflowName;
const description = $json.description;

console.log('\n=== Build Workflow Payload ===');
console.log('Building payload for:', workflowName);
console.log('Number of nodes:', nodes.length);

// ノード間の接続を自動生成(順次接続)
const connections = {};
nodes.forEach((node, index) => {
  if (index < nodes.length - 1) {
    connections[node.name] = {
      main: [
        [
          {
            node: nodes[index + 1].name,  // 次のノードに接続
            type: 'main',
            index: 0
          }
        ]
      ]
    };
  }
});

// n8n API v1 形式のペイロード
const workflowPayload = {
  name: workflowName,
  nodes: nodes,
  connections: connections,
  active: false,  // 非アクティブ状態で作成(手動で有効化が必要)
  settings: {
    executionOrder: 'v1'
  },
  tags: [],
  meta: {
    description: description
  }
};

console.log('Connections created:', Object.keys(connections).length);
console.log('=== Build Workflow Payload - END ===\n');

return [{
  json: {
    workflowPayload: workflowPayload,
    baseUrl: $json.baseUrl,
    apiKey: $json.apiKey,
    workflowName: workflowName,
    description: description,
    validation: $json.validation
  }
}];

生成されるペイロード例:

{
  "name": "Gmail to Slack Notification",
  "nodes": [
    {
      "type": "n8n-nodes-base.manualTrigger",
      "name": "Manual Trigger",
      "parameters": {},
      "typeVersion": 1,
      "position": [0, 100]
    },
    {
      "type": "n8n-nodes-base.gmail",
      "name": "Get Gmail Messages",
      "parameters": {...},
      "credentials": {"gmailOAuth2": {}},
      "typeVersion": 2,
      "position": [200, 100]
    },
    {
      "type": "n8n-nodes-base.slack",
      "name": "Send to Slack",
      "parameters": {...},
      "credentials": {"slackOAuth2Api": {}},
      "typeVersion": 2,
      "position": [400, 100]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [[{"node": "Get Gmail Messages", "type": "main", "index": 0}]]
    },
    "Get Gmail Messages": {
      "main": [[{"node": "Send to Slack", "type": "main", "index": 0}]]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [],
  "meta": {
    "description": "Fetches Gmail and sends to Slack"
  }
}

ポイント
各ノードを順次接続して全体のワークフローを構築します。

ワークフロー作成API実行のためのリクエスト用に整形します。


5-2. Ensure OAuth Credentials

役割: OAuth認証が必要なノードに対して自動的にダミークレデンシャルを作成・割り当て

ノードタイプ: n8n-nodes-base.code

処理フロー:

  1. クレデンシャルが必要なノードを検出:

    const CREDENTIAL_MAPPING = {
      'n8n-nodes-base.github': 'githubOAuth2Api',
      'n8n-nodes-base.slack': 'slackOAuth2Api',
      'n8n-nodes-base.gmail': 'gmailOAuth2',
      'n8n-nodes-base.googleSheets': 'googleSheetsOAuth2Api',
      'n8n-nodes-base.googleDrive': 'googleDriveOAuth2Api',
      'n8n-nodes-base.googleCalendar': 'googleCalendarOAuth2Api',
      ...
    };
    
  2. 各クレデンシャルタイプごとにダミークレデンシャルを作成:

    • n8n Credentials API (POST /api/v1/credentials) を呼び出し
    • ダミーのOAuthトークンデータを含むペイロードを送信
    • 作成されたクレデンシャルIDを取得
  3. ノードにクレデンシャルIDを自動割り当て:

    node.credentials[credType] = {
      id: credentialId,
      name: credentialDisplayName
    };
    

クレデンシャルペイロード例:

{
  "name": "AI githubOAuth2Api Credential 2025-11-20T10:30:00.000Z",
  "type": "githubOAuth2Api",
  "data": {
    "clientId": "dummy_client_id",
    "clientSecret": "dummy_client_secret",
    "oauthTokenData": {
      "access_token": "dummy_token_placeholder",
      "token_type": "bearer",
      "scope": "repo user"
    }
  },
  "nodesAccess": [
    { "nodeType": "n8n-nodes-base.github" }
  ],
  "sharedWithProjects": []
}

APIレスポンス例:

{
  "id": "cred_abc123",
  "name": "AI githubOAuth2Api Credential 2025-11-20T10:30:00.000Z",
  "type": "githubOAuth2Api",
  "createdAt": "2025-11-20T10:30:00.000Z"
}

ポイント

このノードは、ワークフロー作成時にOAuth認証が必要なノード(GitHub、Slack、Gmailなど)に対して、自動的にダミークレデンシャルを作成します。

ダミークレデンシャルには実際の認証情報は含まれていませんが、n8nエディタ上でノードが正常に表示され、ユーザーが後から実際のクレデンシャルに置き換えることができます。

これにより、AIが生成したワークフローがn8nエディタで即座に編集可能な状態になります。


5-3. Call Create Workflow API

役割: n8n REST APIを呼び出してワークフローを登録

ノードタイプ: n8n-nodes-base.httpRequest

リクエスト設定:

  • Method: POST
  • URL: {{ $json.baseUrl }}/api/v1/workflows
  • Authentication: Header Auth
    • Header Name: X-N8N-API-KEY
    • Header Value: {{ $json.apiKey }}
  • Content-Type: application/json
  • Body: {{ $json.workflowPayload }}

APIエンドポイント:

POST http://localhost:5678/api/v1/workflows

リクエストヘッダー:

X-N8N-API-KEY: <your-api-key>
Content-Type: application/json

APIレスポンス例:

{
  "id": "abc123xyz",
  "name": "Gmail to Slack Notification",
  "active": false,
  "createdAt": "2025-11-19T10:30:00.000Z",
  "updatedAt": "2025-11-19T10:30:00.000Z",
  "nodes": [...],
  "connections": {...},
  "settings": {...},
  "staticData": null,
  "tags": [],
  "versionId": "1"
}

ポイント

実際にAPIを実行してワークフローを生成します。


Phase 6: レスポンスフェーズ

6-1. Format API Result

役割: API応答を整形してユーザーに返却

ノードタイプ: n8n-nodes-base.code

実装コード:

const apiResponse = $json;
const validation = $('Build Workflow Payload').first().json.validation;

console.log('\n=== Format API Result ===');
console.log('Workflow created:', apiResponse.id);
console.log('Workflow URL:', `http://localhost:5678/workflow/${apiResponse.id}`);

return [{
  json: {
    success: true,
    statusCode: 200,
    workflowId: apiResponse.id,
    workflowName: apiResponse.name,
    workflowUrl: `http://localhost:5678/workflow/${apiResponse.id}`,
    validation: validation,
    apiResponse: apiResponse
  }
}];

最終レスポンス例:

{
  "success": true,
  "statusCode": 200,
  "workflowId": "abc123xyz",
  "workflowName": "Gmail to Slack Notification",
  "workflowUrl": "http://localhost:5678/workflow/abc123xyz",
  "validation": {
    "totalNodes": 3,
    "validNodes": 3,
    "invalidNodes": 0,
    "isValid": true,
    "invalidNodeDetails": [],
    "validationResults": [...]
  },
  "apiResponse": {
    "id": "abc123xyz",
    "name": "Gmail to Slack Notification",
    "active": false,
    "createdAt": "2025-11-19T10:30:00.000Z",
    ...
  }
}

ポイント

API実行結果から最終的なレスポンスを返します。


実際のワークフロー作成例

上図は「Gmailでメールを取得してSlackに通知する」というリクエストを送った際に作成された実際のワークフローです。

Phase 1〜5の各処理で説明したように、トリガーからワークフロー作成API実行までが自動で構築され、エディタ上ではOAuthクレデンシャルが設定できるノードが接続された状態で表示されます。

生成後は必要に応じてチャンネル名やフィルタ条件を微調整し、ワンクリックで有効化することで運用へ移行できます。

所感

今回のワークフローを作成するにあたり、最初は自然言語だけでなんとかAIの出力を制御しようと悪戦苦闘していましたが、利用できないノードタイプを生成することが多く、結局期待するような結果がなかなか出ませんでした。

利用可能なノードタイプの一覧を導入することでガードレールとして働き、期待する結果が出やすくなったと感じています。
すべてAI任せにした無秩序な命令からは無秩序な回答しか生まれないですね。

ただ、現状ちょっと複雑なリクエストを投げたり、未対応のノードがあったり、これくらいならAIで一発で出せるんじゃないか?と、改善の余地はまだまだ沢山あります...。

リクエストやレスポンスを構造化して、よりうまくAIを活用していきましょう!

Discussion