ChatGPT销售客服AI 评分 · 待评分AI 辅助编辑·未发现明显提示词注入风险n8n.io

用 n8n 自动分诊客服工单:OpenAI 分类 + HubSpot enrichment + Linear/Slack 升级

Webhook 接收工单 → 标准化与查重 → HubSpot 补全客户信息 → GPT-4o-mini 分类并起草回复 → 按紧急程度路由到 Linear 升级、人工复核或 Gmail 草稿 → Google Sheets 审计日志

经验丰富步骤流程提示词贴士
给谁
有 n8n 实例的支持运营团队,需同时管理 Slack、Google Workspace、HubSpot、Linear/OpenAI 凭证
你会得到
自动完成工单验证、去重、客户信息补全、AI 分类与回复草稿、紧急升级路由,并记录审计日志
你需要
n8n · slack · google-sheets · gmail

硬约束

前置条件:n8n 实例(自托管或云);Slack OAuth2、Google Sheets OAuth2、Gmail OAuth2 凭证;HubSpot private app token、OpenAI API key、Linear API key。需创建包含 dedupeKey 列的 Google Sheets「Support Audit Log」工作表。将模板导入 n8n 后,替换所有 REPLACE_WITH_* 占位值,配置 webhook 端点 /ai-support-triage,测试单个 payload 后再激活。

他们做了什么

模板作者 flowforgeai 在 n8n 上构建了一个 24 节点的生产级支持工单分诊工作流。该工作流通过 webhook 接收来自 Zendesk、Intercom、Freshdesk 或自定义应用的原始工单,使用 Code 节点标准化多种字段格式,分配 VIP 域名识别和 SLA 时限,通过 Google Sheets 查重,调用 HubSpot API 补全客户信息,再使用 GPT-4o-mini 生成分类、优先级、情绪、风险说明和回复草稿,最后根据规则将紧急工单升级到 Linear 并告警 Slack,低置信度工单送入人工复核队列,常规工单生成 Gmail 草稿,所有决策记录到 Google Sheets 审计表。

效果如何

该模板已在 n8n.io 发布为官方模板(n8n_id=19097),包含完整的节点连线、错误处理分支(Continue On Fail)和粘性注释文档。作者未提供具体运行数据或 ROI 报告;效果声明基于模板设计目标,未经本站实测验证。

值得知道

1. 凭证需求较多(6 类服务),且需正确配置 OAuth 和 private app 权限。2. VIP 域名和 SLA 阈值在 Code 节点中硬编码为占位值,必须替换。3. Linear 节点可替换为 Jira/Asana/Zendesk,但需自行修改 GraphQL mutation 为对应 API。4. HubSpot 和 OpenAI 节点启用 Continue On Fail,供应商临时故障时会自动落入 human_review 路径而非中断工作流。5. OpenAI 返回的 JSON 若被 markdown 代码块包裹,Parse 节点会清理后解析。6. 本站未执行实际运行验证(reproduction=not_tested),搭建后务必先用测试 payload 验证。

n8n 工作流模板

结构已检查 · 未实际运行

下载后在 n8n 选择“Import from File”,或复制 JSON 后粘贴到编辑器。先配置自己的账号,再用测试数据运行;模板默认未启用。

配置与测试清单

需要绑定的凭证

  • 将 __UKH_BEARER_13__ 替换为实际密钥/令牌(原路径 /nodes/13/parameters/headerParameters/parameters/0/value;literal auth header)。
  • 将 __UKH_BEARER_14__ 替换为实际密钥/令牌(原路径 /nodes/14/parameters/headerParameters/parameters/0/value;literal auth header)。
  • 将 __UKH_BEARER_17__ 替换为实际密钥/令牌(原路径 /nodes/17/parameters/headerParameters/parameters/0/value;literal auth header)。

需要替换的配置

  • 源模板来自 n8n 公开库 #19097(约 24 个节点)。导入后先保持未启用,核对触发器、表达式与外部 API 端点。
  • Sticky Note 中的说明仅供参考;按自己的账号、表结构或频道 ID 替换占位配置。

