(G) LineChatBot + Google Sheets (as a memory)

工作流概述

这是一个包含17个节点的复杂工作流,主要用于自动化处理各种任务。

工作流源代码

下载
{
  "id": "[CENSORED]",
  "meta": {
    "instanceId": "[CENSORED]",
    "templateCredsSetupCompleted": true
  },
  "name": "(G) LineChatBot + Google Sheets (as a memory)",
  "tags": [
    {
      "id": "[CENSORED]",
      "name": "Guitar",
      "createdAt": "2025-04-18T08:59:48.308Z",
      "updatedAt": "2025-04-18T08:59:48.308Z"
    }
  ],
  "nodes": [
    {
      "id": "[CENSORED]",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        560,
        -500
      ],
      "webhookId": "[CENSORED]",
      "parameters": {
        "path": "guitarpa",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "[CENSORED]",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        460,
        -220
      ],
      "parameters": {
        "text": "={{ $json.Prompt }}",
        "options": {
          "systemMessage": "=You are a helpful assistant. Your name is \"ลลิตา\". You will help me in everything I need. You will answer based on user language. You are an AI Agent operating in the Thailand time zone (Asia/Bangkok, UTC+7). Today is {{ $now }}."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "[CENSORED]",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        460,
        -20
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.0-flash-001"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "[CENSORED]",
          "name": "Guitar's Gemini ([CENSORED_EMAIL])"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        780,
        -500
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "[CENSORED]",
              "name": "body.events[0].message.text",
              "type": "string",
              "value": "={{ $('Webhook').item.json.body.events[0].message.text }}"
            },
            {
              "id": "[CENSORED]",
              "name": "body.events[0].replyToken",
              "type": "string",
              "value": "={{ $('Webhook').item.json.body.events[0].replyToken }}"
            },
            {
              "id": "[CENSORED]",
              "name": "body.events[0].source.userId",
              "type": "string",
              "value": "={{ $json.body.events[0].source.userId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "[CENSORED]",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1276,
        -220
      ],
      "parameters": {
        "url": "https://api.line.me/v2/bot/message/reply",
        "method": "POST",
        "options": {},
        "jsonBody": "={
    \"replyToken\": \"{{ $('Edit Fields').item.json.body.events[0].replyToken }}\",
    \"messages\": [
        {
            \"type\": \"text\",
            \"text\": \"{{ $('AI Agent').item.json.output.replaceAll('\t', ' ').replaceAll('\\"', '\\\\"').replaceAll('\n', '\\n').trim() || 'No response available.' }}\"
        }
    ]
}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer [CENSORED]"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "[CENSORED]",
      "name": "Get History",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1000,
        -500
      ],
      "parameters": {
        "options": {
          "outputFormatting": {
            "values": {
              "date": "FORMATTED_STRING",
              "general": "UNFORMATTED_VALUE"
            }
          },
          "returnFirstMatch": true,
          "dataLocationOnSheet": {
            "values": {
              "rangeDefinition": "detectAutomatically"
            }
          }
        },
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Webhook').item.json.body.events[0].source.userId }}",
              "lookupColumn": "UserID "
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "[CENSORED]",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "[CENSORED]"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "[CENSORED]",
          "name": "[Guitar] Google Sheets ([CENSORED_EMAIL])"
        }
      },
      "notesInFlow": false,
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "[CENSORED]",
      "name": "Prepare Prompt",
      "type": "n8n-nodes-base.set",
      "position": [
        1220,
        -500
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "[CENSORED]",
              "name": "Prompt",
              "type": "string",
              "value": "={{
  \"คุณคือลลิตา แชทบอทภาษาไทยที่สุภาพและเป็นมิตร ตอบตามบริบทของการสนทนา:\n\" +

  ($('Get History').item.json.History_Archive_1 || \"\") +
  (($('Get History').item.json.History_Archive_1) ? \"\n\" : \"\") +

  ($('Get History').item.json.History_Archive_2 || \"\") +
  (($('Get History').item.json.History_Archive_2) ? \"\n\" : \"\") +

  ($('Get History').item.json.History_Archive_3 || \"\") +
  (($('Get History').item.json.History_Archive_3) ? \"\n\" : \"\") +

  ($('Get History').item.json.History_Archive_4 || \"\") +
  (($('Get History').item.json.History_Archive_4) ? \"\n\" : \"\") +

  ($('Get History').item.json.History || \"\") +
  (($('Get History').item.json.History) ? \"\n\" : \"\") +

  \"ผู้ใช้: \" + $('Edit Fields').item.json.body.events[0].message.text + \"\nลลิตา: \"
}}
"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "[CENSORED]",
      "name": "Save History",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1056,
        -220
      ],
      "parameters": {
        "columns": {
          "value": {
            "History": "={{ $('Split History').item.json.historyToSave }}",
            "UserID ": "={{ $('Edit Fields').item.json.body.events[0].source.userId }}",
            "LastUpdated": "={{ new Date().toISOString() }}",
            "History_Archive_1": "={{ $('Split History').item.json.historyArchive1 }}",
            "History_Archive_2": "={{ $('Split History').item.json.historyArchive2 }}",
            "History_Archive_3": "={{ $('Split History').item.json.historyArchive3 }}",
            "History_Archive_4": "={{ $('Split History').item.json.historyArchive4 }}"
          },
          "schema": [
            {
              "id": "UserID ",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "UserID ",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "History",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "History",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LastUpdated",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "LastUpdated",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "History_Archive_1",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "History_Archive_1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "History_Archive_2",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "History_Archive_2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "History_Archive_3",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "History_Archive_3",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "History_Archive_4",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "History_Archive_4",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "UserID "
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "[CENSORED]",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "[CENSORED]"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "[CENSORED]",
          "name": "[Guitar] Google Sheets ([CENSORED_EMAIL])"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        460,
        -560
      ],
      "parameters": {
        "content": "### Connect to Line Official Account's API"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        -560
      ],
      "parameters": {
        "width": 180,
        "content": "Prepare the data"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        -560
      ],
      "parameters": {
        "width": 180,
        "content": "Retrieve chat history"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1180,
        -560
      ],
      "parameters": {
        "width": 180,
        "content": "Give our AI previous chat history"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        460,
        -280
      ],
      "parameters": {
        "content": "Get input with this command.   \"{{ $json.Prompt }}\""
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        780,
        -280
      ],
      "parameters": {
        "width": 180,
        "content": "Split history into small chunks (data cleaning)"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Split History",
      "type": "n8n-nodes-base.code",
      "position": [
        840,
        -220
      ],
      "parameters": {
        "jsCode": "// Get the current history, new message, and response
let history = $('Get History').item.json.History || '';
let message = $('Edit Fields').item.json.body.events[0].message.text;
let response = $json.output;
let newExchange = `ผู้ใช้: ${message}\nลลิตา: ${response}`;
let updatedHistory = history + (history ? '\n' : '') + newExchange;

// Threshold: 70% of Google Sheets cell limit (50,000 characters * 0.7 = 35,000)
const threshold = 35000;
let historyToSave = updatedHistory;
let archive1 = $('Get History').item.json.History_Archive_1 || '';
let archive2 = $('Get History').item.json.History_Archive_2 || '';
let archive3 = $('Get History').item.json.History_Archive_3 || '';
let archive4 = $('Get History').item.json.History_Archive_4 || '';

// If history exceeds threshold, split it
if (updatedHistory.length > threshold) {
  // Keep the last 17,500 characters in History (half of threshold for balance)
  const keepLength = 17500;
  const archiveChunk = updatedHistory.substring(0, updatedHistory.length - keepLength);
  historyToSave = updatedHistory.substring(updatedHistory.length - keepLength);

  // Distribute to archive cells, ensuring none exceed 35,000 characters
  if (archive1.length < threshold) {
    archive1 = (archive1 ? archive1 + '\n' : '') + archiveChunk;
    if (archive1.length > threshold) {
      const excess = archive1.substring(threshold);
      archive1 = archive1.substring(0, threshold);
      if (archive2.length < threshold) {
        archive2 = (archive2 ? archive2 + '\n' : '') + excess;
      }
    }
  }
  if (archive2.length < threshold && archive1.length >= threshold) {
    archive2 = (archive2 ? archive2 + '\n' : '') + archiveChunk;
    if (archive2.length > threshold) {
      const excess = archive2.substring(threshold);
      archive2 = archive2.substring(0, threshold);
      if (archive3.length < threshold) {
        archive3 = (archive3 ? archive3 + '\n' : '') + excess;
      }
    }
  }
  if (archive3.length < threshold && archive2.length >= threshold) {
    archive3 = (archive3 ? archive3 + '\n' : '') + archiveChunk;
    if (archive3.length > threshold) {
      const excess = archive3.substring(threshold);
      archive3 = archive3.substring(0, threshold);
      if (archive4.length < threshold) {
        archive4 = (archive4 ? archive4 + '\n' : '') + excess;
      }
    }
  }
  if (archive4.length < threshold && archive3.length >= threshold) {
    archive4 = (archive4 ? archive4 + '\n' : '') + archiveChunk;
    if (archive4.length > threshold) {
      archive4 = archive4.substring(0, threshold);
    }
  }
}

// Return the values to update
return [
  {
    json: {
      historyToSave: historyToSave,
      historyArchive1: archive1,
      historyArchive2: archive2,
      historyArchive3: archive3,
      historyArchive4: archive4,
      lastUpdated: new Date().toISOString()
    }
  }
];"
      },
      "typeVersion": 2
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1000,
        -280
      ],
      "parameters": {
        "width": 180,
        "content": "Save to Google Sheets"
      },
      "typeVersion": 1
    },
    {
      "id": "[CENSORED]",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1220,
        -280
      ],
      "parameters": {
        "width": 180,
        "content": "Send it back to Line"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "[CENSORED]",
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Split History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Get History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get History": {
      "main": [
        [
          {
            "node": "Prepare Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save History": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split History": {
      "main": [
        [
          {
            "node": "Save History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Prompt": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}

功能特点

  • 自动检测新邮件
  • AI智能内容分析
  • 自定义分类规则
  • 批量处理能力
  • 详细的处理日志

技术分析

节点类型及作用

  • Webhook
  • @N8N/N8N Nodes Langchain.Agent
  • @N8N/N8N Nodes Langchain.Lmchatgooglegemini
  • Set
  • Httprequest

复杂度评估

配置难度:
★★★★☆
维护难度:
★★☆☆☆
扩展性:
★★★★☆

实施指南

前置条件

  • 有效的Gmail账户
  • n8n平台访问权限
  • Google API凭证
  • AI分类服务订阅

配置步骤

  1. 在n8n中导入工作流JSON文件
  2. 配置Gmail节点的认证信息
  3. 设置AI分类器的API密钥
  4. 自定义分类规则和标签映射
  5. 测试工作流执行
  6. 配置定时触发器(可选)

关键参数

参数名称 默认值 说明
maxEmails 50 单次处理的最大邮件数量
confidenceThreshold 0.8 分类置信度阈值
autoLabel true 是否自动添加标签

最佳实践

优化建议

  • 定期更新AI分类模型以提高准确性
  • 根据邮件量调整处理批次大小
  • 设置合理的分类置信度阈值
  • 定期清理过期的分类规则

安全注意事项

  • 妥善保管API密钥和认证信息
  • 限制工作流的访问权限
  • 定期审查处理日志
  • 启用双因素认证保护Gmail账户

性能优化

  • 使用增量处理减少重复工作
  • 缓存频繁访问的数据
  • 并行处理多个邮件分类任务
  • 监控系统资源使用情况

故障排除

常见问题

邮件未被正确分类

检查AI分类器的置信度阈值设置,适当降低阈值或更新训练数据。

Gmail认证失败

确认Google API凭证有效且具有正确的权限范围,重新进行OAuth授权。

调试技巧

  • 启用详细日志记录查看每个步骤的执行情况
  • 使用测试邮件验证分类逻辑
  • 检查网络连接和API服务状态
  • 逐步执行工作流定位问题节点

错误处理

工作流包含以下错误处理机制:

  • 网络超时自动重试(最多3次)
  • API错误记录和告警
  • 处理失败邮件的隔离机制
  • 异常情况下的回滚操作