# Build a healthcare assistant with n8n and Regolo: a step-by-step guide

If your agency wants to offer agentic services in healthcare without building from scratch, n8n is the fastest path: a visual orchestrator with a clean API, native LLM nodes, and no custom backend required. Pair it with Regolo as the inference provider and you get a GDPR-aligned stack running entirely on European infrastructure.

Most agentic frameworks force a choice: either a polished UI with limited flexibility, or a code-first approach that requires a developer for every tweak. n8n sits in a useful middle ground.

![](https://regolo.ai/wp-content/uploads/2026/06/generated-image-1024x559.png)## What this n8n workflow does 

The n8n workflow included below implements a **healthcare literature assistant** that follows a four-step research plan, all inference running through our OpenAI-compatible API endpoint (`POST https://api.regolo.ai/v1/chat/completions`).

The pipeline runs in this order:

1. **Return a structured artifact** — the final response includes the narrative summary, an evidence table, `evidence_level`, a mandatory medical disclaimer, a `step_log` with counts per step, and the full executed plan JSON. Every response also carries a `sessionId` and an `inferenceProvider` field identifying Regolo.[](https://regolo.ai/building-a-document-analysis-system-with-regolo-ai-api/)
2. **Receive query** — a webhook accepts a JSON payload with `question`, `population`, `intervention`, and `outcomes`. A Code node immediately strips regex patterns for SSNs and other identifiers before anything else runs. No PHI leaves this node.
3. **Generate a research plan** — a call to Regolo (Llama 3.3 70B, temperature 0.2) asks the model to output *only* a JSON research plan. The prompt forbids free text; if the output is not valid JSON, a validation Code node throws an error with the raw content for debugging.
4. **Execute steps** — the plan's steps array is split and routed through a Switch node. Each step type has its own branch: `search_papers` hits PubMed's E-utilities API (no key required for basic use), `screen_titles` calls Regolo at temperature 0.1 to classify each PMID as KEEP or REMOVE, `extract_outcomes` pulls structured data (study type, sample size, effect size), and `summarize` synthesizes a narrative and a structured table.

---

## How to set up the workflow

### **Credential** Setup

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-16.10.34-1024x531.png)In n8n, create one credential of type *HTTP Header Auth*:

- Header name: `Authorization`
- Value: `Bearer YOUR_REGOLO_API_KEY`

Name it `Regolo API Key`. All three LLM nodes in the workflow reference this credential by name.

### Import Workflow

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-17.25.37-1024x779.png)

Go to n8n → Workflows → Import from file and upload the JSON attached to this article and once uploaded test that it works just sending this request in POST (through Postman or similar tools):

```
POST https://your-n8n/webhook/healthcare-research
{
  "question": "What is the evidence for metformin in type 2 diabetes?",
  "population": "Adults with T2DM",
  "intervention": "Metformin 500-2000mg/day",
  "outcomes": ["HbA1c reduction", "mortality", "cardiovascular events"],
  "userRole": "clinician"
}Code language: Bash (bash)
```

The response will include `summary`, `evidence_level`, `disclaimer`, `table`, `step_log`, and `plan`.

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-18.58.30-1024x521.png)## How does it works 

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-18.52.07-1024x726.png)

The system prompt for the planner node is strict by design and It specifies what to search and the plan to analyze results.

The "Parse &amp; Validate Plan" then checks:

- `steps` exists and is an array
- every `step.type` is in `['search_papers', 'screen_titles', 'extract_outcomes', 'summarize']`
- the plan includes at least one `search_papers` step and one `summarize` step

If validation fails, the workflow throws a descriptive error rather than passing garbage downstream.

This matters in a healthcare context: a failed run you can debug is better than a silently malformed output that reaches a clinician.

## Data Collection &amp; Clean-up

**How the system finds the relevant medical literature**