测试输入与预期结果

  • 在测试环境手动执行一次主路径,确认每个需凭证的节点已连接且无鉴权错误。
  • 用一条合成/脱敏样例数据跑通,核对输出字段与下游写入目标,再考虑生产启用。

模板处理说明

  • 已移除原作者凭证绑定、webhookId 与执行数据,工作流设为未启用;节点、连线与原文提示词保持原样。未在 n8n 中实际运行。
  • 未改业务逻辑;运行前请自行核对密钥、配额与隐私合规。 另将 3 处字面量密钥替换为 __UKH_…__ 占位符。

模板获取日期: 2026-10-25

步骤

  1. 配置凭证与基础环境

    在 n8n 中创建或连接以下凭证:Slack OAuth2(#support-triage、#support-escalations、#support-ops 频道权限)、Google Sheets OAuth2(可编辑审计表格)、Gmail OAuth2(如需创建草稿)。收集 HubSpot private app token(CRM 搜索权限)、OpenAI API key、Linear API key(或准备替换为 Jira/Asana 的 HTTP 节点参数)。在 Google Sheets 新建「Support Audit Log」表,确保包含 dedupeKey 列。复制模板 webhook 生产 URL,配置你的帮助台或表单工具向 /ai-support-triage POST 事件。

  2. 接收工单 Webhook

    模板使用 Webhook 节点监听 POST 请求,路径为 ai-support-triage,responseMode 设为 responseNode(由后续 Respond to Webhook 节点返回)。你的帮助台或表单需发送包含 ticketId、email、name、company、subject、message、ticketUrl、source 等字段的 JSON。

  3. 标准化工单并分配 SLA

    Code 节点「Normalize Ticket + SLA Profile」执行 JS 代码,从多种常见嵌套格式(requester.email、customer.email、description、body 等)提取统一字段:requesterEmail、requesterName、subject、message、ticketId、ticketUrl、sourceSystem、companyName。生成 dedupeKey(邮箱+标准化主题/内容),检查 VIP 域名(默认占位 REPLACE_WITH_VIP_DOMAIN_1.com),分配 defaultSlaMinutes(VIP 30 分钟/普通 240 分钟)。标记缺失必填项(email、subject、message)。

    运行提示词 · 来源原文。n8n Code 节点「Normalize Ticket + SLA Profile」的 JS Code 字段

    const rawTicket = $input.first().json;
    
    const firstNonEmpty = (...values) => {
      for (const value of values) {
        if (value !== undefined && value !== null && String(value).trim() !== '') {
          return String(value).trim();
        }
      }
      return '';
    };
    
    const requesterEmail = firstNonEmpty(
      rawTicket.email,
      rawTicket.requester?.email,
      rawTicket.customer?.email,
      rawTicket.contact?.email,
      rawTicket.from
    ).toLowerCase();
    const requesterName = firstNonEmpty(rawTicket.name, rawTicket.requester?.name, rawTicket.customer?.name, rawTicket.contact?.name, requesterEmail.split('@')[0]);
    const subject = firstNonEmpty(rawTicket.subject, rawTicket.title, rawTicket.summary, 'No subject provided');
    const message = firstNonEmpty(rawTicket.message, rawTicket.description, rawTicket.body, rawTicket.text);
    const ticketId = firstNonEmpty(rawTicket.ticketId, rawTicket.id, rawTicket.external_id, rawTicket.conversationId, `incoming-${Date.now()}`);
    const ticketUrl = firstNonEmpty(rawTicket.ticketUrl, rawTicket.url, rawTicket.html_url);
    const sourceSystem = firstNonEmpty(rawTicket.source, rawTicket.helpdesk, rawTicket.system, 'webhook');
    const companyName = firstNonEmpty(rawTicket.company, rawTicket.organization?.name, rawTicket.account?.name);
    const domain = requesterEmail.includes('@') ? requesterEmail.split('@').pop() : '';
    const normalizedSubject = subject.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 90);
    const normalizedMessage = message.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 160);
    const dedupeKey = [requesterEmail || 'unknown-email', normalizedSubject || normalizedMessage || ticketId].join('::');
    const vipDomains = ['REPLACE_WITH_VIP_DOMAIN_1.com', 'REPLACE_WITH_VIP_DOMAIN_2.com'];
    const vipDomain = vipDomains.includes(domain);
    const missingFields = [];
    if (!requesterEmail) missingFields.push('requester email');
    if (!subject) missingFields.push('subject');
    if (!message) missingFields.push('message/body');
    
    return [{
      json: {
        receivedAt: new Date().toISOString(),
        sourceSystem,
        ticketId,
        ticketUrl,
        requesterEmail,
        requesterName,
        companyName,
        domain,
        subject,
        message,
        messagePreview: message.slice(0, 500),
        dedupeKey,
        vipDomain,
        defaultSlaMinutes: vipDomain ? 30 : 240,
        missingFields,
        hasMissingRequired: missingFields.length > 0,
        rawTicket,
      },
    }];
  4. 验证必填字段并处理异常

    IF 节点检查 hasMissingRequired。若为 true,向 Slack #support-ops 发送错误告警(含缺失字段列表和 ticketId),并通过 Respond to Webhook 返回 HTTP 422 JSON(reason: missing_required_fields)。若验证通过,进入查重分支。

    运行提示词 · 来源原文。Slack 节点「Slack Intake Error Alert」的 Text 字段

    Rejected malformed support ticket from {{$json.sourceSystem}}. Missing: {{$json.missingFields.join(", ")}}. Ticket ID: {{$json.ticketId}}
  5. Google Sheets 查重

    Google Sheets 节点按 dedupeKey 列在「Support Audit Log」表中执行 lookup。若找到匹配(IF 节点判断 row_number/dedupeKey/ticketId 非空),向 #support-triage 发送重复通知,并返回 JSON 标记 duplicate: true。若无重复,继续进入 HubSpot enrichment 分支。

    运行提示词 · 来源原文。Slack 节点「Slack Duplicate Notice」的 Text 字段

    Duplicate support ticket detected for {{$node["Normalize Ticket + SLA Profile"].json.requesterEmail}}: {{$node["Normalize Ticket + SLA Profile"].json.subject}}. Existing audit row: {{$json.row_number || "unknown"}}
  6. HubSpot 客户信息补全与 OpenAI 分类起草

    HTTP Request 节点向 HubSpot CRM API 搜索联系人(按 email),获取 firstname、lastname、company、lifecyclestage、hubspotscore。随后 HTTP Request 节点调用 OpenAI chat/completions(gpt-4o-mini,temperature 0.2,response_format: json_object),传入 system prompt 要求返回严格 JSON(category, priority, sentiment, confidence, internalSummary, suggestedRoute, replySubject, replyDraft, slaRiskReason),以及包含工单主题、发件人、公司、VIP 标识、HubSpot 上下文(截断至 1200 字符)和客户消息的 user prompt。两个节点均启用 Continue On Fail。

    运行提示词 · 来源原文。OpenAI HTTP 请求 messages[0].content(system role)

    You are a senior support operations analyst. Return strict JSON only with keys: category, priority, sentiment, confidence, internalSummary, suggestedRoute, replySubject, replyDraft, slaRiskReason. priority must be one of p0,p1,p2,p3. sentiment must be calm,confused,frustrated,angry. suggestedRoute must be engineering,billing,success,security,general,human_review.

    运行提示词 · 来源原文。OpenAI HTTP 请求 messages[1].content(user role),n8n 表达式模板,需在节点中保持原样

    Ticket: ${$node["Normalize Ticket + SLA Profile"].json.subject}\nFrom: ${$node["Normalize Ticket + SLA Profile"].json.requesterEmail}\nCompany: ${$node["Normalize Ticket + SLA Profile"].json.companyName || "unknown"}\nVIP domain: ${$node["Normalize Ticket + SLA Profile"].json.vipDomain}\nHubSpot lookup: ${JSON.stringify($json).slice(0, 1200)}\nMessage: ${$node["Normalize Ticket + SLA Profile"].json.message}
  7. 解析 AI 结果并路由决策

    Code 节点「Parse AI Triage + Fallback」解析 OpenAI 返回的 JSON(清理 markdown 代码块),若解析失败或 AI 报错则使用 fallback 值(category=general, priority 按 VIP 取 p1/p2, confidence=0.45, suggestedRoute=human_review)。计算紧急升级条件:VIP 域名、priority 为 p0/p1、sentiment=angry、category 为 security/billing、或主题/消息含 urgentKeywords(outage/down/breach 等)。同时判定 needsHumanReview:AI 失败、confidence<0.76、suggestedRoute=human_review 或已触发升级。输出 routeDecision 为 urgent_escalation / human_review / draft_ready。

    运行提示词 · 来源原文。n8n Code 节点「Parse AI Triage + Fallback」的 JS Code 字段

    const ticket = $node["Normalize Ticket + SLA Profile"].json;
    const hubspot = $node["Enrich Contact in HubSpot"].json || {};
    const aiResponse = $input.first().json || {};
    
    const safeParseJson = (content) => {
      if (!content || typeof content !== 'string') return null;
      const cleaned = content.replace(/^\s*```(?:json)?/i, '').replace(/```\s*$/i, '').trim();
      try {
        return JSON.parse(cleaned);
      } catch (error) {
        return null;
      }
    };
    
    const content = aiResponse.choices?.[0]?.message?.content;
    const parsed = safeParseJson(content);
    const aiFailed = !parsed || Boolean(aiResponse.error);
    const fallback = {
      category: 'general',
      priority: ticket.vipDomain ? 'p1' : 'p2',
      sentiment: 'confused',
      confidence: 0.45,
      internalSummary: `Manual review needed for ${ticket.subject}. AI triage was unavailable or returned invalid JSON.`,
      suggestedRoute: 'human_review',
      replySubject: `Re: ${ticket.subject}`,
      replyDraft: `Hi ${ticket.requesterName || 'there'},\n\nThanks for reaching out. We received your request and our team is reviewing it now. We'll follow up with the next step shortly.\n\nBest,\nSupport`,
      slaRiskReason: aiFailed ? 'AI request failed or produced invalid output.' : 'Fallback route selected.',
    };
    const triage = { ...fallback, ...(parsed || {}) };
    const priority = String(triage.priority || fallback.priority).toLowerCase();
    const category = String(triage.category || fallback.category).toLowerCase();
    const sentiment = String(triage.sentiment || fallback.sentiment).toLowerCase();
    const confidence = Number(triage.confidence || fallback.confidence);
    const riskText = `${ticket.subject} ${ticket.message} ${triage.slaRiskReason || ''}`.toLowerCase();
    const urgentKeywords = ['outage', 'down', 'breach', 'security', 'data loss', 'cannot login', 'production', 'payment failed', 'chargeback'];
    const keywordEscalation = urgentKeywords.some((keyword) => riskText.includes(keyword));
    const protectedCategory = ['security', 'billing'].includes(category);
    const needsEscalation = ticket.vipDomain || ['p0', 'p1'].includes(priority) || sentiment === 'angry' || protectedCategory || keywordEscalation;
    const needsHumanReview = aiFailed || confidence < 0.76 || triage.suggestedRoute === 'human_review' || needsEscalation;
    const hubspotContact = Array.isArray(hubspot.results) && hubspot.results.length > 0 ? hubspot.results[0] : null;
    
    return [{
      json: {
        ...ticket,
        hubspotFound: Boolean(hubspotContact),
        hubspotContactId: hubspotContact?.id || '',
        hubspotLifecycleStage: hubspotContact?.properties?.lifecyclestage || '',
        hubspotScore: hubspotContact?.properties?.hubspotscore || '',
        category,
        priority,
        sentiment,
        confidence,
        suggestedRoute: triage.suggestedRoute || fallback.suggestedRoute,
        internalSummary: triage.internalSummary || fallback.internalSummary,
        replySubject: triage.replySubject || fallback.replySubject,
        replyDraft: triage.replyDraft || fallback.replyDraft,
        slaRiskReason: triage.slaRiskReason || fallback.slaRiskReason,
        needsEscalation,
        needsHumanReview,
        aiFailed,
        routeDecision: needsEscalation ? 'urgent_escalation' : needsHumanReview ? 'human_review' : 'draft_ready',
      },
    }];
  8. 执行路由:升级、人工复核或生成草稿

    根据 routeDecision 分支处理: - urgent_escalation:HTTP Request 节点调用 Linear GraphQL API 创建 Issue(需替换 REPLACE_WITH_LINEAR_TEAM_ID),随后 Slack 节点向 #support-escalations 发送带 :rotating_light: 的紧急告警(含 priority、category、VIP 标识、internalSummary、ticketUrl/ticketId)。 - human_review:Slack 节点向 #support-triage 发送 :eyes: 人工复核通知(含 confidence、category、sentiment、internalSummary、replyDraft)。 - draft_ready:Gmail 节点创建回复草稿(收件人 requesterEmail,主题 replySubject,内容 replyDraft)。 无论哪条路径,最终都进入 Google Sheets 节点追加审计行(含 subject、category、priority、sentiment、confidence、routeDecision 等),并由 Respond to Webhook 返回最终 JSON(ok, ticketId, routeDecision, priority, category, humanReview)。

    运行提示词 · 来源原文。Slack 节点「Slack Urgent Routing Alert」的 Text 字段

    :rotating_light: {{$node["Parse AI Triage + Fallback"].json.priority.toUpperCase()}} {{$node["Parse AI Triage + Fallback"].json.category}} ticket from {{$node["Parse AI Triage + Fallback"].json.requesterEmail}} {{$node["Parse AI Triage + Fallback"].json.vipDomain ? "(VIP domain)" : ""}}
    Summary: {{$node["Parse AI Triage + Fallback"].json.internalSummary}}
    Ticket: {{$node["Parse AI Triage + Fallback"].json.ticketUrl || $node["Parse AI Triage + Fallback"].json.ticketId}}

    运行提示词 · 来源原文。Slack 节点「Slack Human Review Queue」的 Text 字段

    :eyes: Human review needed for {{$json.requesterEmail}} — {{$json.subject}}
    Confidence: {{$json.confidence}} | Category: {{$json.category}} | Sentiment: {{$json.sentiment}}
    AI summary: {{$json.internalSummary}}
    Suggested reply:
    {{$json.replyDraft}}

