Files
aio-01d4-stu/Last3Mon/65/n8n/workflows/03-ai-request-flow.json
T
2026-10-13 17:09:59 +09:00

438 lines
14 KiB
JSON

{
"name": "65-03 AI request flow",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "ai-request",
"responseMode": "responseNode",
"options": {}
},
"id": "07b51032-62c2-549b-8cfb-e3f16a851ed7",
"name": "AI request webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
],
"webhookId": "26aa9aad-522f-5f3d-8b58-ae0d7c18ce66"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cf988cf6-5aec-59ef-887c-93252d7d366b",
"name": "llm_url",
"value": "https://api.openai.com/v1",
"type": "string"
},
{
"id": "6d229077-7077-5e85-8ebb-8f6046227539",
"name": "model",
"value": "gpt-4.1-mini",
"type": "string"
},
{
"id": "8a827ce4-4716-5aad-ad39-9c105afcf895",
"name": "user",
"value": "={{ $json.body.user || 'anonymous' }}",
"type": "string"
},
{
"id": "462379ca-3e96-599d-9a1c-64bae0b1095a",
"name": "text",
"value": "={{ $json.body.text }}",
"type": "string"
}
]
},
"includeOtherFields": false,
"options": {}
},
"id": "6c57d7cf-acd9-5007-b34a-b741afa2c060",
"name": "Settings",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "f2c175d2-9545-570a-99a6-e7cdfb17162f",
"leftValue": "={{ $json.text }}",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"rightValue": ""
}
],
"combinator": "and"
},
"options": {}
},
"id": "d19bdfca-d55e-5760-a488-458abc7a5480",
"name": "Has text?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
440,
0
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"error\": \"text is required\" } }}",
"options": {
"responseCode": 400
}
},
"id": "5adfc553-1a2c-55fd-a4bd-d6564197222a",
"name": "Reject empty",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
680,
180
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Settings').first().json.llm_url }}/chat/completions",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: $('Settings').first().json.model, temperature: 0, messages: [ { role: 'system', content: \"Classify the IT request into exactly one of: account, network, device, software, other. Reply with the label only.\" }, { role: 'user', content: $('Settings').first().json.text } ] }) }}",
"options": {
"timeout": 180000
},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"id": "d084b606-aad9-5341-98cd-490070f907ac",
"name": "LLM classify",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
680,
-40
],
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
}
},
{
"parameters": {
"jsCode": "// 1) Clean up the LLM label 2) Search the FAQ list\nconst allowed = ['account', 'network', 'device', 'software', 'other'];\nconst raw = String($input.first().json.choices?.[0]?.message?.content ?? '').trim().toLowerCase();\nconst category = allowed.find(a => raw.includes(a)) ?? 'other';\n\nconst faqs = [\n {\n \"id\": \"FAQ-001\",\n \"title\": \"비밀번호 재설정\",\n \"keywords\": [\n \"비밀번호\",\n \"패스워드\",\n \"password\",\n \"재설정\",\n \"초기화\"\n ],\n \"answer\": \"사내 포털 로그인 화면의 '비밀번호 찾기'에서 휴대폰 인증 후 새 비밀번호를 설정합니다.\"\n },\n {\n \"id\": \"FAQ-002\",\n \"title\": \"계정 잠김 해제\",\n \"keywords\": [\n \"계정\",\n \"잠김\",\n \"잠금\",\n \"로그인\",\n \"실패\"\n ],\n \"answer\": \"로그인 5회 실패 시 30분 동안 잠깁니다. 30분 후 다시 시도하거나 헬프데스크에 잠금 해제를 요청합니다.\"\n },\n {\n \"id\": \"FAQ-003\",\n \"title\": \"VPN 접속 오류\",\n \"keywords\": [\n \"vpn\",\n \"원격\",\n \"재택\",\n \"접속\",\n \"연결\"\n ],\n \"answer\": \"VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다.\"\n },\n {\n \"id\": \"FAQ-004\",\n \"title\": \"프린터 출력 안 됨\",\n \"keywords\": [\n \"프린터\",\n \"출력\",\n \"인쇄\",\n \"용지\"\n ],\n \"answer\": \"프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력합니다.\"\n },\n {\n \"id\": \"FAQ-005\",\n \"title\": \"메일함 용량 초과\",\n \"keywords\": [\n \"메일\",\n \"용량\",\n \"메일함\",\n \"첨부\"\n ],\n \"answer\": \"오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제해 용량을 확보합니다.\"\n },\n {\n \"id\": \"FAQ-006\",\n \"title\": \"소프트웨어 설치 요청\",\n \"keywords\": [\n \"설치\",\n \"소프트웨어\",\n \"프로그램\",\n \"라이선스\"\n ],\n \"answer\": \"IT 포털의 '소프트웨어 신청' 메뉴에서 승인 요청을 올리면 승인 후 원격으로 설치됩니다.\"\n }\n];\nconst settings = $('Settings').first().json;\nconst words = String(settings.text).toLowerCase().split(/\\s+/).filter(w => w.length > 1);\nconst scored = faqs\n .map(f => ({ ...f, score: words.filter(w => f.keywords.some(k => w.includes(k) || k.includes(w))).length }))\n .filter(f => f.score > 0)\n .sort((a, b) => b.score - a.score);\nconst faq = scored[0] ?? null;\n\nreturn [{ json: { user: settings.user, text: settings.text, raw_label: raw, category, found: faq !== null, faq } }];"
},
"id": "aa4be834-45fb-563b-95f5-c6a0dbeb2903",
"name": "Normalize and search FAQ",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
920,
-40
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "6a4b578f-d9da-5899-933d-7e05d901e3eb",
"leftValue": "={{ $json.found }}",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"rightValue": ""
}
],
"combinator": "and"
},
"options": {}
},
"id": "21f335b6-8a0a-5331-b8df-43d3c9f9aafc",
"name": "FAQ found?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1160,
-40
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Settings').first().json.llm_url }}/chat/completions",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: $('Settings').first().json.model, temperature: 0, messages: [ { role: 'system', content: \"Write a polite 2-sentence Korean reply using only the FAQ answer. End with the FAQ id in parentheses.\" }, { role: 'user', content: '문의: ' + $('Normalize and search FAQ').first().json.text + '\\nFAQ: ' + JSON.stringify($('Normalize and search FAQ').first().json.faq) } ] }) }}",
"options": {
"timeout": 180000
},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"id": "a1e86429-1ee7-585c-bdaa-f1aa0d10f49b",
"name": "LLM draft reply",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1400,
-140
],
"credentials": {
"openAiApi": {
"id": "openAiCourse0001",
"name": "OpenAI (.env)"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1e3762b6-7dae-5e2f-9538-8a5b0f5dcae0",
"name": "category",
"value": "={{ $('Normalize and search FAQ').first().json.category }}",
"type": "string"
},
{
"id": "230f5390-e5c9-5e6a-90ec-03e6965652f7",
"name": "faq_id",
"value": "={{ $('Normalize and search FAQ').first().json.faq.id }}",
"type": "string"
},
{
"id": "1f78eb57-6478-5590-94f9-bacb9b2c89b9",
"name": "route",
"value": "자동 답변",
"type": "string"
},
{
"id": "756cec4c-d91f-516a-9d9f-ae74098227e9",
"name": "reply",
"value": "={{ $json.choices[0].message.content.trim() }}",
"type": "string"
}
]
},
"includeOtherFields": false,
"options": {}
},
"id": "da512682-1045-5a8e-83d1-52975eb08e97",
"name": "Auto reply",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1640,
-140
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "bf4ced71-6e32-5ed2-ad7d-4124d462e3d2",
"name": "category",
"value": "={{ $json.category }}",
"type": "string"
},
{
"id": "79bffc37-152f-5b3e-9018-e30121faed11",
"name": "faq_id",
"value": "",
"type": "string"
},
{
"id": "86fde04e-b2a1-5782-97c1-caba13a9ed10",
"name": "route",
"value": "담당자 확인",
"type": "string"
},
{
"id": "d760805f-73f1-5d0a-a7cf-8eb67d03e978",
"name": "reply",
"value": "관련 안내를 찾지 못해 담당자가 확인 후 답변드리겠습니다.",
"type": "string"
}
]
},
"includeOtherFields": false,
"options": {}
},
"id": "604eb70d-bff2-5270-b1a4-f94a3a6c681d",
"name": "Hand off to staff",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1400,
80
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "2de33965-f060-5f8b-bd91-2a7c72a1e618",
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1880,
-40
]
}
],
"connections": {
"AI request webhook": {
"main": [
[
{
"node": "Settings",
"type": "main",
"index": 0
}
]
]
},
"Settings": {
"main": [
[
{
"node": "Has text?",
"type": "main",
"index": 0
}
]
]
},
"Has text?": {
"main": [
[
{
"node": "LLM classify",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject empty",
"type": "main",
"index": 0
}
]
]
},
"LLM classify": {
"main": [
[
{
"node": "Normalize and search FAQ",
"type": "main",
"index": 0
}
]
]
},
"Normalize and search FAQ": {
"main": [
[
{
"node": "FAQ found?",
"type": "main",
"index": 0
}
]
]
},
"FAQ found?": {
"main": [
[
{
"node": "LLM draft reply",
"type": "main",
"index": 0
}
],
[
{
"node": "Hand off to staff",
"type": "main",
"index": 0
}
]
]
},
"LLM draft reply": {
"main": [
[
{
"node": "Auto reply",
"type": "main",
"index": 0
}
]
]
},
"Auto reply": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
},
"Hand off to staff": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"pinData": {},
"id": "493c961026875d"
}