⚡AI驱动的YouTube播放列表和视频摘要与分析v2
高级
这是一个Other, AI领域的自动化工作流,包含 72 个节点。主要使用 If, Set, Code, Limit, Merge 等节点,结合人工智能技术实现智能自动化。 AI YouTube播放列表与视频分析聊天机器人
前置要求
- •Redis 服务器连接信息
- •可能需要目标 API 的认证凭证
- •Qdrant 服务器连接信息
- •Google Gemini API Key
使用的节点 (72)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "4Tq5HZBdETVe7jEb",
"meta": {
"instanceId": "2cb7a61f866faf57392b91b31f47e08a2b3640258f0abd08dd71f087f3243a5a",
"templateCredsSetupCompleted": true
},
"name": "⚡AI驱动的 YouTube 播放列表和视频摘要与分析 v2",
"tags": [],
"nodes": [
{
"id": "505077d1-a2e4-4b0d-99d6-756940022c3d",
"name": "Google Gemini聊天模型1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-440,
-40
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-pro-exp"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "5da369db-b801-4653-888d-0e6042620298",
"name": "处理查询",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-160,
-280
],
"parameters": {
"text": "={{ $('Chat').item.json.chatInput }}",
"options": {
"systemMessage": "=You are an intelligent assistant that can respond to queries related to the content of a Youtube Playlist or a single Video.\n\n# YOUR TASK\nDecide if the user has provided the info required and reply accordingly. If there is no url in context you have to suggest the user to provide one. \n\n\n1. If the user provided a YouTube Playlist or Video URL: reply in structured markdown format to the user based on the formulated questions and context. Assume the user is here because they don't won't / have time to watch such videos so:\n- Use the tool called `chat_playlist_data`, which can analyze YouTube videos. Use this tool effectively to process video content and generate structured summaries.\n- Your answers needs to be exhaustive and minimise bullet points\n- Be verbose in your response\n\n2. 1. If the user provided a YouTube Playlist or Video URL:\n- Do not ask for more specific details - always try to summarize the videos with the data from the tool `chat_playlist_data`\n- Never reply \"already provided a detailed summary\" - always try to summarize again the videos with more info from the tool `chat_playlist_data` - even if you have already provided the data before (so repeat yourself).\n\n3. If the user HAS NOT provided a YouTube Playlist URL or a invaid URL: gently invite the user to provide the URL so you can process it. \n\n# Rules\n\n## YouTube Playlist URL Definition\n- A URL from www.youtube.com or youtube.com.\n- Contains the query parameter `list=` followed by a playlist ID.\n- Example: https://www.youtube.com/playlist?list=PLXXXXXX (where PLXXXXXX is the playlist ID).\n\n## YouTube Video URL Definition\n- A URL from www.youtube.com, youtube.com, or youtu.be.\n- For www.youtube.com or youtube.com, it contains the query parameter `v=` followed by a video ID.\n- For youtu.be, it follows the format https://youtu.be/VIDEO_ID.\n- Examples:\n - https://www.youtube.com/watch?v=VIDEO_ID (where VIDEO_ID is the video ID).\n - https://youtu.be/VIDEO_ID\n- Does NOT have a query parameter `list=`\n\n# Context\n{\n \"intent\": {{ $('Default Intent').item.json.output?.intent || \"NONE\" }},\n \"url\": {{ $('Default Intent').item.json.output?.url || \"\" }},\n \"id\": {{ $('Default Intent').item.json.output?.id || \"\" }},\n \"limit\": {{ $('Default Intent').item.json.output?.limit || 0 }},\n \"status\": {{ $('Default Intent').item.json.output?.status || 'PENDING' }}\n}"
},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "866bf387-3482-4615-94d5-fd72d5db21da",
"name": "分离",
"type": "n8n-nodes-base.splitOut",
"position": [
1380,
-200
],
"parameters": {
"include": "selectedOtherFields",
"options": {},
"fieldToSplitOut": "transcript",
"fieldsToInclude": "youtubeId"
},
"typeVersion": 1
},
{
"id": "359404ce-4bc9-4e4d-9a26-22b9f9b176c9",
"name": "摘要与分析转录文本",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
660,
540
],
"parameters": {
"text": "=Please analyze the given text and create a structured summary following these guidelines:\n\n1. Break down the content into main topics using Level 2 headers (##)\n2. Under each header:\n - List only the most essential concepts and key points\n - Use bullet points for clarity\n - Keep explanations concise\n - Preserve technical accuracy\n - Highlight key terms in bold\n3. Format requirements:\n - Use markdown formatting\n - Keep bullet points simple (no nesting)\n - Bold important terms using **term**\n - Use tables for comparisons\n - Include relevant technical details\n\nPlease provide a clear, structured summary that captures the core concepts while maintaining technical accuracy.\n\n**Make sure the summary is 300-400 max characters long.**\n\nInclude metadata such as video number, id, and title in the summary.\n\n**Here is the text**\n\nVideo number: {{ $json.video_number }}\nTitle: {{ $json.title }}\nYoutube ID: {{ $json.youtubeId }}\nTranscript:\n{{ $json.transcript_text }}",
"promptType": "define"
},
"typeVersion": 1.4
},
{
"id": "036765df-6da4-4430-bcea-af4066fb7c24",
"name": "连接",
"type": "n8n-nodes-base.summarize",
"position": [
1700,
-200
],
"parameters": {
"options": {},
"fieldsToSplitBy": "youtubeId",
"fieldsToSummarize": {
"values": [
{
"field": "transcript.text",
"separateBy": " ",
"aggregation": "concatenate"
}
]
}
},
"typeVersion": 1
},
{
"id": "9152725c-15ca-41a0-8f98-108834e0c8be",
"name": "分离1",
"type": "n8n-nodes-base.splitOut",
"position": [
660,
40
],
"parameters": {
"options": {},
"fieldToSplitOut": "videos"
},
"typeVersion": 1
},
{
"id": "fee0e045-614a-41f0-ac75-051dff773e77",
"name": "限制",
"type": "n8n-nodes-base.limit",
"position": [
860,
40
],
"parameters": {
"maxItems": "={{ $('Update Context Intent').item.json.output.limit }}"
},
"typeVersion": 1
},
{
"id": "a691c1c7-d8c4-4eab-861b-f7cfcbeb0fc8",
"name": "Qdrant Vector Store",
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
"position": [
1680,
480
],
"parameters": {
"mode": "insert",
"options": {},
"qdrantCollection": {
"__rl": true,
"mode": "id",
"value": "={{ $('Update Context Intent').first().json.output.id }}"
}
},
"credentials": {
"qdrantApi": {
"id": "mb8rw8tmUeP6aPJm",
"name": "QdrantApi account"
}
},
"typeVersion": 1
},
{
"id": "c3949e8f-0deb-4106-aaa5-e64403024243",
"name": "递归字符文本分割器",
"type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
"position": [
1800,
860
],
"parameters": {
"options": {},
"chunkSize": 1200,
"chunkOverlap": 200
},
"typeVersion": 1
},
{
"id": "a3b23c6b-14c0-4805-8220-7c6166268276",
"name": "Google Gemini 嵌入",
"type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
"position": [
1660,
700
],
"parameters": {
"modelName": "models/text-embedding-004"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "6f4ee00d-dcdc-4468-9713-115912c1e571",
"name": "Google Gemini 聊天模型2",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
640,
740
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "75442631-512e-4b0e-a5a8-ed3e3a3e1f94",
"name": "嵌入 Google Gemini1",
"type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
"position": [
-120,
320
],
"parameters": {
"modelName": "models/text-embedding-004"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "c33ceeca-9bf0-4dd2-b8df-fc2a1ccdf512",
"name": "聊天",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
-2780,
-460
],
"webhookId": "e66183cc-1eed-4968-b34b-bcecf1bb55e8",
"parameters": {
"public": true,
"options": {
"loadPreviousSession": "notSupported"
},
"initialMessages": "Hi there! 👋\nPlease provide a URL of a Youtube playlist you would like me to analise."
},
"typeVersion": 1.1
},
{
"id": "06bb2dfd-027c-4902-9559-2de080f6c145",
"name": "视频标题",
"type": "n8n-nodes-base.splitOut",
"position": [
1160,
40
],
"parameters": {
"options": {},
"fieldToSplitOut": "id,title"
},
"typeVersion": 1
},
{
"id": "d9bbdda2-ef26-4b6c-94d0-7542f88f1530",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
1700,
20
],
"parameters": {
"mode": "combineBySql",
"query": "SELECT \n ROW_NUMBER() AS video_number,\n input1.youtubeId, input2.title, input1.concatenated_transcript_text as transcript_text FROM input1 LEFT JOIN input2 ON input1.youtubeId = input2.id"
},
"typeVersion": 3
},
{
"id": "e6febdb9-370b-478d-b470-d6c2d3314d7b",
"name": "编辑字段",
"type": "n8n-nodes-base.set",
"position": [
980,
540
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b5e935c5-4973-40a3-adb9-fa76904d2ed9",
"name": "video_number",
"type": "number",
"value": "={{ $('Merge').item.json.video_number }}"
},
{
"id": "e98f417d-123f-4a85-b2f7-64430e7b0250",
"name": "youtubeId",
"type": "string",
"value": "={{ $('Merge').item.json.youtubeId }}"
},
{
"id": "d0ced7fd-c9a3-4a09-bf09-e4b5e45dd03d",
"name": "title",
"type": "string",
"value": "={{ $('Merge').item.json.title }}"
},
{
"id": "31a80e6d-9b02-4d21-b888-1a00c036a04b",
"name": "summary",
"type": "string",
"value": "={{ $json.text }}"
},
{
"id": "ef12f3f2-3d63-4e78-835f-7da004393a07",
"name": "transcript_text",
"type": "string",
"value": "={{ $('Merge').item.json.transcript_text }}"
},
{
"id": "afa841e2-6f8b-4b27-9f28-10368ee32c2e",
"name": "playlistId",
"type": "string",
"value": "={{ $('Update Context Intent').first().json.output.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2736a1e6-a982-4f60-aa8d-658e9a3e9193",
"name": "AI 代理",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2720,
480
],
"parameters": {
"text": "=Please analyze the given \"Transcript summary\" and create a full summary overview, following the below guidelines.\n\n1. Provide a full descriptive break down of the content of each video. Assume the user does not won't or have time to watch such videos, so:\n- Your summary needs to be exhaustive, descriptive, and minimise bullet points\n- Your summary needs to captures all the core concepts while maintaining technical accuracy\n- Your summary will be verbose\n\n2. Consider that the intent of the user is not to watch the videos but rather have all the content required and summaries from on the \"Transcript summary\".\n\n3. Use the tool called `chat_playlist_data`, which can analyze YouTube videos. Use this tool effectively to process video content and generate structured summaries.\n\nUser message:\n{{ $('Chat').item.json.chatInput }}\n\nTranscript summary:\n{{ $('Full Summary').item.json.concatenated_summary }}",
"agent": "conversationalAgent",
"options": {},
"promptType": "define"
},
"executeOnce": true,
"typeVersion": 1.7
},
{
"id": "cf1e5c50-9c89-465f-beca-482cbd9affba",
"name": "Google Gemini 聊天模型4",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
2520,
720
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "1cbcec5e-b58c-4fce-b944-08c47a7385db",
"name": "删除集合",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
1340,
480
],
"parameters": {
"url": "=https://3114dbb7-bd13-4807-8815-c3c8784f66d6.eu-west-1-0.aws.cloud.qdrant.io/collections/{{ $('Update Context Intent').first().json.playlistID }}/points/delete",
"method": "POST",
"options": {},
"jsonBody": "{\n \"filter\": {}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "qdrantApi"
},
"credentials": {
"qdrantApi": {
"id": "mb8rw8tmUeP6aPJm",
"name": "QdrantApi account"
}
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "16d7eaec-1d98-42dd-89aa-2681a9d1697d",
"name": "默认数据加载器",
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
"position": [
1820,
700
],
"parameters": {
"options": {
"metadata": {
"metadataValues": [
{
"name": "video_number",
"value": "={{ $input.item.json.video_number }}"
},
{
"name": "=youtubeId",
"value": "={{ $input.item.json.youtubeId }}"
},
{
"name": "summary",
"value": "={{ $input.item.json.summary }}"
},
{
"name": "title",
"value": "={{ $input.item.json.title }}"
},
{
"name": "playlistId",
"value": "={{ $input.item.json.playlistId }}"
}
]
}
}
},
"typeVersion": 1
},
{
"id": "d3bc9db2-7618-46fd-b825-2cc0ad45fc22",
"name": "聊天缓冲记忆",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-260,
-40
],
"parameters": {
"sessionKey": "={{ $('Chat').item.json.sessionId }}",
"sessionIdType": "customKey",
"contextWindowLength": 10
},
"typeVersion": 1.3
},
{
"id": "fb55b13f-880f-404c-9446-9f8a238c8a5c",
"name": "完整摘要",
"type": "n8n-nodes-base.summarize",
"position": [
2520,
480
],
"parameters": {
"options": {},
"fieldsToSummarize": {
"values": [
{
"field": "summary",
"separateBy": "\n",
"aggregation": "concatenate"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "efc2fc99-c738-425e-9ee1-669e378e197f",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-400
],
"parameters": {
"color": 7,
"width": 1080,
"height": 900,
"content": "## RAG 与回复用户查询"
},
"typeVersion": 1
},
{
"id": "0040adf6-2cfc-4b79-ba81-b76740bfd158",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
-400
],
"parameters": {
"color": 7,
"width": 1380,
"height": 700,
"content": "## 获取并准备播放列表视频转录数据以供处理"
},
"typeVersion": 1
},
{
"id": "59b29fc5-5d88-40c0-a273-9dc71d9f009e",
"name": "聊天缓冲记忆1",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
2700,
720
],
"parameters": {
"sessionKey": "={{ $('Chat').item.json.sessionId }}",
"sessionIdType": "customKey",
"contextWindowLength": 10
},
"typeVersion": 1.3
},
{
"id": "afb6f6be-d299-402e-a9c6-2c60fbf4974e",
"name": "YouTube 转录文本",
"type": "n8n-nodes-youtube-transcription-dmr.youtubeTranscripter",
"position": [
1160,
-200
],
"parameters": {
"videoId": "={{ $json.id }}",
"continueOnFail": true
},
"typeVersion": 1
},
{
"id": "9a43eb4a-9d4c-4fce-b19c-94485aa8af76",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
340
],
"parameters": {
"color": 7,
"width": 640,
"height": 700,
"content": "## 摘要与分析转录文本"
},
"typeVersion": 1
},
{
"id": "676677f0-3dae-4873-a449-92930ced534e",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1240,
340
],
"parameters": {
"color": 7,
"width": 980,
"height": 700,
"content": "## 存储嵌入向量"
},
"typeVersion": 1
},
{
"id": "9ecd4d0d-b8bd-4a5d-981f-49fa515c17be",
"name": "便签6",
"type": "n8n-nodes-base.stickyNote",
"position": [
2260,
340
],
"parameters": {
"color": 7,
"width": 940,
"height": 880,
"content": "## 首次摘要分析"
},
"typeVersion": 1
},
{
"id": "45ae8377-0848-45fb-a7a3-aba8d44e3d35",
"name": "消息意图",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"position": [
-2260,
-460
],
"parameters": {
"text": "= {{ $('Chat').item.json.chatInput }}",
"options": {
"systemMessage": "=**# YOUR TASK:**\nPlease analyze the user's message and decide if the user has provided the info required - and ALWAYS reply using the **Output format** defined below.\n\n# Output format\nYou use the following JSON structure to reply, don't include anything else, and alway inlude all the fields:\n```\n{\n \"intent\": PLAYLIST|VIDEO|NONE,\n \"url\": Youtube Playlist or Video URL or empty string,\n \"id\": Youtube Playlist or Video ID or empty string,\n \"limit\": number, default 0,\n \"status\": Previous context status `{{ $json.context_intent?.status }}` or \"PENDING\"\n}\n```\n\n## INTENT field GUIDELINES:\n\n**Respond with \"PLAYLIST\" if:**\n- The messsage contains a valid Youtube Playlist URL\n\n**Respond with \"VIDEO\" if:**\n- The messsage contains a valid Youtube Video URL\n\n**Respond with \"NONE\" if:**\n- The messsage does not contains a valid Youtube Playlist or Video URL\n\n## LIMIT field GUIDELINE:\nIf the \"Previous Context\" intent or the current intent is a Playlist: Based on current or most recent user message, check if there is an indication of how many videos to process, otherwise default to 0.\n\n## STATUS field GUIDELINE\nIf intent is a Playlist or Video and _different_ from the the \"Previous Context\" then use \"PENDING\" since the user intent is to run a new process. Otherwise use the \"Previous Context\" status value.\n\n\n# Rules for Playlist and Video\n\n## YouTube Playlist URL Definition\n- A URL from www.youtube.com or youtube.com.\n- Contains the query parameter `&list=` followed by a playlist ID.\n- Example: https://www.youtube.com/...&list=PLXXXXXX (where PLXXXXXX is the playlist ID).\n\n## YouTube Video URL Definition\n- A URL from www.youtube.com, youtube.com, or youtu.be.\n- For www.youtube.com or youtube.com, it contains the query parameter `v=` followed by a video ID.\n- For youtu.be, it follows the format https://youtu.be/VIDEO_ID.\n- Examples:\n - https://www.youtube.com/watch?v=VIDEO_ID (where VIDEO_ID is the video ID).\n - https://youtu.be/VIDEO_ID\n- IMPORTANT: YouTube Video URL **Does NOT have a query parameter `list=`**\n\n\n# Previous Context\n{{ JSON.stringify($json.context_intent) }}\n"
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 1.7,
"alwaysOutputData": true
},
{
"id": "a29403f0-ed5c-465c-a43d-84e8dee69662",
"name": "结构化输出解析器1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-2020,
-220
],
"parameters": {
"jsonSchemaExample": "{\n \"intent\": \"PLAYLIST|VIDEO|NONE\",\n \"url\": \"Youtube Playlist or Video URL or empty string,\",\n \"id\": \"Youtube Playlist or Video ID or empty string,\",\n \"limit\": \"number of playlist videos to process or 0\",\n \"status\": \"PENDING|READY|DONE\"\n}"
},
"typeVersion": 1.2
},
{
"id": "b3f43325-aa7b-47f3-8bbb-e27fa0c44a1e",
"name": "更新上下文意图",
"type": "n8n-nodes-base.redis",
"position": [
-1160,
-640
],
"parameters": {
"key": "=context_intent_{{ $('Chat').item.json.sessionId }}",
"value": "=intent {{ $('Process Status').item.json.output?.intent || null }} url {{ $('Process Status').item.json.output?.url || \"\" }} id {{ $('Process Status').item.json.output?.id || \"\" }} limit {{ $('Process Status').item.json.output?.limit || 0 }} status {{ $('Process Status').item.json.output?.status || 'PENDING' }}",
"keyType": "hash",
"operation": "set",
"valueIsJSON": false
},
"credentials": {
"redis": {
"id": "mA0f9F1ROUThyrRW",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "9c296501-049c-44b5-ae8d-8dda2c523278",
"name": "获取先前上下文意图",
"type": "n8n-nodes-base.redis",
"onError": "continueRegularOutput",
"position": [
-2440,
-460
],
"parameters": {
"key": "=context_intent_{{ $('Chat').item.json.sessionId }}",
"keyType": "hash",
"options": {
"dotNotation": false
},
"operation": "get",
"valueIsJSON": false,
"propertyName": "context_intent"
},
"credentials": {
"redis": {
"id": "mA0f9F1ROUThyrRW",
"name": "Redis account"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "9499d9f2-7d6d-432b-bdde-b4b98b90b224",
"name": "路由消息意图",
"type": "n8n-nodes-base.switch",
"position": [
-1700,
-460
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "PROCESS",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "44e008af-4a1a-429d-adb6-039e74b643a6",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ \n ($json.output.intent == \"VIDEO\" || $json.output.intent == \"PLAYLIST\")\n && $json.output.status != \"DONE\" \n}}",
"rightValue": "/PLAYLIST|VIDEO/"
}
]
},
"renameOutput": true
},
{
"outputKey": "QUERY",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "12641857-945d-4470-968e-f3f805bfe1cd",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ \n $json.output.intent == \"NONE\" || $json.output.status == \"DONE\"\n}}",
"rightValue": "NONE"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3.2
},
{
"id": "dfd329e8-98b1-4ae8-8cc1-a1a5215b1c09",
"name": "处理状态",
"type": "n8n-nodes-base.code",
"position": [
-1340,
-640
],
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nif ($input.last().json.output.intent == 'VIDEO') {\n $input.last().json.output.status = 'READY'\n}\n\nelse if ($input.last().json.output.intent == 'PLAYLIST' && parseInt($input.last().json.output.limit) > 0) {\n $input.last().json.output.status = 'READY'\n}\n\nelse {\n $input.last().json.output = {\n intent: $('Default Intent').first().json.output.intent,\n url: $('Default Intent').first().json.output.url,\n id: $('Default Intent').first().json.output.id,\n limit: $('Default Intent').first().json.output.limit,\n status: 'PENDING',\n }\n}\n\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "c84c21c6-0948-4628-b7a8-7cd2a5602cdc",
"name": "简单记忆",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-2180,
-220
],
"parameters": {
"sessionKey": "=intent_{{ $('Chat').item.json.sessionId }}",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "8ad2e1d1-b657-47cd-afa5-4fc49dc7e0e6",
"name": "简单记忆3",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
1040,
-1180
],
"parameters": {
"sessionKey": "=pl_n_{{ $('Chat').item.json.sessionId }}",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "cf4d7c73-d9b1-42da-9ccc-6abccde4110c",
"name": "便签7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2540,
-620
],
"parameters": {
"color": 7,
"width": 1080,
"height": 580,
"content": "## 消息意图路由"
},
"typeVersion": 1
},
{
"id": "c0ecf826-b803-421a-aa2a-cb713131fbb7",
"name": "Google Gemini 聊天模型6",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-2340,
-220
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash-lite"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "513a8c71-e982-47df-87bd-1e3d3ae9c613",
"name": "便签9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1420,
-1020
],
"parameters": {
"color": 7,
"width": 460,
"height": 580,
"content": "## 更新上下文"
},
"typeVersion": 1
},
{
"id": "d4a9d7aa-dfee-418f-900a-9649f0405861",
"name": "便签 10",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
-1480
],
"parameters": {
"color": 7,
"width": 480,
"height": 460,
"content": "## 询问要处理的播放列表视频数量"
},
"typeVersion": 1
},
{
"id": "f89481a4-64a2-4895-821f-effe48f7d331",
"name": "视频数量",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
900,
-1380
],
"parameters": {
"text": "={{ $('Chat').item.json.chatInput }}",
"options": {
"systemMessage": "=**Objective:**\n\nWe are here because the user wants to analyse a playlist in context, but we are missing how many videos he would like to process. Please reply to the user asking user to provide a number.\n\n## Context\n{{ JSON.stringify($json.output) }}"
},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "bb8fa898-4953-4148-8339-2d205b86fc91",
"name": "默认意图",
"type": "n8n-nodes-base.code",
"position": [
-1920,
-460
],
"parameters": {
"jsCode": "if(\n ($('Message Intent').first().json?.output?.intent == 'NONE' \n || Object.keys($('Message Intent').first().json?.output || {}).length == 0)\n && Object.keys($('Get Previous Context Intent').first().json.context_intent).length > 0\n) {\n //use prev context intent\n if(!$input.first().json.output) {\n $input.first().json.output = {}\n }\n $input.first().json.output.intent = $('Get Previous Context Intent').first().json.context_intent?.intent || \"NONE\";\n $input.first().json.output.url = $('Get Previous Context Intent').first().json.context_intent?.url || \"\";\n $input.first().json.output.id = $('Get Previous Context Intent').first().json.context_intent?.id || \"\";\n $input.first().json.output.limit = $('Get Previous Context Intent').first().json.context_intent?.limit || 0;\n $input.first().json.output.status = $('Get Previous Context Intent').first().json.context_intent?.status || \"PENDING\";\n} else {\n // $input.first().json.output.intent = $('Message Intent').first().json.context_intent?.intent || \"NONE\";\n // $input.first().json.output.url = $('Message Intent').first().json.context_intent?.url || \"\";\n // $input.first().json.output.id = $('Message Intent').first().json.context_intent?.id || \"\";\n // $input.first().json.output.limit = $('Message Intent').first().json.context_intent?.limit || 0;\n // $input.first().json.output.status = $('Message Intent').first().json.context_intent?.status || \"PENDING\";\n}\n\n// else use message intent\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "0f1d1bb6-22c0-4b65-beaf-eaf4401d7550",
"name": "播放列表限制",
"type": "n8n-nodes-base.if",
"position": [
160,
-860
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6ee01f0a-9533-4fb3-b023-cfcc422d9011",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Process Status').item.json.output.intent }}",
"rightValue": "PLAYLIST"
},
{
"id": "e9575bb2-3c60-498b-b2c7-436b62e5195c",
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ parseInt($('Process Status').item.json.output.limit) }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "083c17aa-de83-4f3c-8d71-f665903ce3d5",
"name": "播放列表或视频",
"type": "n8n-nodes-base.switch",
"position": [
160,
-640
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "VIDEO",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cc3ec644-7c3d-4d9f-b7a7-89b85824e3e3",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Route Message Intent').item.json.output.intent }}",
"rightValue": "VIDEO"
}
]
},
"renameOutput": true
},
{
"outputKey": "PLAYLIST",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "33beac83-b96b-4e76-9d18-e22df163ea4d",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Route Message Intent').item.json.output.intent }}",
"rightValue": "PLAYLIST"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "0626bc2d-9e3a-4a4f-a331-62e8b6c69840",
"name": "获取摘要字段",
"type": "n8n-nodes-base.code",
"position": [
2340,
480
],
"parameters": {
"jsCode": "return $('Edit Fields').all();"
},
"typeVersion": 2
},
{
"id": "4aaaed18-ce90-4296-aba2-b5fa9492655d",
"name": "更新上下文处理完成1",
"type": "n8n-nodes-base.redis",
"position": [
2040,
480
],
"parameters": {
"key": "=context_intent_{{ $('Chat').first().json.sessionId }}",
"value": "=intent {{ $('Process Status').first().json.output?.intent || null }} url {{ $('Process Status').first().json.output?.url || \"\" }} id {{ $('Process Status').first().json.output?.id || \"\" }} limit {{ $('Process Status').first().json.output?.limit || 0 }} status DONE",
"keyType": "hash",
"operation": "set",
"valueIsJSON": false
},
"credentials": {
"redis": {
"id": "mA0f9F1ROUThyrRW",
"name": "Redis account"
}
},
"executeOnce": true,
"typeVersion": 1
},
{
"id": "d3f71d59-a158-43e2-bfbd-a3bec20dea5b",
"name": "Google Gemini 聊天模型8",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
880,
-1180
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash-thinking-exp"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "fa6b8a79-436e-4742-9732-cd5c1b2d3c88",
"name": "播放列表 HTTP 请求",
"type": "n8n-nodes-base.httpRequest",
"position": [
660,
-200
],
"parameters": {
"url": "={{ $('Update Context Intent').item.json.output.url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "99d6da96-8983-4e7d-8343-b9ac975f5d20",
"name": "YouTube 转录文本1",
"type": "n8n-nodes-youtube-transcription-dmr.youtubeTranscripter",
"position": [
1200,
-860
],
"parameters": {
"videoId": "={{ $('Update Context Intent').item.json.output.id }}",
"continueOnFail": true
},
"typeVersion": 1
},
{
"id": "41a07802-3fba-42fa-8482-f526a7e1b173",
"name": "视频 HTTP 请求",
"type": "n8n-nodes-base.httpRequest",
"position": [
900,
-860
],
"parameters": {
"url": "={{ $('Update Context Intent').item.json.output.url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "02103114-9c22-49b1-9dee-746b84cdef66",
"name": "获取标题和描述",
"type": "n8n-nodes-base.code",
"position": [
1200,
-640
],
"parameters": {
"jsCode": "/**\n * This code node contains a modified version of play-dl,\n * which is licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * Original Library Name: play-dl\n * Original Library Source: https://github.com/play-dl/play-dl/tree/main\n * Original Library License: GNU General Public License Version 3 (GPLv3)\n * (See: https://www.gnu.org/licenses/gpl-3.0.en.html)\n *\n * Modifications were made to the original library for use within this N8N workflow.\n * These modifications are also licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n\n/**\n * Basic function to get specific data (title, description, duration)\n * from pre-fetched HTML body data of a YouTube video page.\n * Assumes the HTML body is passed as the first argument.\n *\n * @param {string} body HTML body data of the YouTube video page.\n * @param {string} video_id YouTube video ID.\n * @param {string} url YouTube video URL.\n * @returns {Promise<{title: string, description: string, duration: number}>} Video Basic Info.\n * @throws {Error} If video ID cannot be extracted, captcha is detected,\n * or necessary data cannot be parsed.\n */\nasync function video_basic_info(body, video_id, url) {\n // --- Input Validation ---\n if (typeof body !== 'string') {\n throw new Error('body parameter must be a string of HTML');\n }\n if (typeof video_id !== 'string' || !video_id.trim()) {\n throw new Error('video_id parameter must be a non-empty string');\n }\n if (typeof url !== 'string' || !url.trim()) {\n throw new Error('url parameter must be a non-empty URL string');\n }\n\n // --- Captcha Check ---\n // Added check for consent page as well\n if (body.includes('Our systems have detected unusual traffic') || body.includes('consent.google.com')) {\n throw new Error('Captcha or Consent page encountered: YouTube likely requires interaction or detected bot-like activity.');\n }\n\n // --- Extract Player Data ---\n let player_data;\n try {\n // More robust regex to find ytInitialPlayerResponse, stopping at the next semicolon\n const player_data_match = body.match(/var ytInitialPlayerResponse\\s*=\\s*({.+?});\\s*(?:var |<\\/script)/);\n if (!player_data_match || !player_data_match[1]) {\n // Fallback attempt with simpler split (less reliable)\n const split_data = body.split('var ytInitialPlayerResponse = ');\n player_data = split_data?.[1]?.split(';</script>')?.[0];\n } else {\n player_data = player_data_match[1];\n }\n\n if (!player_data) {\n // Check for common failure indicators in the HTML if data isn't found\n if (body.includes('<title>YouTube</title>') && !body.includes('videoDetails')) {\n throw new Error('Could not find ytInitialPlayerResponse data. The page might be a generic YouTube page, not a video page, or the structure changed.');\n }\n throw new Error('Could not find ytInitialPlayerResponse data.');\n }\n } catch (error) {\n console.log(\"Error during player_data extraction:\", error);\n throw new Error(`Failed during player data extraction: ${error}`);\n }\n\n\n let player_response;\n try {\n player_response = JSON.parse(player_data);\n } catch (e) {\n console.log(\"Raw player_data that failed parsing:\", player_data.substring(0, 500) + '...'); // Log start of data\n throw new Error(`Failed to parse ytInitialPlayerResponse JSON: ${e}`);\n }\n\n // --- Extract Required Video Details ---\n // Use optional chaining for safety\n const vid = player_response?.videoDetails;\n\n // Add more robust checking, including playability status\n if (!vid) {\n const playabilityStatus = player_response?.playabilityStatus;\n let reason = \"videoDetails object not found in the response.\";\n if (playabilityStatus?.status && playabilityStatus.status !== 'OK') {\n reason = ` Playability status: ${playabilityStatus.status}. Reason: ${playabilityStatus.reason ||\n playabilityStatus.errorScreen?.playerErrorMessageRenderer?.reason?.simpleText ||\n playabilityStatus.errorScreen?.playerKavRenderer?.reason?.simpleText ||\n 'No specific reason provided.'\n }`;\n } else if (playabilityStatus?.status === 'OK' && !vid) {\n reason = \" Playability status is OK, but videoDetails is still missing. Response structure might have changed.\";\n }\n throw new Error(`Could not get video details. ${reason}`);\n }\n\n // --- Return Simplified Data ---\n // Ensure values exist before accessing, provide defaults if necessary\n return {\n title: vid.title || 'N/A',\n description: vid.shortDescription || '', // Default to empty string if missing\n duration: Number(vid.lengthSeconds) || 0, // Default to 0 if missing/invalid\n };\n}\n\n\nreturn video_basic_info($input.first().json.data, $('Update Context Intent').first().json.output.id, $('Update Context Intent').first().json.output.url);"
},
"retryOnFail": true,
"typeVersion": 2,
"alwaysOutputData": true,
"waitBetweenTries": 500
},
{
"id": "6cea694e-0147-4cd0-a601-9b232813a1d3",
"name": "拆分输出2",
"type": "n8n-nodes-base.splitOut",
"position": [
1440,
-860
],
"parameters": {
"include": "selectedOtherFields",
"options": {},
"fieldToSplitOut": "transcript",
"fieldsToInclude": "youtubeId"
},
"typeVersion": 1
},
{
"id": "c479fb51-e17d-477a-b40b-65458cc3e679",
"name": "连接1",
"type": "n8n-nodes-base.summarize",
"position": [
1660,
-860
],
"parameters": {
"options": {},
"fieldsToSplitBy": "youtubeId",
"fieldsToSummarize": {
"values": [
{
"field": "transcript.text",
"separateBy": " ",
"aggregation": "concatenate"
}
]
}
},
"typeVersion": 1
},
{
"id": "ccca5021-248b-4a98-af6b-ee92b2d63dca",
"name": "便签12",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
-1000
],
"parameters": {
"color": 7,
"width": 1140,
"height": 560,
"content": "## 获取并准备单个视频转录数据以供处理"
},
"typeVersion": 1
},
{
"id": "1e9ecc6e-2b4d-4ae8-8949-d4b92eaf9287",
"name": "获取视频",
"type": "n8n-nodes-base.code",
"position": [
1520,
480
],
"parameters": {
"jsCode": "return $('Edit Fields').all();"
},
"typeVersion": 2
},
{
"id": "f8ea7a40-b1f9-4dc2-959d-0e765331b191",
"name": "获取播放列表视频数据",
"type": "n8n-nodes-base.code",
"position": [
860,
-200
],
"parameters": {
"jsCode": "/**\n * This code node contains a modified version of play-dl,\n * which is licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * Original Library Name: play-dl\n * Original Library Source: https://github.com/play-dl/play-dl/tree/main\n * Original Library License: GNU General Public License Version 3 (GPLv3)\n * (See: https://www.gnu.org/licenses/gpl-3.0.en.html)\n *\n * Modifications were made to the original library for use within this N8N workflow.\n * These modifications are also licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n\n/**\n * Gets YouTube playlist info from a playlist url.\n *\n * Example\n * ```js\n * const playlist = await play.playlist_info('youtube playlist url')\n *\n * const playlist = await play.playlist_info('youtube playlist url', { incomplete : true })\n * ```\n * @param body HTML body of the playlist page\n * @param url Playlist URL\n * @returns YouTube Playlist\n */\nfunction playlist_info(body, url) {\n let url_ = url.trim();\n if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)\n throw new Error('Captcha page: YouTube has detected that you are a bot!');\n\n const response = JSON.parse(\n body\n .split('var ytInitialData = ')[1]\n .split(';</script>')[0]\n .split(/;\\s*(var|const|let)\\s/)[0]\n );\n\n if (response.alerts) {\n if (response.alerts[0].alertWithButtonRenderer?.type === 'INFO') {\n throw new Error(\n `While parsing playlist url\\n${response.alerts[0].alertWithButtonRenderer.text.simpleText}`\n );\n } else if (response.alerts[0].alertRenderer?.type === 'ERROR')\n throw new Error(`While parsing playlist url\\n${response.alerts[0].alertRenderer.text.runs[0].text}`);\n else throw new Error('While parsing playlist url\\nUnknown Playlist Error');\n }\n if (response.currentVideoEndpoint) {\n return getWatchPlaylist(response, body, url_);\n } else return getNormalPlaylist(response, body);\n}\n\n/**\n * Function to parse Playlist from YouTube search\n * @param data html data of that request\n * @param limit No. of videos to parse\n * @returns Array of YouTube Video objects.\n */\nfunction getPlaylistVideos(data, limit = Infinity) {\n const videos = [];\n\n for (let i = 0; i < data.length; i++) {\n if (limit === videos.length) break;\n const info = data[i].playlistVideoRenderer;\n if (!info || !info.shortBylineText) continue;\n\n videos.push({\n id: info.videoId,\n duration: parseInt(info.lengthSeconds) || 0,\n duration_raw: info.lengthText?.simpleText ?? '0:00',\n thumbnails: info.thumbnail.thumbnails,\n title: info.title.runs[0].text,\n upcoming: info.upcomingEventData?.startTime\n ? new Date(parseInt(info.upcomingEventData.startTime) * 1000)\n : undefined,\n channel: {\n id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined,\n name: info.shortBylineText.runs[0].text || undefined,\n url: `https://www.youtube.com${info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\n info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url\n }`,\n icon: undefined\n }\n });\n }\n return videos;\n}\n\n\nfunction getWatchPlaylist(response, body, url) {\n const playlist_details = response.contents.twoColumnWatchNextResults.playlist?.playlist;\n if (!playlist_details)\n throw new Error(\"Watch playlist unavailable due to YouTube layout changes.\")\n\n const videos = getWatchPlaylistVideos(playlist_details.contents);\n const videoCount = playlist_details.totalVideos;\n const channel = playlist_details.shortBylineText?.runs?.[0];\n const badge = playlist_details.badges?.[0]?.metadataBadgeRenderer?.style.toLowerCase();\n\n return {\n id: playlist_details.playlistId || '',\n title: playlist_details.title || '',\n videoCount: parseInt(videoCount) || 0,\n videos: videos,\n url: url,\n channel: {\n id: channel?.navigationEndpoint?.browseEndpoint?.browseId || null,\n name: channel?.text || null,\n url: `https://www.youtube.com${channel?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl ||\n channel?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url\n }`,\n verified: Boolean(badge?.includes('verified')),\n artist: Boolean(badge?.includes('artist'))\n }\n };\n}\n\nfunction getNormalPlaylist(response, body) {\n const json_data =\n response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0]\n .itemSectionRenderer.contents[0].playlistVideoListRenderer.contents;\n const playlist_details = response.sidebar.playlistSidebarRenderer.items;\n const videos = getPlaylistVideos(json_data, 100);\n\n const data = playlist_details[0].playlistSidebarPrimaryInfoRenderer;\n if (!data.title.runs || !data.title.runs.length) throw new Error('Failed to Parse Playlist info.');\n\n const author = playlist_details[1]?.playlistSidebarSecondaryInfoRenderer.videoOwner;\n const views = data.stats.length === 3 ? data.stats[1].simpleText.replace(/\\D/g, '') : 0;\n const lastUpdate =\n data.stats\n .find((x) => 'runs' in x && x['runs'].find((y) => y.text.toLowerCase().includes('last update')))\n ?.runs.pop()?.text ?? null;\n const videosCount = data.stats[0].runs[0].text.replace(/\\D/g, '') || 0;\n\n return {\n id: data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId,\n title: data.title.runs[0].text,\n videoCount: parseInt(videosCount) || 0,\n lastUpdate: lastUpdate,\n views: parseInt(views) || 0,\n videos: videos,\n url: `https://www.youtube.com/playlist?list=${data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId}`,\n link: `https://www.youtube.com${data.title.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,\n channel: author\n ? {\n name: author.videoOwnerRenderer.title.runs[0].text,\n id: author.videoOwnerRenderer.title.runs[0].navigationEndpoint.browseEndpoint.browseId,\n url: `https://www.youtube.com${author.videoOwnerRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url ||\n author.videoOwnerRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl\n }`,\n icons: author.videoOwnerRenderer.thumbnail.thumbnails ?? []\n }\n : {},\n thumbnail: data.thumbnailRenderer.playlistVideoThumbnailRenderer?.thumbnail.thumbnails.length\n ? data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails[\n data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails.length - 1\n ]\n : null\n };\n}\n\nfunction parseDuration(text) {\n if (!text) return 0;\n const split = text.split(':');\n\n switch (split.length) {\n case 2:\n return parseInt(split[0]) * 60 + parseInt(split[1]);\n\n case 3:\n return parseInt(split[0]) * 60 * 60 + parseInt(split[1]) * 60 + parseInt(split[2]);\n\n default:\n return 0;\n }\n}\n\nfunction getWatchPlaylistVideos(data, limit = Infinity) {\n const videos = [];\n\n for (let i = 0; i < data.length; i++) {\n if (limit === videos.length) break;\n const info = data[i].playlistPanelVideoRenderer;\n if (!info || !info.shortBylineText) continue;\n const channel_info = info.shortBylineText.runs[0];\n\n videos.push({\n id: info.videoId,\n duration: parseDuration(info.lengthText?.simpleText) || 0,\n duration_raw: info.lengthText?.simpleText ?? '0:00',\n thumbnails: info.thumbnail.thumbnails,\n title: info.title.simpleText,\n upcoming:\n info.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer?.style === 'UPCOMING' || undefined,\n channel: {\n id: channel_info.navigationEndpoint.browseEndpoint.browseId || undefined,\n name: channel_info.text || undefined,\n url: `https://www.youtube.com${channel_info.navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\n channel_info.navigationEndpoint.commandMetadata.webCommandMetadata.url\n }`,\n icon: undefined\n }\n });\n }\n\n return videos;\n}\n\n\nreturn playlist_info($input.first().json.data, $('Update Context Intent').first().json.output.url);"
},
"retryOnFail": true,
"typeVersion": 2,
"alwaysOutputData": true,
"waitBetweenTries": 500
},
{
"id": "71b60b8e-06df-45a8-85a3-7b9e938fb6f0",
"name": "嵌入向量 Google Gemini2",
"type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
"position": [
2640,
1060
],
"parameters": {
"modelName": "models/text-embedding-004"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "58feccf5-1b97-4c88-b274-444e177f4515",
"name": "Qdrant 向量存储3",
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
"position": [
-100,
160
],
"parameters": {
"options": {},
"qdrantCollection": {
"__rl": true,
"mode": "id",
"value": "={{ $('Default Intent').first().json.output?.id }}"
}
},
"credentials": {
"qdrantApi": {
"id": "mb8rw8tmUeP6aPJm",
"name": "QdrantApi account"
}
},
"typeVersion": 1.1
},
{
"id": "6cff2236-3e78-4e8c-bb3d-5140f106c530",
"name": "使用向量存储回答问题",
"type": "@n8n/n8n-nodes-langchain.toolVectorStore",
"position": [
180,
-40
],
"parameters": {
"name": "chat_playlist_data",
"topK": 10,
"description": "=从向量存储中检索关于播放列表或视频的数据。"
},
"typeVersion": 1
},
{
"id": "312081d9-9279-4700-a432-e9f878d5361e",
"name": "Qdrant 向量存储4",
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
"position": [
2700,
900
],
"parameters": {
"options": {},
"qdrantCollection": {
"__rl": true,
"mode": "id",
"value": "={{ $('Default Intent').first().json.output?.id }}"
}
},
"credentials": {
"qdrantApi": {
"id": "mb8rw8tmUeP6aPJm",
"name": "QdrantApi account"
}
},
"typeVersion": 1.1
},
{
"id": "f5cba8b5-226d-4b0a-8cb6-a51728ac8247",
"name": "使用向量存储回答问题1",
"type": "@n8n/n8n-nodes-langchain.toolVectorStore",
"position": [
2860,
700
],
"parameters": {
"name": "chat_playlist_data",
"topK": 6,
"description": "=用户消息:"
},
"typeVersion": 1
},
{
"id": "22f18788-b47f-4187-9146-28e6de5ec7a6",
"name": "Google Gemini 聊天模型",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
3000,
900
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "2aa52dd2-169b-43dc-8202-1d60f5fd55c0",
"name": "Google Gemini Chat Model3",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
220,
160
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "d96d3670-ab95-4afc-a275-33f03383a204",
"name": "Qdrant 向量存储2",
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
"position": [
-820,
-880
],
"parameters": {
"mode": "load",
"prompt": "Are there any documents in the store?",
"options": {},
"qdrantCollection": {
"__rl": true,
"mode": "id",
"value": "={{ $('Process Status').item.json.output?.id }}"
}
},
"credentials": {
"qdrantApi": {
"id": "mb8rw8tmUeP6aPJm",
"name": "QdrantApi account"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "7c289d61-09b2-461e-b771-8781f60828c7",
"name": "嵌入向量 Google Gemini4",
"type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
"position": [
-840,
-640
],
"parameters": {
"modelName": "models/text-embedding-004"
},
"credentials": {
"googlePalmApi": {
"id": "2zwuT5znDglBrUCO",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "59be01b0-3ff9-4cc2-b569-c0cb4dcae3ec",
"name": "如果",
"type": "n8n-nodes-base.if",
"position": [
-260,
-880
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "795da689-23c3-49d5-a312-ca18e2c9d5e3",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.count_document }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ab9bc74d-9659-44fe-aa08-4878609ef808",
"name": "计数内容",
"type": "n8n-nodes-base.summarize",
"position": [
-460,
-880
],
"parameters": {
"options": {},
"fieldsToSummarize": {
"values": [
{
"field": "document"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "5c953435-1052-4e1c-9d2f-2439ba944b28",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
20,
-1020
],
"parameters": {
"color": 7,
"width": 500,
"height": 580,
"content": "## 处理或请求更多细节"
},
"typeVersion": 1
},
{
"id": "78760cd2-74db-482d-b660-bd53a3184b95",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-920,
-1020
],
"parameters": {
"color": 7,
"width": 880,
"height": 580,
"content": "## 已处理?"
},
"typeVersion": 1
},
{
"id": "a8dc1b1e-2ba9-4b85-9f2f-c3d07ec67c32",
"name": "更新上下文意图1",
"type": "n8n-nodes-base.redis",
"position": [
-460,
-640
],
"parameters": {
"key": "=context_intent_{{ $('Chat').item.json.sessionId }}",
"value": "=intent {{ $('Process Status').item.json.output?.intent || null }} url {{ $('Process Status').item.json.output?.url || \"\" }} id {{ $('Process Status').item.json.output?.id || \"\" }} limit {{ $('Process Status').item.json.output?.limit || 0 }} status DONE",
"keyType": "hash",
"operation": "set",
"valueIsJSON": false
},
"credentials": {
"redis": {
"id": "mA0f9F1ROUThyrRW",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "36ea2e25-8d29-4ec6-bc6a-15c98153c390",
"name": "便签8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2540,
20
],
"parameters": {
"width": 1080,
"height": 1320,
"content": "# AI驱动的 YouTube 播放列表和视频摘要与分析聊天机器人"
},
"typeVersion": 1
}
],
"active": true,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "7c1d0978-ccea-4618-bcbf-560b34f77023",
"connections": {
"If": {
"main": [
[
{
"node": "Update Context Intent1",
"type": "main",
"index": 0
}
],
[
{
"node": "Playlist Limit",
"type": "main",
"index": 0
}
]
]
},
"Chat": {
"main": [
[
{
"node": "Get Previous Context Intent",
"type": "main",
"index": 0
}
]
]
},
"Limit": {
"main": [
[
{
"node": "YouTube Transcript",
"type": "main",
"index": 0
},
{
"node": "Video Titles",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Summarize & Analyze Transcript",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Concatenate",
"type": "main",
"index": 0
}
]
]
},
"Get Videos": {
"main": [
[
{
"node": "Qdrant Vector Store",
"type": "main",
"index": 0
}
]
]
},
"Split Out1": {
"main": [
[
{
"node": "Limit",
"type": "main",
"index": 0
}
]
]
},
"Split Out2": {
"main": [
[
{
"node": "Concatenate1",
"type": "main",
"index": 0
}
]
]
},
"Concatenate": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Delete Collection",
"type": "main",
"index": 0
}
]
]
},
"Concatenate1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Full Summary": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Video Titles": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Count Content": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "Message Intent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Default Intent": {
"main": [
[
{
"node": "Route Message Intent",
"type": "main",
"index": 0
}
]
]
},
"Message Intent": {
"main": [
[
{
"node": "Default Intent",
"type": "main",
"index": 0
}
],
[]
]
},
"Playlist Limit": {
"main": [
[
{
"node": "Numb of Videos",
"type": "main",
"index": 0
}
],
[
{
"node": "Playlist or Video",
"type": "main",
"index": 0
}
]
]
},
"Process Status": {
"main": [
[
{
"node": "Update Context Intent",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory3": {
"ai_memory": [
[
{
"node": "Numb of Videos",
"type": "ai_memory",
"index": 0
}
]
]
},
"Delete Collection": {
"main": [
[
{
"node": "Get Videos",
"type": "main",
"index": 0
}
]
]
},
"Playlist or Video": {
"main": [
[
{
"node": "Video HTTP Request",
"type": "main",
"index": 0
}
],
[
{
"node": "Playlist HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Chat Buffer Memory": {
"ai_memory": [
[
{
"node": "Handle Queries",
"type": "ai_memory",
"index": 0
}
]
]
},
"Get Title and Desc": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Video HTTP Request": {
"main": [
[
{
"node": "YouTube Transcript1",
"type": "main",
"index": 0
},
{
"node": "Get Title and Desc",
"type": "main",
"index": 0
}
]
]
},
"YouTube Transcript": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Chat Buffer Memory1": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Default Data Loader": {
"ai_document": [
[
{
"node": "Qdrant Vector Store",
"type": "ai_document",
"index": 0
}
]
]
},
"Qdrant Vector Store": {
"main": [
[
{
"node": "Update Context Process Done1",
"type": "main",
"index": 0
}
]
]
},
"YouTube Transcript1": {
"main": [
[
{
"node": "Split Out2",
"type": "main",
"index": 0
}
]
]
},
"Qdrant Vector Store2": {
"main": [
[
{
"node": "Count Content",
"type": "main",
"index": 0
}
]
]
},
"Qdrant Vector Store3": {
"ai_tool": [
[]
],
"ai_vectorStore": [
[
{
"node": "Answer questions with a vector store",
"type": "ai_vectorStore",
"index": 0
}
]
]
},
"Qdrant Vector Store4": {
"ai_vectorStore": [
[
{
"node": "Answer questions with a vector store1",
"type": "ai_vectorStore",
"index": 0
}
]
]
},
"Route Message Intent": {
"main": [
[
{
"node": "Process Status",
"type": "main",
"index": 0
}
],
[
{
"node": "Handle Queries",
"type": "main",
"index": 0
}
],
[
{
"node": "Handle Queries",
"type": "main",
"index": 0
}
]
]
},
"Playlist HTTP Request": {
"main": [
[
{
"node": "Get Playlist Videos Data",
"type": "main",
"index": 0
}
]
]
},
"Update Context Intent": {
"main": [
[
{
"node": "Qdrant Vector Store2",
"type": "main",
"index": 0
}
]
]
},
"Get Fields for Summary": {
"main": [
[
{
"node": "Full Summary",
"type": "main",
"index": 0
}
]
]
},
"Update Context Intent1": {
"main": [
[
{
"node": "Handle Queries",
"type": "main",
"index": 0
}
]
]
},
"Embeddings Google Gemini": {
"ai_embedding": [
[
{
"node": "Qdrant Vector Store",
"type": "ai_embedding",
"index": 0
}
]
]
},
"Get Playlist Videos Data": {
"main": [
[
{
"node": "Split Out1",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Answer questions with a vector store1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Embeddings Google Gemini1": {
"ai_embedding": [
[
{
"node": "Qdrant Vector Store3",
"type": "ai_embedding",
"index": 0
}
]
]
},
"Embeddings Google Gemini2": {
"ai_embedding": [
[
{
"node": "Qdrant Vector Store4",
"type": "ai_embedding",
"index": 0
}
]
]
},
"Embeddings Google Gemini4": {
"ai_embedding": [
[
{
"node": "Qdrant Vector Store2",
"type": "ai_embedding",
"index": 0
}
]
]
},
"Google Gemini Chat Model1": {
"ai_languageModel": [
[
{
"node": "Handle Queries",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model2": {
"ai_languageModel": [
[
{
"node": "Summarize & Analyze Transcript",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model3": {
"ai_languageModel": [
[
{
"node": "Answer questions with a vector store",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model4": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model6": {
"ai_languageModel": [
[
{
"node": "Message Intent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model8": {
"ai_languageModel": [
[
{
"node": "Numb of Videos",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser1": {
"ai_outputParser": [
[
{
"node": "Message Intent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Get Previous Context Intent": {
"main": [
[
{
"node": "Message Intent",
"type": "main",
"index": 0
}
]
]
},
"Update Context Process Done1": {
"main": [
[
{
"node": "Get Fields for Summary",
"type": "main",
"index": 0
}
]
]
},
"Summarize & Analyze Transcript": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Recursive Character Text Splitter": {
"ai_textSplitter": [
[
{
"node": "Default Data Loader",
"type": "ai_textSplitter",
"index": 0
}
]
]
},
"Answer questions with a vector store": {
"ai_tool": [
[
{
"node": "Handle Queries",
"type": "ai_tool",
"index": 0
}
]
]
},
"Answer questions with a vector store1": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 其他, 人工智能
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
AI 代理餐厅 [模板]
🤖 WhatsApp、Instagram 和 Messenger 的 AI 餐厅助手
If
N8n
Set
+37
239 节点Amanda Benks
其他
宠物店 4
🐶 宠物店预约 AI 代理
If
Set
Code
+41
187 节点Bruno Dias
人工智能
[模板] AI宠物店 v8
🐶 AI宠物店助手 - 集成GPT-4o、Google日历和WhatsApp/Instagram/Facebook
If
N8n
Set
+38
244 节点Amanda Benks
销售
AI驱动的RAG文档处理与聊天机器人 - Google Drive、Supabase、OpenAI
基于Google Drive、Supabase和OpenAI的AI驱动RAG文档处理与聊天机器人
Set
Code
Limit
+19
35 节点Billy Christi
人工智能
交付汉堡店MVP
🤖 餐厅与配送自动化的 AI 驱动 WhatsApp 助手
If
Set
Code
+37
152 节点Bruno Dias