可复制提示词

搭建指导 · 本站建议版本。交给搭建助手,协助用户确认前置条件并导入模板

这一段是提示词,页面不会在这里插入广告。

你是一个资深支持运营分析师助手。用户正在尝试复现 n8n 模板 19097(AI Support Triage)。请先确认用户已准备好以下凭证和环境:
1. n8n 实例(云或自托管)
2. Slack OAuth2(#support-ops、#support-triage、#support-escalations 权限)
3. Google Sheets OAuth2(可编辑审计表)
4. Gmail OAuth2(如需创建草稿)
5. HubSpot private app token(CRM 搜索权限)
6. OpenAI API key
7. Linear API key(或准备替换为 Jira/Asana)
8. Google Sheets「Support Audit Log」表含 dedupeKey 列

确认后,指导用户:
- 从 https://n8n.io/workflows/19097/ 导入模板
- 替换所有 REPLACE_WITH_* 占位值(Sheet ID、VIP 域名、Linear team ID、API key)
- 配置 webhook 端点 /ai-support-triage
- 用单个测试 payload 验证完整流程后再激活

注意:节点内的 OpenAI system prompt、user prompt 模板、Slack 消息模板和 JS 代码均已在模板中配置,无需额外编写;只需替换凭证和占位值即可运行。

先确认凭证齐全,再导入模板并替换占位值,测试后激活

来源

Triage support tickets with OpenAI, HubSpot, Slack, Linear, and Google Sheets · flowforgeai · n8n.io · 2026-09-02

留言与反馈

试过这个方法吗?分享效果、遇到的问题或改进建议。留言审核后公开,请勿填写敏感信息。

正在加载…

    相关用例