{"workflow":{"id":14207,"name":"Proofread markdown blog posts with Gemini, Groq fallback and GitHub auto-commit","views":2,"recentViews":0,"totalViews":2,"createdAt":"2026-03-20T21:08:06.906Z","description":"### Your personal editor that finds tone, grammar, and clarity issues in markdown, then fixes them and commits back to GitHub automatically.\n\nStop manually proofreading markdown files. This workflow uses two AI agents to review your blog posts, generate precise line-by-line fixes, and commit the edits back to GitHub automatically. You write, it checks.\n \nThis is not another \"generate a blog post with AI\" template. It does the opposite. You write the content, and the workflow checks it for you. Your writing style stays the same because the Editor Agent can only replace, insert, or delete specific lines. No full rewrites.\n \n### How the workflow works\n \nThe workflow runs in five stages:\n \n1. **Fetch and prepare:** The workflow pulls your markdown file from GitHub using the API. A code node decodes the base64 response and adds line numbers to every line. This gives the AI agents a coordinate system so they can point to exact locations instead of saying \"somewhere in the introduction.\"\n2. **QA review:** The QA Agent reads the numbered content and looks for issues with tone, clarity, grammar, and structure. It returns a JSON array where each item has the line number, what is wrong, how severe it is, and a suggested fix.\n3. **Severity filter:** A filter node checks the results. Only high and medium severity issues move forward. Low severity issues show up in the report but do not trigger any edits.\n4. **Edit generation:** The Editor Agent takes the filtered issues and converts them into edit operations. Each operation is one of three types: replace, insert_after, or delete. A code node then sorts all operations from bottom to top. This is important because if you delete line 5, every line after it shifts up by one. Sorting from the bottom prevents that problem.\n5. **Commit and report:** If more than half the edits succeed, the workflow commits the updated file to GitHub. It also saves a QA report in a reports folder. If edits fail, the commit is skipped and a failure report is saved instead with next steps.\n \n### Benefits\n \n• **Finds real issues, not nitpicks:** The severity filter means your file only gets changed when something actually matters.\n• **Keeps your writing style:** Three allowed operations. Replace, insert, delete. That is it. No creative rewrites.\n• **Line numbers solve the guessing problem:** I added this after noticing the AI kept misidentifying where problems were in longer posts.\n• **Edits do not break each other:** The bottom-to-top sorting was the trick that took me a while to figure out. Without it, line numbers shift after every edit and the rest of the operations point to the wrong lines.\n• **Fallback model included:** If Gemini is down or rate-limited, the workflow falls back to Groq automatically. Both agent nodes also retry up to 3 times with a 5-second interval.\n• **Reports for every run:** Three report types committed to your repo: fixes applied, no issues found, or edits failed.\n \n### Target Audience\n \n• Technical writers who keep documentation in GitHub\n• Content managers reviewing blog posts before publication\n• SEO specialists checking content quality across multiple markdown files\n• Dev teams that want automated content review similar to a PR review\n• Freelancers and bloggers who do not have an editor to proofread their work\n \n### Required APIs\n \n• **Google Gemini API** for the AI analysis (primary model)\n• **Groq API** as a fallback model if Gemini is unavailable (optional but recommended)\n• **GitHub OAuth2** with repo scope so the workflow can read files and commit changes\n \n### Easy Customization\n \n• **Swap the AI model:** Replace the chat model sub-node with OpenAI, Anthropic, or any provider that handles JSON output. The prompts are model-agnostic.\n• **Change the severity filter:** Open the filter node and include low severity if you want more aggressive editing.\n• **Point it at any file:** The Config node at the start has your repo owner, repo name, and file path. Change those three values and it works on any markdown file in any repository.\n• **Make it automatic:** Replace the Manual Trigger with a GitHub Trigger node listening for push or pull_request events. Now it runs every time someone updates content.\n• **Add notifications:** Drop a Slack, Teams, or email node after the report step to get notified when the workflow finishes a review.","workflow":{"id":"GRGQy17eKiwTWmlSRmig-","meta":{"instanceId":"d1dc073e8e3059a23e2730f69cb1b90065a2ac39039fea0727fdf9bee77a9131","templateCredsSetupCompleted":true},"name":"AI Markdown Proofreader with GitHub Auto-Commit","tags":[],"nodes":[{"id":"33bdd4e6-8838-40e2-b496-09548ba8de69","name":"When clicking ‘Execute workflow’","type":"n8n-nodes-base.manualTrigger","position":[544,1444],"parameters":{},"typeVersion":1},{"id":"4f99d9fb-b157-4032-86e3-764320fecafb","name":"Has Issues?","type":"n8n-nodes-base.if","position":[2016,1444],"parameters":{"options":{},"conditions":{"options":{"version":1,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"or","conditions":[{"id":"b9bee30e-1623-422f-9003-8d8c738c2b65","operator":{"type":"number","operation":"gt"},"leftValue":"={{ $json.issueCount }}","rightValue":0}]}},"typeVersion":2},{"id":"9d36d265-9c5f-496a-8dcc-6108117f5cb0","name":"Edits Applied?","type":"n8n-nodes-base.if","position":[3040,1244],"parameters":{"options":{},"conditions":{"options":{"version":1,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"918549c9-1a46-4e12-b333-1b07c201cb12","operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.success }}","rightValue":true}]}},"typeVersion":2},{"id":"84feed8e-8ab1-403d-ac19-c5eaccedd6ab","name":"Fetch Blog Post from GitHub","type":"n8n-nodes-base.github","position":[992,1444],"webhookId":"86b30329-039a-426c-a420-af099b3f5015","parameters":{"owner":{"__rl":true,"mode":"name","value":"={{ $json.repoOwner }}"},"filePath":"={{ $json.filePath }}","resource":"file","operation":"get","repository":{"__rl":true,"mode":"name","value":"={{ $json.repoName }}"},"authentication":"oAuth2","asBinaryProperty":false,"additionalParameters":{"reference":""}},"typeVersion":1.1},{"id":"5a646f46-5b55-475d-a8cf-7b16d88b67f6","name":"Decode Base64 & Add Line Numbers","type":"n8n-nodes-base.code","position":[1216,1444],"parameters":{"jsCode":"// Decode base64 and add line numbers for QA\n\nconst fileContent = $input.first().json.content;\nconst fileEncoding = $input.first().json.encoding;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\n\nlet decodedContent = fileContent;\nif (fileEncoding === 'base64') {\n  decodedContent = Buffer.from(fileContent, 'base64').toString('utf-8');\n}\n\nconst lines = decodedContent.split('\\n');\nconst numberedLines = lines.map((line, index) => {\n  const lineNumber = index + 1;\n  return `${lineNumber}: ${line}`;\n});\nconst numberedContent = numberedLines.join('\\n');\n\nreturn {\n  json: {\n    originalContent: decodedContent,\n    numberedContent: numberedContent,\n    lineCount: lines.length,\n    sha: fileSha,\n    path: filePath\n  }\n};"},"typeVersion":2},{"id":"c0c3c967-0d82-4d91-bcdd-3a3b651b9f7f","name":"QA Agent - Analyze Content","type":"@n8n/n8n-nodes-langchain.agent","onError":"continueErrorOutput","maxTries":3,"position":[1440,1444],"parameters":{"text":"=Analyze this blog post and return issues as JSON array:\n\n{{ $json.numberedContent }}","options":{"systemMessage":"You are a content QA specialist. Analyze the provided Markdown blog post and identify issues.\n\nFor each issue found, output a JSON object with these fields:\n- line_number: integer (1-indexed line number where issue occurs)\n- issue_type: one of \"tone\", \"clarity\", \"grammar\", \"structure\", \"accuracy\", \"style\"\n- severity: one of \"low\", \"medium\", \"high\"\n- description: brief explanation of the problem\n- suggested_fix: the corrected text for that line (or null if deletion recommended)\n\nCRITICAL RULES:\n1. Output ONLY valid JSON array. No markdown, no explanation, no preamble.\n2. Each issue must reference a specific line number.\n3. Be precise about line numbers - count from 1, include blank lines.\n4. Focus on substantive issues, not nitpicks.\n5. suggested_fix should be the COMPLETE corrected line, not a partial fix."},"promptType":"define","needsFallback":true},"retryOnFail":true,"typeVersion":3.1,"waitBetweenTries":5000},{"id":"d0714374-0aa1-43c7-96e7-158188715b7f","name":"QA Agent LLM","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[1448,1668],"parameters":{"options":{}},"typeVersion":1},{"id":"bec92205-1dfc-476d-bf30-3b25584ff92f","name":"Parse QA Issues JSON","type":"n8n-nodes-base.code","position":[1792,1444],"parameters":{"jsCode":"// Parse QA output and filter to high/medium severity only\n\nconst qaAgentResponse = $input.first().json.output;\n\nlet cleanedResponse = qaAgentResponse.trim();\nif (cleanedResponse.startsWith('```json')) {\n  cleanedResponse = cleanedResponse.slice(7);\n}\nif (cleanedResponse.startsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(3);\n}\nif (cleanedResponse.endsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(0, -3);\n}\ncleanedResponse = cleanedResponse.trim();\n\ntry {\n  const allIssues = JSON.parse(cleanedResponse);\n  \n  if (!Array.isArray(allIssues)) {\n    throw new Error('QA output is not an array');\n  }\n  \n  const validIssues = allIssues.filter(issue => {\n    const hasLineNumber = issue.line_number && typeof issue.line_number === 'number';\n    const hasIssueType = issue.issue_type;\n    const hasDescription = issue.description;\n    const isHighOrMedium = issue.severity === 'high' || issue.severity === 'medium';\n    \n    return hasLineNumber && hasIssueType && hasDescription && isHighOrMedium;\n  });\n  \n  const originalContent = $('Decode Base64 & Add Line Numbers').first().json.originalContent;\n  const fileSha = $('Decode Base64 & Add Line Numbers').first().json.sha;\n  const filePath = $('Decode Base64 & Add Line Numbers').first().json.path;\n  \n  return {\n    json: {\n      issues: validIssues,\n      issueCount: validIssues.length,\n      originalContent: originalContent,\n      sha: fileSha,\n      path: filePath,\n      parseSuccess: true\n    }\n  };\n\n} catch (error) {\n  return {\n    json: {\n      issues: [],\n      issueCount: 0,\n      parseError: error.message,\n      rawResponse: qaAgentResponse,\n      parseSuccess: false\n    }\n  };\n}"},"typeVersion":2},{"id":"4ac7a9f7-a656-4c5b-baf3-e7f09ab00af9","name":"Editor Agent - Generate Edit Ops","type":"@n8n/n8n-nodes-langchain.agent","onError":"continueErrorOutput","maxTries":3,"position":[2240,1140],"parameters":{"text":"=Convert these QA issues into edit operations:\n\n{{ JSON.stringify($json.issues, null, 2) }}","options":{"systemMessage":"You are a precise text editor. You receive QA issues and convert them into executable edit instructions.\n\nOutput: A JSON array of edit operations. Each operation must have:\n- operation: one of \"replace\", \"insert_after\", \"delete\"\n- line_number: integer (which line to modify)\n- new_text: string (the new content, required for replace/insert_after, omit for delete)\n\nCRITICAL RULES:\n1. Output ONLY valid JSON array. No markdown, no explanation.\n2. Sort operations by line_number in DESCENDING order (highest first).\n3. Only include operations where suggested_fix is not null.\n4. For \"replace\": new_text is the complete replacement line.\n5. Validate that line_number is a positive integer."},"promptType":"define","needsFallback":true},"retryOnFail":true,"typeVersion":3.1,"waitBetweenTries":5000},{"id":"7e2ccc24-e5de-4bc4-b3a8-5c35a6018c71","name":"Editor Agent LLM","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[2248,1364],"parameters":{"options":{}},"typeVersion":1},{"id":"1946202a-a925-4fc4-a2f0-f98e09f96aa7","name":"Parse Edit Operations JSON","type":"n8n-nodes-base.code","position":[2592,1244],"parameters":{"jsCode":"// Parse editor output and sort edits descending (bottom-to-top)\n\nconst editorAgentResponse = $input.first().json.output;\n\nlet cleanedResponse = editorAgentResponse.trim();\nif (cleanedResponse.startsWith('```json')) {\n  cleanedResponse = cleanedResponse.slice(7);\n}\nif (cleanedResponse.startsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(3);\n}\nif (cleanedResponse.endsWith('```')) {\n  cleanedResponse = cleanedResponse.slice(0, -3);\n}\ncleanedResponse = cleanedResponse.trim();\n\ntry {\n  const editOperations = JSON.parse(cleanedResponse);\n  \n  if (!Array.isArray(editOperations)) {\n    throw new Error('Editor output is not an array');\n  }\n  \n  const sortedEdits = editOperations.sort((a, b) => {\n    return b.line_number - a.line_number;\n  });\n  \n  const originalContent = $('Parse QA Issues JSON').first().json.originalContent;\n  const fileSha = $('Parse QA Issues JSON').first().json.sha;\n  const filePath = $('Parse QA Issues JSON').first().json.path;\n  const issues = $('Parse QA Issues JSON').first().json.issues;\n  \n  return {\n    json: {\n      edits: sortedEdits,\n      editCount: sortedEdits.length,\n      originalContent: originalContent,\n      sha: fileSha,\n      path: filePath,\n      issues: issues,\n      parseSuccess: true\n    }\n  };\n\n} catch (error) {\n  return {\n    json: {\n      edits: [],\n      editCount: 0,\n      parseError: error.message,\n      rawResponse: editorAgentResponse,\n      parseSuccess: false\n    }\n  };\n}"},"typeVersion":2},{"id":"c83f964b-4ca9-4666-9d26-a89cc99b0700","name":"Apply Line-by-Line Edits","type":"n8n-nodes-base.code","position":[2816,1244],"parameters":{"jsCode":"// Apply edits to content, skip invalid lines, track success rate\n\nconst originalContent = $input.first().json.originalContent;\nconst editOperations = $input.first().json.edits;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\nconst issues = $input.first().json.issues;\n\nlet lines = originalContent.split('\\n');\nconst totalLines = lines.length;\n\neditOperations.sort((a, b) => b.line_number - a.line_number);\n\nlet appliedCount = 0;\nlet skippedCount = 0;\nconst appliedChanges = [];\nconst skippedChanges = [];\n\nfor (const edit of editOperations) {\n  const arrayIndex = edit.line_number - 1;\n  \n  if (arrayIndex < 0 || arrayIndex >= totalLines) {\n    skippedCount++;\n    skippedChanges.push({\n      line: edit.line_number,\n      reason: 'Line number out of range'\n    });\n    continue;\n  }\n  \n  if (edit.operation === 'replace') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'replace',\n      oldText: lines[arrayIndex],\n      newText: edit.new_text\n    });\n    lines[arrayIndex] = edit.new_text;\n    appliedCount++;\n    \n  } else if (edit.operation === 'insert_after') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'insert_after',\n      insertedText: edit.new_text\n    });\n    lines.splice(arrayIndex + 1, 0, edit.new_text);\n    appliedCount++;\n    \n  } else if (edit.operation === 'delete') {\n    appliedChanges.push({\n      line: edit.line_number,\n      operation: 'delete',\n      deletedText: lines[arrayIndex]\n    });\n    lines.splice(arrayIndex, 1);\n    appliedCount++;\n    \n  } else {\n    skippedCount++;\n    skippedChanges.push({\n      line: edit.line_number,\n      reason: 'Unknown operation: ' + edit.operation\n    });\n  }\n}\n\nconst totalEdits = appliedCount + skippedCount;\nconst successRate = totalEdits > 0 ? appliedCount / totalEdits : 1;\nconst isSuccess = successRate >= 0.5 && appliedCount > 0;\nconst updatedContent = lines.join('\\n');\n\nreturn {\n  json: {\n    updatedContent: updatedContent,\n    sha: fileSha,\n    path: filePath,\n    issues: issues,\n    applied: appliedCount,\n    skipped: skippedCount,\n    successRate: successRate,\n    success: isSuccess,\n    appliedChanges: appliedChanges,\n    skippedChanges: skippedChanges\n  }\n};"},"typeVersion":2},{"id":"21eda142-611b-4d86-86aa-862dae40b7aa","name":"Commit Updated File to GitHub","type":"n8n-nodes-base.github","position":[3264,1296],"webhookId":"be0f77b8-0ef7-4985-b00e-aa9c7f7f6aed","parameters":{"owner":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoOwner }}"},"filePath":"={{ $('Github Config').item.json.filePath }}","resource":"file","operation":"edit","repository":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoName }}"},"fileContent":"={{ $json.updatedContent }}","commitMessage":"=AI QA: Applied {{ $json.applied }} edit(s) - {{ new Date().toISOString().slice(0,10) }}","authentication":"oAuth2"},"typeVersion":1},{"id":"02c73e7f-f250-42f7-a0ac-2f5bba509353","name":"Format QA Report","type":"n8n-nodes-base.code","position":[3264,1104],"parameters":{"jsCode":"// Build markdown report with applied changes\n\nconst issues = $input.first().json.issues;\nconst appliedChanges = $input.first().json.appliedChanges;\nconst skippedChanges = $input.first().json.skippedChanges;\nconst successRate = $input.first().json.successRate;\nconst fileSha = $input.first().json.sha;\nconst filePath = $input.first().json.path;\nconst updatedContent = $input.first().json.updatedContent;\nconst isSuccess = $input.first().json.success;\n\nconst today = new Date().toISOString().slice(0, 10);\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## Summary\\n`;\nreport += `- Applied: ${appliedChanges.length}\\n`;\nreport += `- Skipped: ${skippedChanges.length}\\n`;\nreport += `- Success Rate: ${(successRate * 100).toFixed(0)}%\\n\\n`;\n\nreport += `## Issues Found\\n\\n`;\nfor (const issue of issues) {\n  report += `### Line ${issue.line_number} (${issue.issue_type})\\n`;\n  report += `${issue.description}\\n\\n`;\n}\n\nreport += `## Changes Applied\\n\\n`;\nfor (const change of appliedChanges) {\n  report += `- Line ${change.line}: ${change.operation}\\n`;\n}\n\nif (skippedChanges.length > 0) {\n  report += `\\n## Skipped Edits\\n\\n`;\n  for (const skip of skippedChanges) {\n    report += `- Line ${skip.line}: ${skip.reason}\\n`;\n  }\n}\n\nreturn {\n  json: {\n    report: report,\n    sha: fileSha,\n    path: filePath,\n    updatedContent: updatedContent,\n    success: isSuccess\n  }\n};"},"typeVersion":2},{"id":"50cff18d-b1a2-4863-aa60-5777652e116d","name":"Save QA Report to GitHub","type":"n8n-nodes-base.github","position":[3488,1104],"webhookId":"4468c4f5-a17c-43a5-a44f-d7f6b9f859e8","parameters":{"owner":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoOwner }}"},"filePath":"=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-report.md","resource":"file","repository":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoName }}"},"fileContent":"={{ $json.report }}","commitMessage":"=AI QA: Content improvements - {{ new Date().toISOString().slice(0,10) }}","authentication":"oAuth2"},"typeVersion":1.1},{"id":"67756c95-93a2-4a75-9ba6-d0d292bb6de3","name":"Save QA Report to GitHub Without Issues","type":"n8n-nodes-base.github","position":[2624,1664],"webhookId":"4468c4f5-a17c-43a5-a44f-d7f6b9f859e8","parameters":{"owner":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoOwner }}"},"filePath":"=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-CLEAN-report.md","resource":"file","repository":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoName }}"},"fileContent":"={{ $json.report }}","commitMessage":"=AI QA: Content improvements - {{ new Date().toISOString().slice(0,10) }}","authentication":"oAuth2"},"typeVersion":1.1},{"id":"4290553e-f274-4fe5-bac2-0938fd1076cc","name":"Format Clean Report","type":"n8n-nodes-base.code","position":[2336,1664],"parameters":{"jsCode":"// Report when no issues found\n\nconst today = new Date().toISOString().slice(0, 10);\nconst filePath = $('Parse QA Issues JSON').first().json.path;\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## Summary\\n`;\nreport += `✅ No issues detected\\n\\n`;\nreport += `The blog post passed QA review with no changes required.\\n`;\n\nreturn {\n  json: {\n    report: report,\n    path: filePath\n  }\n};"},"typeVersion":2},{"id":"6bcfdc7f-1a6e-4e93-bd98-a79bcde46999","name":"Format Failure Report","type":"n8n-nodes-base.code","position":[3264,1488],"parameters":{"jsCode":"// Report when edits failed to apply\n\nconst skippedChanges = $input.first().json.skippedChanges || [];\nconst appliedCount = $input.first().json.applied || 0;\nconst skippedCount = $input.first().json.skipped || 0;\nconst successRate = $input.first().json.successRate || 0;\n\nconst today = new Date().toISOString().slice(0, 10);\n\nlet report = '';\nreport += `# QA Report - ${today}\\n\\n`;\nreport += `## ⚠️ Commit Skipped\\n\\n`;\nreport += `The edits could not be safely applied. No changes were made to the file.\\n\\n`;\nreport += `## Summary\\n`;\nreport += `- Applied: ${appliedCount}\\n`;\nreport += `- Skipped: ${skippedCount}\\n`;\nreport += `- Success Rate: ${(successRate * 100).toFixed(0)}%\\n\\n`;\n\nif (skippedChanges.length > 0) {\n  report += `## Skipped Edits\\n\\n`;\n  for (const skip of skippedChanges) {\n    report += `- Line ${skip.line}: ${skip.reason}\\n`;\n  }\n}\n\nreport += `\\n## Next Steps\\n`;\nreport += `- Review the QA issues manually\\n`;\nreport += `- Check if line numbers are correct\\n`;\nreport += `- Re-run the workflow after fixing the source content\\n`;\n\nreturn {\n  json: {\n    report: report,\n    path: $input.first().json.path\n  }\n};"},"typeVersion":2},{"id":"64fe16da-4204-4614-a4c6-cea41a45cea9","name":"Create a file","type":"n8n-nodes-base.github","position":[3488,1488],"webhookId":"f3448c38-859f-42ee-8038-cfabe8219e8e","parameters":{"owner":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoOwner }}"},"filePath":"=qa-reports/{{ new Date().toISOString().slice(0,19).replace(/:/g, '-') }}-FAILED-report.md","resource":"file","repository":{"__rl":true,"mode":"name","value":"={{ $('Github Config').item.json.repoName }}"},"fileContent":"={{ $json.report }}","commitMessage":"=AI QA: Edit application failed - {{ new Date().toISOString().slice(0,10) }}","authentication":"oAuth2"},"typeVersion":1.1},{"id":"04705019-90b0-4a47-8856-8576d30923be","name":"Fallback Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatGroq","position":[1576,1668],"parameters":{"model":"openai/gpt-oss-20b","options":{}},"typeVersion":1},{"id":"a85b931f-f5e2-4772-9f60-cb756aa6e7f9","name":"Fallback Model","type":"@n8n/n8n-nodes-langchain.lmChatGroq","position":[2376,1364],"parameters":{"model":"openai/gpt-oss-20b","options":{}},"typeVersion":1},{"id":"5a71abda-d41c-4651-b945-2e94f252d9dd","name":"Github Config","type":"n8n-nodes-base.set","position":[768,1444],"parameters":{"options":{},"assignments":{"assignments":[{"id":"755e6a1a-c3ec-4428-890d-8c5ffa26d97f","name":"repoOwner","type":"string","value":"YOUR_GITHUB_USERNAME"},{"id":"04fe558c-666e-4b7f-8423-8b19492852c4","name":"repoName","type":"string","value":"blog-n8n"},{"id":"dc73920f-6c6c-4f6c-821c-7dbace4427d0","name":"filePath","type":"string","value":"blog-post.md"},{"id":"2b742aa2-9782-4e8c-ba9f-864418cfbb77","name":"reportFolder","type":"string","value":"qa-reports"}]}},"typeVersion":3.4},{"id":"bac9b915-2374-47ed-a3dd-f0b3b6cd008b","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-32,1088],"parameters":{"width":480,"height":832,"content":"## AI Automated Markdown Blog QA\n\n### How it works\n\n1.  Fetch: A manual trigger fetches your target markdown file from GitHub.\n\n2. Prep: The file is decoded and numbered for the AI.\n\n3. Agent 1 (Analysis): The QA Agent identifies tone, clarity, and grammar issues, outputting JSON.\n\n4. Agent 2 (Editing): The Editor Agent translates those issues into precise line-by-line edits.\n\n5. Execute: The workflow applies the edits, then commits the updated file and a Markdown report to GitHub.\n\n### Setup steps\n\n- [ ] Connect GitHub OAuth2 to all GitHub nodes.\n- [ ] Add Google Gemini and Groq API keys.\n- [ ] Update the Github Config node (repoOwner, repoName, filePath).\n\n\n### Customization\n\n- Customize\n\n- Style: Edit the QA Agent's prompt to enforce brand rules.\n\n- Models: Swap Gemini/Groq for OpenAI or Anthropic.\n\n- Routing: Adjust the GitHub nodes to change report destinations."},"typeVersion":1},{"id":"387ddb16-cabc-400a-9cf3-8c5b9369163c","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[480,1360],"parameters":{"color":7,"width":912,"height":304,"content":"## Get blog post"},"typeVersion":1},{"id":"26baa3ac-0294-47bf-be5f-224c15366366","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[1408,1360],"parameters":{"color":7,"width":736,"height":464,"content":"## Analyze content\n"},"typeVersion":1},{"id":"3a88ad17-39f9-4563-9aa8-e410142ce4cd","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[2192,1040],"parameters":{"color":7,"width":976,"height":480,"content":"## Generate & apply edits\n"},"typeVersion":1},{"id":"20e71b57-af0b-491e-811f-302837aca8d9","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[3200,1024],"parameters":{"color":7,"width":512,"height":720,"content":"## Update repository"},"typeVersion":1},{"id":"a30e6809-8331-4a78-bb9a-5dd3a0126120","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[2272,1584],"parameters":{"color":7,"width":560,"height":288,"content":"## Path: No Issues Found"},"typeVersion":1}],"active":false,"pinData":{},"settings":{"availableInMCP":false,"executionOrder":"v1"},"versionId":"f64eb8e8-dc8b-4f93-ab85-7d3d231858ef","connections":{"Has Issues?":{"main":[[{"node":"Editor Agent - Generate Edit Ops","type":"main","index":0}],[{"node":"Format Clean Report","type":"main","index":0}]]},"QA Agent LLM":{"ai_languageModel":[[{"node":"QA Agent - Analyze Content","type":"ai_languageModel","index":0}]]},"Github Config":{"main":[[{"node":"Fetch Blog Post from GitHub","type":"main","index":0}]]},"Edits Applied?":{"main":[[{"node":"Commit Updated File to GitHub","type":"main","index":0},{"node":"Format QA Report","type":"main","index":0}],[{"node":"Format Failure Report","type":"main","index":0}]]},"Fallback Model":{"ai_languageModel":[[{"node":"Editor Agent - Generate Edit Ops","type":"ai_languageModel","index":1}]]},"Editor Agent LLM":{"ai_languageModel":[[{"node":"Editor Agent - Generate Edit Ops","type":"ai_languageModel","index":0}]]},"Format QA Report":{"main":[[{"node":"Save QA Report to GitHub","type":"main","index":0}]]},"Fallback Chat Model":{"ai_languageModel":[[{"node":"QA Agent - Analyze Content","type":"ai_languageModel","index":1}]]},"Format Clean Report":{"main":[[{"node":"Save QA Report to GitHub Without Issues","type":"main","index":0}]]},"Parse QA Issues JSON":{"main":[[{"node":"Has Issues?","type":"main","index":0}]]},"Format Failure Report":{"main":[[{"node":"Create a file","type":"main","index":0}]]},"Apply Line-by-Line Edits":{"main":[[{"node":"Edits Applied?","type":"main","index":0}]]},"Parse Edit Operations JSON":{"main":[[{"node":"Apply Line-by-Line Edits","type":"main","index":0}]]},"QA Agent - Analyze Content":{"main":[[{"node":"Parse QA Issues JSON","type":"main","index":0}]]},"Fetch Blog Post from GitHub":{"main":[[{"node":"Decode Base64 & Add Line Numbers","type":"main","index":0}]]},"Decode Base64 & Add Line Numbers":{"main":[[{"node":"QA Agent - Analyze Content","type":"main","index":0}]]},"Editor Agent - Generate Edit Ops":{"main":[[{"node":"Parse Edit Operations JSON","type":"main","index":0}]]},"When clicking ‘Execute workflow’":{"main":[[{"node":"Github Config","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":28,"nodeTypes":{"n8n-nodes-base.if":{"count":2},"n8n-nodes-base.set":{"count":1},"n8n-nodes-base.code":{"count":7},"n8n-nodes-base.github":{"count":5},"n8n-nodes-base.stickyNote":{"count":6},"n8n-nodes-base.manualTrigger":{"count":1},"@n8n/n8n-nodes-langchain.agent":{"count":2},"@n8n/n8n-nodes-langchain.lmChatGroq":{"count":2},"@n8n/n8n-nodes-langchain.lmChatGoogleGemini":{"count":2}}},"status":"published","readyToDemo":null,"user":{"name":"Mychel Garzon","username":"mychel-garzon","bio":"n8n Verified Creator and Junction 2025 n8n Tech Challenge Winner based in Helsinki, Finland. Full Stack Engineer specializing in AI automation workflows, multi-agent systems, RAG pipelines, and automated incident triage. Node.js, TypeScript, React, LLMs (OpenAI, Anthropic, Gemini, Groq). 99.9% production uptime.\n\nCustom n8n workflows: mychel.garzon@gmail.com","verified":true,"links":["https://mychelgarzon.com/"],"avatar":"https://gravatar.com/avatar/8937dc435f1eb7cc47cfc0139be315f5e28add64bc872edc5e5315137ee12b75?r=pg&d=retro&size=200"},"nodes":[{"id":16,"icon":"file:github.svg","name":"n8n-nodes-base.github","codex":{"data":{"resources":{"generic":[{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.github/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/github/"}]},"categories":["Development"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\"]","defaults":{"name":"GitHub"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMC4wMTY1IDBDOC45NDc5MSAwIDAgOS4wMTM4OCAwIDIwLjE2NTNDMCAyOS4wNzkyIDUuNzMzMjQgMzYuNjI0NiAxMy42ODY4IDM5LjI5NTJDMTQuNjgxMiAzOS40OTYgMTUuMDQ1NCAzOC44NjEzIDE1LjA0NTQgMzguMzI3NEMxNS4wNDU0IDM3Ljg1OTkgMTUuMDEyNiAzNi4yNTc1IDE1LjAxMjYgMzQuNTg3OUM5LjQ0NDUgMzUuNzkgOC4yODQ5OCAzMi4xODQxIDguMjg0OTggMzIuMTg0MUM3LjM5MDE1IDI5Ljg0NyA2LjA2NDI5IDI5LjI0NjMgNi4wNjQyOSAyOS4yNDYzQzQuMjQxODUgMjguMDExIDYuMTk3MDQgMjguMDExIDYuMTk3MDQgMjguMDExQzguMjE4NjEgMjguMTQ0NiA5LjI3OTM4IDMwLjA4MSA5LjI3OTM4IDMwLjA4MUMxMS4wNjg2IDMzLjE1MjIgMTMuOTUxOCAzMi4yODQ0IDE1LjExMTggMzEuNzUwMkMxNS4yNzczIDMwLjQ0ODEgMTUuODA3OSAyOS41NDY3IDE2LjM3MTMgMjkuMDQ2QzExLjkzMDMgMjguNTc4NSA3LjI1NzgxIDI2Ljg0MjUgNy4yNTc4MSAxOS4wOTY3QzcuMjU3ODEgMTYuODkzMiA4LjA1MjY3IDE1LjA5MDUgOS4zMTIxNiAxMy42ODg0QzkuMTEzNDQgMTMuMTg3NyA4LjQxNzMyIDExLjExNzQgOS41MTEyOCA4LjM0NjQ0QzkuNTExMjggOC4zNDY0NCAxMS4yMDE0IDcuODEyMTcgMTUuMDEyMiAxMC40MTY0QzE2LjY0MzggOS45NzQ5NSAxOC4zMjYzIDkuNzUwNCAyMC4wMTY1IDkuNzQ4NTFDMjEuNzA2NyA5Ljc0ODUxIDIzLjQyOTUgOS45ODI0NiAyNS4wMjA1IDEwLjQxNjRDMjguODMxNyA3LjgxMjE3IDMwLjUyMTggOC4zNDY0NCAzMC41MjE4IDguMzQ2NDRDMzEuNjE1OCAxMS4xMTc0IDMwLjkxOTIgMTMuMTg3NyAzMC43MjA1IDEzLjY4ODRDMzIuMDEzMiAxNS4wOTA1IDMyLjc3NTMgMTYuODkzMiAzMi43NzUzIDE5LjA5NjdDMzIuNzc1MyAyNi44NDI1IDI4LjEwMjggMjguNTQ0OSAyMy42Mjg3IDI5LjA0NkMyNC4zNTggMjkuNjgwMiAyNC45ODczIDMwLjg4MiAyNC45ODczIDMyLjc4NTFDMjQuOTg3MyAzNS40ODkzIDI0Ljk1NDUgMzcuNjU5NiAyNC45NTQ1IDM4LjMyN0MyNC45NTQ1IDM4Ljg2MTMgMjUuMzE5MiAzOS40OTYgMjYuMzEzMiAzOS4yOTU2QzM0LjI2NjcgMzYuNjI0MiAzOS45OTk5IDI5LjA3OTIgMzkuOTk5OSAyMC4xNjUzQzQwLjAzMjcgOS4wMTM4OCAzMS4wNTIgMCAyMC4wMTY1IDBaIiBmaWxsPSIjMjQyOTJGIi8+Cjwvc3ZnPgo="},"displayName":"GitHub","typeVersion":1,"nodeCategories":[{"id":5,"name":"Development"}]},{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":838,"icon":"fa:mouse-pointer","name":"n8n-nodes-base.manualTrigger","codex":{"data":{"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.manualworkflowtrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\"]","defaults":{"name":"When clicking ‘Execute workflow’","color":"#909298"},"iconData":{"icon":"mouse-pointer","type":"icon"},"displayName":"Manual Trigger","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":1119,"icon":"fa:robot","name":"@n8n/n8n-nodes-langchain.agent","codex":{"data":{"alias":["LangChain","Chat","Conversational","Plan and Execute","ReAct","Tools"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Agents","Root Nodes"]}}},"group":"[\"transform\"]","defaults":{"name":"AI Agent","color":"#404040"},"iconData":{"icon":"robot","type":"icon"},"displayName":"AI Agent","typeVersion":3,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1262,"icon":"file:google.svg","name":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatgooglegemini/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Language Models","Root Nodes"],"Language Models":["Chat Models (Recommended)"]}}},"group":"[\"transform\"]","defaults":{"name":"Google Gemini Chat Model"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDggNDgiPjxkZWZzPjxwYXRoIGlkPSJhIiBkPSJNNDQuNSAyMEgyNHY4LjVoMTEuOEMzNC43IDMzLjkgMzAuMSAzNyAyNCAzN2MtNy4yIDAtMTMtNS44LTEzLTEzczUuOC0xMyAxMy0xM2MzLjEgMCA1LjkgMS4xIDguMSAyLjlsNi40LTYuNEMzNC42IDQuMSAyOS42IDIgMjQgMiAxMS44IDIgMiAxMS44IDIgMjRzOS44IDIyIDIyIDIyYzExIDAgMjEtOCAyMS0yMiAwLTEuMy0uMi0yLjctLjUtNCIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBmaWxsPSIjRkJCQzA1IiBkPSJNMCAzN1YxMWwxNyAxM3oiIGNsaXAtcGF0aD0idXJsKCNiKSIvPjxwYXRoIGZpbGw9IiNFQTQzMzUiIGQ9Im0wIDExIDE3IDEzIDctNi4xTDQ4IDE0VjBIMHoiIGNsaXAtcGF0aD0idXJsKCNiKSIvPjxwYXRoIGZpbGw9IiMzNEE4NTMiIGQ9Im0wIDM3IDMwLTIzIDcuOSAxTDQ4IDB2NDhIMHoiIGNsaXAtcGF0aD0idXJsKCNiKSIvPjxwYXRoIGZpbGw9IiM0Mjg1RjQiIGQ9Ik00OCA0OCAxNyAyNGwtNC0zIDM1LTEweiIgY2xpcC1wYXRoPSJ1cmwoI2IpIi8+PC9zdmc+"},"displayName":"Google Gemini Chat Model","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]},{"id":1263,"icon":"file:groq.svg","name":"@n8n/n8n-nodes-langchain.lmChatGroq","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatgroq/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Language Models","Root Nodes"],"Language Models":["Chat Models (Recommended)"]}}},"group":"[\"transform\"]","defaults":{"name":"Groq Chat Model"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgaWQ9IkxheWVyXzIiCiAgIHZpZXdCb3g9IjAgMCA0OTkuOTk5OTkgNDk5Ljk5OTk5IgogICB2ZXJzaW9uPSIxLjEiCiAgIHdpZHRoPSI1MDAiCiAgIGhlaWdodD0iNTAwIgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzCiAgICAgaWQ9ImRlZnM0IiAvPjxnCiAgICAgaWQ9IlBBR0VTIj48Y2lyY2xlCiAgICAgICBzdHlsZT0iZmlsbDojZjU0ZjM1O2ZpbGwtb3BhY2l0eToxO3N0cm9rZS13aWR0aDoxLjEzNjIyIgogICAgICAgaWQ9InBhdGg0IgogICAgICAgY3g9IjI1MCIKICAgICAgIGN5PSIyNTAiCiAgICAgICByPSIyNTAiIC8+PHBhdGgKICAgICAgIGQ9Ik0gMjUwLjUzNjY0LDk3LjEyMjk5NCBDIDE5Mi43MTkzMSw5Ni41ODg2MzggMTQ1LjQ4MjIyLDE0Mi45NzA3NSAxNDQuOTQ3ODYsMjAwLjc4ODA4IGMgLTAuNTM0MzQsNTcuODE3MzMgNDUuODQ3NzcsMTA1LjA1NDQyIDEwMy42NjUxLDEwNS41ODg3NyBoIDM2LjMzNjIxIHYgLTM5LjIyMTc0IGggLTM0LjQxMjUzIGMgLTM2LjEyMjQ4LDAuNDI3NSAtNjUuNzI1OCwtMjguNTM0NjIgLTY2LjE1MzI5LC02NC42NTcwOCAtMC40Mjc0OSwtMzYuMTIyNDggMjguNTM0NjMsLTY1LjcyNTgxIDY0LjY1NzA4LC02Ni4xNTMzIGggMS40OTYyMSBjIDM2LjEyMjQ4LDAgNjUuNDA1MiwyOS4yODI3MiA2NS41MTIwNyw2NS40MDUyIHYgMCA5Ni4zOTc4MyAwIGMgMCwzNS44MDE4NyAtMjkuMTc1ODUsNjQuOTc3NzMgLTY0Ljg3MDgzLDY1LjQwNTIxIC0xNy4wOTk0MSwtMC4xMDY4OCAtMzMuNDUwNzEsLTcuMDUzNTEgLTQ1LjUyNzE3LC0xOS4xMjk5NSBsIC0yNy43ODY1LDI3Ljc4NjUxIGMgMTkuMjM2ODEsMTkuMzQzNyA0NS4zMTMzOSwzMC4zNTE0MyA3Mi41NjU1NiwzMC42NzIwNSBoIDEuMzg5MzMgYyA1Ny4wNjkyNCwtMC44NTQ5NyAxMDIuOTE3LC00Ny4xMzAyMiAxMDMuMjM3NiwtMTA0LjE5OTQ1IFYgMTk5LjI5MTg5IEMgMzUzLjY2NzM5LDE0Mi40MzYzOSAzMDcuMjg1MjcsOTcuMTIyOTk0IDI1MC41MzY2NCw5Ny4xMjI5OTQgWiIKICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlLXdpZHRoOjBweCIKICAgICAgIGlkPSJwYXRoMS0zIiAvPjwvZz48L3N2Zz4K"},"displayName":"Groq Chat Model","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]}],"categories":[{"id":35,"name":"Document Extraction"},{"id":49,"name":"AI Summarization"}],"image":[]}}