{
  "id": "dVHDSa7Hkd3WYyYb",
  "meta": {
    "templateId": "3577",
    "templateCredsSetupCompleted": true
  },
  "name": "Semantic Cache with a Redis Vector Store",
  "tags": [],
  "nodes": [
    {
      "id": "44708aff-a859-4d15-923f-b33f38000db7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1744,
        -400
      ],
      "parameters": {
        "width": 580,
        "height": 1216,
        "content": "## Try it out!\n\n### This workflow implements an intelligent semantic caching system that reduces LLM costs and improves response times by caching similar queries.\n\n### How It Works\n\n**Chat Trigger \u2192 Vector Search \u2192 Cache Decision \u2192 Response**\n\n1. **Receive Chat Message**: User sends a question through the chat interface\n2. **Check for Similar Prompts**: Searches Redis vector store for semantically similar previous queries using OpenAI embeddings\n3. **Analyze Results**: Evaluates similarity scores to determine if there's a cache hit (similar enough query exists)\n4. **Decision Point**:\n   - **Cache Hit** (similar query found): Returns the cached response immediately - fast and cost-effective!\n   - **Cache Miss** (no similar query): Routes to LLM Agent for a fresh response\n\n### Cache Miss Flow (New Query)\n5. **LLM Agent**: Generates a new response using GPT-4.1-mini with conversation memory\n6. **Store in Cache**: Saves the query-response pair as an embedded vector in Redis for future use\n7. **Respond to User**: Delivers the LLM-generated response\n\n### Key Components\n- **Redis Vector Store**: Stores embedded query-response pairs for semantic similarity search\n- **Redis Chat Memory**: Maintains conversation context across interactions\n- **Score Threshold**: Configurable similarity threshold (default: 0.3) to balance precision vs. recall\n- **OpenAI Embeddings**: Converts text to vectors for semantic comparison\n\n### Requirements\n* Redis database with initialized index\n* OpenAI [API key](https://platform.openai.com/settings/organization/api-keys) for LLM  generation \n_(alternatively) Replace the node with another provider_\n* Hugging Face [token](https://huggingface.co/settings/tokens) for generating embeddings \n_(alternatively) Replace the node with another provider_\n* Redis 8.x OSS or Redis Stack\n_(or any Redis deployment with the Redis Query Engine module installed)_"
      },
      "typeVersion": 1
    },
    {
      "id": "7c408b00-c33a-4953-94c6-007cde96a416",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1024,
        672
      ],
      "parameters": {
        "color": 5,
        "width": 736,
        "height": 136,
        "content": "### Tuning the Cache\nAdjust the `distanceThreshold` in the `Analyze results from store` node to control cache sensitivity:\n- **Lower threshold** (e.g., 0.2): More strict matching, fewer false positives, more LLM calls\n- **Higher threshold** (e.g., 0.5): More lenient matching, more cache hits, potential for less relevant responses"
      },
      "typeVersion": 1
    },
    {
      "id": "485fe302-f0d6-4889-812e-549244cf5ffe",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -112,
        496
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "08cc2596-a105-45ce-9645-158607387623",
      "name": "Redis Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryRedisChat",
      "position": [
        64,
        496
      ],
      "parameters": {},
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.5
    },
    {
      "id": "50407adf-e53c-4a6c-a32f-0bd2a62425b3",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -1088,
        288
      ],
      "parameters": {
        "options": {
          "responseMode": "responseNodes"
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "acc2d01d-4694-47fc-88f5-f79c2ca47d36",
      "name": "Analyze results from store",
      "type": "n8n-nodes-base.code",
      "position": [
        -528,
        288
      ],
      "parameters": {
        "jsCode": "// Modify this to tweak the ratio between false positives and false negatives\nconst distanceThreshold = 0.4; \n// Step 1. - find all documents that score below the threshold\nconst allMatches = $input.all().filter(item => item.json.score < distanceThreshold);\n// Step 2. - choose the one with the best score\nreturn allMatches.length > 1 ? allMatches.reduce((min, item) => item.json.score < min.json.score ? item : min) : allMatches;\n// AT THIS POINT ONLY ONE (OR ZERO) DOCUMENTS WOULD PASS\n// 1 DOCUMENT - document with highest score, above the score threshold (cache hit)\n// 0 DOCUMENTS - none of the documents are above the score threshold (cache miss)"
      },
      "executeOnce": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "9a49690b-fab4-4759-a633-9f7dfcef2be7",
      "name": "Check for similar prompts",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreRedis",
      "position": [
        -864,
        288
      ],
      "parameters": {
        "mode": "load",
        "prompt": "={{ $json.chatInput }}",
        "options": {},
        "redisIndex": {
          "__rl": true,
          "mode": "list",
          "value": "chat_cache"
        }
      },
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "70b60272-9e7f-4d06-9d3c-2da8d5c83ee5",
      "name": "Respond to Chat (from semantic cache)",
      "type": "@n8n/n8n-nodes-langchain.chat",
      "position": [
        -32,
        -32
      ],
      "parameters": {
        "message": "={{ $json.document.metadata.reply }}",
        "options": {},
        "waitUserReply": false
      },
      "typeVersion": 1,
      "alwaysOutputData": false
    },
    {
      "id": "00e5c3d4-eada-4caf-bf5e-a3b49d73196e",
      "name": "Respond to Chat (from LLM)",
      "type": "@n8n/n8n-nodes-langchain.chat",
      "position": [
        768,
        288
      ],
      "parameters": {
        "message": "={{ $json.metadata.reply }}",
        "options": {},
        "waitUserReply": false
      },
      "typeVersion": 1
    },
    {
      "id": "f7919340-de57-41ca-a2b6-d41528ed6c34",
      "name": "LLM Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -80,
        288
      ],
      "parameters": {
        "text": "={{ $('When chat message received').item.json.chatInput }}",
        "options": {
          "maxIterations": 10,
          "systemMessage": "You are a helpful assistant helping out with generic questions. Reply to the user with your best answer and don't invent much."
        },
        "promptType": "define"
      },
      "typeVersion": 1.8
    },
    {
      "id": "ad7dfa5e-4687-4cce-be6f-694779b7ff83",
      "name": "Store entry in cache",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreRedis",
      "position": [
        304,
        288
      ],
      "parameters": {
        "mode": "insert",
        "options": {
          "overwriteDocuments": true
        },
        "redisIndex": {
          "__rl": true,
          "mode": "list",
          "value": "chat_cache",
          "cachedResultName": "chat_cache"
        }
      },
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "e60f4189-4d99-4f95-98d5-2485a80509a0",
      "name": "Add response as metadata",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        400,
        496
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "reply",
                "value": "={{ $json.output }}"
              }
            ]
          }
        },
        "jsonData": "={{ $('When chat message received').item.json.chatInput }}",
        "jsonMode": "expressionData"
      },
      "typeVersion": 1
    },
    {
      "id": "2546c015-da8f-4481-bb61-87113f82e206",
      "name": "Recursive Character Text Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "position": [
        496,
        672
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "16887dcb-ccba-44a7-a64e-eb99db4256bc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 152,
        "content": "### Embedding model\nObviously using your own model to calculate the embeddings would not only increase performance but may also drastically reduce the costs.\n\nEven with the existing popular models though calling an embedding model is still much more cheaper than calling the chat model."
      },
      "typeVersion": 1
    },
    {
      "id": "d7cf0fd0-c042-45b5-8a24-29288c92cea4",
      "name": "Is this a cache hit?",
      "type": "n8n-nodes-base.if",
      "position": [
        -304,
        288
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c59204e1-85b1-4d40-89ca-04718c693b36",
              "operator": {
                "type": "object",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.document }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": false
    },
    {
      "id": "96820700-4963-492f-a91f-b039d6efba03",
      "name": "Embeddings HuggingFace Inference",
      "type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference",
      "position": [
        -864,
        480
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "huggingFaceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e5b313d3-ce98-4188-a593-890f5ae09b6b",
      "name": "Embeddings HuggingFace Inference1",
      "type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference",
      "position": [
        256,
        496
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "huggingFaceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "56925ea7-b773-486b-91af-c26837844e94",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1104,
        -400
      ],
      "parameters": {
        "color": 3,
        "width": 884,
        "height": 640,
        "content": "\n## Initialize the index\n_(manually creating the index can be done using the [langchain library](https://docs.langchain.com/oss/javascript/integrations/vectorstores/redis) )_\n\nIn case your Redis deployment does not\nhave a suitable index created you could run\nthe following manual flow to create it"
      },
      "typeVersion": 1
    },
    {
      "id": "b47e75f4-9b9e-43ca-b100-95a88728dc83",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -864,
        -128
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "e4595eb8-ad5e-47be-b718-3897fcd8d120",
      "name": "Initialize Redis store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreRedis",
      "position": [
        -512,
        -272
      ],
      "parameters": {
        "mode": "insert",
        "options": {
          "overwriteDocuments": false
        },
        "redisIndex": {
          "__rl": true,
          "mode": "list",
          "value": "chat_cache",
          "cachedResultName": "chat_cache"
        }
      },
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "7988808f-8d22-40c5-afe4-2e26d0d676f5",
      "name": "Process sample data",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        -528,
        -64
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "reply",
                "value": "={{ $('When clicking \u2018Execute workflow\u2019').item.json.answer }}"
              }
            ]
          }
        },
        "jsonData": "={{ $('When clicking \u2018Execute workflow\u2019').item.json.question }}",
        "jsonMode": "expressionData"
      },
      "typeVersion": 1
    },
    {
      "id": "66828a6a-00ec-4377-b5bc-c25808050b34",
      "name": "Use Huggingface for embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsHuggingFaceInference",
      "position": [
        -720,
        80
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "huggingFaceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5e69ae73-70e5-463d-8e90-df327211d47f",
      "name": "Recursive Character Text Splitter1",
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "position": [
        -528,
        80
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {},
  "versionId": "0f65253d-9c23-418f-8bb4-4c811092a429",
  "connections": {
    "LLM Agent": {
      "main": [
        [
          {
            "node": "Store entry in cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Redis Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "LLM Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Process sample data": {
      "ai_document": [
        [
          {
            "node": "Initialize Redis store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Is this a cache hit?": {
      "main": [
        [
          {
            "node": "Respond to Chat (from semantic cache)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LLM Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store entry in cache": {
      "main": [
        [
          {
            "node": "Respond to Chat (from LLM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add response as metadata": {
      "ai_document": [
        [
          {
            "node": "Store entry in cache",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Check for similar prompts": {
      "main": [
        [
          {
            "node": "Analyze results from store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze results from store": {
      "main": [
        [
          {
            "node": "Is this a cache hit?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "node": "Check for similar prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Use Huggingface for embeddings": {
      "ai_embedding": [
        [
          {
            "node": "Initialize Redis store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings HuggingFace Inference": {
      "ai_embedding": [
        [
          {
            "node": "Check for similar prompts",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings HuggingFace Inference1": {
      "ai_embedding": [
        [
          {
            "node": "Store entry in cache",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Recursive Character Text Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Add response as metadata",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "Recursive Character Text Splitter1": {
      "ai_textSplitter": [
        [
          {
            "node": "Process sample data",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Initialize Redis store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}