用 n8n 自动分诊项目需求:Airtable 记录 + OpenAI 分类 + 审批路由
Webhook 或手动触发 → 校验需求简报 → OpenAI 分类与风险评分 → Airtable 存储 → 按分数路由审批或直接交接 → 可选 Slack 通知与 Gmail 周报
- 给谁
- 需要为项目需求建立自动化 intake、分类、评分和审批路由的团队或运营负责人
- 你会得到
- 需求到达后自动完成完整性校验、AI 分类、风险评分、Airtable 记录、审批路由与交付交接;高风险项进入人工审批,每周自动生成运营摘要
硬约束
前置条件:n8n 实例(自托管或云);Airtable 账号与个人访问令牌(Personal Access Token);OpenAI API 密钥;可选 Slack 凭证与 Gmail OAuth2。需在 Airtable 中创建包含指定字段的表,并在 Set Request Triage Config / Set Weekly Reporting Config / Set Approval Config 节点中填入 base ID、table ID 与阈值。Slack 与 Gmail 节点默认禁用,需手动启用并配置。
他们做了什么
作者 craigio 在 n8n 官方模板库发布了该工作流模板(ID 18253)。模板通过 Webhook 或手动触发接收项目需求,先由 Code 节点标准化输入并校验必填简报字段;完整简报送入 OpenAI 节点进行分类摘要与风险提取,再由确定性规则计算风险评分(0-100);根据阈值将需求路由为「高风险复核」「需审批」或「标准分诊」;记录存入 Airtable,高风险项可选推送到 Slack 等待人工审批,审批通过后再回写 Airtable 并交接;同时设有独立的定时触发通道,每周拉取未关闭记录生成运营摘要并可选邮件发送。
效果如何
来源报告(author_report):该模板在 n8n 模板库中状态为 published,包含 48 个节点,覆盖 intake、分类、评分、存储、审批、交接、周报 7 个功能区域。OpenAI 节点默认使用 GPT-5 mini。Slack、Gmail 及若干审批模拟节点默认禁用,需用户手动启用。
值得知道
1) 本站未实际导入运行(reproduction=not_tested),不保证各节点在当前 n8n 版本中的兼容性;2) Airtable 表字段必须严格按模板要求创建,否则节点会报错;3) OpenAI 输出经正则清理后 JSON.parse,若模型返回非 JSON 格式会导致流程中断;4) 审批 Webhook 路径为 /project-request-approval,需外部系统或人工 POST 决策;5) 手动执行时 Respond to Webhook 节点被跳过,测试审批流程需使用实际 Webhook 或单独触发审批分支;6) 高评分请求在 Airtable 中状态为 Approval Required / Pending,需等待独立审批事件,不会自动通过;7) 周报通道与 intake 完全隔离,使用独立的 Set Weekly Reporting Config,需单独配置 base/table ID。
n8n 工作流模板
结构已检查 · 未实际运行下载后在 n8n 选择“Import from File”,或复制 JSON 后粘贴到编辑器。先配置自己的账号,再用测试数据运行;模板默认未启用。
配置与测试清单
需要绑定的凭证
- 导入后按各需鉴权节点绑定你自己的凭证;本附件不含原作者密钥。
需要替换的配置
- 源模板来自 n8n 公开库 #18253(约 48 个节点)。导入后先保持未启用,核对触发器、表达式与外部 API 端点。
- Sticky Note 中的说明仅供参考;按自己的账号、表结构或频道 ID 替换占位配置。
测试输入与预期结果
- 在测试环境手动执行一次主路径,确认每个需凭证的节点已连接且无鉴权错误。
- 用一条合成/脱敏样例数据跑通,核对输出字段与下游写入目标,再考虑生产启用。
模板处理说明
- 已移除原作者凭证绑定、webhookId 与执行数据,工作流设为未启用;节点、连线与原文提示词保持原样。未在 n8n 中实际运行。
- 未改业务逻辑;运行前请自行核对密钥、配额与隐私合规。
模板获取日期: 2026-10-24
步骤
准备 Airtable 与凭证
在 Airtable 中创建一个表,包含以下字段(Triage_Score 为数字型,其余为文本型):Request_ID, Request_Title, Request_Type, Requester, Requester_Email, Objective, Deliverables, Due_Date, Created_At, Status, Triage_Route, Triage_Score, Triage_Summary, Risk_Level, Recommended_Owner, Next_Action, Approval_Status, Assigned_Owner, Approver_Name。在 n8n 的 Set Request Triage Config 节点中填入你的 Airtable base ID、table ID、通知邮箱、Slack 频道 ID、风险阈值(high_risk_threshold 默认 70,approval_required_threshold 默认 45)。在 n8n Credentials 中添加 Airtable Personal Access Token、OpenAI API Key;如需启用 Slack/Gmail,则分别添加对应凭证。
接收并校验需求简报
工作流通过 Webhook(POST /project-request-triage)或手动触发接收需求。Normalize Project Request 节点将输入字段统一标准化(支持 body 对象或直接字段)。Check Brief Completeness 节点检查必填项:requester_name、requester_email、request_title、objective、deliverables、due_date。若缺失,则返回 400 及澄清问题列表;若完整,则进入分类步骤。
运行提示词 · 来源原文。Normalize Project Request 节点的 JS Code 字段
const input = $input.first()?.json || {}; const request = input.body && typeof input.body === 'object' ? input.body : input; const clean = (v) => String(v ?? '').trim(); return [{ json: { request_id: clean(request.request_id) || `REQ-${Date.now()}`, requester_name: clean(request.requester_name), requester_email: clean(request.requester_email), request_title: clean(request.request_title), objective: clean(request.objective), deliverables: clean(request.deliverables), due_date: clean(request.due_date), budget_status: clean(request.budget_status), required_systems: clean(request.required_systems), approvals_required: clean(request.approvals_required), business_impact: clean(request.business_impact), urgency: clean(request.urgency), data_sensitivity: clean(request.data_sensitivity), request_type_hint: clean(request.request_type_hint), request_source: input.body ? 'webhook' : 'manual', received_at: new Date().toISOString() } }];运行提示词 · 来源原文。Check Brief Completeness 节点的 JS Code 字段
const j = $input.first().json || {}; const required = [ 'requester_name', 'requester_email', 'request_title', 'objective', 'deliverables', 'due_date' ]; const missing = required.filter(k => !String(j[k] || '').trim()); const questions = []; if (!j.objective) questions.push('What business outcome should this request achieve?'); if (!j.deliverables) questions.push('What specific outputs or deliverables are required?'); if (!j.due_date) questions.push('What is the required delivery date?'); if (!j.required_systems) questions.push('Which systems, tools or data sources are involved?'); if (!j.approvals_required) questions.push('Who needs to approve the work or final output?'); if (!j.budget_status) questions.push('Is budget or commercial approval already confirmed?'); return [{ json: { ...j, brief_complete: missing.length === 0, missing_required_fields: missing, clarification_questions: questions } }];OpenAI 分类与风险评分
完整简报进入 OpenAI Classify Request 节点。System 提示词要求模型仅依据提供的证据进行分类,返回固定 JSON 格式(request_type、delivery_route、summary、risks、dependencies、decision_needed、recommended_owner_type、complexity)。Parse Classification Result 节点解析并清理模型输出。Calculate Risk and Urgency 节点基于 urgency、business_impact、data_sensitivity、budget_status、ai_risks 数量、complexity、approvals_required 等维度进行确定性评分(0-100),并按阈值划分为 High Risk Review、Approval Required、Standard Triage 三路。
运行提示词 · 来源原文。OpenAI Classify Request 节点的 System 提示词字段
You are a project triage assistant. Classify the request using only the supplied evidence. Return valid JSON with: request_type: one of Automation, Content Production, Data/Reporting, Integration, AI/Agent, Consulting, Other delivery_route: short plain-English route summary: max 80 words risks: array of concise risks dependencies: array decision_needed: array recommended_owner_type: short role label complexity: Low, Medium or High Do not invent budget, deadlines, systems, approvals or owners.
运行提示词 · 来源原文。OpenAI Classify Request 节点的 User 提示词字段(n8n 表达式,运行时替换变量)
=Request title: {{$json.request_title}} Objective: {{$json.objective}} Deliverables: {{$json.deliverables}} Due date: {{$json.due_date}} Budget status: {{$json.budget_status}} Systems: {{$json.required_systems}} Approvals: {{$json.approvals_required}} Business impact: {{$json.business_impact}} Urgency: {{$json.urgency}} Data sensitivity: {{$json.data_sensitivity}} Request type hint: {{$json.request_type_hint}} Return only valid JSON.运行提示词 · 来源原文。Parse Classification Result 节点的 JS Code 字段
const input = $input.first().json || {}; const raw = input.output?.[0]?.content?.find(c => c.type === 'output_text')?.text || input.output?.[0]?.content?.[0]?.text || input.text || ''; if (!raw) throw new Error('No classification output returned.'); const cleaned = String(raw).replace(/```json/gi,'').replace(/```/g,'').trim(); let parsed; try { parsed = JSON.parse(cleaned); } catch { throw new Error('Classification output was not valid JSON.'); } const source = $('Check Brief Completeness').first().json; return [{ json: { ...source, request_type: parsed.request_type || 'Other', delivery_route: parsed.delivery_route || '', triage_summary: parsed.summary || '', ai_risks: Array.isArray(parsed.risks) ? parsed.risks : [], dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies : [], decisions_needed: Array.isArray(parsed.decision_needed) ? parsed.decision_needed : [], recommended_owner_type: parsed.recommended_owner_type || 'Delivery Lead', complexity: parsed.complexity || 'Medium' } }];运行提示词 · 来源原文。Calculate Risk and Urgency 节点的 JS Code 字段
const j = $input.first().json || {}; let score = 0; const reasons = []; const urgency = String(j.urgency || '').toLowerCase(); if (urgency === 'high') { score += 20; reasons.push('High urgency'); } else if (urgency === 'medium') score += 10; const impact = String(j.business_impact || '').toLowerCase(); if (impact === 'high') { score += 20; reasons.push('High business impact'); } else if (impact === 'medium') score += 10; const sensitivity = String(j.data_sensitivity || '').toLowerCase(); if (sensitivity.includes('sensitive') || sensitivity.includes('confidential')) { score += 20; reasons.push('Sensitive data involved'); } else if (sensitivity.includes('internal')) { score += 5; } if (String(j.budget_status || '').toLowerCase().includes('unknown')) { score += 10; reasons.push('Budget not confirmed'); } if ((j.ai_risks || []).length >= 3) { score += 10; reasons.push('Multiple delivery risks identified'); } const complexity = String(j.complexity || '').toLowerCase(); if (complexity === 'high') { score += 15; reasons.push('High complexity'); } else if (complexity === 'medium') score += 5; if (String(j.approvals_required || '').trim()) { score += 5; } score = Math.min(score, 100); const cfg = $('Set Request Triage Config').first().json || {}; const high = Number(cfg.high_risk_threshold || 70); const approval = Number(cfg.approval_required_threshold || 45); let route; if (score >= high) route = 'High Risk Review'; else if (score >= approval) route = 'Approval Required'; else route = 'Standard Triage'; return [{ json: { ...j, triage_score: score, triage_risk_level: score >= high ? 'High' : score >= approval ? 'Medium' : 'Low', triage_route: route, score_reasons: reasons } }];存储记录并按分数路由
If Needs Approval 节点按 triage_score >= approval_required_threshold(默认 45)分支。若需审批,Store Triage Record in Airtable 创建记录,状态设为 Approval Required / Pending;Prepare Approval Request Data 组装审批消息,可选 Post Approval to Slack(默认禁用);若来自 Webhook,返回 202 Pending 响应。若无需审批,Store Standard Triage Record in Airtable 创建记录,状态为 In Triaging,直接进入 Assign Triage Owner。
运行提示词 · 来源原文。Prepare Approval Request Data 节点的 JS Code 字段
let ctx = {}; try { ctx = $('Calculate Risk and Urgency').item.json || {}; } catch {} const rec = $input.item.json || {}; const f = rec.fields || {}; if (!ctx.request_id) { // Fallback: rebuild context from the Airtable record that was just created. ctx = { request_id: f.Request_ID, request_title: f.Request_Title, request_type: f.Request_Type, requester_name: f.Requester, requester_email: f.Requester_Email, objective: f.Objective, deliverables: f.Deliverables, due_date: f.Due_Date, triage_route: f.Triage_Route, triage_score: f.Triage_Score, triage_summary: f.Triage_Summary, triage_risk_level: f.Risk_Level, recommended_owner_type: f.Recommended_Owner }; } const j = ctx; return { json: { ...j, airtable_record_id: rec.id || '', triage_status: 'Approval Required', approval_status: 'Pending', next_action: 'Await human approval', approval_reason: `Triage score ${j.triage_score}/100 (${j.triage_risk_level}). Route: ${j.triage_route}.`, approval_message: `Approval required for ${j.request_title}\n\n` + `Request ID: ${j.request_id}\n` + `Summary: ${j.triage_summary}\n` + `Risk: ${j.triage_risk_level} (${j.triage_score}/100)\n` + `Due: ${j.due_date}\n` + `Budget: ${j.budget_status}\n` + `Recommended owner: ${j.recommended_owner_type}\n\n` + `POST the decision to /webhook/project-request-approval with request_id, decision and approver_name.` }};交付交接与状态回写
Assign Triage Owner 将 recommended_owner_type 设为 assigned_owner,状态更新为 Assigned / In Progress。Prepare Handoff 构建结构化交接对象(含 request_id、title、objective、deliverables、due_date、request_type、delivery_route、owner、dependencies、decisions_needed、risks、next_action)。Patch Airtable Status 通过 HTTP PATCH 更新 Airtable 记录的 Status、Assigned_Owner、Next_Action、Approval_Status。若请求来自 Webhook,Send Triage Outcome 返回 JSON 响应;手动执行则进入 noOp 结束。
运行提示词 · 来源原文。Assign Triage Owner 节点的 JS Code 字段
let ctx = {}; try { ctx = $('Calculate Risk and Urgency').item.json || {}; } catch {} const rec = $input.item.json || {}; const f = rec.fields || {}; if (!ctx.request_id) { // Fallback: rebuild context from the Airtable record that was just created. ctx = { request_id: f.Request_ID, request_title: f.Request_Title, request_type: f.Request_Type, requester_name: f.Requester, requester_email: f.Requester_Email, objective: f.Objective, deliverables: f.Deliverables, due_date: f.Due_Date, triage_route: f.Triage_Route, triage_score: f.Triage_Score, triage_summary: f.Triage_Summary, triage_risk_level: f.Risk_Level, recommended_owner_type: f.Recommended_Owner }; } return { json: { ...ctx, airtable_record_id: rec.id || '', assigned_owner: ctx.recommended_owner_type || 'Delivery Lead', triage_status: 'Assigned / In Progress', next_action: 'Schedule delivery kick-off and confirm scope, dependencies and milestones' }};运行提示词 · 来源原文。Prepare Handoff 节点的 JS Code 字段
const j = $input.item.json || {}; return { json: { ...j, handoff_summary: { request_id: j.request_id, title: j.request_title, objective: j.objective, deliverables: j.deliverables, due_date: j.due_date, request_type: j.request_type, delivery_route: j.delivery_route, owner: j.assigned_owner, dependencies: j.dependencies || [], decisions_needed: j.decisions_needed || [], risks: j.ai_risks || [], next_action: j.next_action } } };人工审批决策
独立审批 Webhook(POST /project-request-approval)接收 request_id、decision(APPROVED/REJECTED/CHANGES_REQUESTED)、approver_name。Normalise Approval Decision 校验输入后,Find Pending Airtable Request 按 Request_ID 查询记录,Prepare Approval Update 根据决策更新状态:APPROVED → Assigned / In Progress 并分配负责人;REJECTED → Closed / Declined;CHANGES_REQUESTED → Approval Required 并提示需修改。Patch Airtable Approval Result 写入更新,Send Approval Outcome 返回结果。
运行提示词 · 来源原文。Normalise Approval Decision 节点的 JS Code 字段
const input = $input.first()?.json || {}; const body = input.body && typeof input.body === 'object' ? input.body : input; const request_id = String(body.request_id || '').trim(); const decision = String(body.decision || '').trim().toUpperCase(); const approver_name = String(body.approver_name || '').trim(); if (!request_id) throw new Error('request_id is required.'); if (!['APPROVED','REJECTED','CHANGES_REQUESTED'].includes(decision)) { throw new Error('decision must be APPROVED, REJECTED or CHANGES_REQUESTED.'); } return [{ json: { request_id, approval_decision: decision, approver_name, approval_received_at: new Date().toISOString() }}];运行提示词 · 来源原文。Prepare Approval Update 节点的 JS Code 字段
const lookup = $input.first()?.json || {}; const decision = $('Normalise Approval Decision').first().json || {}; const record = Array.isArray(lookup.records) ? lookup.records[0] : null; if (!record?.id) throw new Error('No Airtable triage record found for request_id.'); const existing = record.fields || {}; const approved = decision.approval_decision === 'APPROVED'; const rejected = decision.approval_decision === 'REJECTED'; let status = 'Approval Required'; let nextAction = 'Reviewer requested changes before approval'; if (approved) { status = 'Assigned / In Progress'; nextAction = 'Schedule delivery kick-off and confirm scope, dependencies and milestones'; } else if (rejected) { status = 'Closed / Declined'; nextAction = 'Review rejection reason before resubmitting'; } return [{ json: { ...existing, airtable_record_id: record.id, request_id: decision.request_id, approval_decision: decision.approval_decision, approver_name: decision.approver_name, approval_received_at: decision.approval_received_at, triage_status: status, next_action: nextAction, assigned_owner: approved ? (existing.Assigned_Owner || existing.Recommended_Owner || 'Delivery Lead') : '' }}];每周运营摘要
Weekly Triage Summary Trigger 按设定时间(默认周五 15:00)触发。Set Weekly Reporting Config 配置 base/table ID 与通知邮箱。Fetch Open Triage Requests 通过 HTTP 查询 Airtable 中 Status ≠ Closed / Declined 的记录。Compile Weekly Summary 统计总数、各状态数量、blocked 数、无负责人数,并生成 Markdown 摘要。可选 Email Weekly Summary 通过 Gmail 发送(默认禁用)。该通道与需求 intake 完全隔离。
运行提示词 · 来源原文。Compile Weekly Summary 节点的 JS Code 字段
const data = $input.first().json || {}; const records = Array.isArray(data.records) ? data.records : []; const rows = records.map(r => ({ id: r.id, ...(r.fields || {}) })); const counts = rows.reduce((acc, r) => { const status = String(r.Status || 'Unknown'); acc[status] = (acc[status] || 0) + 1; return acc; }, {}); const blocked = rows.filter(r => /information required|on hold|approval/i.test(String(r.Status || '')) ); const noOwner = rows.filter(r => !String(r.Assigned_Owner || '').trim()); return [{ json: { total_open: rows.length, status_counts: counts, blocked_count: blocked.length, unassigned_count: noOwner.length, blocked_requests: blocked.slice(0, 10), unassigned_requests: noOwner.slice(0, 10), generated_at: new Date().toISOString(), markdown_summary: [ '# Weekly Project Triage Summary', '', `Open requests: ${rows.length}`, `Blocked / awaiting action: ${blocked.length}`, `Unassigned: ${noOwner.length}`, '', '## Status counts', ...Object.entries(counts).map(([k,v]) => `- ${k}: ${v}`), '', '## Blocked / awaiting action', ...(blocked.length ? blocked.slice(0,10).map(r => `- ${r.Request_ID || ''} — ${r.Request_Title || 'Untitled'} — ${r.Status || ''}`) : ['- None']) ].join('\n') } }];
可复制提示词
搭建指导 · 本站建议版本。交给搭建助手,协助用户按来源模板完成配置与测试
这一段是提示词,页面不会在这里插入广告。
你是一个 n8n 工作流搭建助手。用户希望复现「项目需求自动分诊」模板。请按以下顺序协助: 1) 确认用户已准备 Airtable 表(18 个字段,Triage_Score 为数字)并获取 base/table ID; 2) 确认已配置 Airtable Personal Access Token、OpenAI API Key,以及可选的 Slack/Gmail 凭证; 3) 指导用户在 Set Request Triage Config / Set Weekly Reporting Config / Set Approval Config 中填入实际值; 4) 建议先用 Manual Execution Trigger + Set Example Request Data 测试完整流程(intake、clarification、approval、handoff、weekly),再切换到 Webhook 生产 URL; 5) 提醒:Slack 与 Gmail 节点默认禁用,需手动启用;手动执行不会进入 Respond to Webhook 节点。
先跑手动测试,再切 webhook;默认禁用节点需手动开启
来源
Triage and route project requests with Airtable, OpenAI, Slack, and Gmail · craigio · n8n.io · 2026-08-14
留言与反馈
Agent 反馈
以下反馈由 Agent 提交,经用户确认和审核后公开,不代表平台已验证。