{
  "name": "Chatbot Streaming + Manual RAG + Score Filter",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ragatha",
        "responseMode": "streaming",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -144,
        -80
      ],
      "id": "a3b661c2-5180-4241-851f-bd001ac4bbe8",
      "name": "Webhook"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2:embedContent",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googlePalmApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"models/gemini-embedding-2\",\n  \"content\": {\n    \"parts\": [{\"text\": {{ JSON.stringify($json.body.question) }}}]\n  }\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        80,
        -80
      ],
      "id": "fd006ca5-2e21-43b4-ac77-c869ca4e570a",
      "name": "Embed Question",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://qdrant:6333/collections/knowledge_base_epson/points/search",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"vector\": {{ JSON.stringify($json.embedding.values) }},\n  \"limit\": 10,\n  \"with_payload\": true\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        304,
        -80
      ],
      "id": "2b3a36f9-bb45-499b-ada8-5a7c3f7b9624",
      "name": "Search Qdrant"
    },
    {
      "parameters": {
        "jsCode": "const results = $input.item.json.result;\nconst question = $('Webhook').item.json.body.question;\n\nconst documents = results.map(r => ({\n  text: r.payload.content || r.payload.pageContent || '',\n  id: r.id,\n  score: r.score\n}));\n\nreturn [{\n  json: {\n    question,\n    documents\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        528,
        -80
      ],
      "id": "e1efa64c-0f69-40f2-9787-fa522e6a3a5e",
      "name": "Prepare Rerank Input"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.cohere.com/v2/rerank",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "cohereApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"rerank-v3.5\",\n  \"query\": {{ JSON.stringify($json.question) }},\n  \"documents\": {{ JSON.stringify($json.documents.map(d => d.text)) }},\n  \"top_n\": 10\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        736,
        -80
      ],
      "id": "9e43cb43-e3c0-44b3-91f3-81071e633516",
      "name": "Cohere Rerank",
      "credentials": {
        "cohereApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const rerankResults = $input.item.json.results;\nconst prepData = $('Prepare Rerank Input').item.json;\nconst question = prepData.question;\nconst originalDocs = prepData.documents;\n\nconst threshold = 0.6;\nconst filtered = rerankResults\n  .filter(r => r.relevance_score >= threshold)\n  .slice(0, 4);\n\nlet contextString = '';\nif (filtered.length === 0) {\n  contextString = 'Tidak ada dokumen yang relevan ditemukan.';\n} else {\n  contextString = filtered\n    .map((r, i) => `[Dokumen ${i+1}]\\n${originalDocs[r.index].text}`)\n    .join('\\n\\n');\n}\n\nreturn [{\n  json: {\n    question,\n    context: contextString,\n    found: filtered.length > 0,\n    user_id: $('Webhook').item.json.body.user_id\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        -80
      ],
      "id": "5090ce10-d5b6-4ad5-912b-afa48ab09f3f",
      "name": "Filter by Score > 0.6"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.question }}",
        "needsFallback": true,
        "options": {
          "systemMessage": "=ATURAN BAHASA (WAJIB):\n* Jangan menampilkan proses berpikir.\n* Deteksi bahasa dari pesan TERAKHIR pengguna.\n* Jika Bahasa Indonesia \u2192 jawab seluruhnya dalam Bahasa Indonesia.\n* Jika English \u2192 jawab seluruhnya dalam English.\n* Jika campuran \u2192 gunakan bahasa yang dominan.\n* Abaikan bahasa pada dokumen, konteks, atau percakapan sebelumnya.\n\nPERAN:\nKamu adalah Chatson, asisten helpdesk resmi Epson Indonesia.\n\nIKUTI LANGKAH BERIKUT SECARA BERURUTAN:\n\nTentukan apakah pertanyaan:\nsecara eksplisit menyebut Epson, ATAU\nmasih berkaitan dengan aktivitas penggunaan perangkat cetak/scan/proyektor yang dapat melibatkan produk Epson.\n\nContoh yang TETAP dianggap relevan:\n\ncara ngeprint di Microsoft Office\ncara print label\ncara scan PDF\ncara install driver printer\nhasil cetak buram\nprinter tidak terdeteksi\n\nContoh yang TIDAK relevan:\n\ncara membuat website\ncara memakai Excel\ncara edit video\nbelajar Python\n* printer\n* printing / mencetak\n* scanner\n* tinta\n* driver\n* instalasi perangkat\n* koneksi perangkat\n* proyektor\n* hasil cetak\n* error perangkat\n* troubleshooting umum\n* penggunaan fitur perangkat\n\nJika tidak relevan:\n\"Maaf, saya hanya dapat membantu pertanyaan seputar produk Epson.\"\nLalu berhenti.\n\nANGGAP MASIH RELEVAN jika berkaitan dengan:\n\nPertanyaan umum yang masih mungkin terkait penggunaan perangkat Epson TETAP dianggap relevan.\n\nJika BENAR-BENAR di luar domain tersebut, balas:\n\"Maaf, saya hanya dapat membantu pertanyaan seputar produk Epson.\"\n\nLalu berhenti.\n\n2. CEK KELENGKAPAN INFORMASI\n   Jika informasi pengguna belum cukup untuk memberi solusi spesifik\n   (contoh: tidak ada model perangkat, gejala tidak jelas, atau pertanyaan terlalu singkat),\n   balas:\n\n\"Mohon jelaskan kendala Anda lebih detail, misalnya tipe perangkat Epson yang digunakan, pesan error (jika ada), dan gejala yang muncul.\"\n\nLalu berhenti.\n\nJANGAN menolak hanya karena jumlah kata sedikit.\n\n3. GUNAKAN KNOWLEDGE BASE\n   Gunakan HANYA informasi dari konteks berikut.\n\nKONTEKS:\n{{ $json.context }}\n\nJika konteks kosong, tidak relevan, atau tidak cukup untuk menjawab:\n\n\"Informasi ini belum tersedia di knowledge base. Pertanyaan Anda akan diteruskan ke tim admin.\"\n\nLalu berhenti.\n\n4. MENJAWAB\n\n* Jawab berdasarkan konteks saja.\n* Jangan mengarang fakta baru.\n* Jangan menyebut bahwa kamu menggunakan konteks atau knowledge base.\n* Rangkum dengan jelas dan langsung ke solusi.\n* Jika konteks berisi langkah-langkah \u2192 tampilkan langkah secara berurutan.\n* Jika konteks tidak cukup \u2192 gunakan aturan langkah 3.\n"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        1184,
        -80
      ],
      "id": "3f75cbb2-4f23-4c8e-9d6c-84e8224616b0",
      "name": "AI Agent",
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Webhook').item.json.body.user_id }}",
        "contextWindowLength": 10
      },
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "typeVersion": 1.4,
      "position": [
        1344,
        192
      ],
      "id": "402b8d3e-eb8a-452f-b970-60be287abe00",
      "name": "Postgres Chat Memory",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "deepseek-v4-flash",
          "mode": "list",
          "cachedResultName": "deepseek-v4-flash"
        },
        "builtInTools": {},
        "options": {
          "temperature": 0.1
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        1040,
        192
      ],
      "id": "972341a5-2692-434c-af6c-b0a6167ef7ad",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        1200,
        192
      ],
      "id": "8bc6c793-1df3-4856-b5cd-f6f30445357b",
      "name": "OpenAI Chat Model1",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Embed Question",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embed Question": {
      "main": [
        [
          {
            "node": "Search Qdrant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Qdrant": {
      "main": [
        [
          {
            "node": "Prepare Rerank Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Rerank Input": {
      "main": [
        [
          {
            "node": "Cohere Rerank",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cohere Rerank": {
      "main": [
        [
          {
            "node": "Filter by Score > 0.6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter by Score > 0.6": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "timeSavedMode": "fixed",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "timeSavedPerExecution": 2
  },
  "versionId": "b86bc148-7f99-4f07-ad8c-6a05340b595c",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodeGroups": [],
  "id": "mhm69VzwFwXk6rzw",
  "tags": []
}