This workflow follows the Chainllm → Anthropic Chat recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"id": "ba48d480-83b8-461a-869c-832413dd226d",
"name": "Normalize Chat Input",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1880,
320
],
"alwaysOutputData": true,
"parameters": {
"jsCode": "const toStr = (v) => String(v ?? '').trim();\nconst norm = (v) => toStr(v)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/\\s+/g, ' ')\n .trim();\n\nconst getEnv = (key) => {\n try {\n if ($env && $env[key]) return String($env[key]).trim();\n } catch (_e) {}\n try {\n const p = (typeof globalThis !== 'undefined' && globalThis.process && globalThis.process.env)\n ? globalThis.process.env[key]\n : '';\n if (p) return String(p).trim();\n } catch (_e) {}\n return '';\n};\n\nconst getStore = () => {\n if (typeof $getWorkflowStaticData === 'function') return $getWorkflowStaticData('global');\n if (this && typeof this.getWorkflowStaticData === 'function') return this.getWorkflowStaticData('global');\n throw new Error('[Normalize Chat Input] static data indisponivel no Code node.');\n};\n\nconst raw = $json || {};\nconst message = toStr(raw.chatInput || raw.message || raw.text || raw.input || '');\nconst sessionId = toStr(raw.sessionId || raw.session_id || raw.chatSessionId || raw.metadata?.sessionId || 'default');\n\nconst store = getStore();\nif (!store.chatMemory) store.chatMemory = {};\nif (!Array.isArray(store.chatMemory[sessionId])) store.chatMemory[sessionId] = [];\nif (!store.selectedOption) store.selectedOption = {};\nif (!store.impactIntakeState) store.impactIntakeState = {};\n\nconst pushMemory = (role, content) => {\n const text = toStr(content);\n if (!text) return;\n store.chatMemory[sessionId].push({ role, content: text, at: new Date().toISOString() });\n if (store.chatMemory[sessionId].length > 40) {\n store.chatMemory[sessionId] = store.chatMemory[sessionId].slice(-40);\n }\n};\n\nconst chatHistory = () => store.chatMemory[sessionId]\n .slice(-12)\n .map((t) => ({ role: t.role, content: toStr(t.content) }));\n\nconst menuText = [\n 'Escolha uma opcao para continuar (envie apenas 1, 2 ou 3):',\n '1) Nova analise de Impacto',\n '2) Duvida sobre funcionalidade ja implementada',\n '3) Correcao de PRs abertas'\n].join('\\n');\n\nconst invalidOutput = 'op\u00e7\u00e3o inv\u00e1lida! Escolha 1,2 ou 3';\n\nif (!message) {\n return [{\n json: {\n option: 'invalid',\n route: 'impact_collect_question',\n session_id: sessionId,\n chat_history: chatHistory(),\n output: menuText,\n }\n }];\n}\n\nconst lower = norm(message);\nif (lower === '/reset') {\n delete store.selectedOption[sessionId];\n delete store.impactIntakeState[sessionId];\n return [{\n json: {\n option: 'invalid',\n route: 'impact_collect_question',\n session_id: sessionId,\n chat_history: chatHistory(),\n output: menuText,\n }\n }];\n}\n\nif (lower === '/menu') {\n return [{\n json: {\n option: 'invalid',\n route: 'impact_collect_question',\n session_id: sessionId,\n chat_history: chatHistory(),\n output: menuText,\n }\n }];\n}\n\npushMemory('user', message);\n\nconst isOption = /^(1|2|3)$/.test(lower);\nif (isOption) {\n const option = lower;\n store.selectedOption[sessionId] = option;\n\n if (option === '1') {\n const currentState = store.impactIntakeState[sessionId] || {\n problema: '',\n mudanca: '',\n evidencias: '',\n restricoes: '',\n status: '',\n diagnostico: '',\n evidencias_lista: []\n };\n store.impactIntakeState[sessionId] = currentState;\n\n return [{\n json: {\n option: '1',\n route: 'impact_intake',\n is_selection: true,\n session_id: sessionId,\n chat_history: chatHistory(),\n user_message: message,\n question: message,\n impact_intake_state: currentState,\n }\n }];\n }\n\n if (option === '2') {\n return [{\n json: {\n option: '2',\n route: 'pr_qa',\n is_selection: true,\n session_id: sessionId,\n chat_history: chatHistory(),\n output: 'Opcao 2 selecionada. Agora descreva sua duvida sobre funcionalidade ja implementada.',\n }\n }];\n }\n\n return [{\n json: {\n option: '3',\n route: 'correction_propose',\n is_selection: true,\n session_id: sessionId,\n chat_history: chatHistory(),\n output: 'Opcao 3 selecionada. Agora descreva a correcao desejada nas PRs abertas.',\n }\n }];\n}\n\nconst selected = toStr(store.selectedOption[sessionId]);\nif (!selected) {\n return [{\n json: {\n option: 'invalid',\n route: 'impact_collect_question',\n session_id: sessionId,\n chat_history: chatHistory(),\n output: invalidOutput,\n }\n }];\n}\n\nif (selected === '1') {\n const currentState = store.impactIntakeState[sessionId] || {\n problema: '',\n mudanca: '',\n evidencias: '',\n restricoes: '',\n status: '',\n diagnostico: '',\n evidencias_lista: []\n };\n\n return [{\n json: {\n option: '1',\n route: 'impact_intake',\n is_selection: false,\n session_id: sessionId,\n chat_history: chatHistory(),\n user_message: message,\n question: message,\n impact_intake_state: currentState,\n }\n }];\n}\n\nif (selected === '2') {\n return [{\n json: {\n option: '2',\n route: 'pr_qa',\n is_selection: false,\n session_id: sessionId,\n chat_history: chatHistory(),\n question: message,\n owner: toStr(getEnv('GITHUB_OWNER') || 'oliversoft-tech'),\n github_token: toStr(getEnv('GITHUB_TOKEN') || ''),\n llm_model: toStr(getEnv('LLM_MODEL') || 'claude-3-5-sonnet-20241022')\n }\n }];\n}\n\nif (selected === '3') {\n return [{\n json: {\n option: '3',\n route: 'correction_propose',\n is_selection: false,\n session_id: sessionId,\n chat_history: chatHistory(),\n question: message,\n owner: toStr(getEnv('GITHUB_OWNER') || 'oliversoft-tech'),\n github_token: toStr(getEnv('GITHUB_TOKEN') || ''),\n llm_model: toStr(getEnv('LLM_MODEL') || 'claude-3-5-sonnet-20241022')\n }\n }];\n}\n\nreturn [{\n json: {\n option: 'invalid',\n route: 'impact_collect_question',\n session_id: sessionId,\n chat_history: chatHistory(),\n output: invalidOutput,\n }\n}];\n"
}
},
{
"id": "1c5326bb-0f6d-4a3b-8e4e-4d4cb53ca111",
"name": "Switch Option",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"position": [
-1620,
320
],
"parameters": {
"dataType": "string",
"value1": "={{$json.option}}",
"rules": [
{
"operation": "equal",
"value2": "1"
},
{
"operation": "equal",
"value2": "2"
},
{
"operation": "equal",
"value2": "3"
}
]
}
},
{
"id": "c78b5043-4f88-4d0f-a7e2-f4b7fdc2f2b4",
"name": "Switch Option 2 Step",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"position": [
-1360,
900
],
"parameters": {
"dataType": "string",
"value1": "={{$json.is_selection ? 'selection' : 'message'}}",
"rules": [
{
"operation": "equal",
"value2": "selection"
},
{
"operation": "equal",
"value2": "message"
}
]
}
},
{
"id": "0f52b6cf-ef20-4d4f-8a98-ec1a5ed6e9a4",
"name": "Switch Option 3 Step",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"position": [
-1360,
620
],
"parameters": {
"dataType": "string",
"value1": "={{$json.is_selection ? 'selection' : 'message'}}",
"rules": [
{
"operation": "equal",
"value2": "selection"
},
{
"operation": "equal",
"value2": "message"
}
]
}
},
{
"id": "d8358c60-3476-4bd0-944f-91558e3fd7ef",
"name": "Basic LLM Chain (Impact Intake)",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.7,
"position": [
-1360,
280
],
"parameters": {
"promptType": "define",
"text": "={{[\n 'Voce e um assistente de intake para Impact Analysis.',\n 'Seu objetivo e coletar os campos abaixo com exemplos claros:',\n '- problema',\n '- mudanca',\n '- evidencias',\n '- restricoes',\n '- status',\n '- diagnostico',\n '- evidencias_lista (array de strings)',\n '',\n 'Retorne SOMENTE JSON valido sem markdown, neste formato exato:',\n '{',\n ' \"complete\": true|false,',\n ' \"reply\": \"string\",',\n ' \"updates\": {',\n ' \"problema\": \"string\",',\n ' \"mudanca\": \"string\",',\n ' \"evidencias\": \"string\",',\n ' \"restricoes\": \"string\",',\n ' \"status\": \"string\",',\n ' \"diagnostico\": \"string\",',\n ' \"evidencias_lista\": [\"string\"]',\n ' }',\n '}',\n '',\n 'Regras de comportamento:',\n '1) Se faltarem campos obrigatorios (problema,mudanca,evidencias,restricoes), reply deve perguntar objetivamente o proximo campo e incluir exemplo.',\n '2) Se o usuario ja tiver fornecido valor para algum campo, preserve e apenas complemente.',\n '3) complete=true somente quando os 4 campos obrigatorios estiverem preenchidos.',\n '4) Nao invente dados fora do que o usuario informou.',\n '',\n 'Estado atual (JSON):',\n JSON.stringify($json.impact_intake_state || {}, null, 2),\n '',\n 'Historico recente (JSON):',\n JSON.stringify($json.chat_history || [], null, 2),\n '',\n 'Mensagem atual do usuario:',\n $json.user_message || $json.question || ''\n].join('\\n')}}",
"hasOutputParser": false,
"messages": {
"messageValues": [
{
"message": "Coletar dados para Impact Analysis de forma objetiva e incremental. Sempre responder em JSON valido conforme schema solicitado."
}
]
},
"batching": {
"batchSize": 1
}
}
},
{
"id": "5ea171f9-11eb-4eb8-bcb8-3cc7e9e7a458",
"name": "Anthropic Chat Model - Impact Intake",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"typeVersion": 1.2,
"position": [
-1580,
170
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "claude-3-5-sonnet-20241022"
},
"options": {
"temperature": 0,
"maxTokens": 900
}
}
},
{
"id": "44e2ce8e-6a3e-4ba3-a2c6-0b8e93eae382",
"name": "Parse Impact Intake Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1100,
280
],
"alwaysOutputData": true,
"parameters": {
"jsCode": "const toStr = (v) => String(v ?? '').trim();\n\nconst rawText = toStr($json.text || $json.response?.text || '');\nif (!rawText) throw new Error('[Parse Impact Intake Response] LLM retornou texto vazio.');\n\nconst tryParse = (txt) => {\n const candidates = [txt];\n const fenced = txt.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n if (fenced && fenced[1]) candidates.push(toStr(fenced[1]));\n for (const c of candidates) {\n try {\n return JSON.parse(c);\n } catch (_e) {}\n }\n throw new Error('[Parse Impact Intake Response] JSON invalido no retorno do LLM.');\n};\n\nconst parsed = tryParse(rawText);\nconst updates = parsed && typeof parsed === 'object' ? (parsed.updates || {}) : {};\n\nconst fromNormalize = (() => {\n try { return $items('Normalize Chat Input', 0, 0)[0]?.json || {}; } catch (_e) { return {}; }\n})();\n\nconst sessionId = toStr(fromNormalize.session_id || 'default');\nconst getStore = () => {\n if (typeof $getWorkflowStaticData === 'function') return $getWorkflowStaticData('global');\n if (this && typeof this.getWorkflowStaticData === 'function') return this.getWorkflowStaticData('global');\n throw new Error('[Parse Impact Intake Response] static data indisponivel no Code node.');\n};\n\nconst store = getStore();\nif (!store.impactIntakeState) store.impactIntakeState = {};\nif (!store.selectedOption) store.selectedOption = {};\n\nconst base = store.impactIntakeState[sessionId] || {\n problema: '',\n mudanca: '',\n evidencias: '',\n restricoes: '',\n status: '',\n diagnostico: '',\n evidencias_lista: []\n};\n\nconst pickStr = (a, b) => {\n const x = toStr(a);\n if (x) return x;\n return toStr(b);\n};\n\nbase.problema = pickStr(updates.problema, parsed.problema || base.problema);\nbase.mudanca = pickStr(updates.mudanca, parsed.mudanca || base.mudanca);\nbase.evidencias = pickStr(updates.evidencias, parsed.evidencias || base.evidencias);\nbase.restricoes = pickStr(updates.restricoes, parsed.restricoes || base.restricoes);\nbase.status = pickStr(updates.status, parsed.status || base.status);\nbase.diagnostico = pickStr(updates.diagnostico, parsed.diagnostico || base.diagnostico);\n\nconst listA = Array.isArray(base.evidencias_lista) ? base.evidencias_lista : [];\nconst listB = Array.isArray(updates.evidencias_lista) ? updates.evidencias_lista : (Array.isArray(parsed.evidencias_lista) ? parsed.evidencias_lista : []);\nbase.evidencias_lista = [...new Set([...listA, ...listB].map((x) => toStr(x)).filter(Boolean))];\n\nstore.impactIntakeState[sessionId] = base;\n\nconst required = ['problema', 'mudanca', 'evidencias', 'restricoes'];\nconst missing = required.filter((k) => !toStr(base[k]));\nconst complete = missing.length === 0;\n\nif (complete) {\n delete store.selectedOption[sessionId];\n delete store.impactIntakeState[sessionId];\n\n return [{\n json: {\n route: 'impact_run_manual',\n session_id: sessionId,\n impact_payload: {\n problema: toStr(base.problema),\n mudanca: toStr(base.mudanca),\n evidencias: toStr(base.evidencias),\n restricoes: toStr(base.restricoes),\n },\n status: toStr(base.status),\n diagnostico: toStr(base.diagnostico),\n evidencias_lista: base.evidencias_lista,\n output: 'Dados coletados. Vou disparar o Impact Analysis agora.'\n }\n }];\n}\n\nconst firstMissing = missing[0] || 'problema';\nconst examples = {\n problema: 'Exemplo de problema: Alto indice de insucesso na entrega por ausencia do morador.',\n mudanca: 'Exemplo de mudanca: Enviar SMS D-1 com opcoes de confirmacao/reagendamento/endereco alternativo.',\n evidencias: 'Exemplo de evidencias: SMS enviado e resposta recebida/persistida com timestamp.',\n restricoes: 'Exemplo de restricoes: Sem aumento de custo por mensagem acima de 10%.',\n};\n\nconst llmReply = toStr(parsed.reply || '');\nconst output = llmReply || [\n 'Para continuar, informe o campo: ' + firstMissing,\n examples[firstMissing] || ''\n].filter(Boolean).join('\\n');\n\nreturn [{\n json: {\n route: 'impact_collect_question',\n session_id: sessionId,\n output,\n draft: base,\n }\n}];\n"
}
},
{
"id": "4bc4ec58-079f-4ae0-9fef-c6f21e0f3e10",
"name": "Switch Impact Intake Route",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"position": [
-900,
280
],
"parameters": {
"dataType": "string",
"value1": "={{$json.route}}",
"rules": [
{
"operation": "equal",
"value2": "impact_run_manual"
},
{
"operation": "equal",
"value2": "impact_collect_question"
}
]
}
}
],
"connections": {
"Normalize Chat Input": {
"main": [
[
{
"node": "Switch Option",
"type": "main",
"index": 0
}
]
]
},
"Switch Option": {
"main": [
[
{
"node": "Basic LLM Chain (Impact Intake)",
"type": "main",
"index": 0
}
],
[
{
"node": "Switch Option 2 Step",
"type": "main",
"index": 0
}
],
[
{
"node": "Switch Option 3 Step",
"type": "main",
"index": 0
}
]
]
},
"Switch Option 2 Step": {
"main": null
},
"Switch Option 3 Step": {
"main": null
},
"Basic LLM Chain (Impact Intake)": {
"main": [
[
{
"node": "Parse Impact Intake Response",
"type": "main",
"index": 0
}
]
]
},
"Parse Impact Intake Response": {
"main": [
[
{
"node": "Switch Impact Intake Route",
"type": "main",
"index": 0
}
]
]
},
"Switch Impact Intake Route": {
"main": null
},
"Anthropic Chat Model - Impact Intake": {
"ai_languageModel": [
[
{
"node": "Basic LLM Chain (Impact Intake)",
"type": "ai_languageModel",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Nodes-Alterados-Switch-Intake-Option-Router. Uses chainLlm, lmChatAnthropic. Manual trigger; 8 nodes.
Source: https://github.com/oliverbill/fastroute-changemanagement/blob/01c24d7d3e3621e97a82a810e066b272ee8a0025/docs/n8n/nodes-alterados-switch-intake-option-router.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Takes your raw, unpolished voice transcripts and transforms them into well-structured LinkedIn posts using AI. Perfect for when you have good ideas but they come out as rambling thoughts.
Nodes-Alterados-Switch-Intake-Option-Router-V3. Uses chainLlm, lmChatAnthropic. Manual trigger; 8 nodes.
This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest — You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract — OCR + parsing pulls out key fie
Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 91 nodes.
A Telegram bot that converts natural-language work descriptions into detailed cost estimates using AI parsing, vector search, and the open-source DDC CWICR database with 55,000+ construction work item