72일차 자료 추가(답 제외)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aaf77d4e18
commit
febde9757c
63 files changed
+12260
No files matched your search
@@ -0,0 +1,442 @@
|
||||
{
|
||||
"name": "72 IT helpdesk baseline (provided)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "base-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "f7f86794-1216-5eae-93b9-9e69bb515ce3",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "73fa6963-23cf-5fab-be79-21db3461385e"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "108e39d5-615a-517a-9060-e09e7c4379f9"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "1d7d4c12-dba9-5291-95eb-fbcf440257be"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "e44002cd-d810-5fbe-9eef-d0d9e897b974"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
520
|
||||
],
|
||||
"id": "555c72bf-fdc6-5560-9c4a-386b1cf78fe7"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "169c07e6-bc8e-5307-9502-4a8114c854fa"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "7459ee39-7124-57c0-8bdf-c596f65f7deb"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "70d6cf5f-6064-5790-a7e6-628e044f95b9"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $('Parse category').first().json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "bd94eff5-ddeb-5a04-bfb7-e8d62ca7ed45"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
520
|
||||
],
|
||||
"id": "e40a7e46-cf90-5b11-a629-6f92fe5a043c"
|
||||
},
|
||||
{
|
||||
"name": "Review",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=초안: {{ $('Answer').first().json.output }}",
|
||||
"options": {
|
||||
"systemMessage": "You review a Korean IT help-desk answer before it is sent. Keep the facts exactly. Make it polite (합니다체), remove anything not supported by the data, keep at most 3 sentences. Return only the final answer text.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "540513f3-f96b-5672-913f-ba4846dd768b"
|
||||
},
|
||||
{
|
||||
"name": "Review model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
520
|
||||
],
|
||||
"id": "6f1ffb5c-b821-593b-8cd2-ebb68d63a277"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').first().json.category;\nconst answer = String($('Review').first().json.output ?? '').trim();\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('review');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 1;\nconst result = { route: '자동 답변', category, answer, sources };\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "a749f7a2-8109-50f4-a3c4-dd1dbe29f8e8"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "86789b60-2654-528e-aef9-adf67767dbd3"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
{
|
||||
"name": "72 IT helpdesk + timings (reference)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "my-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "f7eddb61-8a29-5748-9dfe-a33e4db5fb4e"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "f10269df-f57b-55be-9527-7d99273541e5"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "ae5fdf1d-0e8e-5b9e-a375-d1c2a8159ff0"
|
||||
},
|
||||
{
|
||||
"name": "Start clock",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "34e026d9-8555-5c58-98bc-0c8cb3433674"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "8ed1076b-57e7-5692-a07b-0dfc406d717d"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
520
|
||||
],
|
||||
"id": "115afa58-b938-5f5f-aa27-b90886caafef"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "e3a4245a-0d3e-52f9-8bba-fac408559c43"
|
||||
},
|
||||
{
|
||||
"name": "After classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "6367ccc8-0f9a-53b5-ab75-b5b97411bcf4"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "e9049433-bf26-5f16-9025-ee8e45168e66"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "015582e0-c7ba-5450-afdb-31fe69bd4ed0"
|
||||
},
|
||||
{
|
||||
"name": "After data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "724f4528-be4b-5a27-95fa-dc4cbe9adc54"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $('Parse category').first().json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "73f8249d-0f63-5a27-ba24-7c0160dd4f37"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
520
|
||||
],
|
||||
"id": "253ae3fd-037d-50f8-a521-974f74f1ea02"
|
||||
},
|
||||
{
|
||||
"name": "After answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2200,
|
||||
300
|
||||
],
|
||||
"id": "c91805b8-8a84-5d53-8ef1-24f414f43b4f"
|
||||
},
|
||||
{
|
||||
"name": "Review",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=초안: {{ $('Answer').first().json.output }}",
|
||||
"options": {
|
||||
"systemMessage": "You review a Korean IT help-desk answer before it is sent. Keep the facts exactly. Make it polite (합니다체), remove anything not supported by the data, keep at most 3 sentences. Return only the final answer text.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
300
|
||||
],
|
||||
"id": "58a6606c-b82d-585d-98c2-ad0b50d978d2"
|
||||
},
|
||||
{
|
||||
"name": "Review model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
520
|
||||
],
|
||||
"id": "9738bf2a-5100-51be-ab12-e932ef259273"
|
||||
},
|
||||
{
|
||||
"name": "After review",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2640,
|
||||
300
|
||||
],
|
||||
"id": "aafed179-dfd6-5b4d-affa-ba03915a19bf"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').first().json.category;\nconst answer = String($('Review').first().json.output ?? '').trim();\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('review');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 1;\nconst result = { route: '자동 답변', category, answer, sources };\nconst t = (name) => (ran(name) ? $(name).first().json.t : null);\nconst t0 = t('Start clock');\nconst marks = [['classify', t('After classify')], ['data', t('After data')], ['answer', t('After answer')], ['review', t('After review')]];\nconst timings = {};\nlet last = t0;\nfor (const [label, mark] of marks) {\n if (mark === null) continue;\n timings[`${label}_ms`] = mark - last;\n last = mark;\n}\ntimings.total_ms = Date.now() - t0;\nObject.assign(result, { llm_calls, cached: false, steps, timings });\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"id": "4da41b13-2fc9-5423-a782-fb61727ea33a"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
300
|
||||
],
|
||||
"id": "2fa9e489-3282-5de4-9c91-f536e53b568a"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Start clock",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start clock": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
{
|
||||
"name": "72 IT helpdesk + rules first (reference)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "my-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "943597e2-d5eb-5f3c-b36b-f18ee54db8d7"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "ca5c79c8-323b-5eac-b1c7-50d7383f1fc5"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "030b2d85-73de-567f-95ce-d25c8729794d"
|
||||
},
|
||||
{
|
||||
"name": "Start clock",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "2240260c-639c-501a-9347-9da083549304"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "aabca858-7174-5c55-b7f5-ae3ab4e6a565"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "92beb818-7b68-528c-9d35-f1c1645ea215"
|
||||
},
|
||||
{
|
||||
"name": "After data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "dbf68168-4cb7-55db-8ba9-9752ff11f684"
|
||||
},
|
||||
{
|
||||
"name": "Rule classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const question = String($('Question webhook').first().json.body.question ?? '');\nconst results = $('Search FAQ').first().json.results ?? [];\nlet category = 'unknown';\nif (/T-?\\d{4}/i.test(question)) category = 'ticket';\nelse if (results.length > 0) category = 'faq';\nreturn [{ json: { category, by: 'rule' } }];"
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "36d285f0-4fb3-5697-9053-5ae5e3e8d0b1"
|
||||
},
|
||||
{
|
||||
"name": "Known?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category }}",
|
||||
"rightValue": "unknown",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "fd83a3d5-bc74-5739-8185-1924a7939ed8"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "63d9e328-246e-5bd4-9596-40e9b9715464"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
520
|
||||
],
|
||||
"id": "8aabd2dc-204a-5730-9633-1bd0858969ba"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "1150b73c-a088-5d4f-ac61-38faec69bc23"
|
||||
},
|
||||
{
|
||||
"name": "After classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2200,
|
||||
300
|
||||
],
|
||||
"id": "337a90fe-b0bf-5e29-82ec-c1178ec1eeec"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
300
|
||||
],
|
||||
"id": "e23b0a82-1cb0-50e9-bf9c-ea5eb3b053f9"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
520
|
||||
],
|
||||
"id": "ce2907bb-2524-54e7-9e0a-92bed701764f"
|
||||
},
|
||||
{
|
||||
"name": "After answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2640,
|
||||
300
|
||||
],
|
||||
"id": "a0321f1e-05f2-5e4f-b7cf-f0eb06eaf0f1"
|
||||
},
|
||||
{
|
||||
"name": "Review",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=초안: {{ $('Answer').first().json.output }}",
|
||||
"options": {
|
||||
"systemMessage": "You review a Korean IT help-desk answer before it is sent. Keep the facts exactly. Make it polite (합니다체), remove anything not supported by the data, keep at most 3 sentences. Return only the final answer text.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"id": "5ee9442d-00be-5492-83bb-a99147e6e282"
|
||||
},
|
||||
{
|
||||
"name": "Review model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
520
|
||||
],
|
||||
"id": "b496eea1-f048-550c-9893-515dcc94c84e"
|
||||
},
|
||||
{
|
||||
"name": "After review",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
300
|
||||
],
|
||||
"id": "c955c14a-8a9b-538f-94db-27d3fd579751"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').isExecuted ? $('Parse category').first().json.category : $('Rule classify').first().json.category;\nconst answer = String($('Review').first().json.output ?? '').trim();\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('review');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 1;\nconst result = { route: '자동 답변', category, answer, sources };\nconst t = (name) => (ran(name) ? $(name).first().json.t : null);\nconst t0 = t('Start clock');\nconst marks = [['data', t('After data')], ['classify', t('After classify')], ['answer', t('After answer')], ['review', t('After review')]];\nconst timings = {};\nlet last = t0;\nfor (const [label, mark] of marks) {\n if (mark === null) continue;\n timings[`${label}_ms`] = mark - last;\n last = mark;\n}\ntimings.total_ms = Date.now() - t0;\nObject.assign(result, { llm_calls, cached: false, steps, timings });\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
300
|
||||
],
|
||||
"id": "185dc7f9-4954-5cef-ba24-f45145bd192a"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3520,
|
||||
300
|
||||
],
|
||||
"id": "4dd5ed77-a95a-5969-a9f7-78ab7d32fb21"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Start clock",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start clock": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rule classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Rule classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Known?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Known?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
{
|
||||
"name": "72 IT helpdesk + cache (reference)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "my-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "c9a72036-8a27-592b-bd8b-38a2db4d6d06"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "6c786a1e-1989-51a4-90a5-db92e73869b1"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "a4a18f10-bc62-5d90-9b67-68c9feee398e"
|
||||
},
|
||||
{
|
||||
"name": "Start clock",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "997a12bd-2387-5f2b-9bd8-41d0fd96b3c0"
|
||||
},
|
||||
{
|
||||
"name": "Cache key",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const body = $('Question webhook').first().json.body;\nconst key = String(body.question ?? '').trim().toLowerCase().replace(/\\s+/g, ' ');\nreturn [{ json: { ...$json, key, use_cache: body.no_cache !== true } }];"
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "f5beb6a3-5fd1-5dd9-9940-8180d0461375"
|
||||
},
|
||||
{
|
||||
"name": "Cache lookup",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "e4ac7f07-094d-5369-877c-b6124bb369be"
|
||||
},
|
||||
{
|
||||
"name": "Cached?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.value !== undefined && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "d7643f14-e849-5965-98c1-0984a9164992"
|
||||
},
|
||||
{
|
||||
"name": "Cached result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const value = $json.value;\nconst t0 = $('Start clock').first().json.t;\nreturn [{ json: { route: value.route, category: value.category, answer: value.answer, sources: value.sources ?? [],\n llm_calls: 0, cached: true, steps: ['cache'], timings: { total_ms: t0 ? Date.now() - t0 : 0 } } }];"
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
40
|
||||
],
|
||||
"id": "1eb4a1d1-0752-549e-8419-f421001f6ce7"
|
||||
},
|
||||
{
|
||||
"name": "Send cached",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
40
|
||||
],
|
||||
"id": "ef4c0dcc-6923-515a-9a47-16b96f870458"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "c0ca4c44-24ab-5e35-bc09-3e1a46b5eaf2"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "8bfa2f94-cfd3-5c47-9907-9cbe520709f5"
|
||||
},
|
||||
{
|
||||
"name": "After data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "ec991b78-5d32-523e-8650-1b705d664347"
|
||||
},
|
||||
{
|
||||
"name": "Rule classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const question = String($('Question webhook').first().json.body.question ?? '');\nconst results = $('Search FAQ').first().json.results ?? [];\nlet category = 'unknown';\nif (/T-?\\d{4}/i.test(question)) category = 'ticket';\nelse if (results.length > 0) category = 'faq';\nreturn [{ json: { category, by: 'rule' } }];"
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "3aea9b36-7147-5a1e-b214-dde00e3b07a8"
|
||||
},
|
||||
{
|
||||
"name": "Known?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category }}",
|
||||
"rightValue": "unknown",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
2200,
|
||||
300
|
||||
],
|
||||
"id": "cb84100b-77a0-54e2-ab8a-82439f9a06a2"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
300
|
||||
],
|
||||
"id": "24cdba37-3d33-5b58-bdf8-843fdceb921d"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
520
|
||||
],
|
||||
"id": "14a4b607-e7e0-5946-a6dd-429db1749efe"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
2640,
|
||||
300
|
||||
],
|
||||
"id": "e2e83cde-1e8e-57a3-9873-e21b349e8e9c"
|
||||
},
|
||||
{
|
||||
"name": "After classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"id": "52489cc0-dc17-55fd-abc1-d0560f984a09"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
300
|
||||
],
|
||||
"id": "af3fff36-6c0f-561b-892c-c114ba138f00"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
520
|
||||
],
|
||||
"id": "a17533bf-9c80-521d-b6e3-1a50309fecba"
|
||||
},
|
||||
{
|
||||
"name": "After answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
300
|
||||
],
|
||||
"id": "f8f2abf1-fe1f-5e26-bbdf-a7265cc0af82"
|
||||
},
|
||||
{
|
||||
"name": "Review",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=초안: {{ $('Answer').first().json.output }}",
|
||||
"options": {
|
||||
"systemMessage": "You review a Korean IT help-desk answer before it is sent. Keep the facts exactly. Make it polite (합니다체), remove anything not supported by the data, keep at most 3 sentences. Return only the final answer text.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3520,
|
||||
300
|
||||
],
|
||||
"id": "db2e207e-bf3f-573b-b812-8903cfe2e8b0"
|
||||
},
|
||||
{
|
||||
"name": "Review model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3520,
|
||||
520
|
||||
],
|
||||
"id": "45781a30-03fe-5886-8936-2d8a9ae3bd31"
|
||||
},
|
||||
{
|
||||
"name": "After review",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3740,
|
||||
300
|
||||
],
|
||||
"id": "e4200614-0722-52ad-b86b-166d8325db9e"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').isExecuted ? $('Parse category').first().json.category : $('Rule classify').first().json.category;\nconst answer = String($('Review').first().json.output ?? '').trim();\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('review');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 1;\nconst result = { route: '자동 답변', category, answer, sources };\nconst t = (name) => (ran(name) ? $(name).first().json.t : null);\nconst t0 = t('Start clock');\nconst marks = [['data', t('After data')], ['classify', t('After classify')], ['answer', t('After answer')], ['review', t('After review')]];\nconst timings = {};\nlet last = t0;\nfor (const [label, mark] of marks) {\n if (mark === null) continue;\n timings[`${label}_ms`] = mark - last;\n last = mark;\n}\ntimings.total_ms = Date.now() - t0;\nObject.assign(result, { llm_calls, cached: false, steps, timings });\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
3960,
|
||||
300
|
||||
],
|
||||
"id": "aca47054-6aa3-5773-91c4-1b71155360a1"
|
||||
},
|
||||
{
|
||||
"name": "Cache it?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category === 'faq' && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4180,
|
||||
300
|
||||
],
|
||||
"id": "6173c5a7-0f27-5472-b65d-7b1e1e4a8c2a"
|
||||
},
|
||||
{
|
||||
"name": "Store in cache",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($('Cache key').first().json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"method": "POST",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ value: { route: $json.route, category: $json.category, answer: $json.answer, sources: $json.sources }, ttl: 600 }) }}"
|
||||
},
|
||||
"position": [
|
||||
4400,
|
||||
180
|
||||
],
|
||||
"id": "131ba1d7-a5c6-59d8-b998-6d56b6dcbb90"
|
||||
},
|
||||
{
|
||||
"name": "Result again",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: $('Build result').first().json }];"
|
||||
},
|
||||
"position": [
|
||||
4620,
|
||||
180
|
||||
],
|
||||
"id": "9fa5ecbb-8ba2-53cf-9ed0-e39b0f0abce4"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4840,
|
||||
300
|
||||
],
|
||||
"id": "6452676c-2624-523c-a63e-cb1f2cdf88ce"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Start clock",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start clock": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache key",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache key": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache lookup",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache lookup": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send cached",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rule classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Rule classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Known?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Known?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Review",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache it?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache it?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Store in cache",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Store in cache": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Result again",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Result again": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,938 @@
|
||||
{
|
||||
"name": "72 IT helpdesk + timeout and fallback (reference)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "my-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "d723516c-f180-54b9-87ca-9d10479930ca"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "b2ec2f0b-bda5-57b7-90bb-a12187a87ada"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "e8162a04-f42f-5d77-9fb2-8b48bb83fa93"
|
||||
},
|
||||
{
|
||||
"name": "Start clock",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "8298edad-e01f-5083-a050-e61d239cdd06"
|
||||
},
|
||||
{
|
||||
"name": "Cache key",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const body = $('Question webhook').first().json.body;\nconst key = String(body.question ?? '').trim().toLowerCase().replace(/\\s+/g, ' ');\nreturn [{ json: { ...$json, key, use_cache: body.no_cache !== true } }];"
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "947ac5f9-250f-59f0-a8c2-4f178d3e27bf"
|
||||
},
|
||||
{
|
||||
"name": "Cache lookup",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "bca22ac4-98e9-5840-9565-0a08abd392dd"
|
||||
},
|
||||
{
|
||||
"name": "Cached?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.value !== undefined && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "c6f11d8c-4cf4-56da-a0c2-86b43d5875b7"
|
||||
},
|
||||
{
|
||||
"name": "Cached result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const value = $json.value;\nconst t0 = $('Start clock').first().json.t;\nreturn [{ json: { route: value.route, category: value.category, answer: value.answer, sources: value.sources ?? [],\n llm_calls: 0, cached: true, steps: ['cache'], timings: { total_ms: t0 ? Date.now() - t0 : 0 } } }];"
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
40
|
||||
],
|
||||
"id": "87241232-65a7-5929-8aab-f793cd5eb252"
|
||||
},
|
||||
{
|
||||
"name": "Send cached",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
40
|
||||
],
|
||||
"id": "c9ad7484-9647-56c6-bc21-489ae4ba90c9"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {
|
||||
"timeout": 2000
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"retryOnFail": true,
|
||||
"maxTries": 2,
|
||||
"waitBetweenTries": 500,
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "7fadfe95-f11a-53e1-9381-6eaabe3b0e33"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
},
|
||||
"timeout": 2000
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"retryOnFail": true,
|
||||
"maxTries": 2,
|
||||
"waitBetweenTries": 500,
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "63fcb7b8-6a86-5ff2-9ec7-dbf12d90ba53"
|
||||
},
|
||||
{
|
||||
"name": "After data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "e03ac097-cb39-5e96-b35f-6fef794dccc0"
|
||||
},
|
||||
{
|
||||
"name": "Rule classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const question = String($('Question webhook').first().json.body.question ?? '');\nconst results = $('Search FAQ').first().json.results ?? [];\nlet category = 'unknown';\nif (/T-?\\d{4}/i.test(question)) category = 'ticket';\nelse if (results.length > 0) category = 'faq';\nreturn [{ json: { category, by: 'rule' } }];"
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "ad169caa-8acb-5747-9d80-de0809af7722"
|
||||
},
|
||||
{
|
||||
"name": "Known?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category }}",
|
||||
"rightValue": "unknown",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
2200,
|
||||
300
|
||||
],
|
||||
"id": "217e4a57-9524-59ca-8a5c-8d802a6ed928"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
300
|
||||
],
|
||||
"id": "84844dd8-a4c4-5466-8bfa-0164024d6889"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
520
|
||||
],
|
||||
"id": "f97e877f-20e8-56a3-bbe5-504dceb6297f"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
2640,
|
||||
300
|
||||
],
|
||||
"id": "79533502-a17e-5fce-a050-1f2577b3ef9f"
|
||||
},
|
||||
{
|
||||
"name": "After classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"id": "bd326baa-dd79-5020-9634-e80a1503221f"
|
||||
},
|
||||
{
|
||||
"name": "Data down",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "d1",
|
||||
"name": "route",
|
||||
"value": "담당자 연결",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "d2",
|
||||
"name": "answer",
|
||||
"value": "사내 데이터 시스템이 응답하지 않아 담당자에게 연결합니다.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "d3",
|
||||
"name": "reason",
|
||||
"value": "data_api_down",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
820
|
||||
],
|
||||
"id": "277957fb-f7b9-52f0-a345-6b0210360668"
|
||||
},
|
||||
{
|
||||
"name": "Send data down",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
820
|
||||
],
|
||||
"id": "1a26c632-4637-5a77-bf25-ad0068c539fe"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
300
|
||||
],
|
||||
"id": "df2b22cd-48a6-5b76-b884-ed5f116bee5a"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
520
|
||||
],
|
||||
"id": "933cc07a-882b-5803-a339-ce7fff18d24e"
|
||||
},
|
||||
{
|
||||
"name": "After answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
300
|
||||
],
|
||||
"id": "1126ce89-1de8-5dfd-af02-dfe16f941d0c"
|
||||
},
|
||||
{
|
||||
"name": "Review check",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "let answer = String($('Answer').first().json.output ?? '').trim();\nconst problems = [];\nif (!answer) problems.push('empty');\nif (!/[가-힣]/.test(answer)) problems.push('not_korean');\nif (answer.length > 400) { answer = answer.slice(0, 400); problems.push('too_long'); }\nreturn [{ json: { ...$json, answer, review_problems: problems } }];"
|
||||
},
|
||||
"position": [
|
||||
3520,
|
||||
300
|
||||
],
|
||||
"id": "4ceb73fa-4d1a-5166-9713-53a66329aa4b"
|
||||
},
|
||||
{
|
||||
"name": "After review",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3740,
|
||||
300
|
||||
],
|
||||
"id": "c27c79b7-4a80-5197-9eb8-cc8ea3af07de"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').isExecuted ? $('Parse category').first().json.category : $('Rule classify').first().json.category;\nconst answer = $('Review check').first().json.answer;\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('check');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 0;\nconst result = { route: '자동 답변', category, answer, sources };\nconst t = (name) => (ran(name) ? $(name).first().json.t : null);\nconst t0 = t('Start clock');\nconst marks = [['data', t('After data')], ['classify', t('After classify')], ['answer', t('After answer')], ['review', t('After review')]];\nconst timings = {};\nlet last = t0;\nfor (const [label, mark] of marks) {\n if (mark === null) continue;\n timings[`${label}_ms`] = mark - last;\n last = mark;\n}\ntimings.total_ms = Date.now() - t0;\nObject.assign(result, { llm_calls, cached: false, steps, timings });\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
3960,
|
||||
300
|
||||
],
|
||||
"id": "4e59eba2-74ef-5ad9-9322-f2a61dd24c2e"
|
||||
},
|
||||
{
|
||||
"name": "Cache it?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category === 'faq' && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4180,
|
||||
300
|
||||
],
|
||||
"id": "b02df770-a2da-5ff5-8506-be2aa57f15c4"
|
||||
},
|
||||
{
|
||||
"name": "Store in cache",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($('Cache key').first().json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"method": "POST",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ value: { route: $json.route, category: $json.category, answer: $json.answer, sources: $json.sources }, ttl: 600 }) }}"
|
||||
},
|
||||
"position": [
|
||||
4400,
|
||||
180
|
||||
],
|
||||
"id": "5e7d5d4d-1b3e-53cb-a622-f8b9057b7948"
|
||||
},
|
||||
{
|
||||
"name": "Result again",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: $('Build result').first().json }];"
|
||||
},
|
||||
"position": [
|
||||
4620,
|
||||
180
|
||||
],
|
||||
"id": "a3625557-95dd-5b42-b0cc-a71bf5dfb881"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4840,
|
||||
300
|
||||
],
|
||||
"id": "ec557367-5b2f-59d5-b7e1-ad7965d5b893"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Start clock",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start clock": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache key",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache key": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache lookup",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache lookup": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send cached",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rule classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Rule classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Known?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Known?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Data down": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review check",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache it?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache it?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Store in cache",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Store in cache": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Result again",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Result again": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,993 @@
|
||||
{
|
||||
"name": "72 IT helpdesk + LLM fallback (reference)",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Question webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "my-helpdesk",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "de518e93-ed19-541f-a6fc-687a4efaf1b2",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"id": "da213575-a9a7-5052-9259-1235f4e340f0"
|
||||
},
|
||||
{
|
||||
"name": "Has question?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.body.question }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
],
|
||||
"id": "4ea38fda-f28a-5e1d-a504-05874f837f02"
|
||||
},
|
||||
{
|
||||
"name": "Reject empty",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{ \"error\": \"question is required\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
560
|
||||
],
|
||||
"id": "ff743009-a8ea-5c5f-8b83-9e29a2c60111"
|
||||
},
|
||||
{
|
||||
"name": "Start clock",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
440,
|
||||
300
|
||||
],
|
||||
"id": "f1cc57e6-9966-5f49-90c4-cbd65be68041"
|
||||
},
|
||||
{
|
||||
"name": "Cache key",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const body = $('Question webhook').first().json.body;\nconst key = String(body.question ?? '').trim().toLowerCase().replace(/\\s+/g, ' ');\nreturn [{ json: { ...$json, key, use_cache: body.no_cache !== true } }];"
|
||||
},
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"id": "3c70cd68-4a52-59c2-a5c3-d30c86914021"
|
||||
},
|
||||
{
|
||||
"name": "Cache lookup",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
880,
|
||||
300
|
||||
],
|
||||
"id": "506d8e43-b396-596a-8197-08f2aa58381b"
|
||||
},
|
||||
{
|
||||
"name": "Cached?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.value !== undefined && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1100,
|
||||
300
|
||||
],
|
||||
"id": "1113fdf6-f964-5534-8d8a-dd41344c2725"
|
||||
},
|
||||
{
|
||||
"name": "Cached result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const value = $json.value;\nconst t0 = $('Start clock').first().json.t;\nreturn [{ json: { route: value.route, category: value.category, answer: value.answer, sources: value.sources ?? [],\n llm_calls: 0, cached: true, steps: ['cache'], timings: { total_ms: t0 ? Date.now() - t0 : 0 } } }];"
|
||||
},
|
||||
"position": [
|
||||
1320,
|
||||
40
|
||||
],
|
||||
"id": "82a7f0f0-356b-5164-baaa-a478f74db65e"
|
||||
},
|
||||
{
|
||||
"name": "Send cached",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
1540,
|
||||
40
|
||||
],
|
||||
"id": "addd6ac5-76a5-5da7-ac2b-84c005eb7520"
|
||||
},
|
||||
{
|
||||
"name": "Search FAQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "http://data-api:8800/faq",
|
||||
"options": {
|
||||
"timeout": 2000
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"value": "={{ $('Question webhook').first().json.body.question }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"retryOnFail": true,
|
||||
"maxTries": 2,
|
||||
"waitBetweenTries": 500,
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"id": "52b6a8a9-bf1f-5b58-8644-0077cfd01dd0"
|
||||
},
|
||||
{
|
||||
"name": "Get ticket",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/tickets/{{ (($('Question webhook').first().json.body.question.match(/T-?\\d{4}/i) || ['NONE'])[0]).toUpperCase().replace(/^T(\\d)/, 'T-$1') }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
},
|
||||
"timeout": 2000
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "requester",
|
||||
"value": "={{ $('Question webhook').first().json.body.user ?? '' }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"retryOnFail": true,
|
||||
"maxTries": 2,
|
||||
"waitBetweenTries": 500,
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
1540,
|
||||
300
|
||||
],
|
||||
"id": "0a16cd98-b769-5067-b867-beabec01b00d"
|
||||
},
|
||||
{
|
||||
"name": "After data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
1760,
|
||||
300
|
||||
],
|
||||
"id": "ba6f2475-7cea-5c3d-95a6-dc6125a0441c"
|
||||
},
|
||||
{
|
||||
"name": "Rule classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const question = String($('Question webhook').first().json.body.question ?? '');\nconst results = $('Search FAQ').first().json.results ?? [];\nlet category = 'unknown';\nif (/T-?\\d{4}/i.test(question)) category = 'ticket';\nelse if (results.length > 0) category = 'faq';\nreturn [{ json: { category, by: 'rule' } }];"
|
||||
},
|
||||
"position": [
|
||||
1980,
|
||||
300
|
||||
],
|
||||
"id": "a2e2c597-6870-5bcc-833c-a44526b343f3"
|
||||
},
|
||||
{
|
||||
"name": "Known?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category }}",
|
||||
"rightValue": "unknown",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
2200,
|
||||
300
|
||||
],
|
||||
"id": "e0758a6f-de55-5309-a696-3ed0755ab1f0"
|
||||
},
|
||||
{
|
||||
"name": "Classify",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "={{ $('Question webhook').first().json.body.question }}",
|
||||
"options": {
|
||||
"systemMessage": "You sort one company IT help-desk question. Categories: faq (how to fix or use something: password, account lock, VPN, printer, mail box, software install), ticket (asks about the status of an IT request ticket like T-1002), other (anything else). Return only JSON, no code fence: {\"category\": \"faq\"}",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
2420,
|
||||
300
|
||||
],
|
||||
"id": "05ed7c16-110e-53ab-9b03-ce872d616695"
|
||||
},
|
||||
{
|
||||
"name": "Classify model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
2420,
|
||||
520
|
||||
],
|
||||
"id": "5a7c30c2-472e-54b3-89c2-85b53e3f432b"
|
||||
},
|
||||
{
|
||||
"name": "Parse category",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const raw = String($json.output ?? '').trim().replace(/^```(json)?\\s*|```$/g, '');\nlet category = 'other';\ntry { category = JSON.parse(raw).category ?? 'other'; } catch (error) { category = 'other'; }\nif (!['faq', 'ticket', 'other'].includes(category)) category = 'other';\nreturn [{ json: { category, by: 'llm' } }];"
|
||||
},
|
||||
"position": [
|
||||
2640,
|
||||
300
|
||||
],
|
||||
"id": "1407c587-1e10-5d76-b43a-9bf6570eb619"
|
||||
},
|
||||
{
|
||||
"name": "After classify",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"id": "77f26c5e-fd71-59c5-b02f-cee875e87ee7"
|
||||
},
|
||||
{
|
||||
"name": "Data down",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "d1",
|
||||
"name": "route",
|
||||
"value": "담당자 연결",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "d2",
|
||||
"name": "answer",
|
||||
"value": "사내 데이터 시스템이 응답하지 않아 담당자에게 연결합니다.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "d3",
|
||||
"name": "reason",
|
||||
"value": "data_api_down",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
820
|
||||
],
|
||||
"id": "7db7288e-627d-5b0f-8634-2bd12ba22e35"
|
||||
},
|
||||
{
|
||||
"name": "Send data down",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
820
|
||||
],
|
||||
"id": "e582596a-b12d-550c-8ba8-ea5661be5b32"
|
||||
},
|
||||
{
|
||||
"name": "Answer",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=질문: {{ $('Question webhook').first().json.body.question }}\n분류: {{ $json.category }}\nFAQ: {{ JSON.stringify($('Search FAQ').first().json.results ?? []) }}\n접수 건: {{ JSON.stringify($('Get ticket').first().json) }}",
|
||||
"options": {
|
||||
"systemMessage": "You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say \"찾지 못했습니다\" and that a staff member will help.",
|
||||
"maxIterations": 1
|
||||
}
|
||||
},
|
||||
"onError": "continueErrorOutput",
|
||||
"position": [
|
||||
3080,
|
||||
300
|
||||
],
|
||||
"id": "a0b37e8a-ba45-5496-baf5-3fe7eb4e11cb"
|
||||
},
|
||||
{
|
||||
"name": "Answer model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.2,
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "gpt-4.1-mini"
|
||||
},
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "openAiCourse0001",
|
||||
"name": "OpenAI (.env)"
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
3080,
|
||||
520
|
||||
],
|
||||
"id": "eadbe0ed-8031-5d85-91c2-258b144dcc6d"
|
||||
},
|
||||
{
|
||||
"name": "After answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3300,
|
||||
300
|
||||
],
|
||||
"id": "2a0a5546-0677-5a3b-89b9-cfb1887a696b"
|
||||
},
|
||||
{
|
||||
"name": "Review check",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "let answer = String($('Answer').first().json.output ?? '').trim();\nconst problems = [];\nif (!answer) problems.push('empty');\nif (!/[가-힣]/.test(answer)) problems.push('not_korean');\nif (answer.length > 400) { answer = answer.slice(0, 400); problems.push('too_long'); }\nreturn [{ json: { ...$json, answer, review_problems: problems } }];"
|
||||
},
|
||||
"position": [
|
||||
3520,
|
||||
300
|
||||
],
|
||||
"id": "24064ede-b012-51e6-8e8a-a40032982e5b"
|
||||
},
|
||||
{
|
||||
"name": "After review",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: { ...$json, t: Date.now() } }];"
|
||||
},
|
||||
"position": [
|
||||
3740,
|
||||
300
|
||||
],
|
||||
"id": "d6ea34a2-56fe-5d19-b1fe-a07a2176b6cf"
|
||||
},
|
||||
{
|
||||
"name": "Build result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const ran = (name) => { try { return $(name).isExecuted; } catch (error) { return false; } };\nconst body = $('Question webhook').first().json.body;\nconst category = $('Parse category').isExecuted ? $('Parse category').first().json.category : $('Rule classify').first().json.category;\nconst answer = $('Review check').first().json.answer;\nconst results = $('Search FAQ').first().json.results ?? [];\nconst sources = category === 'faq' ? results.map((r) => r.id) : [];\nconst steps = [];\nif (ran('Rule classify')) steps.push('rule');\nif (ran('Classify')) steps.push('classify');\nsteps.push('faq', 'ticket', 'answer');\nsteps.push('check');\nconst llm_calls = (ran('Classify') ? 1 : 0) + 1 + 0;\nconst result = { route: '자동 답변', category, answer, sources };\nconst t = (name) => (ran(name) ? $(name).first().json.t : null);\nconst t0 = t('Start clock');\nconst marks = [['data', t('After data')], ['classify', t('After classify')], ['answer', t('After answer')], ['review', t('After review')]];\nconst timings = {};\nlet last = t0;\nfor (const [label, mark] of marks) {\n if (mark === null) continue;\n timings[`${label}_ms`] = mark - last;\n last = mark;\n}\ntimings.total_ms = Date.now() - t0;\nObject.assign(result, { llm_calls, cached: false, steps, timings });\nreturn [{ json: result }];"
|
||||
},
|
||||
"position": [
|
||||
3960,
|
||||
300
|
||||
],
|
||||
"id": "ddb667d7-58f4-591a-99b1-9fdee196e7a9"
|
||||
},
|
||||
{
|
||||
"name": "Cache it?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose",
|
||||
"version": 2
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.category === 'faq' && $('Cache key').first().json.use_cache }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4180,
|
||||
300
|
||||
],
|
||||
"id": "be269673-e6ef-5484-adb9-d2b266f59f97"
|
||||
},
|
||||
{
|
||||
"name": "Store in cache",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"parameters": {
|
||||
"url": "=http://data-api:8800/cache/{{ encodeURIComponent($('Cache key').first().json.key) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"method": "POST",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ value: { route: $json.route, category: $json.category, answer: $json.answer, sources: $json.sources }, ttl: 600 }) }}"
|
||||
},
|
||||
"position": [
|
||||
4400,
|
||||
180
|
||||
],
|
||||
"id": "1d551b65-ef7f-527e-8339-ce95e2272e2d"
|
||||
},
|
||||
{
|
||||
"name": "Result again",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "return [{ json: $('Build result').first().json }];"
|
||||
},
|
||||
"position": [
|
||||
4620,
|
||||
180
|
||||
],
|
||||
"id": "056a1325-c8d0-501f-9dab-599a8380748e"
|
||||
},
|
||||
{
|
||||
"name": "Send result",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4840,
|
||||
300
|
||||
],
|
||||
"id": "0bc9ba7d-cc11-5f62-a05e-107089fe53b4"
|
||||
},
|
||||
{
|
||||
"name": "Simple answer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"jsCode": "const body = $('Question webhook').first().json.body;\nlet results = [];\ntry { results = $('Search FAQ').first().json.results ?? []; } catch (error) { results = []; }\nlet ticket = {};\ntry { ticket = $('Get ticket').first().json ?? {}; } catch (error) { ticket = {}; }\nconst isTicket = /T-?\\d{4}/i.test(String(body.question ?? ''));\nif (isTicket && ticket.status) {\n return [{ json: { route: '간단 답변', category: 'ticket', answer: `${ticket.ticket_id} 접수 건은 현재 '${ticket.status}' 상태입니다. ${ticket.note ?? ''}`.trim(),\n sources: [], llm_calls: 0, reason: 'llm_down' } }];\n}\nif (!isTicket && results.length > 0) {\n return [{ json: { route: '간단 답변', category: 'faq', answer: `${results[0].answer} (자동 답변 기능을 잠시 쓸 수 없어 FAQ 원문으로 안내합니다.)`,\n sources: [results[0].id], llm_calls: 0, reason: 'llm_down' } }];\n}\nreturn [{ json: { route: '담당자 연결', category: 'other', answer: '지금은 자동 답변을 할 수 없어 담당자에게 연결합니다.', sources: [], llm_calls: 0, reason: 'llm_down' } }];"
|
||||
},
|
||||
"position": [
|
||||
4400,
|
||||
1060
|
||||
],
|
||||
"id": "9d9bceef-74c6-5bea-8e09-8d6fca3dd417"
|
||||
},
|
||||
{
|
||||
"name": "Send simple answer",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {}
|
||||
},
|
||||
"position": [
|
||||
4620,
|
||||
1060
|
||||
],
|
||||
"id": "3ab6f0cb-6d8f-5292-86c1-86b2645c74b5"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Question webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has question?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has question?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Start clock",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Reject empty",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start clock": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache key",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache key": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache lookup",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache lookup": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cached result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Search FAQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cached result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send cached",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search FAQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get ticket",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get ticket": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rule classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Rule classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Known?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Known?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Classify",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse category",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Simple answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse category": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After classify",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Data down": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send data down",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After classify": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Answer",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Simple answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Review check",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Review check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "After review",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"After review": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache it?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache it?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Store in cache",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Store in cache": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Result again",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Result again": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Simple answer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send simple answer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user