{
  "name": "11 - RAG Internal Knowledge Assistant",
  "nodes": [
    {
      "id": "node-1",
      "name": "Webhook - Slack Question",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [240, 300],
      "parameters": { "httpMethod": "POST", "path": "slack-knowledge-bot", "responseMode": "responseNode" }
    },
    {
      "id": "node-2",
      "name": "Code - Extract Question",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [480, 300],
      "parameters": {
        "jsCode": "const p = $input.first().json;\nif (p.type === 'url_verification') return [{ json: { challenge: p.challenge, is_challenge: true } }];\nconst event = p.event || p;\nconst question = (event.text||'').replace(/<@[A-Z0-9]+>/g,'').replace(/<[^>]+>/g,'').trim();\nreturn [{ json: { question, user: event.user||'unknown', channel: event.channel||'', thread_ts: event.thread_ts||event.ts||'', is_challenge: false, asked_at: new Date().toISOString() } }];"
      }
    },
    {
      "id": "node-3",
      "name": "IF - Not Challenge",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [720, 300],
      "parameters": {
        "conditions": {
          "conditions": [{ "id": "c1", "leftValue": "={{ $json.is_challenge }}", "rightValue": false, "operator": { "type": "boolean", "operation": "false" } }],
          "combinator": "and"
        }
      }
    },
    {
      "id": "node-4",
      "name": "OpenAI - Embed Question",
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1.8,
      "position": [960, 160],
      "credentials": { "openAiApi": { "id": "YOUR_OPENAI_CREDENTIAL_ID", "name": "OpenAI account" } },
      "parameters": {
        "resource": "text",
        "operation": "complete",
        "modelId": { "__rl": true, "value": "text-embedding-3-small", "mode": "list", "cachedResultName": "text-embedding-3-small" },
        "prompt": "={{ $json.question }}",
        "options": {}
      }
    },
    {
      "id": "node-5",
      "name": "HTTP - Query Pinecone",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1200, 160],
      "parameters": {
        "method": "POST",
        "url": "https://YOUR_INDEX-YOUR_PROJECT.svc.YOUR_ENV.pinecone.io/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Api-Key", "value": "YOUR_PINECONE_API_KEY" },
            { "name": "Content-Type", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"vector\": {{ JSON.stringify($input.first().json.embedding) }},\n  \"topK\": 5,\n  \"includeMetadata\": true,\n  \"namespace\": \"company-docs\"\n}"
      }
    },
    {
      "id": "node-6",
      "name": "Code - Build Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1440, 160],
      "parameters": {
        "jsCode": "const matches = $input.first().json.matches || [];\nconst question = $('Code - Extract Question').first().json.question;\nconst channel = $('Code - Extract Question').first().json.channel;\nconst thread_ts = $('Code - Extract Question').first().json.thread_ts;\nconst context = matches.map((m,i) => `[Source ${i+1}: ${m.metadata?.source||'Internal Doc'}]\\n${m.metadata?.text||''}`).join('\\n\\n---\\n\\n');\nreturn [{ json: { question, context, channel, thread_ts, match_count: matches.length, sources: matches.map(m=>m.metadata?.source||'Internal Doc') } }];"
      }
    },
    {
      "id": "node-7",
      "name": "OpenAI - Generate Answer",
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1.8,
      "position": [1680, 160],
      "credentials": { "openAiApi": { "id": "YOUR_OPENAI_CREDENTIAL_ID", "name": "OpenAI account" } },
      "parameters": {
        "resource": "chat",
        "operation": "complete",
        "modelId": { "__rl": true, "value": "gpt-4o", "mode": "list", "cachedResultName": "gpt-4o" },
        "messages": {
          "values": [
            { "role": "system", "content": "You are an internal knowledge assistant. Answer ONLY using the provided context. If the answer isn't in context, say so clearly. Always cite your source." },
            { "role": "user", "content": "=Context:\n{{ $json.context }}\n\nQuestion: {{ $json.question }}" }
          ]
        },
        "options": { "temperature": 0.2 }
      }
    },
    {
      "id": "node-8",
      "name": "Slack - Reply in Thread",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [1920, 160],
      "credentials": { "slackApi": { "id": "YOUR_SLACK_CREDENTIAL_ID", "name": "Slack account" } },
      "parameters": {
        "resource": "message",
        "operation": "send",
        "channel": { "__rl": true, "value": "={{ $('Code - Build Context').item.json.channel }}", "mode": "id" },
        "text": "=🧠 *Knowledge Assistant:*\n\n{{ $input.first().json.message.content }}\n\n_Searched {{ $('Code - Build Context').item.json.match_count }} documents | Sources: {{ $('Code - Build Context').item.json.sources.slice(0,3).join(', ') }}_",
        "otherOptions": { "thread_ts": "={{ $('Code - Build Context').item.json.thread_ts }}" }
      }
    },
    {
      "id": "node-9",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [2160, 160],
      "parameters": { "respondWith": "json", "responseBody": "{ \"status\": \"ok\" }" }
    }
  ],
  "connections": {
    "Webhook - Slack Question": { "main": [[{ "node": "Code - Extract Question", "type": "main", "index": 0 }]] },
    "Code - Extract Question": { "main": [[{ "node": "IF - Not Challenge", "type": "main", "index": 0 }]] },
    "IF - Not Challenge": {
      "main": [[{ "node": "OpenAI - Embed Question", "type": "main", "index": 0 }], []]
    },
    "OpenAI - Embed Question": { "main": [[{ "node": "HTTP - Query Pinecone", "type": "main", "index": 0 }]] },
    "HTTP - Query Pinecone": { "main": [[{ "node": "Code - Build Context", "type": "main", "index": 0 }]] },
    "Code - Build Context": { "main": [[{ "node": "OpenAI - Generate Answer", "type": "main", "index": 0 }]] },
    "OpenAI - Generate Answer": { "main": [[{ "node": "Slack - Reply in Thread", "type": "main", "index": 0 }]] },
    "Slack - Reply in Thread": { "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" },
  "active": false,
  "tags": [{ "name": "Portfolio" }, { "name": "AI" }, { "name": "RAG" }]
}