- **Parallel Search:** Instead of checking one database at a time, the system takes the medical question and searches four major scientific libraries at once (PubMed, EuropePMC, OpenAlex, and Crossref). This ensures no important studies are missed.
- **Unified Format:** Since each library returns information in a different layout, the system translates all the results into a single, standardized template (title, year, abstract, etc.) to make the data easier to process.
- **Removing Duplicates:** It is common for the exact same study to appear in multiple libraries. Before the AI reads the texts, the system compares titles and ID numbers to delete duplicates. This saves time and prevents the system from analyzing the same article twice.

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-19.01.17-1003x1024.png)

## Smart Analysis &amp; Summary

- 

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-19.02.29-1024x500.png)

- **Phase 1: Filtering the Best:** the AI reads the cleaned-up list and makes an initial cut. It only keeps the articles that truly answer the clinical question.
- **Phase 2: Extracting Key Data:** extracts the most important details into a clear structure: How many patients were tested? What was the drug? What were the results? The AI is strictly instructed *never to invent* numbers that are not explicitly written in the original text.
- **Phase 3: The Final Report:** finally, the system combines all the gathered data to write an easy-to-read summary for the clinician. It includes a quick-reference table, an evidence reliability score, and a mandatory legal disclaimer.

The entire process is tracked step-by-step, so you always know exactly where every piece of information came from.

---

## What this is not

This workflow operates on published literature and public PubMed data and other sources you see in the workflow, It's not a clinical decision support system and It does not access EHR data, patient records, or any personally identifiable health information. The mandatory disclaimer in every response ("This is an evidence summary for research purposes only. It does not constitute medical advice.") is embedded in the summarizer's system prompt — the model is instructed to include it verbatim and cannot omit it.

For use cases involving patient-linked data, a separate pseudonymization layer and a stricter data-flow architecture are required before connecting to this pipeline.

---

## N8N Workflow

Before copy and paste this workflow in n8n you must install regolo module:

1. Go to the main menu and open **Settings**.
2. Click on **Community Nodes**.
3. Click the **Install** button.
4. Enter the exact package name: `n8n-nodes-regoloai`.
5. Confirm and wait for the installation to finish. The Regolo node will now be available in your workflow builder.

