{
  "id": "JvH5loo41aV3kDJS",
  "name": "CV Anonymization",
  "tags": [
    {
      "id": "7KpQ60aaqOmOSCWd",
      "name": "production",
      "createdAt": "2025-12-15T04:58:39.944Z",
      "updatedAt": "2025-12-15T04:58:39.944Z"
    },
    {
      "id": "e1Rw4nSdxQtYN5wl",
      "name": "recruitment",
      "createdAt": "2025-12-15T04:58:39.971Z",
      "updatedAt": "2025-12-15T04:58:39.971Z"
    },
    {
      "id": "8WEb52fbYUF71JRh",
      "name": "cv-anonymization",
      "createdAt": "2025-12-15T04:58:39.909Z",
      "updatedAt": "2025-12-15T04:58:39.909Z"
    }
  ],
  "nodes": [
    {
      "id": "25f4f988-2776-4afd-92e5-29cc85c94a67",
      "name": "Split PDF Pages1",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "POST: http://stirling-pdf:8080...",
      "position": [
        3568,
        432
      ],
      "parameters": {
        "url": "http://stirlingpdf:8080/api/v1/general/split-pages",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "fileInput",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            },
            {
              "name": "pages",
              "value": "all"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "accept",
              "value": "*/*"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a27e6703-b38a-4aa2-97c0-9b0cf50271be",
      "name": "Compression",
      "type": "n8n-nodes-base.compression",
      "position": [
        3792,
        432
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9c5bc545-a718-4bcd-bbc4-c1563dfa616e",
      "name": "PDF \u2192 Text1",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "POST: http://stirling-pdf:8080...",
      "position": [
        4240,
        432
      ],
      "parameters": {
        "url": "http://stirlingpdf:8080/api/v1/convert/pdf/text",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "fileInput",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "file_0"
            },
            {
              "name": "outputFormat",
              "value": "txt"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "accept",
              "value": "*/*"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "95f97f37-7aa0-425f-b5b0-aa35789f1a66",
      "name": "Aggregate1",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        4688,
        432
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "pages"
      },
      "typeVersion": 1
    },
    {
      "id": "6f4b7c90-af7c-4ea9-857e-bac9bbff534d",
      "name": "Basic LLM Chain",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        4944,
        432
      ],
      "parameters": {
        "text": "={{ $json.pages.map(p => p.text).join('\\n\\n') }}",
        "messages": {
          "messageValues": [
            {
              "message": "=You are an expert CV anonymization system.\n\nCRITICAL TASK: Transform the CV text provided in the user message into a clean, anonymized, professionally formatted HTML document.\n\nLANGUAGE HANDLING \u2014 ABSOLUTE RULE:\n- The input CV may be written in ANY language.\n- The anonymized CV MUST remain FULLY in the ORIGINAL language of the input.\n\nANONYMIZATION RULES \u2014 FOLLOW EXACTLY:\n\n1. Replace ALL personally identifiable information with language-matching placeholders:\n   - Full names \u2192 localized equivalent of \u201c[CANDIDATE NAME]\u201d\n   - Email addresses \u2192 localized equivalent of \u201c[EMAIL REDACTED]\u201d\n   - Phone numbers \u2192 localized equivalent of \u201c[PHONE REDACTED]\u201d\n   - Full addresses \u2192 keep only country/region (localized)\n   - Specific city names \u2192 remove or replace with localized \u201c[CITY]\u201d\n   - Company names \u2192 localized descriptive replacements\n   - University / school names \u2192 localized \u201c[TOP UNIVERSITY]\u201d equivalent\n   - Client names \u2192 localized \u201c[CLIENT NAME]\u201d equivalent\n   - Any other personal names \u2192 localized \u201c[NAME]\u201d\n\n   IMPORTANT:\n   - Do NOT translate the CV.\n   - Use natural, professional wording in the CV\u2019s language.\n   - Do NOT translate Placeholders.\n\n2. PRESERVE exactly as written from the input CV:\n   - Job titles\n   - Skills and technologies\n   - Years and dates\n   - Achievements and metrics\n   - Certifications (remove issuer name only)\n   - Work experience descriptions word-for-word\n   - Testimonials (names anonymized only)\n   - Original language, grammar, and writing style\n\nHTML STRUCTURE REQUIREMENTS:\n- Complete HTML5 document with <!DOCTYPE html>\n- Embedded CSS in <style> tag\n- Use Arial or sans-serif font family\n- Professional color scheme\n- Centered H1 using localized candidate name placeholder\n- Proper RTL support if the language requires it\n- Maximum width: 800px, centered layout\n\nOUTPUT FORMAT \u2014 CRITICAL:\nReturn ONLY a valid JSON object:\n{\"html\": \"complete HTML document as a single escaped string\"}\n\nFORMATTING RULES:\n- Escape all double quotes\n- Single continuous JSON string\n- NO markdown\n- NO comments\n- NO extra text\n\nVERIFICATION BEFORE RESPONDING:\n1. Language of output matches input CV exactly\n2. No English text appears unless the CV itself is English\n3. Placeholders are professional\n4. Skills, titles, and metrics are preserved\n5. Output is valid JSON with only the \"html\" key\n"
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.4
    },
    {
      "id": "3730d74c-0831-4091-8825-cf67ea2dff3c",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        4912,
        656
      ],
      "parameters": {
        "model": "gpt-4-turbo",
        "options": {
          "maxTokens": 3000,
          "temperature": 0.3
        }
      },
      "typeVersion": 1
    },
    {
      "id": "177bee50-4cb7-409a-8fc7-757b6568ac09",
      "name": "Code1",
      "type": "n8n-nodes-base.code",
      "position": [
        5504,
        432
      ],
      "parameters": {
        "jsCode": "// Extract HTML from Structured Output Parser\nconst item = $input.first().json;\n\n// The HTML is at item.output.html\nlet htmlContent;\n\nif (item.output && item.output.html) {\n  htmlContent = item.output.html;\n} else {\n  throw new Error('No HTML found. Structure: ' + JSON.stringify(Object.keys(item)));\n}\n\n// Unescape the HTML (it comes with escaped quotes)\nhtmlContent = htmlContent\n  .replace(/\\\\\"/g, '\"')\n  .replace(/\\\\n/g, '\\n')\n  .trim();\n\n// Create binary file\nconst htmlBuffer = Buffer.from(htmlContent, 'utf-8');\nconst base64Data = htmlBuffer.toString('base64');\n\nreturn {\n  json: {\n    timestamp: new Date().toISOString()\n  },\n  binary: {\n    data: {\n      data: base64Data,\n      mimeType: 'text/html',\n      fileName: 'anonymized_cv.html',\n      fileExtension: 'html'\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6f9d3d48-f811-449c-b466-eac7072af16d",
      "name": "Any File \u2192 PDF2",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5728,
        432
      ],
      "parameters": {
        "url": "http://root-stirlingpdf-1:8080/api/v1/convert/file/pdf",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "fileInput",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            },
            {
              "name": "outputFormat",
              "value": "pdf"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "accept",
              "value": "*/*"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": false
    },
    {
      "id": "359ce35d-38f6-4b87-827b-59d494241e2e",
      "name": "Auto-fixing Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserAutofixing",
      "position": [
        5040,
        656
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "ae060f63-f97f-43d1-98e6-bf22328d445e",
      "name": "OpenAI Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        5008,
        864
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "a5e6b875-91b4-4719-b77b-db35fdcb77b3",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        5136,
        864
      ],
      "parameters": {
        "autoFix": true,
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"html\": {\n      \"type\": \"string\",\n      \"description\": \"Complete anonymized HTML CV document\"\n    }\n  },\n  \"required\": [\"html\"],\n  \"additionalProperties\": false\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e97dc72f-684b-44ad-b4fb-c8987fed1182",
      "name": "Any file to pdf",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2896,
        352
      ],
      "parameters": {
        "url": "http://stirlingpdf:8080/api/v1/convert/file/pdf",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "fileInput",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "UploadCV0"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "accept",
              "value": "*/*"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "77a52c63-37a1-4d08-856f-5b5fe68e7534",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        4016,
        432
      ],
      "parameters": {
        "jsCode": "// Prepare PDF binary data for Stirling PDF text extraction\nconst items = $input.all();\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n  const item = items[i];\n  \n  // Ensure the binary data is properly formatted\n  if (item.binary && item.binary.file_0) {\n    results.push({\n      json: {\n        ...item.json,\n        pageNumber: i + 1,\n        fileName: item.binary.file_0.fileName || `page_${i + 1}.pdf`\n      },\n      binary: {\n        file_0: item.binary.file_0\n      }\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "e0664377-8365-4fa2-9ffd-83f20d0b5738",
      "name": "OpenAI Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        5216,
        1072
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "831640d7-25d2-411f-a858-b158a69f0921",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        4464,
        432
      ],
      "parameters": {
        "jsCode": "// Extract text from Stirling PDF response\nconst items = $input.all();\n\nreturn items.map(item => {\n  let extractedText = '';\n  \n  console.log('Processing item:', item.json.pageNumber);\n  console.log('Available keys:', Object.keys(item));\n  \n  // Method 1: Check if text is directly in json\n  if (item.json.text) {\n    extractedText = item.json.text;\n    console.log('Found text in json.text');\n  }\n  // Method 2: Check if text is in response field\n  else if (item.json.response) {\n    extractedText = item.json.response;\n    console.log('Found text in json.response');\n  }\n  // Method 3: Check if text is in output field  \n  else if (item.json.output) {\n    extractedText = item.json.output;\n    console.log('Found text in json.output');\n  }\n  // Method 4: Check binary data (if it exists)\n  else if (item.binary && item.binary.data) {\n    console.log('Checking binary data...');\n    console.log('Binary data type:', typeof item.binary.data);\n    \n    try {\n      // If binary.data is already a Buffer\n      if (Buffer.isBuffer(item.binary.data)) {\n        extractedText = item.binary.data.toString('utf-8');\n      }\n      // If binary.data is a base64 string\n      else if (typeof item.binary.data === 'string') {\n        extractedText = Buffer.from(item.binary.data, 'base64').toString('utf-8');\n      }\n      // If binary.data is an object with 'data' property\n      else if (item.binary.data.data) {\n        extractedText = Buffer.from(item.binary.data.data, 'base64').toString('utf-8');\n      }\n      console.log('Extracted from binary');\n    } catch (error) {\n      console.log('Binary conversion error:', error.message);\n    }\n  }\n  \n  console.log(`Text length: ${extractedText.length}`);\n  console.log(`First 200 chars: ${extractedText.substring(0, 200)}`);\n  \n  return {\n    json: {\n      text: extractedText,\n      pageNumber: item.json.pageNumber || 0,\n      filename: item.json.fileName || item.json.filename || 'unknown'\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "4f8ebb5e-ecd8-4253-9452-fe633cc3294e",
      "name": "Code in JavaScript2",
      "type": "n8n-nodes-base.code",
      "position": [
        3344,
        432
      ],
      "parameters": {
        "jsCode": "// Normalize binary field name to 'data'\nconst items = $input.all();\n\nreturn items.map(item => {\n  // If binary.data already exists, return as-is\n  if (item.binary && item.binary.data) {\n    return item;\n  }\n  \n  // If binary exists but not as 'data', rename it\n  if (item.binary) {\n    const binaryKeys = Object.keys(item.binary);\n    \n    if (binaryKeys.length > 0) {\n      const firstKey = binaryKeys[0]; // Get first binary field (file_0, UploadCV, etc.)\n      \n      return {\n        json: item.json,\n        binary: {\n          data: item.binary[firstKey] // Rename to 'data'\n        }\n      };\n    }\n  }\n  \n  // If no binary at all, return unchanged\n  return item;\n})"
      },
      "typeVersion": 2
    },
    {
      "id": "6e2844b7-b2f5-49bf-bc06-333b1fdd6001",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2448,
        432
      ],
      "parameters": {
        "path": "1faa18ed-f037-48a7-b1ff-ee9975836147",
        "options": {
          "binaryPropertyName": "UploadCV0"
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "bd8a0ba2-41e7-4b29-aebf-abef148c4074",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        3120,
        432
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "aebde939-af4d-4156-88b5-0501e7dc3f37",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        5952,
        432
      ],
      "parameters": {
        "options": {},
        "respondWith": "binary"
      },
      "typeVersion": 1.5
    },
    {
      "id": "36b06d55-ee9d-46ee-abe8-6d2cc4a860bc",
      "name": "If1",
      "type": "n8n-nodes-base.if",
      "position": [
        2672,
        432
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "782f6cc8-edc8-45c8-b722-b0953e4c7889",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{$binary.UploadCV0.mimeType}}",
              "rightValue": "application/pdf"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9f0a5bbf-0b01-4142-9505-47f76fbf5367",
      "name": "CV Anonymization Workflow",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        80
      ],
      "parameters": {
        "width": 420,
        "height": 972,
        "content": "# CV Anonymization Workflow\n\nAutomatically removes personally identifiable information from CVs while preserving skills, experience, and achievements.\n\n### How it works\n\n1. **File Upload**: Webhook receives CV files (PDF, DOCX, etc.) via POST request\n2. **PDF Conversion**: Non-PDF files are converted to PDF format using Stirling PDF\n3. **Page Splitting**: Multi-page PDFs are split into individual pages for accurate processing\n4. **Text Extraction**: Each page is converted to plain text\n5. **AI Anonymization**: GPT-4 removes all PII (names, emails, phones, addresses) while maintaining original language and preserving job titles, skills, dates, and achievements\n6. **HTML Generation**: Creates professionally formatted HTML output with structured layout\n7. **PDF Output**: Converts anonymized HTML back to PDF and returns via webhook\n\n### Setup steps\n\n1. Configure OpenAI credentials for all three OpenAI Chat Model nodes\n2. Add Stirling PDF authorization headers to HTTP Request nodes (Any file to pdf, Split PDF Pages1, PDF \u2192 Text1, Any File \u2192 PDF2)\n3. Test webhook endpoint with sample CV file\n\n### Customization\n\n- Adjust anonymization rules in Basic LLM Chain system message\n- Modify HTML styling requirements in the prompt\n- Change GPT model versions for cost/quality tradeoffs"
      },
      "typeVersion": 1
    },
    {
      "id": "7762a961-3900-416b-bed6-69b4e1c3b1db",
      "name": "File Input & Conversion",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        320
      ],
      "parameters": {
        "color": 4,
        "width": 560,
        "height": 240,
        "content": "## File Input & Conversion\nWebhook receives files \u2192 If checks format \u2192 converts to PDF if needed"
      },
      "typeVersion": 1
    },
    {
      "id": "f02af0a2-86e1-45f9-8672-aba1c36aadce",
      "name": "PDF Processing & Text Extraction",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3264,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 880,
        "height": 240,
        "content": "## PDF Processing & Text Extraction\nSplit pages \u2192 decompress \u2192 extract text from each page \u2192 aggregate all text"
      },
      "typeVersion": 1
    },
    {
      "id": "4f66f6eb-2916-496b-a139-a5553fe267f0",
      "name": "AI Anonymization1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4928,
        240
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 240,
        "content": "## AI Anonymization\nGPT-4 removes PII, preserves skills/experience, maintains original language, outputs structured HTML"
      },
      "typeVersion": 1
    },
    {
      "id": "e8c749e6-2635-41d4-b2d3-1373e15271eb",
      "name": "Output Generation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5504,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 560,
        "height": 240,
        "content": "## Output Generation\nExtract HTML \u2192 convert to PDF \u2192 return via webhook"
      },
      "typeVersion": 1
    },
    {
      "id": "6b371946-aa02-4c7f-a6c3-d0e96cd0be0b",
      "name": "Stirling PDF Auth Required",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4176,
        272
      ],
      "parameters": {
        "color": 3,
        "width": 280,
        "height": 180,
        "content": "\u26a0\ufe0f STIRLING PDF AUTH REQUIRED\n\nAll 4 Stirling PDF HTTP nodes need Authorization headers configured before this workflow will function."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "35b0400e-b8ac-4e25-8558-f633995c36e8",
  "connections": {
    "If1": {
      "main": [
        [
          {
            "node": "Any file to pdf",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Code1": {
      "main": [
        [
          {
            "node": "Any File \u2192 PDF2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Code in JavaScript2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate1": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compression": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF \u2192 Text1": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any file to pdf": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Code1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split PDF Pages1": {
      "main": [
        [
          {
            "node": "Compression",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any File \u2192 PDF2": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "PDF \u2192 Text1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Auto-fixing Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Aggregate1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript2": {
      "main": [
        [
          {
            "node": "Split PDF Pages1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Auto-fixing Output Parser",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Auto-fixing Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}