Files
aio-01d4-stu/Last3Mon/78/reference/lab03-example2-import.json

677 lines
17 KiB
JSON

{
"name": "78 FAQ import + release fixed rows (reference)",
"nodes": [
{
"name": "Import webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"parameters": {
"httpMethod": "POST",
"path": "my-faq-import",
"responseMode": "responseNode",
"options": {}
},
"position": [
0,
300
],
"id": "d3a86b18-edfc-51e7-95c7-8aa2c58fdbda",
"webhookId": "c4169baa-ac65-5afd-ac30-52094389dbf7"
},
{
"name": "Run check",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://localhost:5678/webhook/my-faq-check",
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 30000
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.body) }}"
},
"position": [
220,
300
],
"id": "1d6a0bcd-c4ae-5b95-b04a-8c021adbd523"
},
{
"name": "Checked?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ Array.isArray($json.rows) && $json.rows.length > 0 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
440,
300
],
"id": "b753a2cd-44be-5af3-8683-b154c2443f0f"
},
{
"name": "Reject body",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ error: $json.error ?? 'check failed' }) }}",
"options": {
"responseCode": 400
}
},
"position": [
660,
760
],
"id": "36f0ca97-2abb-53d3-a0da-4fe3e3d2ae74"
},
{
"name": "Batch ok?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.invalid / $json.total <= ($('Import webhook').first().json.body.max_invalid_ratio ?? 0.3) }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
660,
300
],
"id": "edceb049-33db-5490-bac7-bed1554f414f"
},
{
"name": "Reject batch",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ error: 'batch rejected', batch: $('Run check').first().json.batch, invalid_ratio: Math.round($('Run check').first().json.invalid / $('Run check').first().json.total * 10000) / 10000, max_invalid_ratio: ($('Import webhook').first().json.body.max_invalid_ratio ?? 0.3), codes: $('Run check').first().json.codes, llm_calls: 0 }) }}",
"options": {
"responseCode": 422
}
},
"position": [
1320,
560
],
"id": "256d2a55-b303-51f1-a8a5-3ea934fb7f3b"
},
{
"name": "Log rejected",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/quality-log",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ batch: $json.batch, dataset: 'faq', total: $json.total, valid: $json.valid, quarantined: 0, rejected: true, score: 0, codes: $json.codes }) }}"
},
"position": [
880,
560
],
"id": "7f06265c-c872-5e48-8c72-0ea9beaf1cdc"
},
{
"name": "Alert rejected",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/alerts",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ workflow: 'faq-import', node: 'Batch ok?', severity: 'high', reason: 'batch_rejected', batch: $('Run check').first().json.batch, message: $('Run check').first().json.batch + ' rejected' }) }}"
},
"position": [
1100,
560
],
"id": "4ba91d1f-4b86-552d-b572-08706cd4d170"
},
{
"name": "Split rows",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const c = $('Run check').first().json;\nconst items = $('Import webhook').first().json.body.items;\nreturn c.rows.map((r, i) => ({ json: { ...r, row: items[i], batch: c.batch } }));"
},
"position": [
880,
300
],
"id": "ebce06e1-7140-5292-8687-4ab2b641c047"
},
{
"name": "Only passed",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "return $input.all().filter((i) => i.json.ok);"
},
"position": [
1100,
300
],
"id": "b7a235dd-8495-5b00-a635-3dbd3920a1c4"
},
{
"name": "Judge",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"parameters": {
"promptType": "define",
"text": "=제목: {{ $json.row.title }}\n답: {{ $json.row.answer }}",
"options": {
"systemMessage": "You check a company IT FAQ entry before it is published. Does the answer actually answer the title's topic? Ignore style. Return only JSON, no code fence: {\"match\": true, \"reason\": \"short reason in Korean\"}",
"maxIterations": 1
}
},
"onError": "continueRegularOutput",
"position": [
1320,
300
],
"id": "6273964f-6cb4-56ab-885d-2dc3faf7f7ff"
},
{
"name": "Judge 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": "dd634caa-4367-5cdb-aac1-559d0114d7ff"
},
{
"name": "Decide",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const judged = $('Only passed').all().map((item, i) => {\n let v = { match: false, reason: 'judge unreadable' };\n try { v = JSON.parse(String($('Judge').all()[i].json.output ?? '').replace(/^```(json)?\\s*|```$/g, '')); } catch (e) {}\n return { ...item.json, ok: v.match === true, errors: v.match === true ? [] : ['llm_mismatch'], reason: String(v.reason ?? '') };\n});\nlet k = 0;\nreturn $('Split rows').all().map((i) => ({ json: i.json.ok ? judged[k++] : i.json }));"
},
"position": [
1540,
300
],
"id": "af54c0ea-0a6a-569a-8cc5-9e4b831fda3c"
},
{
"name": "Store row",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "={{ $json.ok ? 'http://data-api:8800/faq/add' : 'http://data-api:8800/quarantine' }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.ok ? $json.row : { items: [{ batch: $json.batch, row: $json.row, errors: $json.errors }] }) }}"
},
"position": [
1760,
300
],
"id": "b1b7176d-9a05-5f4b-82e7-71b8264ed74c"
},
{
"name": "Summarize",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "const c = $('Run check').first().json;\nconst rows = $('Decide').all().map((i) => i.json);\nconst imported = rows.filter((r) => r.ok);\nconst quarantined = rows.filter((r) => !r.ok);\nconst codes = { ...c.codes };\nfor (const r of quarantined) for (const e of r.errors) if (e === 'llm_mismatch') codes[e] = (codes[e] ?? 0) + 1;\nreturn [{ json: { batch: c.batch, total: c.total, imported: imported.length, quarantined: quarantined.length, rejected: false,\n score: Math.round(imported.length / c.total * 10000) / 10000, max_invalid_ratio: ($('Import webhook').first().json.body.max_invalid_ratio ?? 0.3),\n codes, imported_ids: imported.map((r) => r.id), quarantined_ids: quarantined.map((r) => r.id), llm_calls: $('Only passed').all().length } }];"
},
"position": [
1980,
300
],
"id": "9e940251-c204-5813-9457-72980199a956"
},
{
"name": "Release fixed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/quarantine/remove",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ids: $json.imported_ids }) }}"
},
"position": [
2200,
300
],
"id": "7239d9dc-daf2-5aea-a13c-be25358337a2"
},
{
"name": "Log quality",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/quality-log",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ batch: $('Summarize').first().json.batch, dataset: 'faq', total: $('Summarize').first().json.total, valid: $('Summarize').first().json.imported, quarantined: $('Summarize').first().json.quarantined, rejected: false, score: $('Summarize').first().json.score, codes: $('Summarize').first().json.codes }) }}"
},
"position": [
2420,
300
],
"id": "67ebf40c-0364-5bde-9f95-eb77a10689c3"
},
{
"name": "Low score?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $('Summarize').first().json.score < 0.9 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"position": [
2640,
300
],
"id": "4f8eb1e7-753f-5515-8485-e9b0ff7d8bc7"
},
{
"name": "Alert low score",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"url": "http://data-api:8800/alerts",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ workflow: 'faq-import', node: 'Low score?', severity: 'high', reason: 'low_quality', batch: $('Summarize').first().json.batch, score: $('Summarize').first().json.score, message: $('Summarize').first().json.batch + ' quality ' + $('Summarize').first().json.score }) }}"
},
"position": [
2860,
160
],
"id": "68033c5c-b22b-535f-9ddc-ff603ab7d3c2"
},
{
"name": "Result again",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"parameters": {
"jsCode": "return [{ json: $('Summarize').first().json }];"
},
"position": [
3080,
300
],
"id": "2ca2f7fc-885a-5ac9-9121-62b79d203f74"
},
{
"name": "Send result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"position": [
3300,
300
],
"id": "c58470f8-9dd9-521a-be5b-746bbdb86d3a"
}
],
"connections": {
"Import webhook": {
"main": [
[
{
"node": "Run check",
"type": "main",
"index": 0
}
]
]
},
"Run check": {
"main": [
[
{
"node": "Checked?",
"type": "main",
"index": 0
}
]
]
},
"Checked?": {
"main": [
[
{
"node": "Batch ok?",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject body",
"type": "main",
"index": 0
}
]
]
},
"Batch ok?": {
"main": [
[
{
"node": "Split rows",
"type": "main",
"index": 0
}
],
[
{
"node": "Log rejected",
"type": "main",
"index": 0
}
]
]
},
"Log rejected": {
"main": [
[
{
"node": "Alert rejected",
"type": "main",
"index": 0
}
]
]
},
"Alert rejected": {
"main": [
[
{
"node": "Reject batch",
"type": "main",
"index": 0
}
]
]
},
"Split rows": {
"main": [
[
{
"node": "Only passed",
"type": "main",
"index": 0
}
]
]
},
"Only passed": {
"main": [
[
{
"node": "Judge",
"type": "main",
"index": 0
}
]
]
},
"Judge": {
"main": [
[
{
"node": "Decide",
"type": "main",
"index": 0
}
]
]
},
"Judge model": {
"ai_languageModel": [
[
{
"node": "Judge",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Decide": {
"main": [
[
{
"node": "Store row",
"type": "main",
"index": 0
}
]
]
},
"Store row": {
"main": [
[
{
"node": "Summarize",
"type": "main",
"index": 0
}
]
]
},
"Summarize": {
"main": [
[
{
"node": "Release fixed",
"type": "main",
"index": 0
}
]
]
},
"Release fixed": {
"main": [
[
{
"node": "Log quality",
"type": "main",
"index": 0
}
]
]
},
"Log quality": {
"main": [
[
{
"node": "Low score?",
"type": "main",
"index": 0
}
]
]
},
"Low score?": {
"main": [
[
{
"node": "Alert low score",
"type": "main",
"index": 0
}
],
[
{
"node": "Result again",
"type": "main",
"index": 0
}
]
]
},
"Alert low score": {
"main": [
[
{
"node": "Result again",
"type": "main",
"index": 0
}
]
]
},
"Result again": {
"main": [
[
{
"node": "Send result",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}