This workflow follows the Chainllm → HTTP Request 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 →
{
"name": "LLM Code Review in GitLab MR",
"nodes": [
{
"parameters": {
"content": "## Edit your own prompt \u2b07\ufe0f\n"
},
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
460
],
"typeVersion": 1,
"id": "11973963-c9f2-4be7-a9ae-3959618b284b"
},
{
"parameters": {
"content": "## Filter comments and customize your trigger words \u2b07\ufe0f"
},
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-220,
500
],
"typeVersion": 1,
"id": "dbefd028-be11-459d-84a6-3f32bf26d7ca"
},
{
"parameters": {
"content": "## Replace your gitlab URL and token \u2b07\ufe0f"
},
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
40,
480
],
"typeVersion": 1,
"id": "28a74e51-1947-48d9-8b19-6c7ccd535767"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nvar diff = $input.item.json.gitDiff\n\nlet lines = diff.trimEnd().split('\\n');\n\nlet originalCode = '';\nlet newCode = '';\n\nlines.forEach(line => {\n console.log(line)\n if (line.startsWith('-')) {\n originalCode += line + \"\\n\";\n } else if (line.startsWith('+')) {\n newCode += line + \"\\n\";\n } else {\n originalCode += line + \"\\n\";\n newCode += line + \"\\n\";\n }\n});\n\nreturn {\n originalCode:originalCode,\n newCode:newCode\n};\n\n"
},
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
860,
460
],
"typeVersion": 2,
"id": "ae159635-c46c-481f-a609-ef74ba8b4a7d"
},
{
"parameters": {
"content": "## Replace your gitlab URL and token \u2b07\ufe0f"
},
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
460
],
"typeVersion": 1,
"id": "7ec3cb71-90cd-4fa2-9724-17feed2fcb1f"
},
{
"parameters": {
"httpMethod": "POST",
"path": "e21095c0-1876-4cd9-9e92-a2eac737f03e",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-620,
680
],
"typeVersion": 1.1,
"id": "8620b174-5c10-45ec-9ffa-c08883618205"
},
{
"parameters": {
"model": "=GLM-4-Plus",
"options": {
"baseURL": "https://open.bigmodel.cn/api/paas/v4/"
}
},
"name": "big model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1040,
780
],
"typeVersion": 1,
"id": "58310bd6-85f9-47de-9b49-140043451b3f",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $('Webhook').item.json[\"body\"][\"project\"][\"id\"] }}/merge_requests/{{ $('Webhook').item.json[\"body\"][\"object_attributes\"][\"iid\"] }}/changes",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "<redacted-credential>"
}
]
},
"options": {}
},
"name": "Get Changes",
"type": "n8n-nodes-base.httpRequest",
"position": [
80,
660
],
"typeVersion": 4.1,
"id": "b4db85dd-2a50-4493-9a7c-4904d888e13b"
},
{
"parameters": {
"fieldToSplitOut": "changes",
"options": {}
},
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
280,
660
],
"typeVersion": 1,
"id": "ca8190af-dc9f-4b72-927d-ff7d6bbe7939"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.renamed_file }}",
"rightValue": ""
},
{
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.deleted_file }}",
"rightValue": ""
},
{
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.diff }}",
"rightValue": "@@"
}
],
"combinator": "and"
},
"options": {}
},
"name": "Skip File Change",
"type": "n8n-nodes-base.if",
"position": [
480,
660
],
"typeVersion": 2,
"id": "32886e0d-57a9-4492-9a5f-39af0221f34f"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const parseLastDiff = (gitDiff) => {\n gitDiff = gitDiff.replace(/\\n\\\\ No newline at end of file/, '')\n \n const diffList = gitDiff.trimEnd().split('\\n').reverse();\n const lastLineFirstChar = diffList?.[0]?.[0];\n const lastDiff =\n diffList.find((item) => {\n return /^@@ \\-\\d+,\\d+ \\+\\d+,\\d+ @@/g.test(item);\n }) || '';\n\n const [lastOldLineCount, lastNewLineCount] = lastDiff\n .replace(/@@ \\-(\\d+),(\\d+) \\+(\\d+),(\\d+) @@.*/g, ($0, $1, $2, $3, $4) => {\n return `${+$1 + +$2},${+$3 + +$4}`;\n })\n .split(',');\n \n if (!/^\\d+$/.test(lastOldLineCount) || !/^\\d+$/.test(lastNewLineCount)) {\n return {\n lastOldLine: -1,\n lastNewLine: -1,\n gitDiff,\n };\n }\n\n\n const lastOldLine = lastLineFirstChar === '+' ? null : (parseInt(lastOldLineCount) || 0) - 1;\n const lastNewLine = lastLineFirstChar === '-' ? null : (parseInt(lastNewLineCount) || 0) - 1;\n\n return {\n lastOldLine,\n lastNewLine,\n gitDiff,\n };\n};\n\nreturn parseLastDiff($input.item.json.diff)\n"
},
"name": "Parse Last Diff Line",
"type": "n8n-nodes-base.code",
"position": [
680,
460
],
"typeVersion": 2,
"id": "89317b32-8452-4794-bbd8-d73ebb199ea2"
},
{
"parameters": {
"method": "POST",
"url": "=https://gitlab.com/api/v4/projects/{{ $('Webhook').item.json[\"body\"][\"project\"][\"id\"] }}/merge_requests/{{ $('Webhook').item.json[\"body\"][\"object_attributes\"][\"iid\"] }}/discussions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "<redacted-credential>"
}
]
},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "body",
"value": "={{ $('Basic LLM Chain').item.json[\"text\"] }}"
},
{
"name": "position[position_type]",
"value": "text"
},
{
"name": "position[old_path]",
"value": "={{ $('Split Out').item.json.old_path }}"
},
{
"name": "position[new_path]",
"value": "={{ $('Split Out').item.json.new_path }}"
},
{
"name": "position[start_sha]",
"value": "={{ $('Get Changes').item.json.diff_refs.start_sha }}"
},
{
"name": "position[head_sha]",
"value": "={{ $('Get Changes').item.json.diff_refs.head_sha }}"
},
{
"name": "position[base_sha]",
"value": "={{ $('Get Changes').item.json.diff_refs.base_sha }}"
},
{
"name": "position[new_line]",
"value": "={{ $('Parse Last Diff Line').item.json.lastNewLine || '' }}"
},
{
"name": "position[old_line]",
"value": "={{ $('Parse Last Diff Line').item.json.lastOldLine || '' }}"
}
]
},
"options": {}
},
"name": "Post Discussions",
"type": "n8n-nodes-base.httpRequest",
"position": [
1420,
640
],
"typeVersion": 4.1,
"id": "3085eba8-c47b-4518-ae50-3e9cbed7ff6a"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $('Get Review Count').all().filter(item => !item.json.system) }}",
"rightValue": 0
}
],
"combinator": "or"
},
"options": {}
},
"name": "Need Review",
"type": "n8n-nodes-base.if",
"position": [
-180,
680
],
"typeVersion": 2,
"id": "6686637b-facc-43c9-bbd9-aa78e9ff90c1"
},
{
"parameters": {
"content": "## Replace your gitlab URL and token \u2b07\ufe0f"
},
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
500
],
"typeVersion": 1,
"id": "a5409431-6822-4e56-acbf-1637df9252d8"
},
{
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/{{ $json[\"body\"][\"project\"][\"id\"] }}/merge_requests/{{ $json[\"body\"][\"object_attributes\"][\"iid\"] }}/notes",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "PRIVATE-TOKEN",
"value": "<redacted-credential>"
}
]
},
"options": {}
},
"id": "17b3f0e9-3c28-487b-924f-e0014dcc8f4c",
"name": "Get Review Count",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-400,
680
]
},
{
"parameters": {
"prompt": "=File path\uff1a{{ $('Skip File Change').item.json.new_path }}\n\n```Original code\n {{ $json.originalCode }}\n```\nchange to\n```New code\n {{ $json.newCode }}\n```\nPlease review the code changes in this section:",
"hasOutputParser": true,
"messages": {
"messageValues": [
{
"type": "AIMessagePromptTemplate",
"message": "# Overview:| \u60a8\u662f\u9ad8\u7ea7\u7f16\u7a0b\u4e13\u5bb6Bot\uff0c\u8d1f\u8d23\u5ba1\u67e5\u4ee3\u7801\u66f4\u6539\u5e76\u63d0\u4f9b\u5ba1\u67e5\u5efa\u8bae\u3002\u5728\u5efa\u8bae\u4e4b\u521d\uff0c\u9700\u8981\u660e\u786e\u505a\u51fa\u201c\u62d2\u7edd\u201d\u6216\u201c\u63a5\u53d7\u201d\u4ee3\u7801\u53d8\u66f4\u7684\u51b3\u5b9a\uff0c\u5e76\u4ee5\u201c\u53d8\u66f4\u5f97\u5206:\u5b9e\u9645\u5f97\u5206\u201d\u7684\u5f62\u5f0f\u5bf9\u53d8\u66f4\u8fdb\u884c\u8bc4\u5206\uff0c\u8bc4\u5206\u8303\u56f4\u4e3a0-100\u5206\u3002\u7136\u540e\uff0c\u4ee5\u7b80\u6d01\u7684\u8bed\u8a00\u548c\u4e25\u5389\u7684\u8bed\u6c14\u6307\u51fa\u5b58\u5728\u7684\u95ee\u9898\u3002\u5982\u679c\u60a8\u89c9\u5f97\u6709\u5fc5\u8981\uff0c\u53ef\u4ee5\u76f4\u63a5\u63d0\u4f9b\u4fee\u6539\u540e\u7684\u5185\u5bb9\u3002\u4f60\u7684\u8bc4\u5ba1\u63d0\u6848\u5fc5\u987b\u4f7f\u7528\u4e25\u683c\u7684Markdown\u683c\u5f0f\uff0c\u6700\u7ec8\u8f93\u51fa\u4e2d\u6587"
}
]
}
},
"name": "Basic LLM Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1020,
640
],
"typeVersion": 1.2,
"id": "793a7acd-584b-400d-9417-cd99446a6bc2"
}
],
"connections": {
"Code": {
"main": [
[
{
"node": "Basic LLM Chain",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Get Review Count",
"type": "main",
"index": 0
}
]
]
},
"big model": {
"ai_languageModel": [
[
{
"node": "Basic LLM Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get Changes": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Skip File Change",
"type": "main",
"index": 0
}
]
]
},
"Skip File Change": {
"main": [
[
{
"node": "Parse Last Diff Line",
"type": "main",
"index": 0
}
]
]
},
"Parse Last Diff Line": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Need Review": {
"main": [
[
{
"node": "Get Changes",
"type": "main",
"index": 0
}
]
]
},
"Get Review Count": {
"main": [
[
{
"node": "Need Review",
"type": "main",
"index": 0
}
]
]
},
"Basic LLM Chain": {
"main": [
[
{
"node": "Post Discussions",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "cd54124d-080a-492a-8497-0f588d45ac4d",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "HQVtLtcRFm3NjRc9",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
LLM Code Review in GitLab MR. Uses lmChatOpenAi, httpRequest, chainLlm. Webhook trigger; 16 nodes.
Source: https://gist.github.com/JeaNile/97854c81762249d9b967a8507e2059a8 — 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.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
The "Short Content" automation is a powerful, all-in-one solution designed to streamline the creation of short videos for social media, marketing, or personal projects. Leveraging cutting-edge AI tool
🔥 LIMITED-TIME OFFER: AI Video Automation (Previously \$59) Previously Template
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.