This workflow corresponds to n8n.io template #6820 — we link there as the canonical source.
This workflow follows the Agent → Emailsend 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 →
{
"id": "IZyQovW8UWFtnpvx",
"name": "My workflow",
"tags": [],
"nodes": [
{
"id": "fa588a43-f33b-4b51-8722-b4c2c1fc38b7",
"name": "Form",
"type": "n8n-nodes-base.formTrigger",
"position": [
48,
432
],
"parameters": {
"options": {},
"formTitle": "Content Task",
"formFields": {
"values": [
{
"fieldLabel": "Domain of Expertize",
"placeholder": "The field of expertize, e.g. Marketing"
},
{
"fieldLabel": "Keywords",
"placeholder": "What user will search for, e.g. B2B Marketing Strategy"
},
{
"fieldLabel": "Language",
"placeholder": "Desired output language, e.g. Espa\u00f1ol"
}
]
},
"formDescription": "Content Generation Task"
},
"typeVersion": 2.2
},
{
"id": "5ccd6c19-3c66-4be7-8c4a-bc0adc3eb18d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
256
],
"parameters": {
"width": 576,
"height": 432,
"content": "## Input \n\nUser input and initial parameters, system prompts used in LLM calls"
},
"typeVersion": 1
},
{
"id": "982c9ced-71d2-4798-84af-b7d62954923a",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
592,
256
],
"parameters": {
"color": 5,
"width": 1936,
"height": 432,
"content": "## Initial Research for Articles and Citations\n\nGenerate additional set of keywords basing on user input, search for authoritative sources on Internet "
},
"typeVersion": 1
},
{
"id": "65dd020b-016a-42f4-9572-c39ea7457a18",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
704
],
"parameters": {
"color": 7,
"width": 576,
"height": 496,
"content": "## Model Params \n\n- **`simple_model`** OpenAI model name for simple text tasks.\ne.g. `gpt-4.1-mini` | `gpt-4o`\n\n- **`advanced_model`** OpenAI model name for advanced text writing tasks.\ne.g. `gpt-4.1` | `o3` | `o3-pro`\n\n- **`system_prompt_...`** System prompts for various tasks in the workflow\n\n- **`working_language`** Language to search, collect and analyse materials.\ne.g. `English`\n\n- **`output_language`** Language to generate output, set in user form or can be hard-coded. e.g. `Espa\u00f1ol`\n\n\n"
},
"typeVersion": 1
},
{
"id": "c90e6f80-b962-4487-b565-d21f109886b4",
"name": "Generate Keywords",
"type": "n8n-nodes-base.httpRequest",
"position": [
672,
432
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('LLM Params').item.json.system_prompt_generate_kw) }}\n },\n {\n \"role\": \"user\",\n \"content\": \"## Working language:\\n{{ $json.working_language }}\\n\\n## Keywords for processing\\n{{ $('Form').item.json.Keywords }}\"\n }\n ]\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "5cbc4881-975a-4f43-b73e-0177e26b8a98",
"name": "Collect Keywords",
"type": "n8n-nodes-base.code",
"position": [
896,
432
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "8897d56f-ff95-4c9b-8a9f-3764ba3df645",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
592,
704
],
"parameters": {
"color": 7,
"width": 528,
"height": 496,
"content": "## Javascript code \n\nCode block **Collect ...** used here to collect OpenAI model output from various formats, that different OpenAI models produce, so when you change model name in **LLM Params** node, workflow does not break"
},
"typeVersion": 1
},
{
"id": "cd74aad4-b3ff-4737-a6f2-824b3172ba3e",
"name": "LLM Params",
"type": "n8n-nodes-base.set",
"position": [
432,
432
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f4da46c9-0dd6-4e1e-abb7-74beb7156d54",
"name": "simple_model",
"type": "string",
"value": "gpt-4.1-mini"
},
{
"id": "84617555-0db4-4ea1-a5c7-a58549775d68",
"name": "advanced_model",
"type": "string",
"value": "gpt-4.1"
},
{
"id": "3b948266-ea4e-4ad5-bd68-7938caa2a2c7",
"name": "working_language",
"type": "string",
"value": "English"
},
{
"id": "aef8c5ce-4df4-4686-b0a8-eb48e0bcf6c8",
"name": "=system_prompt_generate_kw",
"type": "string",
"value": "=You are a seasoned marketing director with 10+ years of SEO expertise.\n\nTask: \nRefine the keyword list provided by the user so it is ready for article discovery and high-impact blog-content creation.\n\nGuidelines:\n- Make sure to translate the keywords to {{ $json.main_language }}, if required\n- Determine the search intent of every original keyword.\n- Add relevant long-tail variations, synonyms, and closely related phrases that boost organic reach.\n- Remove duplicates and low-intent terms.\n- Order the final list from highest to lowest strategic value.\n\n\nOutput (strict):\nReturn only the enhanced keywords\u2014no commentary, headings, or additional text, separated by commas"
},
{
"id": "2d9e5524-1921-4b74-9811-9f71f7492e16",
"name": "=system_prompt_article_outlines",
"type": "string",
"value": "=Task:\nYou are given the raw output of a search module. Your job is to extract and reformat this information into a JSON structure with the following format:\n{\n \"articleOutlines\": [\n {\n \"title\": \"\",\n \"markdownOutline\": \"\"\n }\n ]\n}\n\nInstructions:\n- Extract three outlines from the provided text.\n- Translate the content to {{ $json.main_language }} if required.\n- For each outline, populate the fields \"title\" and \"markdownOutline\".\n- Replace all newline characters (\\n) in the \"markdownOutline\" field with a literal \\\\n, as the JSON will be sent via an HTTP request.\n- Use only the fields \"title\" and \"markdownOutline\".\n- If any required information is missing, leave the corresponding value as an empty string (\"\").\n- Output only the final JSON. Do not include any code fences such as ```json or explanatory text.\n- Do not include any introductionary text like \"Article outline is the following...\", include ONLY the outline itself, no additional comments."
},
{
"id": "e203aae6-0894-4834-88d9-edb63fa3cff9",
"name": "system_prompt_outline",
"type": "string",
"value": "=You are a established writer assitant in {{ $('Form').item.json['Domain of Expertize'] }}.\n\nYour task is to create a high-quality, comprehensive, and well-structured professional artile outline that also have excellent SEO in {{ $json.output_language }}.\n\nYou will be provided with:\n- Three outlines from existing articles on the same or related topics (scraped from the web)\n- Several citations with one-line conclusions\n- The target keywords of the article for SEO optimization\n\nInstructions:\n1. Thoroughly analyze all three provided outlines.\n2. Understand the citations and one-line conclusions.\n3. Synthesize their content to generate a new, original outline that:\n- Draws inspiration from the key ideas, structure, and concepts of the originals.\n- Greatly expands on their scope, depth, and clarity.\n- Adds new angles, subtopics, or connections not present in the original outlines.\n- Weave in the citations.\n- Adressed the problem of target persona\n4. Make sure that resulting outline composition is well-structured and MECE, has no overlaping topics and no repetitions. \n5. Do not copy any sections verbatim. Instead, reframe and combine the best parts while retaining the meaning and introducing improvements.\n6. Preserve the names of important terms, tools, products, or techniques as they appear in the source outlines.\n7. Avoid including any personal information such as author names, article sources, or publication details.\n8. Make sure to include all the given citations, remove any utm_ parameters from the links, use Markdown (ATX) format when linking them.\n\nOutput Format:\n- Use Markdown (ATX) format.\n- Structure your outline with clear hierarchy using # for article title, ##, and ### headers for section and subsection titles.\n- Use bullet points, numbered lists, or other markdown elements where appropriate.\n- Aim for clarity, completeness, and usefulness\u2014this outline should feel like the \"ultimate\" version of what the original articles were trying to cover."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "56b5839c-2638-4fdf-a718-6a5e434ffdea",
"name": "Search Citations",
"type": "n8n-nodes-base.httpRequest",
"position": [
2208,
432
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"tools\": [\n { \"type\": \"web_search_preview\" }\n ],\n \"input\": \"Retrieve exactly five authoritative, recent (\u2264 3 years) sources on the following keywords: {{ $('Collect Keywords').item.json.output }} in {{ $('Language').item.json.main_language }}.\\n\\nFor each source return:\\n\u2022 Title\\n\u2022 Clean canonical URL (strip UTM/tracking parameters)\\n\u2022 Publication date (YYYY-MM-DD)\\n\u2022 Content type (journal article | industry report | government/NGO data | educational material)\\n\u2022 One-sentence takeaway (\u2264 20 words) that highlights a key statistic, framework, maturity model, or other actionable insight.\\n\\nSelection rules:\\n1. Credibility first \u2014 peer-reviewed journals, established industry publishers, government or university sites; no self-published blogs or AI-generated pages.\\n2. Diversity \u2014 at least one academic and one industry source; avoid duplicate domains.\\n3. Recency \u2014 skip anything older than three years unless it is a seminal work that is still cited.\\n4. Accessibility \u2014 prefer full-text or open-access material; avoid paywalled abstracts only.\\n5. Exclude links with marketing UTM parameters.\\n\\nReturn the results in the requested order; if fewer than five qualifying sources are found, state that explicitly.\"\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "be5252bb-138f-486d-a44d-700d1f1b6464",
"name": "Collect Citations",
"type": "n8n-nodes-base.code",
"position": [
2400,
432
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "abc76ebf-5fa4-4814-90f4-f7953c24ad9c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1136,
704
],
"parameters": {
"color": 7,
"width": 704,
"height": 496,
"content": "## Structured Output \n\n**Generate Outlines** node is using structured output defined in system prompt in node **LLM Params**. Payload for API call defined in node **Define Schema** where target object schema is defined, see left ->\n```json\n{\n \"articleOutlines\": [\n {\n \"title\": \"\",\n \"markdownOutline\": \"\"\n }\n ]\n}\n```"
},
"typeVersion": 1
},
{
"id": "5849e7df-3b40-45dd-a816-b01152d74e5b",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1856,
704
],
"parameters": {
"color": 7,
"width": 672,
"height": 496,
"content": "## Object Schema for Structured Output \n\nUsing the following schema in **Define Schema** node to define **Generate Outlines** structured output. LLM will return list of sample outlines with `title` and `markdownOutline` properties\n```json\n{\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"articleOutlines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" },\n \"markdownOutline\": { \"type\": \"string\" }\n },\n \"required\": [ \"title\", \"markdownOutline\" ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [ \"articleOutlines\" ],\n \"additionalProperties\": false\n }\n}\n```"
},
"typeVersion": 1
},
{
"id": "f5e195f5-bd40-4fc3-860b-88a677b00d62",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
1216
],
"parameters": {
"color": 4,
"width": 2512,
"height": 480,
"content": "## Generate Outline\n\nGenerate article outline basing on research and citations "
},
"typeVersion": 1
},
{
"id": "3431caee-a897-4644-a679-12153d6c5967",
"name": "S1",
"type": "n8n-nodes-base.noOp",
"position": [
2624,
432
],
"parameters": {},
"typeVersion": 1
},
{
"id": "9879cd7d-aa9b-494e-9ff0-220e32f8e92c",
"name": "S2",
"type": "n8n-nodes-base.noOp",
"position": [
2608,
1088
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bfdb5f19-7f43-44a0-93d5-02f02144b3ba",
"name": "S3",
"type": "n8n-nodes-base.noOp",
"position": [
608,
1440
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7fab584b-2d74-4da4-b95e-45bad14025e6",
"name": "Search Articles",
"type": "n8n-nodes-base.httpRequest",
"position": [
1120,
432
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"tools\": [\n { \"type\": \"web_search_preview\" }\n ],\n \"input\": \"Locate **exactly three** authoritative articles published **within the last 12 months** on \u201c{{ $json.output }}\u201d. Prefer peer-reviewed journals, major newspapers, or official (.gov, .edu, well-established .org) sources. Prefer articles in {{ $('Language').item.json.main_language }} \\n\\nReturn an **ordered Markdown list** (1., 2., 3.), where each item contains: \\n\u2022 **Title** \u2013 the full headline \\n\u2022 **Source & Date** \u2013 outlet name, DD MMM YYYY \\n\u2022 **URL** \u2013 canonical link \\n\u2022 **Outline** \u2013 4-6 concise bullet points summarising the article\u2019s main structure or argument flow. \\n\\nKeep each outline under 120 words and avoid duplicate sources.\"\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "25cff070-009c-4c89-a562-6c1d27f7d00a",
"name": "Collect Articles",
"type": "n8n-nodes-base.code",
"position": [
1344,
432
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "0b716ff8-a676-4e45-8b34-ece1f06cb122",
"name": "Draft Outline",
"type": "n8n-nodes-base.httpRequest",
"position": [
1088,
1440
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.advanced_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('LLM Params').item.json.system_prompt_outline) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.user_prompt) }}\n }\n ]\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "f72de2fb-5aa1-4d32-8a52-1fad3a8eba86",
"name": "Collect Outline",
"type": "n8n-nodes-base.code",
"position": [
1280,
1440
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "b80c3709-4b0f-4c66-9f8d-1a601a74c10a",
"name": "Set Outline Prompt",
"type": "n8n-nodes-base.set",
"position": [
848,
1440
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b9c3e5d4-3cda-4223-8335-7eaa2fc72b55",
"name": "user_prompt",
"type": "string",
"value": "=## SEO keywords\n{{ $('Form').item.json.Keywords }}\n\n## Outline No. 1\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[0].title }}\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[0].markdownOutline }}\n\n## Outline No. 2\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[1].title }}\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[1].markdownOutline }}\n\n## Outline No. 3\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[2].title }}\\n{{ $('Collect Outlines').item.json.output.parseJson().articleOutlines[2].markdownOutline }}\\\n\n## Authoritative Citations\n{{ $('Collect Citations').item.json.output }}\""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2015c523-382a-4613-ae62-03d6e6d4b0b6",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1648,
1312
],
"parameters": {
"color": 3,
"width": 848,
"height": 352,
"content": "## You can do more! \n\nAdd Human-In-The-Loop interaction or AI-reflection on generated outline. \nCheck, refine and improve the outline to the perfection"
},
"typeVersion": 1
},
{
"id": "7239dca1-75ef-4335-a1fa-9df917bdde59",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
1728
],
"parameters": {
"width": 2512,
"height": 1696,
"content": "## Generate Article\n\nGenerate article sections 1 by 1. \nEach section starts with web search relevant to that section, then `advanced_model` from **LLM Params** node is used to write section text. \nSee **Section Prompts** for LLM instructions, tweak them as required for your task"
},
"typeVersion": 1
},
{
"id": "b394ba65-722a-43bb-bb73-a47cba483799",
"name": "Send email",
"type": "n8n-nodes-base.emailSend",
"disabled": true,
"position": [
1696,
1472
],
"parameters": {
"options": {}
},
"typeVersion": 2.1
},
{
"id": "172d0a7d-35c5-46c3-a706-1d57695c5574",
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"disabled": true,
"position": [
1904,
1472
],
"parameters": {
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "f6883195-f08e-4470-b45b-b4af36891274",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"disabled": true,
"position": [
2128,
1472
],
"parameters": {
"options": {}
},
"typeVersion": 2.1
},
{
"id": "0154b394-c0e0-4234-b90b-5605c9f8e1f5",
"name": "S4",
"type": "n8n-nodes-base.noOp",
"position": [
-128,
2128
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1ce113ff-a94e-490c-9826-f9b3b3d0f14e",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
672,
2128
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "219740ce-5c11-41c7-becd-2e08eec2b4c4",
"name": "Language",
"type": "n8n-nodes-base.set",
"position": [
240,
432
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "4941ff9e-e267-4a9a-b782-c7f63d7e51b2",
"name": "main_language",
"type": "string",
"value": "English"
},
{
"id": "7f30c14e-70ac-413a-9602-e907cafee97c",
"name": "output_language",
"type": "string",
"value": "={{ $json.Language }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2d1657d4-779f-495d-8b8a-84bda321b7b6",
"name": "Section Prompts",
"type": "n8n-nodes-base.set",
"position": [
112,
2128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "aa16f13a-e56e-49e2-a8b7-1dfcf7af5279",
"name": "sytem_prompt_analyze_section",
"type": "string",
"value": "=You are an expert content generator specialized in {{ $('Form').item.json['Domain of Expertize'] }}. \n\nYour task is to create comprehensive, accurate, and well-structured content for a designated section of the article. Use your knowledge to thoroughly address all points described in the section, adhering closely to provided requirements.\n\n## Input:\n\n1. Complete article <outline>\n2. Your <section> structure:\n\n\n## Process:\n1. Understand the overall article structure.\n2. Identify the context and role of your section within the article.\n3. Generate content that is thorough for this section, ensuring no unnecessary duplication with other sections.\n\n## Content Requirements:\n\n1. Progress from general concepts to specifics, dive into details, and conclude with a high-level summary.\n2. Address each subsection in depth, following the order provided.\n3. Incorporate relevant examples, explanations, and context.\n4. Integrate up-to-date knowledge and established concepts from the field.\n5. Maintain an academic and informative tone suitable for research publications.\n6. Organize the content using logical headings and subheadings.\n\n## Content Guidelines:\n\n### Depth and Breadth:\n- Provide comprehensive coverage of every subsection.\n- Define key terms and concepts clearly.\n- Discuss current understanding, applications, and implications.\n- Explain relationships between different concepts as appropriate.\n\n### Structure:\n- Use hierarchical formatting with clear, descriptive headings.\n- Format the section title as a level 2 heading (##).\n- Format each subsection as a level 3 heading (###).\n- Use paragraphs and bullet points for clarity and logical flow.\n- Ensure smooth transitions between subsections.\n\n### Content Quality:\n- Ensure accuracy, depth, and clarity.\n- Illustrate concepts with specific examples.\n- Include relevant data, statistics, or research findings when available.\n- Maintain an objective, scholarly style.\n- Avoid redundant content and unnecessary repetition.\n\n### Technical Considerations:\n- Use markdown formatting for all headings, lists, and emphasis.\n- Apply precise technical terminology; define specialized terms on first use.\n- Include code snippets, formulas, or mathematical notation if relevant.\n\n## Output Format:\nReturn only the fully formatted markdown content for the section. Do not include commentary, process notes, or disclaimers. The output should be ready for direct inclusion in the research report.\n\nRely exclusively on your verified knowledge\u2014do not fabricate studies, data, or direct quotations you cannot substantiate.\n\"\"\"\n"
},
{
"id": "1199cfcd-96de-43a7-ba01-14bc12d520c7",
"name": "system_prompt_generate_queries",
"type": "string",
"value": "=You are a specialized search query generator for a research assistant system. Your task is to create highly effective search queries based on section outline information. These queries will be used to retrieve relevant information from web search APIs to enhance research article content.\n\n## Your Task:\nGenerate up to 5 effective search queries that will retrieve the most relevant information for the given section and its subsections.\n\n## Input\nYou will receive an article section draft.\n\n## Query Generation Process:\n1. Analyze the section name and all subsection descriptions thoroughly\n2. Identify the core concepts, key terms, and relationships that need to be researched\n3. Prioritize fundamental information needs first\n4. Create specific, targeted queries for the most important information\n5. Ensure coverage across all subsections, but prioritize depth over breadth\n6. Include technical terminology and domain-specific language when appropriate\n\n\n## Query Construction Guidelines:\n\n1. **Specificity**: Create targeted queries that are likely to return relevant results\n - Include specific technical terms rather than general descriptions\n - Incorporate domain knowledge and specialized terminology\n\n2. **Diversity**: Ensure variety in your query approaches\n - Vary query structure (questions, keyword sets, specific facts to verify)\n - Target different aspects of the subsections\n - Include different perspectives or viewpoints when relevant\n\n3. **Prioritization**: Order queries by importance\n - Place queries for fundamental or critical information first\n - Prioritize queries addressing explicit reflection feedback\n - Ensure the most important subsections are covered in the limited query count\n\n4. **Effectiveness**: Optimize for search engine performance\n - Use search operators when helpful (quotes for exact phrases, etc.)\n - Keep queries concise but descriptive (typically 4-10 words)\n - Include year/recency indicators for time-sensitive topics\n\nRemember: The most important queries should come first in your list, as the system may only use a subset of your generated queries based on the user's max_queries setting which is 5.\n"
},
{
"id": "204e4dc5-b894-45d2-87e0-98d300003e04",
"name": "system_prompt_search_summary",
"type": "string",
"value": "=You are a specialized agent responsible for curating and synthesizing raw search results. \n\nYour task is to transform unstructured web content into coherent, relevant, and organized information that can be used for report generation.\n\n## Input\nYou will receive a list of SearchResult objects, each containing:\n1. A Query object with the search query that was used\n2. A list of raw_content strings containing text extracted from web pages\n\n## Process\nFor each search result provided:\n\n1. ANALYZE the raw_content to identify:\n - Key information relevant to the associated query\n - Main concepts, definitions, and relationships\n - Supporting evidence, statistics, or examples\n - Credible sources or authorities mentioned\n - Formulae, equations, and mathematical notations\n\n2. FILTER OUT:\n - Irrelevant website navigation elements and menus\n - Advertisements and promotional content\n - Duplicate information\n - Footers, headers, and other website template content\n - Form fields, subscription prompts, and UI text\n - Clearly outdated information\n\n3. ORGANIZE the information into:\n - Core concepts and definitions\n - Key findings and insights\n - Supporting evidence and examples\n - Contrasting viewpoints (if present)\n - Contextual background information\n\n4. SYNTHESIZE the content by:\n - Consolidating similar information from multiple sources\n - Resolving contradictions where possible (noting them explicitly otherwise)\n - Ensuring logical flow of information\n - Maintaining appropriate context\n\n## Guidelines\n- Maintain neutrality and balance in presenting information\n- Preserve technical precision when dealing with specialized topics\n- Note explicitly when information appears contradictory or uncertain\n- When information appears to be from commercial sources, note potential bias\n- Prioritize more recent information over older content\n- Maintain proper attribution when specific sources are referenced\n- Do not add \n- NO IMPORTANT DETAILS SHOULD BE LEFT OUT. YOU MUST BE DETAILED, THOROUGH AND COMPREHENSIVE.\n- DO NOT TRY TO OVERSIMPLIFY ANY TOPIC. COMPREHENSIVENESS IS KEY. IT IS GOING TO BE USED IN A RESEARCH REPORT.\n- Return only the search results in Markdown. Do not include commentary or front matter.\n"
},
{
"id": "ca4026ea-cf6a-41ad-a145-da0641693a5c",
"name": "system_prompt_write_section",
"type": "string",
"value": "=You are a seasoned copywriter of higly engaging articles in {{ $('Form').item.json['Domain of Expertize'] }}.\n\nToday is {{ $now.format('dd/LL/yyyy') }}\n\nYour mission:\n\nWrite a high-quality, comprehensive section of an article based on the provided <section_outline>. This is not the full article outline\u2014only a single section. Focus strictly on the content described in this section.\n\n\nYou will receive:\n- **Section Outline** \u2013 **write only this section**\n\nas a supplementary materials you also will receive:\n- **SEO keywords** to write text with this keywords in mind.\n- **Research summary** - this is web search summary containing information relevant to this section\n- **Citations** to use them in the section text, if appropriate, to back up your ideas and insights.\n- **Article Outline** - for context only and to understand what content will be in other sections to avoid duplication with other sections\n\nOutput Format:\nReturn **only** the section text in {{ $('Language').item.json.output_language }} in Markdown - no commentary, no front-matter.\n\n\nStyle & Formatting Guidelines:\n- Use Markdown (ATX) format.\n- Begin the section with an ## heading (H2).\n- Section header (H2) must be the same as in the given section outline. \n- Do not include content outside the scope of the provided section.\n- Do not write introductions, conclusions, or calls to action unless the section title explicitly requires them (e.g., \u201cIntroduction\u201d).\n- If citations are suitable for your sections, incorporate citation, removing any utm_ parameters from URLs. Use standard Markdown link formatting. Pull insights from the provided sources; cite with standard Markdown links **after the relevant sentence**.\n- Aim for a clear, straightforward tone\u2014no fluff. \n- Use Markdown elements like bullet points, numbered lists, or code blocks when helpful.\n- Keep paragraphs to 3-4 lines, insert sub-headings or bullets when you exceed that.\n- Cap most sentences at 20 words; aim for one idea per sentence. Sentences \u2264 20 words whenever possible; vary length for rhythm. \n- **Readability:** 7th\u20139th grade (Flesch Reading Ease \u2248 60\u201380).\n- Paragraphs 1\u20133 sentences. \n- \u2265 90 % active voice. \n- Do not use intensifiers which sound sales-y, let the facts do the work.\n- Ensure the section is complete, useful, and insightful\u2014it should feel like the \"ultimate\" guide to that part of the outline.\n- Include examples to clarify key points.\n- Integrate insights from the provided sources where relevant.\n- Mention all tools, terms, products, or techniques listed in the outline exactly as written.\n- Prefer facts and references, cut the filling words\n- Add as many examples, as appropriate\n- Do not write conlclusion of this is not a conclusion sections. There will be other sections otherwise, so it's not appropriate for conclusion in the middle of the text\n- Make sure that full section content is indeed in {{ $('Language').item.json.output_language }} language. But do not translate weaved in links, mentioned paper and article names.\n- NO IMPORTANT DETAILS SHOULD BE LEFT OUT. YOU MUST BE DETAILED, THOROUGH AND COMPREHENSIVE.\n- DO NOT TRY TO OVERSIMPLIFY ANY TOPIC. COMPREHENSIVENESS IS KEY. IT IS GOING TO BE USED IN A RESEARCH REPORT"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2a7641d9-abb5-4884-b5b6-ace6a33eb669",
"name": "Analyze Section",
"type": "n8n-nodes-base.httpRequest",
"position": [
960,
2496
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('Section Prompts').item.json.sytem_prompt_analyze_section) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.user_prompt) }}\n }\n ]\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "1cb7269e-abe2-4018-9d6f-4ab31529f57c",
"name": "Separate Sections",
"type": "n8n-nodes-base.set",
"position": [
304,
2128
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n \"sections\": {{ $('Collect Outline').item.json.output.split(\"\\n## \").slice(1) }}\n}\n"
},
"typeVersion": 3.4
},
{
"id": "a7e7cd54-0dd2-467d-9226-501bf99f4463",
"name": "Analysis Prompt",
"type": "n8n-nodes-base.set",
"position": [
752,
2496
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b9c3e5d4-3cda-4223-8335-7eaa2fc72b55",
"name": "user_prompt",
"type": "string",
"value": "=## Section\n{{ $json.sections }}\n\n## Full Article Ouline\n{{ $('Collect Outline').item.json.output }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a5b6f018-9224-4c8d-afdc-6e1398253e0e",
"name": "Generate Queries",
"type": "n8n-nodes-base.httpRequest",
"position": [
1376,
2496
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('Section Prompts').item.json.system_prompt_generate_queries) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.output) }}\n }\n ],\n \"temperature\": 0.5,\n \"text\": {\n \"format\": {\n \"type\": \"json_schema\",\n \"name\": \"result\",\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"result\": {\n \"description\": \"queries list\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"minItems\": 5,\n \"maxItems\": 5\n }\n },\n \"required\": [\"result\"],\n \"additionalProperties\": false\n }\n }\n }\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "f4543d4e-17a5-439f-b30e-9c980ec15845",
"name": "Collect Queries",
"type": "n8n-nodes-base.code",
"position": [
1584,
2496
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "c97703c0-e977-4de6-8629-d9e33ed8d21c",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
1376,
2752
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "search_result"
}
]
}
},
"typeVersion": 1
},
{
"id": "2c94c4a2-8986-405c-a8b6-1ccba3205ac5",
"name": "SearchResult",
"type": "n8n-nodes-base.set",
"position": [
1168,
2752
],
"parameters": {
"options": {
"ignoreConversionErrors": true
},
"assignments": {
"assignments": [
{
"id": "bcfe29d7-e563-4159-833e-5f4479e191fe",
"name": "search_result",
"type": "array",
"value": "={\n \"query\": \"{{ $json.output[0].action.query }}\",\n \"results\":[\n {\n \"content\": \"{{ $json.output[1].content[0].text }}\"\n }\n ]\n}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ef4c7177-e190-4990-a810-a8496b032112",
"name": "Prepare Prompt",
"type": "n8n-nodes-base.set",
"position": [
752,
2752
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "69886048-3e67-4f83-a598-e55a3b1de531",
"name": "prompt",
"type": "string",
"value": "=Locate **exactly three** authoritative articles published **within the last 12 months** on {{ $json.result }}. \n\nPrefer peer-reviewed journals, major newspapers, or official (.gov, .edu, well-established .org) sources. \n\nPrefer articles in {{ $('Language').item.json.main_language }}\n\nReturn an **ordered Markdown list** (1., 2., 3.), where each item contains:\n- **Title** \u2013 the full headline \n- **Source & Date** \u2013 outlet name, DD MMM YYYY \n- **URL** \u2013 canonical link without any utm_ attributes \n- **Outline** \u2013 4-6 concise bullet points summarising the article\u2019s main structure or argument flow.\n\nKeep each outline under 120 words and avoid duplicate sources."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f5e3a3ac-54ad-479e-875a-f75395b18c20",
"name": "Convert to Array",
"type": "n8n-nodes-base.code",
"position": [
1792,
2496
],
"parameters": {
"language": "python",
"pythonCode": "import json\n\noutput_items = []\n\nfor item in _input.all():\n # Raw JSON string from previous node\n raw = item.json.get('output')\n if not raw:\n continue\n \n # Parse into a dict\n data = json.loads(raw)\n results = data.get(\"result\", [])\n \n # Wrap each string in a dict under 'json'\n for result in results:\n output_items.append({'json': {'result': result}})\n\n# Return the list of items\nreturn output_items\n"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "0e295bb9-6271-4c92-9aec-8224eb5bf8a5",
"name": "Split Queries",
"type": "n8n-nodes-base.splitOut",
"position": [
1984,
2496
],
"parameters": {
"options": {},
"fieldToSplitOut": "result"
},
"typeVersion": 1
},
{
"id": "a29a0e2e-d616-46aa-8980-47f13ae5e3fe",
"name": "Split Sections",
"type": "n8n-nodes-base.splitOut",
"position": [
480,
2128
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "=sections"
},
"typeVersion": 1
},
{
"id": "d8295eff-99ec-41f7-bb05-ddac4089f1b6",
"name": "Web Search",
"type": "n8n-nodes-base.httpRequest",
"position": [
960,
2752
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"tools\": [\n { \"type\": \"web_search_preview\" }\n ],\n \"input\": {{ JSON.stringify($json.prompt) }}\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "35ce90d1-36b6-48af-b54d-7f55aa7797ad",
"name": "Payload",
"type": "n8n-nodes-base.set",
"position": [
1568,
432
],
"parameters": {
"options": {
"ignoreConversionErrors": true
},
"assignments": {
"assignments": [
{
"id": "5b281021-8b90-42cf-8fab-1b6350f4e710",
"name": "=payload",
"type": "object",
"value": "={\n \"model\": \"{{ $('LLM Params').item.json.simple_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('LLM Params').item.json.system_prompt_article_outlines) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.output) }}\n }\n ],\n \"text\": {\n \"format\": {\n \"type\": \"json_schema\",\n \"name\": \"result\",\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"articleOutlines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": { \"type\": \"string\" },\n \"markdownOutline\": { \"type\": \"string\" }\n },\n \"required\": [ \"title\", \"markdownOutline\" ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [ \"articleOutlines\" ],\n \"additionalProperties\": false\n }\n }\n }\n}\n"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6aaad0e7-e720-423f-ba5c-5bf8d7414aba",
"name": "Search Summary",
"type": "n8n-nodes-base.httpRequest",
"position": [
1792,
2752
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.advanced_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('Section Prompts').item.json.system_prompt_search_summary) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ $json.search_results }}\n }\n ]\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "b48df2e7-fb03-41fa-a46e-4f198e80db4e",
"name": "Collect Summary",
"type": "n8n-nodes-base.code",
"position": [
1984,
2752
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "59a19f81-5b35-4cb6-b1ca-0f72230491d3",
"name": "Result Prompt",
"type": "n8n-nodes-base.set",
"position": [
1584,
2752
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "92da2000-ee4d-473a-870d-165b86495235",
"name": "search_results",
"type": "string",
"value": "={{ $json.search_result.map(item => ({\"type\": \"input_text\", \"text\": item})) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "34f6aa58-4aba-4c08-ba97-88888f2a4cd4",
"name": "Research for Section1",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
2976
],
"parameters": {
"color": 4,
"width": 2288,
"height": 352,
"content": "## Write Section \u2a09 N\n\nFor each section: write section text"
},
"typeVersion": 1
},
{
"id": "f00490a0-c558-4200-bc7e-b733f1acba15",
"name": "Section Prompt",
"type": "n8n-nodes-base.set",
"position": [
752,
3072
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "69886048-3e67-4f83-a598-e55a3b1de531",
"name": "user_prompt",
"type": "string",
"value": "=<section_outline>\n{{ $('Loop Over Items').item.json.sections }}\n</section_outline>\n\n<SEO keywords>\n{{ $('Collect Keywords').item.json.output }}\n</SEO keywords>\n\n<search_summary>\n{{ $json.output }}\n</search_summary>\n\n<citations>\n{{ $('Collect Citations').item.json.output }}\n</citations>\n"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "01e98ad0-d572-4ec5-992d-e669acb2d937",
"name": "Write Section",
"type": "n8n-nodes-base.httpRequest",
"position": [
960,
3072
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"{{ $('LLM Params').item.json.advanced_model }}\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('Section Prompts').item.json.system_prompt_write_section) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.user_prompt) }}\n }\n ]\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "4a31ec72-99e9-44b8-92f9-17addf2288ac",
"name": "Collect Analysis",
"type": "n8n-nodes-base.code",
"position": [
1168,
2496
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "daf3e561-0fd1-4d37-9a3c-a3fe9ca754db",
"name": "Collect Section",
"type": "n8n-nodes-base.code",
"position": [
1168,
3072
],
"parameters": {
"jsCode": "// input: one item that contains the raw API JSON under $.json\n// output: one item with assistant text at $.json.text\nconst run = items[0].json; // \u2190 what the HTTP Request / OpenAI node returned\nlet assistantText = null;\n\nfor (const chunk of run.output ?? []) {\n // assistants/Responses API format -------------------------------\n if (chunk.type === 'message') {\n const textPart = (chunk.message?.content || [])\n .find(p => p.type === 'text' || p.type === 'output_text');\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n // chat-completions fallback (older cheap models) -----------------\n if (Array.isArray(chunk.content)) {\n const textPart = chunk.content.find(p => p.text);\n if (textPart?.text) { assistantText = textPart.text; break; }\n }\n}\n\nreturn [{ json: { output: assistantText } }];\n"
},
"typeVersion": 2
},
{
"id": "14763a59-42b9-450a-8dba-6720054db531",
"name": "Next Section",
"type": "n8n-nodes-base.noOp",
"position": [
1392,
3072
],
"parameters": {},
"typeVersion": 1
},
{
"id": "16d278d1-1c0f-4837-9620-df8547b3f915",
"name": "Research for Section2",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
1888
],
"parameters": {
"color": 5,
"width": 1424,
"height": 448,
"content": "## Finalize Article\n\nCombine all section of the article"
},
"typeVersion": 1
},
{
"id": "2422affb-c559-4568-9028-2d46b2aed4f1",
"name": "Aggregate Sections",
"type": "n8n-nodes-base.aggregate",
"position": [
1072,
2080
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "01fd1e92-d575-44f8-be72-fb7cfff28f4d",
"name": "Combine Article",
"type": "n8n-nodes-base.set",
"position": [
1280,
2080
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "61a815f5-a2d0-4668-87ce-45e6af0d3274",
"name": "title",
"type": "string",
"value": "=# {{ $('Collect Outline').item.json.output.split('\\n##')[0].replaceAll(\"# \", \"\").replaceAll('\\n', '') }}"
},
{
"id": "c683efda-c807-41ec-ada6-1ad7b1ace4d2",
"name": "article",
"type": "string",
"value": "={{ $('Aggregate Sections').item.json.data.map(item => item.output).join('\\n\\n') }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e03f139f-1347-4269-8056-e41a14566ee6",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1696,
1936
],
"parameters": {
"color": 3,
"width": 672,
"height": 368,
"content": "## Article is Ready \n\nThe article is ready. It's in Markdown format.\n\nYou can add more polishing: improve readability, cross-check SEO, etc.\nInsert SEO title. Export to target format like HTML / PDF."
},
"typeVersion": 1
},
{
"id": "4e7fb5d0-82eb-44cd-b4e7-6945f6d70519",
"name": "Markdown",
"type": "n8n-nodes-base.markdown",
"disabled": true,
"position": [
2112,
2112
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "02aea2e1-01aa-4080-b1eb-c97edf998ed3",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
1712,
3024
],
"parameters": {
"color": 3,
"width": 640,
"height": 240,
"content": "## Section is Ready \n\nYou can add reflection - analysing the section and creating a list of required changes, that after applied to original section text.\n\nYou can check readability, SEO score, etc. whatever suits your task"
},
"typeVersion": 1
},
{
"id": "cbd11406-880b-41fc-8567-03af336dd8d5",
"name": "AI Agent1",
"type": "@n8n/n8n-nodes-langchain.agent",
"disabled": true,
"position": [
1744,
2112
],
"parameters": {
"options": {}
},
"typeVersion": 2.1
},
{
"id": "aa0962fc-e082-4241-80de-98ed9dcd083d",
"name": "Research for Section",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
2352
],
"parameters": {
"color": 4,
"width": 2288,
"height": 608,
"content": "## Research Section \u2a09 N\n\nFor each section: collect info
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.
openAiApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Generate research-backed article with n8n
Source: https://n8n.io/workflows/6820/ — 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.
How it Works
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
📄 Documentation: Notion Guide
Code Schedule. Uses memoryBufferWindow, agent, stickyNote, outputParserStructured. Event-driven trigger; 45 nodes.
3790. Uses memoryBufferWindow, agent, outputParserStructured, lmChatOpenAi. Event-driven trigger; 45 nodes.