260308:1208 20260308:1200 fix n8n workflow add model
Build and Deploy / deploy (push) Successful in 1m9s

This commit is contained in:
admin
2026-03-08 12:08:10 +07:00
parent d3428f73b5
commit ec5f83dd4c
3 changed files with 28 additions and 2 deletions
@@ -49,7 +49,7 @@
```bash
# แนะนำ: llama3.2:3b (เร็ว, VRAM ~3GB, เหมาะ Classification) หรือ ollama run llama3.2:3b
ollama pull llama3.2:3b
ollama pull qwen2.5:7b-instruct-q4_K_M
# Fallback: mistral:7b-instruct-q4_K_M (แม่นกว่า, VRAM ~5GB)
# ollama pull mistral:7b-instruct-q4_K_M
```
+5 -1
View File
@@ -16,6 +16,7 @@
{
"parameters": {
"jsCode": "// ============================================\n// CONFIGURATION - แก้ไขค่าที่นี่\n// ============================================\nconst CONFIG = {\n // Ollama Settings\n OLLAMA_HOST: 'http://192.168.20.100:11434',\n OLLAMA_MODEL_PRIMARY: 'llama3.2:3b',\n OLLAMA_MODEL_FALLBACK: 'mistral:7b-instruct-q4_K_M',\n \n // Backend Settings\n BACKEND_URL: 'https://backend.np-dms.work',\n MIGRATION_TOKEN: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1pZ3JhdGlvbl9ib3QiLCJzdWIiOjUsInNjb3BlIjoiR2xvYmFsIiwiaWF0IjoxNzcyNzc0MzI5LCJleHAiOjQ5Mjg1MzQzMjl9.TtA8zoHy7G9J5jPgYQPv7yw-9X--B_hl-Nv-c9V4PaA',\n \n // Batch Settings\n BATCH_SIZE: 2,\n BATCH_ID: 'migration_20260226',\n DELAY_MS: 2000,\n \n // Thresholds\n CONFIDENCE_HIGH: 0.85,\n CONFIDENCE_LOW: 0.60,\n MAX_RETRY: 3,\n FALLBACK_THRESHOLD: 5,\n \n // Source Definitions - แก้ไขโฟลเดอร์และไฟล์ทำงานที่นี่\n EXCEL_FILE: '/home/node/.n8n-files/staging_ai/C22024.xlsx',\n SOURCE_PDF_DIR: '/home/node/.n8n-files/staging_ai/Incoming/08C.2/2567',\n LOG_PATH: '/home/node/.n8n-files/migration_logs',\n \n // Database\n DB_HOST: '192.168.10.8',\n DB_PORT: 3306,\n DB_NAME: 'lcbp3',\n DB_USER: 'migration_bot',\n DB_PASSWORD: 'Center2025',\n PROJECT_ID: 1\n};\n\nreturn [{ json: { config_loaded: true, timestamp: new Date().toISOString(), config: CONFIG } }];"
// Ollama Settings\n OLLAMA_HOST: 'http://192.168.20.100:11434',\n OLLAMA_MODEL_PRIMARY: 'qwen2.5:7b',\n OLLAMA_MODEL_FALLBACK: 'mistral:7b-instruct-q4_K_M',\n \n // Backend Settings\n BACKEND_URL: 'https://backend.np-dms.work',\n MIGRATION_TOKEN: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1pZ3JhdGlvbl9ib3QiLCJzdWIiOjUsInNjb3BlIjoiR2xvYmFsIiwiaWF0IjoxNzcyNzc0MzI5LCJleHAiOjQ5Mjg1MzQzMjl9.TtA8zoHy7G9J5jPgYQPv7yw-9X--B_hl-Nv-c9V4PaA',\n \n // Batch Settings\n BATCH_SIZE: 2,\n BATCH_ID: 'migration_20260226',\n DELAY_MS: 2000,\n \n // Thresholds\n CONFIDENCE_HIGH: 0.85,\n CONFIDENCE_LOW: 0.60,\n MAX_RETRY: 3,\n FALLBACK_THRESHOLD: 5,\n \n // Source Definitions - แก้ไขโฟลเดอร์และไฟล์ทำงานที่นี่\n EXCEL_FILE: '/home/node/.n8n-files/staging_ai/C22024.xlsx',\n SOURCE_PDF_DIR: '/home/node/.n8n-files/staging_ai/Incoming/08C.2/2567',\n LOG_PATH: '/home/node/.n8n-files/migration_logs',\n \n // Database\n DB_HOST: '192.168.10.8',\n DB_PORT: 3306,\n DB_NAME: 'lcbp3',\n DB_USER: 'migration_bot',\n DB_PASSWORD: 'Center2025',\n PROJECT_ID: 1\n};\n\nreturn [{ json: { config_loaded: true, timestamp: new Date().toISOString(), config: CONFIG } }];"
},
"id": "bc8c9b9d-284d-4ce5-b7ff-d5b4bb36e748",
"name": "Set Configuration",
@@ -220,6 +221,7 @@
{
"parameters": {
"jsCode": "const config = $('Set Configuration').first().json.config;\nconst fallbackState = $input.first().json[0] || { is_fallback_active: false, recent_error_count: 0 };\n\nconst isFallback = fallbackState.is_fallback_active || false;\nconst model = isFallback ? config.OLLAMA_MODEL_FALLBACK : config.OLLAMA_MODEL_PRIMARY;\n\n// Safely pull categories from the first Check node\nlet systemCategories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];\ntry { systemCategories = $('File Mount Check').first().json.system_categories || systemCategories; } catch (e) {}\n\nconst items = $('Extract PDF Text').all();\n\nreturn items.map(item => {\n const docNum = String(item.json.document_number || '');\n const title = String(item.json.title || '');\n const legacyNum = String(item.json.legacy_number || '');\n\n const isRFA = docNum.includes('-RFA-') || title.toLowerCase().includes('rfa');\n\n const systemPrompt = `You are an expert Document Controller for a construction project (LCBP3) in Thailand.\nThe documents are primarily in THAI and ENGLISH.\nYour task is to classify documents and extract metadata from noisy OCR text.\nIf the OCR text is unreadable or gibberish, rely on the provided EXCEL METADATA.\nRespond ONLY with valid JSON.`;\n\n const pdfText = String(item.json.data || '').substring(0, 3500).replace(/[^a-zA-Z0-9ก-๙\\s\\.\\/\\-:\\[\\]\\(\\)]/g, ' ');\n\n const userPrompt = `Analyze this document:\n[EXCEL METADATA]\nDocument Number: ${docNum || 'Not provided'}\nTitle: ${title || 'Not provided'}\nLegacy Number: ${legacyNum || 'Not provided'}\n\n[OCR TEXT EXTRACTION]\n${pdfText}\n\nRules:\n1. Category must be one of: ${JSON.stringify(systemCategories)}\n2. If Document Number contains \"-RFA-\", suggest_category MUST be \"RFA\".\n3. For RFA, extract \"ref_no\" and \"response_to\".\n4. For Letters, identify \"from_org\" and \"to_org\".\n5. IMPORTANT: You MUST write a new 1-3 sentence summary in Thai evaluating the [OCR TEXT EXTRACTION] and place it in the \"body\" field. If the OCR is gibberish, write \"ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\" in the body.\n6. DO NOT invent non-existent English or Thai words for suggested_title. If you cannot find a clear title from the text, just use the exact EXCEL METADATA Title (${title}).\n\nRespond ONLY with this EXACT JSON structure:\n{\n \"is_valid\": true,\n \"confidence\": 0.95,\n \"suggested_category\": \"${isRFA ? 'RFA' : 'Correspondence'}\",\n \"detected_issues\": [],\n \"suggested_title\": \"${title}\",\n \"suggested_tags\": [\"Construction\", \"${isRFA ? 'Request' : 'Letter'}\"],\n \"metadata\": {\n \"ref_no\": null,\n \"response_to\": null,\n \"from_org\": null,\n \"to_org\": null,\n \"body\": \"สรุปสั้นๆ เป็นภาษาไทย 1-3 ประโยค หรือ ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\"\n }\n}`;\n\n return {\n json: {\n ...item.json,\n active_model: model,\n is_fallback: isFallback,\n system_categories: systemCategories,\n ollama_payload: {\n model: model,\n prompt: `${systemPrompt}\\n\\n${userPrompt}`,\n stream: false,\n format: 'json'\n }\n }\n };\n});"
const model = isFallback ? config.OLLAMA_MODEL_FALLBACK : config.OLLAMA_MODEL_PRIMARY;\n\n// Safely pull categories and tags from the first Check node\nlet systemCategories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];\nlet existingTags = [];\ntry {\n const checkData = $('File Mount Check').first().json;\n systemCategories = checkData.system_categories || systemCategories;\n existingTags = checkData.existing_tags || [];\n} catch (e) {}\n\nconst items = $('Extract PDF Text').all();\n\nreturn items.map(item => {\n const docNum = String(item.json.document_number || '');\n const title = String(item.json.title || '');\n const legacyNum = String(item.json.legacy_number || '');\n\n const isRFA = docNum.includes('-RFA-') || title.toLowerCase().includes('rfa');\n\n const systemPrompt = `You are an expert Document Controller for a construction project (LCBP3) in Thailand.\nThe documents are primarily in THAI and ENGLISH.\nYour task is to classify documents and extract metadata from noisy OCR text.\nIf the OCR text is unreadable or gibberish, rely on the provided EXCEL METADATA.\nRespond ONLY with valid JSON.`;\n\n const pdfText = String(item.json.data || '').substring(0, 3500).replace(/[^a-zA-Z0-9ก-๙\\s\\.\\/\\-:\\[\\]\\(\\)]/g, ' ');\n\n const userPrompt = `Analyze this document:\n[EXCEL METADATA]\nDocument Number: ${docNum || 'Not provided'}\nTitle: ${title || 'Not provided'}\nLegacy Number: ${legacyNum || 'Not provided'}\n\n[OCR TEXT EXTRACTION]\n${pdfText}\n\nRules:\n1. Category must be one of: ${JSON.stringify(systemCategories)}\n2. If Document Number contains \"-RFA-\", suggest_category MUST be \"RFA\".\n3. For RFA, extract \"ref_no\" and \"response_to\".\n4. For Letters, identify \"from_org\" and \"to_org\".\n5. Extract \"document_date\" from text (e.g., \"Date:\", \"วันที่\"). Convert Thai Year (BE 2567) to AD (2024) by subtracting 543. Format as YYYY-MM-DD. If not found, return null.\n6. IMPORTANT: You MUST write a new 1-3 sentence summary in Thai evaluating the [OCR TEXT EXTRACTION] and place it in the \"body\" field. If the OCR is gibberish, write \"ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\" in the body.\n7. DO NOT invent non-existent English or Thai words for suggested_title. If you cannot find a clear title from the text, just use the exact EXCEL METADATA Title (${title}).\n8. Suggest 3-5 relevant tags based on content. Check against EXISTING TAGS: ${JSON.stringify(existingTags)}. Use existing tags if possible, otherwise suggest new concise tags (English/Thai).\n\nRespond ONLY with this EXACT JSON structure:\n{\n \"is_valid\": true,\n \"confidence\": 0.95,\n \"suggested_category\": \"${isRFA ? 'RFA' : 'Correspondence'}\",\n \"detected_issues\": [],\n \"suggested_title\": \"${title}\",\n \"suggested_tags\": [\"Construction\", \"${isRFA ? 'Request' : 'Letter'}\"],\n \"metadata\": {\n \"ref_no\": null,\n \"response_to\": null,\n \"from_org\": null,\n \"to_org\": null,\n \"document_date\": null,\n \"body\": \"สรุปสั้นๆ เป็นภาษาไทย 1-3 ประโยค หรือ ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\"\n }\n}`;\n\n return {\n json: {\n ...item.json,\n active_model: model,\n is_fallback: isFallback,\n system_categories: systemCategories,\n ollama_payload: {\n model: model,\n prompt: `${systemPrompt}\\n\\n${userPrompt}`,\n stream: false,\n format: 'json'\n }\n }\n };\n});"
},
"id": "9f82950f-7533-4cbd-8e1e-8e441c1cb2a5",
"name": "Build AI Prompt",
@@ -255,6 +257,8 @@
{
"parameters": {
"jsCode": "const ollamaItems = $input.all();\nconst originalItems = $('Build AI Prompt').all();\nconst results = [];\n\nfor (let i = 0; i < ollamaItems.length; i++) {\n const ollamaItem = ollamaItems[i];\n const originalItem = originalItems[i];\n\n if (!originalItem) continue; // safety check\n\n // Reconstruct original JSON\n const baseJson = originalItem.json;\n\n try {\n let raw = ollamaItem.json.response || '';\n\n // Clean markdown and whitespace\n raw = raw.replace(/\\`\\`\\`json/gi, '').replace(/\\`\\`\\`/g, '').trim();\n if (!raw) throw new Error('Empty response from AI');\n\n const result = JSON.parse(raw);\n\n // Metadata mapping & normalization\n const meta = result.metadata || {};\n const metadata = {\n ref_no: String(meta.ref_no || '').trim() || null,\n response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;"
response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n document_date: String(meta.document_date || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;"
response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n document_date: String(meta.document_date || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || metadata.document_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;"
},
"id": "281dc950-a3b6-4412-a0b4-76663b8c37ea",
"name": "Parse & Validate AI Response",
@@ -953,4 +957,4 @@
},
"id": "u7CLP05AyFb8Um0P",
"tags": []
}
}