![](https://regolo.ai/wp-content/uploads/2026/06/Screenshot-2026-06-03-alle-19.05.56-1024x450.png)

## Copy and Paste in a new workflow

```
{
  "name": "Healthcare Consultant",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "healthcare-research",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "70f7ee57-316c-4829-ad68-1471b3266bd3",
      "name": "Webhook – Receive Query",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -1424,
        240
      ],
      "webhookId": "healthcare-research-webhook"
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body || $input.first().json;\nconst raw = JSON.stringify(body || {});\nconst stripped = raw\n  .replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, '[REDACTED_SSN]')\n  .replace(/\\b[A-Z]{2}\\d{6,}\\b/g, '[REDACTED_ID]');\nconst data = JSON.parse(stripped || '{}');\nconst outcomes = Array.isArray(data.outcomes)\n  ? data.outcomes\n  : (typeof data.outcomes === 'string' && data.outcomes.trim()\n      ? data.outcomes.split(',').map(s => s.trim()).filter(Boolean)\n      : ['efficacy', 'safety', 'mortality']);\nconst sessionId = 'sess_' + Date.now() + '_' + Math.random().toString(36).slice(2, 9);\nreturn [{\n  json: {\n    question: data.question || '',\n    population: data.population || '',\n    intervention: data.intervention || '',\n    outcomes,\n    sessionId,\n    timestamp: new Date().toISOString(),\n    userRole: data.userRole || 'clinician'\n  }\n}];"
      },
      "id": "74057d79-7946-4395-a770-aedc89bb7440",
      "name": "Pre-process – Sanitize Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1200,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\n// Supporta diversi formati di output LLM\nconst raw =\n  item.text ||\n  item.content ||\n  item.response ||\n  item.choices?.[0]?.message?.content ||\n  '';\n\nif (!raw) {\n  throw new Error(\n    'No planner output found. Available keys: ' +\n    JSON.stringify(Object.keys(item))\n  );\n}\n\nlet plan;\n\ntry {\n  // Rimuove eventuali blocchi markdown\n  let cleaned = raw\n    .replace(/^```json\\s*/i, '')\n    .replace(/^```\\s*/i, '')\n    .replace(/\\s*```$/i, '')\n    .trim();\n\n  // Estrae il JSON anche se il modello aggiunge testo prima o dopo\n  const start = cleaned.indexOf('{');\n  const end = cleaned.lastIndexOf('}');\n\n  if (start === -1 || end === -1) {\n    throw new Error(\n      'No JSON object found. Content received: ' +\n      cleaned.substring(0, 500)\n    );\n  }\n\n  cleaned = cleaned.slice(start, end + 1);\n\n  plan = JSON.parse(cleaned);\n\n} catch (e) {\n  throw new Error(\n    'Planner returned invalid JSON.\\n\\n' +\n    'Original content:\\n' +\n    raw.substring(0, 1000) +\n    '\\n\\nParse error:\\n' +\n    e.message\n  );\n}\n\n// Validazione struttura piano\nif (!plan.steps || !Array.isArray(plan.steps)) {\n  throw new Error('Plan missing steps array');\n}\n\nconst allowedTypes = [\n  'search_papers',\n  'screen_titles',\n  'extract_outcomes',\n  'summarize'\n];\n\nconst allowedSources = [\n  'PubMed',\n  'EuropePMC',\n  'OpenAlex',\n  'Crossref',\n  'merged_search_results',\n  'screened_results',\n  'extracted_data'\n];\n\nfor (const step of plan.steps) {\n  if (!allowedTypes.includes(step.type)) {\n    throw new Error(\n      `Invalid step type: ${step.type}`\n    );\n  }\n\n  if (\n    step.source &&\n    !allowedSources.includes(step.source)\n  ) {\n    throw new Error(\n      `Invalid source: ${step.source}`\n    );\n  }\n}\n\n// Verifica presenza dei 4 motori di ricerca\nconst searchSteps = plan.steps.filter(\n  s => s.type === 'search_papers'\n);\n\nif (searchSteps.length !== 4) {\n  throw new Error(\n    `Plan must include exactly 4 search_papers steps. Found ${searchSteps.length}`\n  );\n}\n\nfor (const source of [\n  'PubMed',\n  'EuropePMC',\n  'OpenAlex',\n  'Crossref'\n]) {\n  if (\n    !searchSteps.some(\n      s => s.source === source\n    )\n  ) {\n    throw new Error(\n      `Missing search source: ${source}`\n    );\n  }\n}\n\n// Verifica step obbligatori\nif (\n  !plan.steps.some(\n    s => s.type === 'screen_titles'\n  )\n) {\n  throw new Error(\n    'Plan must include a screen_titles step'\n  );\n}\n\nif (\n  !plan.steps.some(\n    s => s.type === 'extract_outcomes'\n  )\n) {\n  throw new Error(\n    'Plan must include an extract_outcomes step'\n  );\n}\n\nif (\n  !plan.steps.some(\n    s => s.type === 'summarize'\n  )\n) {\n  throw new Error(\n    'Plan must include a summarize step'\n  );\n}\n\n// Recupera sessionId in modo sicuro\nlet sessionId = null;\n\ntry {\n  sessionId = $('Pre-process – Sanitize Input')\n    .first()\n    .json\n    .sessionId;\n} catch (e) {\n  // ignora se il nodo non esiste\n}\n\nreturn [\n  {\n    json: {\n      plan,\n      sessionId\n    }\n  }\n];"
      },
      "id": "8ecf3c2c-2d81-4dd5-86c0-8762b15e391b",
      "name": "Parse & Validate Plan",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -704,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const plan = $input.first().json.plan;\nconst sessionId = $input.first().json.sessionId;\nreturn plan.steps.map(step => ({ json: { step, plan, sessionId } }));"
      },
      "id": "3a378508-33ea-4ddf-a2ea-26261c3963d1",
      "name": "Split Steps",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -464,
        240
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ ($json.step.type || '') + '|' + ($json.step.source || '') }}",
                    "rightValue": "search_papers|PubMed",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "4f5e3db5-21d1-44f2-a864-b584aa497659"
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ ($json.step.type || '') + '|' + ($json.step.source || '') }}",
                    "rightValue": "search_papers|EuropePMC",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "07bff6f9-915c-437e-a420-5ffa099de3a4"
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ ($json.step.type || '') + '|' + ($json.step.source || '') }}",
                    "rightValue": "search_papers|OpenAlex",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "eb1751f1-c587-44c7-8413-3c92ea2b7dc4"
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ ($json.step.type || '') + '|' + ($json.step.source || '') }}",
                    "rightValue": "search_papers|Crossref",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "b78b9b1d-9420-4221-be57-54b7ccd2fbe0"
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "e9784127-6f11-4902-8b9a-2ef57b425f83",
      "name": "Step Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        -192,
        208
      ]
    },
    {
      "parameters": {
        "url": "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "db",
              "value": "pubmed"
            },
            {
              "name": "term",
              "value": "={{ $json.step.query }}"
            },
            {
              "name": "retmax",
              "value": "20"
            },
            {
              "name": "retmode",
              "value": "json"
            },
            {
              "name": "usehistory",
              "value": "y"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "2fd00fe3-49f4-43fa-836e-8b1cacbfec44",
      "name": "PubMed Search",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        208,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const step = $('Split Steps').all().map(x => x.json.step).find(s => s.type === 'search_papers' && s.source === 'PubMed') || {};\nconst idList = $input.first().json.esearchresult?.idlist || [];\nconst items = idList.map((pmid, index) => ({\n  source: 'PubMed',\n  id: 'PMID:' + pmid,\n  title: '',\n  year: '',\n  doi: '',\n  authors: [],\n  journal: '',\n  abstract: '',\n  url: 'https://pubmed.ncbi.nlm.nih.gov/' + pmid + '/',\n  rank: index + 1\n}));\nreturn [{ json: { source: 'PubMed', query: step.query || '', count: items.length, items } }];"
      },
      "id": "ce636ffc-f7aa-484d-b072-da6fe209ab7d",
      "name": "Format PubMed Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        448,
        0
      ]
    },
    {
      "parameters": {
        "url": "https://www.ebi.ac.uk/europepmc/webservices/rest/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ $json.step.query }}"
            },
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "pageSize",
              "value": "20"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "a694e410-5c1c-4d4b-88f6-f9418879a727",
      "name": "EuropePMC Search",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        208,
        160
      ]
    },
    {
      "parameters": {
        "jsCode": "const step = $('Split Steps').all().map(x => x.json.step).find(s => s.type === 'search_papers' && s.source === 'EuropePMC') || {};\nconst records = $input.first().json.resultList?.result || [];\nconst items = records.map((r, index) => ({\n  source: 'EuropePMC',\n  id: r.id ? 'EPMC:' + r.id : 'EPMC:' + String(index + 1),\n  title: r.title || '',\n  year: r.pubYear || '',\n  doi: r.doi || '',\n  authors: (r.authorString || '').split(',').map(s => s.trim()).filter(Boolean),\n  journal: r.journalTitle || '',\n  abstract: r.abstractText || '',\n  url: (r.fullTextUrlList?.fullTextUrl?.[0]?.url) || (r.pmid ? 'https://europepmc.org/article/MED/' + r.pmid : ''),\n  rank: index + 1\n}));\nreturn [{ json: { source: 'EuropePMC', query: step.query || '', count: items.length, items } }];"
      },
      "id": "80ae61cf-f05a-46ad-9d03-a01bbf27eb01",
      "name": "Format EuropePMC Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        448,
        160
      ]
    },
    {
      "parameters": {
        "url": "https://api.openalex.org/works",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "search",
              "value": "={{ $json.step.query }}"
            },
            {
              "name": "per-page",
              "value": "20"
            },
            {
              "name": "mailto",
              "value": "ops@example.com"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "fe0ba8b9-fa84-496c-861b-141c9d760f83",
      "name": "OpenAlex Search",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        208,
        320
      ]
    },
    {
      "parameters": {
        "jsCode": "const step = $('Split Steps').all().map(x => x.json.step).find(s => s.type === 'search_papers' && s.source === 'OpenAlex') || {};\nconst works = $input.first().json.results || [];\nconst items = works.map((w, index) => ({\n  source: 'OpenAlex',\n  id: w.id || 'OA:' + String(index + 1),\n  title: w.display_name || '',\n  year: w.publication_year || '',\n  doi: w.doi ? w.doi.replace('https://doi.org/', '') : '',\n  authors: (w.authorships || []).map(a => a.author?.display_name).filter(Boolean),\n  journal: w.primary_location?.source?.display_name || '',\n  abstract: '',\n  url: w.primary_location?.landing_page_url || w.id || '',\n  rank: index + 1\n}));\nreturn [{ json: { source: 'OpenAlex', query: step.query || '', count: items.length, items } }];"
      },
      "id": "473010c4-4d6c-4c5c-97fe-8090b7beadb4",
      "name": "Format OpenAlex Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        448,
        320
      ]
    },
    {
      "parameters": {
        "url": "https://api.crossref.org/works",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ $json.step.query }}"
            },
            {
              "name": "rows",
              "value": "20"
            },
            {
              "name": "mailto",
              "value": "ops@example.com"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "59bf8c31-ed97-4c8e-86d5-6e9e6277dd55",
      "name": "Crossref Search",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        208,
        480
      ]
    },
    {
      "parameters": {
        "jsCode": "const step = $('Split Steps').all().map(x => x.json.step).find(s => s.type === 'search_papers' && s.source === 'Crossref') || {};\nconst works = $input.first().json.message?.items || [];\nconst items = works.map((w, index) => ({\n  source: 'Crossref',\n  id: w.DOI ? 'DOI:' + w.DOI : 'CR:' + String(index + 1),\n  title: Array.isArray(w.title) ? (w.title[0] || '') : (w.title || ''),\n  year: w.issued?.['date-parts']?.[0]?.[0] || '',\n  doi: w.DOI || '',\n  authors: (w.author || []).map(a => [a.given, a.family].filter(Boolean).join(' ')).filter(Boolean),\n  journal: Array.isArray(w['container-title']) ? (w['container-title'][0] || '') : '',\n  abstract: '',\n  url: w.URL || '',\n  rank: index + 1\n}));\nreturn [{ json: { source: 'Crossref', query: step.query || '', count: items.length, items } }];"
      },
      "id": "d6876d05-c062-47ac-b0c1-f36acf5186a3",
      "name": "Format Crossref Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        448,
        480
      ]
    },
    {
      "parameters": {
        "numberInputs": 4
      },
      "id": "328f517c-ec63-4905-aa67-785e0d8b8c3b",
      "name": "Merge Search Results",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        688,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const merged = $input.all().flatMap(i => i.json.items || []);\nconst seen = new Map();\nfor (const item of merged) {\n  const key = [item.doi || '', item.id || '', item.title || ''].join('|').toLowerCase().trim();\n  if (!key || key === '||') continue;\n  if (!seen.has(key)) seen.set(key, item);\n}\nconst items = Array.from(seen.values());\nreturn [{\n  json: {\n    source: 'merged_search_results',\n    totalBeforeDedup: merged.length,\n    totalAfterDedup: items.length,\n    items\n  }\n}];"
      },
      "id": "c4442ab5-d63e-4711-8d86-ca65101dc7db",
      "name": "Normalize & Deduplicate Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        928,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.choices?.[0]?.message?.content || '[]';\nlet kept = [];\ntry {\n  const cleaned = raw.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n  kept = JSON.parse(cleaned);\n} catch (e) {\n  kept = [];\n}\nreturn [{\n  json: {\n    stepId: 'screen_titles',\n    stepType: 'screen_titles',\n    kept,\n    keptCount: kept.length,\n    totalScreened: $('Normalize & Deduplicate Results').first().json.totalAfterDedup || 0\n  }\n}];"
      },
      "id": "ffa4c03a-9cf6-45fe-9132-c111346d1359",
      "name": "Format Screening Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1376,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.choices?.[0]?.message?.content || '[]';\nlet extractions = [];\ntry {\n  const cleaned = raw.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n  extractions = JSON.parse(cleaned);\n} catch (e) {\n  extractions = [];\n}\nreturn [{\n  json: {\n    stepId: 'extract_outcomes',\n    stepType: 'extract_outcomes',\n    extractions,\n    extractionCount: extractions.length\n  }\n}];"
      },
      "id": "dce1e0db-2d19-44f1-ad7e-16e9d5cef913",
      "name": "Format Extraction Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1856,
        240
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.choices?.[0]?.message?.content || '{}';\nlet summary;\ntry {\n  const cleaned = raw.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n  summary = JSON.parse(cleaned);\n} catch (e) {\n  summary = {\n    narrative: raw,\n    evidence_level: 'Unknown',\n    disclaimer: 'This is an evidence summary for research purposes only. It does not constitute medical advice.',\n    table: []\n  };\n}\nconst preprocess = $('Pre-process – Sanitize Input').first().json;\nconst planData = $('Parse & Validate Plan').first().json.plan;\nconst merged = $('Normalize & Deduplicate Results').first().json;\nconst screened = $('Format Screening Results').first().json;\nconst extracted = $('Format Extraction Results').first().json;\nreturn [{\n  json: {\n    sessionId: preprocess.sessionId,\n    timestamp: new Date().toISOString(),\n    question: preprocess.question,\n    population: preprocess.population,\n    intervention: preprocess.intervention,\n    outcomes: preprocess.outcomes,\n    model: 'gemma4-31b',\n    inferenceProvider: 'Regolo.ai – EU infrastructure',\n    sources_used: ['PubMed', 'EuropePMC', 'OpenAlex', 'Crossref'],\n    summary: summary.narrative || '',\n    evidence_level: summary.evidence_level || 'Unknown',\n    disclaimer: summary.disclaimer || 'This is an evidence summary for research purposes only. It does not constitute medical advice.',\n    table: summary.table || [],\n    step_log: {\n      search: {\n        sources: ['PubMed', 'EuropePMC', 'OpenAlex', 'Crossref'],\n        pubmed_found: $('Format PubMed Results').first().json.count || 0,\n        europepmc_found: $('Format EuropePMC Results').first().json.count || 0,\n        openalex_found: $('Format OpenAlex Results').first().json.count || 0,\n        crossref_found: $('Format Crossref Results').first().json.count || 0,\n        papers_found_before_dedup: merged.totalBeforeDedup || 0,\n        papers_found_after_dedup: merged.totalAfterDedup || 0\n      },\n      screening: {\n        papers_kept: screened.keptCount || 0,\n        papers_total: screened.totalScreened || 0\n      },\n      extraction: {\n        studies_extracted: extracted.extractionCount || 0\n      }\n    },\n    records: {\n      merged_search_results: merged.items || [],\n      screened_results: screened.kept || [],\n      extracted_data: extracted.extractions || []\n    },\n    plan: planData\n  }\n}];"
      },
      "id": "46ef4cce-be8d-4aa8-85e8-49a0b17e0341",
      "name": "Assemble Final Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2304,
        240
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "fb2b45b5-9824-4edf-8e06-b8847afede33",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2592,
        240
      ]
    },
    {
      "parameters": {
        "model": "gemma4-31b",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are a medical evidence workflow planner. Output ONLY a valid JSON object with this schema: {\\\"question\\\": string, \\\"population\\\": string, \\\"intervention\\\": string, \\\"outcomes\\\": [string], \\\"search_query\\\": string, \\\"steps\\\": [{\\\"id\\\": string, \\\"type\\\": \\\"search_papers\\\" | \\\"screen_titles\\\" | \\\"extract_outcomes\\\" | \\\"summarize\\\", \\\"source\\\": string, \\\"query\\\": string, \\\"criteria\\\": [string], \\\"input\\\": string, \\\"format\\\": string}]}. Create exactly 7 steps in this order: 1) search_papers PubMed, 2) search_papers EuropePMC, 3) search_papers OpenAlex, 4) search_papers Crossref, 5) screen_titles source merged_search_results, 6) extract_outcomes source screened_results, 7) summarize source extracted_data. Use the same clinically precise query for the four search_papers steps. No markdown fences. No explanation."
            },
            {
              "content": "=Question: {{ $json.question }}\\nPopulation: {{ $json.population }}\\nIntervention: {{ $json.intervention }}\\nOutcomes: {{ $json.outcomes.join(', ') }}"
            }
          ]
        },
        "options": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-regoloai.regoloAi",
      "typeVersion": 1,
      "position": [
        -992,
        240
      ],
      "id": "e3d248b2-65a4-4069-88ac-9adb1b44649d",
      "name": "Create a chat completion",
      "credentials": {
        "regoloApi": {
          "id": "SmkZZ3I4yS3kBDls",
          "name": "Regolo AI account"
        }
      }
    },
    {
      "parameters": {
        "model": "qwen3.5-122b",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are a medical evidence screener. Output ONLY a valid JSON array. Keep only the records most relevant to the clinical question. For each kept record return: source, id, title, year, doi, journal, url, reason."
            },
            {
              "content": "=Question: {{ $('Pre-process – Sanitize Input').first().json.question }}\\nPopulation: {{ $('Pre-process – Sanitize Input').first().json.population }}\\nIntervention: {{ $('Pre-process – Sanitize Input').first().json.intervention }}\\nOutcomes: {{ $('Pre-process – Sanitize Input').first().json.outcomes.join(', ') }}\\nCriteria: {{ $('Parse & Validate Plan').first().json.plan.steps.find(s => s.type === 'screen_titles').criteria.join('; ') }}\\nCandidates: {{ JSON.stringify($json.items.slice(0, 50)) }}"
            }
          ]
        },
        "options": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-regoloai.regoloAi",
      "typeVersion": 1,
      "position": [
        1136,
        240
      ],
      "id": "5b1ac272-755b-48d4-92ef-d30934f9045c",
      "name": "Create a chat completion1",
      "credentials": {
        "regoloApi": {
          "id": "SmkZZ3I4yS3kBDls",
          "name": "Regolo AI account"
        }
      }
    },
    {
      "parameters": {
        "model": "qwen3.6-27b",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are a medical evidence extractor. Output ONLY a valid JSON array. For each study return: study_id, title, design, population, intervention, comparator, outcomes, efficacy_summary, safety_summary, limitations. Use only the provided records and do not invent numerical results if not present."
            },
            {
              "content": "=Question: {{ $('Pre-process – Sanitize Input').first().json.question }}\\nRequested outcomes: {{ $('Pre-process – Sanitize Input').first().json.outcomes.join(', ') }}\\nScreened records: {{ JSON.stringify($json.kept) }}"
            }
          ]
        },
        "options": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-regoloai.regoloAi",
      "typeVersion": 1,
      "position": [
        1600,
        240
      ],
      "id": "67d6e7d0-c008-41ee-bba3-59e82957a3e2",
      "name": "Create a chat completion2",
      "credentials": {
        "regoloApi": {
          "id": "SmkZZ3I4yS3kBDls",
          "name": "Regolo AI account"
        }
      }
    },
    {
      "parameters": {
        "model": "Llama-3.3-70B-Instruct",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are a medical evidence summarizer. Output ONLY a valid JSON object with keys narrative, evidence_level, disclaimer, table. The narrative must be concise and written for a clinician. Include uncertainty and do not make treatment recommendations beyond the evidence described."
            },
            {
              "content": "=Question: {{ $('Pre-process – Sanitize Input').first().json.question }}\\nPopulation: {{ $('Pre-process – Sanitize Input').first().json.population }}\\nIntervention: {{ $('Pre-process – Sanitize Input').first().json.intervention }}\\nOutcomes: {{ $('Pre-process – Sanitize Input').first().json.outcomes.join(', ') }}\\nExtractions: {{ JSON.stringify($json.extractions) }}"
            }
          ]
        },
        "options": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-regoloai.regoloAi",
      "typeVersion": 1,
      "position": [
        2064,
        240
      ],
      "id": "04ea4189-1af7-4dba-ac5a-03c41c20c6bb",
      "name": "Summarize evidence",
      "credentials": {
        "regoloApi": {
          "id": "SmkZZ3I4yS3kBDls",
          "name": "Regolo AI account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook – Receive Query": {
      "main": [
        [
          {
            "node": "Pre-process – Sanitize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pre-process – Sanitize Input": {
      "main": [
        [
          {
            "node": "Create a chat completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Validate Plan": {
      "main": [
        [
          {
            "node": "Split Steps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Steps": {
      "main": [
        [
          {
            "node": "Step Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Step Router": {
      "main": [
        [
          {
            "node": "PubMed Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "EuropePMC Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAlex Search",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Crossref Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PubMed Search": {
      "main": [
        [
          {
            "node": "Format PubMed Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "EuropePMC Search": {
      "main": [
        [
          {
            "node": "Format EuropePMC Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAlex Search": {
      "main": [
        [
          {
            "node": "Format OpenAlex Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crossref Search": {
      "main": [
        [
          {
            "node": "Format Crossref Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format PubMed Results": {
      "main": [
        [
          {
            "node": "Merge Search Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format EuropePMC Results": {
      "main": [
        [
          {
            "node": "Merge Search Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Format OpenAlex Results": {
      "main": [
        [
          {
            "node": "Merge Search Results",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Format Crossref Results": {
      "main": [
        [
          {
            "node": "Merge Search Results",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Merge Search Results": {
      "main": [
        [
          {
            "node": "Normalize & Deduplicate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize & Deduplicate Results": {
      "main": [
        [
          {
            "node": "Create a chat completion1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Screening Results": {
      "main": [
        [
          {
            "node": "Create a chat completion2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Extraction Results": {
      "main": [
        [
          {
            "node": "Summarize evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Final Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a chat completion": {
      "main": [
        [
          {
            "node": "Parse & Validate Plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a chat completion1": {
      "main": [
        [
          {
            "node": "Format Screening Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a chat completion2": {
      "main": [
        [
          {
            "node": "Format Extraction Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize evidence": {
      "main": [
        [
          {
            "node": "Assemble Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "44cc7383-2c38-49bb-bfd1-858452258168",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "71da8e164986ee5134ba4e40401fd2d00c0fb7ec24ca0ea136f688b37d12c5c2"
  },
  "id": "8Xg9tocnCevBtEP7",
  "tags": []
}Code language: JSON / JSON with Comments (json)
```

---

St**art your free 30-day trial at [regolo.ai](https://regolo.ai/) and deploy LLMs with complete privacy by design.**

👉 [Talk with our Engineers](https://regolo.ai/contacts/) or [Start your 30 days free →](https://regolo.ai/pricing)

---

- [Discord](https://discord.gg/ZzZvuR2y) - Share your thoughts
- [GitHub Repo](https://github.com/regolo-ai/) - Code of blog articles ready to start
- Follow Us on X [@regolo\_ai](https://x.com/regolo_ai)
- Open discussion on our [Subreddit Community](https://www.reddit.com/r/regolo_ai/)

---

*Built with ❤️ by the Regolo team. Questions? [regolo.ai/contact](https://regolo.ai/contact)* or chat with us on [Discord](https://discord.gg/ZzZvuR2y)