{
  "updatedAt": "2026-05-06T23:25:33.293Z",
  "createdAt": "2026-05-06T23:25:29.005Z",
  "id": "gYyOggC4J98keLbj",
  "name": "RAG Pipeline — Ingest & Query",
  "description": "Disabled 2026-05-24 after PD Ollama/LiteLLM hammering destabilized PlausibleDeniability. Needs redesign before re-enable.",
  "active": false,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "rag/ingest",
        "responseMode": "lastNode",
        "responseData": "allEntries",
        "options": {}
      },
      "id": "31ce8413-d15c-490f-92d1-956ef214e9a3",
      "name": "Ingest Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "411ad4e3-eb9f-49e2-97c4-2f30e01dd609"
    },
    {
      "parameters": {
        "jsCode": "// Split incoming text into chunks for embedding\nconst input = $input.first().json.body || $input.first().json;\nconst text = input.text || input.content || '';\nconst source = input.source || 'manual';\nconst metadata = input.metadata || {};\n\nconst CHUNK_SIZE = 500;\nconst OVERLAP = 50;\n\nconst chunks = [];\nconst sentences = text.split(/(?<=[.!?])\\s+/);\nlet current = '';\n\nfor (const sentence of sentences) {\n  if ((current + ' ' + sentence).length > CHUNK_SIZE && current.length > 0) {\n    chunks.push(current.trim());\n    // Keep overlap\n    const words = current.split(' ');\n    current = words.slice(-OVERLAP).join(' ') + ' ' + sentence;\n  } else {\n    current = current ? current + ' ' + sentence : sentence;\n  }\n}\nif (current.trim()) chunks.push(current.trim());\n\nreturn chunks.map((chunk, i) => ({\n  json: {\n    chunk,\n    chunkIndex: i,\n    totalChunks: chunks.length,\n    source,\n    metadata\n  }\n}));"
      },
      "id": "5d5aa0b1-c84b-48f6-9b5e-618cb05e5e61",
      "name": "Chunk Text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://10.5.30.6:11434/v1/embeddings",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"nomic-embed-text\",\n  \"input\": \"{{ $json.chunk }}\"\n}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 5,
              "batchInterval": 500
            }
          }
        }
      },
      "id": "89c692c6-b987-409c-b5c1-3533097d06e3",
      "name": "Get Embedding",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        448,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const embeddingResponse = $input.first().json;\nconst chunkData = $('Chunk Text').item;\nconst embedding = embeddingResponse.data?.[0]?.embedding || [];\n\nconst pointId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); });\n\nreturn [{\n  json: {\n    id: pointId,\n    vector: embedding,\n    payload: {\n      text: chunkData.json.chunk,\n      source: chunkData.json.source,\n      chunkIndex: chunkData.json.chunkIndex,\n      totalChunks: chunkData.json.totalChunks,\n      metadata: chunkData.json.metadata,\n      ingested_at: new Date().toISOString()\n    }\n  }\n}];"
      },
      "id": "fec59066-958f-4b96-8224-6f4e48ad5abb",
      "name": "Prepare Qdrant Point",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        0
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "http://10.5.30.6:6333/collections/knowledge_base/points",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"points\": [\n    {\n      \"id\": \"{{ $json.id }}\",\n      \"vector\": {{ JSON.stringify($json.vector) }},\n      \"payload\": {{ JSON.stringify($json.payload) }}\n    }\n  ]\n}",
        "options": {}
      },
      "id": "9404c8fe-46ae-4d8a-bf35-35ecba4939c0",
      "name": "Upsert to Qdrant",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "rag/query",
        "responseMode": "lastNode",
        "responseData": "allEntries",
        "options": {}
      },
      "id": "1a5c921c-0351-4a5e-a3e4-0aab71313af1",
      "name": "Query Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        304
      ],
      "webhookId": "aad4fe04-74b0-4136-80e9-380a7e4db1c2"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://10.5.30.6:11434/v1/embeddings",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"nomic-embed-text\",\n  \"input\": \"{{ $json.body.query || $json.query }}\"\n}",
        "options": {}
      },
      "id": "0a294ba5-a890-4dfa-8ae7-ba25cd0a4534",
      "name": "Embed Query",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        224,
        304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://10.5.30.6:6333/collections/knowledge_base/points/search",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"vector\": {{ JSON.stringify($json.data[0].embedding) }},\n  \"limit\": 5,\n  \"with_payload\": true\n}",
        "options": {}
      },
      "id": "2cb68891-c57e-4006-b3ac-c290aec28220",
      "name": "Search Qdrant",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        448,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "const searchResults = $input.first().json.result || [];\nconst query = $('Query Webhook').first().json.body?.query || $('Query Webhook').first().json.query || '';\n\nconst context = searchResults.map((r, i) => \n  `[${i+1}] (score: ${r.score.toFixed(3)}) ${r.payload.text}`\n).join('\\n\\n');\n\nreturn [{\n  json: {\n    query,\n    context,\n    sources: searchResults.map(r => ({\n      source: r.payload.source,\n      score: r.score,\n      text_preview: r.payload.text.substring(0, 100) + '...'\n    }))\n  }\n}];"
      },
      "id": "d32ee814-81d0-4ea6-8f28-59632d09b1b9",
      "name": "Build Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://10.5.30.6:4000/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "={{'Bearer ' + $env.LITELLM_API_KEY}}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"medium\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a helpful knowledge base assistant. Answer the user's question using ONLY the provided context. If the context doesn't contain enough information, say so. Cite which source numbers you used.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Context:\\n{{ $json.context }}\\n\\nQuestion: {{ $json.query }}\"\n    }\n  ],\n  \"max_tokens\": 500,\n  \"temperature\": 0.2\n}",
        "options": {}
      },
      "id": "e1360454-8457-40ce-b179-670c182da59e",
      "name": "LiteLLM Answer",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst contextData = $('Build Context').first().json;\n\nreturn [{\n  json: {\n    answer: response.choices?.[0]?.message?.content || 'No answer generated',\n    sources: contextData.sources,\n    query: contextData.query\n  }\n}];"
      },
      "id": "fe16d3a1-0d11-442f-b941-56fb74804601",
      "name": "Format Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1104,
        304
      ]
    }
  ],
  "connections": {
    "Ingest Webhook": {
      "main": [
        [
          {
            "node": "Chunk Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chunk Text": {
      "main": [
        [
          {
            "node": "Get Embedding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Embedding": {
      "main": [
        [
          {
            "node": "Prepare Qdrant Point",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Qdrant Point": {
      "main": [
        [
          {
            "node": "Upsert to Qdrant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Webhook": {
      "main": [
        [
          {
            "node": "Embed Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embed Query": {
      "main": [
        [
          {
            "node": "Search Qdrant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Qdrant": {
      "main": [
        [
          {
            "node": "Build Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Context": {
      "main": [
        [
          {
            "node": "LiteLLM Answer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LiteLLM Answer": {
      "main": [
        [
          {
            "node": "Format Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "staticData": null,
  "meta": null,
  "pinData": {},
  "versionId": "832248e5-106e-4fc4-8ebd-a5d0034e2b14",
  "activeVersionId": "832248e5-106e-4fc4-8ebd-a5d0034e2b14",
  "versionCounter": 14,
  "triggerCount": 2,
  "tags": [
    {
      "updatedAt": "2026-05-06T22:38:51.678Z",
      "createdAt": "2026-05-06T22:38:51.678Z",
      "id": "S9FxzVfHLsHmyzyt",
      "name": "ai"
    },
    {
      "updatedAt": "2026-05-06T22:39:33.284Z",
      "createdAt": "2026-05-06T22:39:33.284Z",
      "id": "7uuEyQrIlcuruZBk",
      "name": "rag"
    }
  ],
  "shared": [
    {
      "updatedAt": "2026-05-06T23:25:29.005Z",
      "createdAt": "2026-05-06T23:25:29.005Z",
      "role": "workflow:owner",
      "workflowId": "gYyOggC4J98keLbj",
      "projectId": "dxCRnBdX5uJizCGa",
      "project": {
        "updatedAt": "2026-05-06T01:10:37.484Z",
        "createdAt": "2026-05-06T01:08:07.721Z",
        "id": "dxCRnBdX5uJizCGa",
        "name": "Wilfred Fizzlepoof <admin@paccoco.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "47b2227f-2fc2-4a08-bd35-ea157b92df0d"
      }
    }
  ],
  "versionMetadata": {
    "name": "Version 832248e5",
    "description": ""
  }
}