用对话构建n8n工作流
我希望有一个agent,能够通过聊天对话的方式指挥LLM(Agent)帮我创建n8n工作流。比如,我通过聊天的方式告诉智能体要定时获取B站的热搜榜单并写入到某一地方,它就帮我创建好工作流,我自己检查完成后,手动激活即可。
-
如何保证工作流生成的准确性?
LLM本身知识库不靠谱,因为已经滞后;网络搜索也不靠谱,因为干扰太多;最靠谱的只有官方文档或者经过验证的示例。因为文档是实时更新的,所以最好使用mcp服务,让LLM通过MCP获取最新的文档。
-
如何让Agent创建工作流?
Agent的自动化只有两种方式,行为脚本或者API调用。刚好n8n的架构提供了api-server用于操作工作流,那么同理可以通过llm调用mcp,mcp再调用n8n api,完成工作流的管理。
-
核心过程:编写n8n的mcp服务--编写可以执行tool_call的Agent--用户对话--Agent调用MCP--MCP调用API--作用于n8n上。
最终效果:
链路过程
-
找到n8n-mcp服务:github.com/czlonkowski…
- 配置mcp通讯方式。一般使用http(streamable http),调试的时候就会使用stido。
- 配置n8n的api_key,用于api调用的鉴权,否则只有文档问答功能。个人的api_key只能管理个人空间的工作流。
-
配置LLM,用于识别工具列表与构造调用参数。对深度思考与编程能力有要求,经过测试,效果比较好的是claude 4.
-
写一个agent。Agent = LLM + MCP + Code 。 因为langchain深入人心,可以使用Typescript快速开发,所以直接导入langchain,编写Agent。
- 处理LLM调用,使用流式传输。
- 适配mcp多种通讯协议与服务认证
- 处理用户界面的流式响应
- 处理tool_call的调用与输出
-
启动n8n-mcp服务,配置api key。
-
启动Agent并配置mcp连接。
-
开始对话。
-
创建或者修改完成后,检查n8n工作流,验证、激活并运行。
落地方式
-
方式一:通过构建Agent与chat界面使用。
- 自己实现chat界面与对应的通讯方式,比如直接多次http请求,或者通过Websocket通讯,定制化程度高。
- 自己实现Agent,配置LLM与MCP,自己控制tool_call,可操作性强,还可以叠加扩展功能,比如加入内部RAG功能或者调用第三方的AI服务。
-
方式二:通过AI编程IDE配合MCP服务使用。
这种方式使用比较简单,只需要配置好mcp服务与选择对应的模型,就可以直接通过对话操作n8n工作流。
-
方式三:手动创建一个n8n工作流,配置Agent + MCP节点。通过指挥这个母工作流创建新的工作流。
效果
-
热搜爬取并推送飞书机器人。问答次数:8次。
-
定时拉取文件,内容解析与向量化后存入向量数据库。问答次数:15次。
指令: 帮我创建一个工作流,通过code节点访问谷歌drive的数据,再再通过POST请求请求 一个服务将内容向量化,得到响应后,将相应内容通过code节节点发送给某一个服务 比如MilvusDB存储起来。 先查找工具,然后输入具体步骤,再执行工具调用。
-
调用AI服务商接口完成AI试衣功能。
指令: 帮我创建AI虚拟试衣工作流。步骤: -表单触发器接收用户照片和服装图片URL -调用AI模型生成虚拟试衣效果 -多节点智能轮询等待AI处理完成 -http重定向最终结果的url
补充
- LLM搭建工作流本身也是在编程领域范围,所以LLM模型很重要,实测是claude4 比较好用,有深度思考比没深度思考效果更好。
- Prompt也很关键,决定了调用链路。在最开始需要让LLM知道如何利用工具一步步完成对话提出的需求。
- 如果要优化tool_call步骤,必须使用websocket双向传输的模型,Agent可以实时判断是否调用tools,调用之后,通过用户操作将结果回传LLM,再分析是否进入更深层次的调用。否则只能通过上下文输入"继续",让Agent继续完成需求——不可能一次对话就完成工作流的创建。
附录
Ts流式传输
通过生成器函数 + yield关键字。使用 async function* 创建异步生成器 ,然后yield逐步返回数据块,页面实时接收与展示,实现流式传输的效果。(只不过如果通过sse协议传输,页面还需要按照协议自己切分数据)。
public async *chatStream(messages: ChatMessage[]): AsyncGenerator<StreamChunk> {
const stream = await llmWithTools.stream(langchainMessages);
for await (const chunk of stream) {
......
yield {
toolCall: {
name: toolCall.name,
args: toolCall.args,
id: toolCall.id
}
}
......
}
}
工作流JSON数据
AI初步生成,需要优化后使用,仅demo。
B站热搜工作流
点击查看代码
{
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"triggerAtMinute": 22
}
]
}
},
"id": "schedule-trigger",
"name": "每小时触发",
"position": [
240,
300
],
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2
},
{
"parameters": {
"url": "https://xzdx.top/api/tophub",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "type",
"value": "bilihot"
}
]
},
"options": {}
},
"id": "http-request",
"name": "获取B站热搜",
"position": [
460,
300
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"parameters": {
"jsCode": "// 获取B站热搜数据\nconst response = $input.all()[0].json;\n\n// 检查响应结构\nif (!response || response.code !== 0 || !response.data) {\n throw new Error('API响应格式错误或请求失败');\n}\n\nconst hotlistData = response.data;\nconst currentTime = new Date().toLocaleString('zh-CN', {timeZone: 'Asia/Shanghai'});\nconst totalCount = hotlistData.length;\n\n// 处理前15条热搜\nconst top15 = hotlistData.slice(0, 15);\n\n// 生成卡片内容\nlet cardContent = `**📅 更新时间:** ${currentTime}\\n\\n`;\ncardContent += `**📊 共 ${totalCount} 条热搜**\\n\\n`;\ncardContent += `---\\n\\n`;\n\n// 添加热搜列表\ntop15.forEach((item, index) => {\n const rank = index + 1;\n const emoji = rank <= 3 ? ['🥇', '🥈', '🥉'][rank - 1] : `**${rank}.**`;\n \n cardContent += `${emoji} [${item.title || '无标题'}](${item.url || '#'})\\n\\n`;\n \n // 如果有描述,添加描述\n if (item.desc && item.desc.trim()) {\n cardContent += ` *${item.desc}*\\n\\n`;\n }\n});\n\ncardContent += `---\\n\\n`;\ncardContent += `💡 *数据来源:B站热搜榜*\\n`;\ncardContent += `🤖 *由 n8n 自动采集*`;\n\n// 构建飞书Card消息格式\nconst feishuCardMessage = {\n \"msg_type\": \"interactive\",\n \"card\": {\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"header\": {\n \"title\": {\n \"tag\": \"plain_text\",\n \"content\": \"📺 B站热搜榜\"\n },\n \"template\": \"blue\"\n },\n \"elements\": [\n {\n \"tag\": \"markdown\",\n \"content\": cardContent + \"\\n @Sean Zeng\"\n }\n ]\n }\n};\n\n// 返回结果\nreturn [{ \n json: {\n feishuMessage: feishuCardMessage,\n cardContent: cardContent,\n rawData: response\n }\n}];"
},
"id": "code-process",
"name": "生成飞书消息",
"position": [
680,
300
],
"type": "n8n-nodes-base.code",
"typeVersion": 2
},
{
"parameters": {
"method": "POST",
"url": "https://open.feishu.cn/xxxxxxx",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.feishuMessage }}",
"options": {}
},
"id": "http-post",
"name": "发送到飞书",
"position": [
900,
300
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
}
],
"connections": {
"每小时触发": {
"main": [
[
{
"index": 0,
"node": "获取B站热搜",
"type": "main"
}
]
]
},
"获取B站热搜": {
"main": [
[
{
"index": 0,
"node": "生成飞书消息",
"type": "main"
}
]
]
},
"生成飞书消息": {
"main": [
[
{
"index": 0,
"node": "发送到飞书",
"type": "main"
}
]
]
}
},
"pinData": {},
"meta": {
"instanceId": "yyyyyyy"
}
}
向量化工作流
点击查看代码
{
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "手动触发",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
240,
300
]
},
{
"parameters": {
"operation": "download",
"options": {}
},
"id": "google-drive",
"name": "获取Google Drive文件",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "// 提取Google Drive文件内容\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n // 获取文件的二进制数据\n const binaryData = item.binary;\n let content = '';\n let fileName = '';\n let mimeType = '';\n \n if (binaryData && binaryData.data) {\n // 获取文件信息\n fileName = binaryData.data.fileName || 'unknown';\n mimeType = binaryData.data.mimeType || 'unknown';\n \n // 根据文件类型提取内容\n if (mimeType.includes('text/') || mimeType.includes('application/json')) {\n // 文本文件直接读取\n const buffer = Buffer.from(binaryData.data.data, 'base64');\n content = buffer.toString('utf8');\n } else if (mimeType.includes('application/pdf')) {\n // PDF文件需要特殊处理(这里简化处理)\n content = '[PDF文件内容 - 需要PDF解析器]';\n } else {\n content = '[二进制文件 - 无法直接提取文本内容]';\n }\n }\n \n results.push({\n json: {\n fileName: fileName,\n mimeType: mimeType,\n content: content,\n contentLength: content.length,\n timestamp: new Date().toISOString(),\n fileId: item.json?.id || 'unknown'\n }\n });\n } catch (error) {\n results.push({\n json: {\n error: `文件处理失败: ${error.message}`,\n timestamp: new Date().toISOString()\n }\n });\n }\n}\n\nreturn results;"
},
"id": "code-extract",
"name": "提取文件内容",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://your-vectorization-service.com/api/embed",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "Bearer YOUR_API_KEY"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{}
]
},
"options": {}
},
"id": "http-vectorize",
"name": "向量化服务",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
900,
300
]
},
{
"parameters": {
"jsCode": "// 处理向量化响应数据\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n const response = item.json;\n \n // 假设向量化服务返回的数据结构\n const vectorData = {\n id: `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n vector: response.embedding || response.vector || [],\n metadata: {\n fileName: response.metadata?.fileName || 'unknown',\n fileId: response.metadata?.fileId || 'unknown',\n mimeType: response.metadata?.mimeType || 'unknown',\n timestamp: response.metadata?.timestamp || new Date().toISOString(),\n contentLength: response.metadata?.contentLength || 0,\n model: response.model || 'unknown'\n },\n text: response.text || response.content || ''\n };\n \n // 验证向量数据\n if (!vectorData.vector || vectorData.vector.length === 0) {\n throw new Error('向量数据为空或无效');\n }\n \n results.push({\n json: vectorData\n });\n \n } catch (error) {\n results.push({\n json: {\n error: `向量数据处理失败: ${error.message}`,\n timestamp: new Date().toISOString()\n }\n });\n }\n}\n\nreturn results;"
},
"id": "code-process-vectors",
"name": "处理向量数据",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://your-milvus-endpoint.com/v1/vector/insert",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "Bearer YOUR_MILVUS_TOKEN"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{}
]
},
"options": {}
},
"id": "http-milvus",
"name": "存储到MilvusDB",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
300
]
},
{
"parameters": {
"jsCode": "// 处理最终存储结果\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n const response = item.json;\n \n const result = {\n success: true,\n message: '向量数据已成功存储到MilvusDB',\n timestamp: new Date().toISOString(),\n milvusResponse: response,\n status: response.code === 200 ? 'success' : 'warning'\n };\n \n results.push({\n json: result\n });\n \n } catch (error) {\n results.push({\n json: {\n success: false,\n error: `MilvusDB存储失败: ${error.message}`,\n timestamp: new Date().toISOString()\n }\n });\n }\n}\n\nreturn results;"
},
"id": "code-final-result",
"name": "处理最终结果",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
300
]
}
],
"connections": {
"手动触发": {
"main": [
[
{
"node": "获取Google Drive文件",
"type": "main",
"index": 0
}
]
]
},
"获取Google Drive文件": {
"main": [
[
{
"node": "提取文件内容",
"type": "main",
"index": 0
}
]
]
},
"提取文件内容": {
"main": [
[
{
"node": "向量化服务",
"type": "main",
"index": 0
}
]
]
},
"向量化服务": {
"main": [
[
{
"node": "处理向量数据",
"type": "main",
"index": 0
}
]
]
},
"处理向量数据": {
"main": [
[
{
"node": "存储到MilvusDB",
"type": "main",
"index": 0
}
]
]
},
"存储到MilvusDB": {
"main": [
[
{
"node": "处理最终结果",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"meta": {
"instanceId": "yyyy"
}
}
AI试衣
点击查看代码
mcp接口响应:
{
"success": true,
"data": {
"id": "xxxx",
"name": "AI虚拟试衣工作流",
"active": false,
"nodes": [
{
"id": "form-trigger",
"name": "虚拟试衣表单",
"parameters": {
"formDescription": "上传您的照片和想要试穿的服装图片,体验AI虚拟试衣效果!",
"formFields": {
"values": [
{
"fieldLabel": "您的照片URL",
"fieldOptions": {
"placeholder": "请输入您的照片链接地址"
},
"fieldType": "text",
"requiredField": true
},
{
"fieldLabel": "服装图片URL",
"fieldOptions": {
"placeholder": "请输入想要试穿的服装图片链接"
},
"fieldType": "text",
"requiredField": true
}
]
},
"formTitle": "AI虚拟试衣",
"options": {}
},
"position": [
240,
300
],
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.2
},
{
"id": "create-task",
"name": "创建AI试衣任务",
"parameters": {
"authentication": "none",
"contentType": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "Key YOUR_FAL_AI_API_KEY"
}
]
},
"jsonBody": "{\n \"human_image_url\": \"{{ $json.您的照片URL }}\",\n \"garment_image_url\": \"{{ $json.服装图片URL }}\",\n \"category\": \"tops\"\n}",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"specifyHeaders": "keypair",
"url": "https://queue.fal.run/fal-ai/kling/v1-5/kolors-virtual-try-on"
},
"position": [
460,
300
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "wait-10s-1",
"name": "等待10秒",
"parameters": {
"amount": 10,
"resume": "timeInterval",
"unit": "seconds"
},
"position": [
680,
300
],
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1
},
{
"id": "get-status-1",
"name": "获取状态",
"parameters": {
"authentication": "none",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Key YOUR_FAL_AI_API_KEY"
}
]
},
"method": "GET",
"options": {},
"sendHeaders": true,
"specifyHeaders": "keypair",
"url": "={{ 'https://queue.fal.run/fal-ai/kling/v1-5/kolors-virtual-try-on/requests/' + $node['创建AI试衣任务'].json.request_id }}"
},
"position": [
900,
300
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "check-completed-1",
"name": "是否完成?",
"parameters": {
"conditions": {
"combinator": "and",
"conditions": [
{
"id": "status-check",
"leftValue": "={{ $json.status }}",
"operator": {
"operation": "equals",
"type": "string"
},
"rightValue": "COMPLETED"
}
],
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
}
},
"options": {}
},
"position": [
1120,
300
],
"type": "n8n-nodes-base.if",
"typeVersion": 2.2
},
{
"id": "wait-10s-2",
"name": "等待10秒(第2次)",
"parameters": {
"amount": 10,
"resume": "timeInterval",
"unit": "seconds"
},
"position": [
1340,
400
],
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1
},
{
"id": "get-status-2",
"name": "获取状态(第2次)",
"parameters": {
"authentication": "none",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Key YOUR_FAL_AI_API_KEY"
}
]
},
"method": "GET",
"options": {},
"sendHeaders": true,
"specifyHeaders": "keypair",
"url": "={{ 'https://queue.fal.run/fal-ai/kling/v1-5/kolors-virtual-try-on/requests/' + $node['创建AI试衣任务'].json.request_id }}"
},
"position": [
1560,
400
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "check-completed-2",
"name": "是否完成?(第2次)",
"parameters": {
"conditions": {
"combinator": "and",
"conditions": [
{
"id": "status-check",
"leftValue": "={{ $json.status }}",
"operator": {
"operation": "equals",
"type": "string"
},
"rightValue": "COMPLETED"
}
],
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
}
},
"options": {}
},
"position": [
1780,
400
],
"type": "n8n-nodes-base.if",
"typeVersion": 2.2
},
{
"id": "wait-10s-3",
"name": "等待10秒(第3次)",
"parameters": {
"amount": 10,
"resume": "timeInterval",
"unit": "seconds"
},
"position": [
2000,
500
],
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1
},
{
"id": "get-final-result",
"name": "获取最终结果",
"parameters": {
"authentication": "none",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Key YOUR_FAL_AI_API_KEY"
}
]
},
"method": "GET",
"options": {},
"sendHeaders": true,
"specifyHeaders": "keypair",
"url": "={{ 'https://queue.fal.run/fal-ai/kling/v1-5/kolors-virtual-try-on/requests/' + $node['创建AI试衣任务'].json.request_id }}"
},
"position": [
2220,
500
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
},
{
"id": "request-result-image",
"name": "请求结果图片",
"parameters": {
"authentication": "none",
"method": "GET",
"options": {},
"url": "={{ $json.output && $json.output.image ? $json.output.image.url : '' }}"
},
"position": [
1340,
200
],
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2
}
],
"connections": {
"虚拟试衣表单": {
"main": [
[
{
"index": 0,
"node": "创建AI试衣任务",
"type": "main"
}
]
]
},
"创建AI试衣任务": {
"main": [
[
{
"index": 0,
"node": "等待10秒",
"type": "main"
}
]
]
},
"等待10秒": {
"main": [
[
{
"index": 0,
"node": "获取状态",
"type": "main"
}
]
]
},
"获取状态": {
"main": [
[
{
"index": 0,
"node": "是否完成?",
"type": "main"
}
]
]
},
"是否完成?": {
"main": [
[
{
"index": 0,
"node": "请求结果图片",
"type": "main"
}
],
[
{
"index": 0,
"node": "等待10秒(第2次)",
"type": "main"
}
]
]
},
"等待10秒(第2次)": {
"main": [
[
{
"index": 0,
"node": "获取状态(第2次)",
"type": "main"
}
]
]
},
"获取状态(第2次)": {
"main": [
[
{
"index": 0,
"node": "是否完成?(第2次)",
"type": "main"
}
]
]
},
"是否完成?(第2次)": {
"main": [
[
{
"index": 0,
"node": "请求结果图片",
"type": "main"
}
],
[
{
"index": 0,
"node": "等待10秒(第3次)",
"type": "main"
}
]
]
},
"等待10秒(第3次)": {
"main": [
[
{
"index": 0,
"node": "获取最终结果",
"type": "main"
}
]
]
},
"获取最终结果": {
"main": [
[
{
"index": 0,
"node": "请求结果图片",
"type": "main"
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"timezone": "Asia/Shanghai",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"staticData": null,
"meta": null,
"pinData": null,
"versionId": "a78ebe7d-61a3-49b7-a6da-9e63d4c0b8d9",
"triggerCount": 0
},
"message": "Workflow \"AI虚拟试衣工作流\" updated successfully"
}