From 29c873dd695978f6a4a30b2eb65ddf98a2bd82c4 Mon Sep 17 00:00:00 2001 From: Brian Sam-Bodden Date: Thu, 22 Aug 2024 08:53:02 -0700 Subject: [PATCH] [docs]: update Redis (langchain-redis) documentation notebooks (vectorstore, llm caching, chat message history) (#25113) - **Description:** Adds notebooks for Redis Partner Package (langchain-redis) - **Issue:** N/A - **Dependencies:** None - **Twitter handle:** `@bsbodden` and `@redis` --------- Co-authored-by: Chester Curme --- .../caches/redis_llm_caching.ipynb | 424 +++++++ docs/docs/integrations/llm_caching.ipynb | 4 +- .../memory/redis_chat_message_history.ipynb | 316 +++-- .../integrations/vectorstores/redis.ipynb | 1073 ++++++++++------- 4 files changed, 1292 insertions(+), 525 deletions(-) create mode 100644 docs/docs/integrations/caches/redis_llm_caching.ipynb diff --git a/docs/docs/integrations/caches/redis_llm_caching.ipynb b/docs/docs/integrations/caches/redis_llm_caching.ipynb new file mode 100644 index 0000000000000..d30cd8241d94e --- /dev/null +++ b/docs/docs/integrations/caches/redis_llm_caching.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Redis Cache for LangChain\n", + "\n", + "This notebook demonstrates how to use the `RedisCache` and `RedisSemanticCache` classes from the langchain-redis package to implement caching for LLM responses." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's install the required dependencies and ensure we have a Redis instance running." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -U langchain-core langchain-redis langchain-openai redis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ensure you have a Redis server running. You can start one using Docker with:\n", + "\n", + "```\n", + "docker run -d -p 6379:6379 redis:latest\n", + "```\n", + "\n", + "Or install and run Redis locally according to your operating system's instructions." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting to Redis at: redis://redis:6379\n" + ] + } + ], + "source": [ + "import os\n", + "\n", + "# Use the environment variable if set, otherwise default to localhost\n", + "REDIS_URL = os.getenv(\"REDIS_URL\", \"redis://localhost:6379\")\n", + "print(f\"Connecting to Redis at: {REDIS_URL}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Importing Required Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "from langchain.globals import set_llm_cache\n", + "from langchain.schema import Generation\n", + "from langchain_openai import OpenAI, OpenAIEmbeddings\n", + "from langchain_redis import RedisCache, RedisSemanticCache" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import langchain_core\n", + "import langchain_openai\n", + "import openai\n", + "import redis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set OpenAI API key" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API key not found in environment variables.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please enter your OpenAI API key: ········\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API key has been set for this session.\n" + ] + } + ], + "source": [ + "from getpass import getpass\n", + "\n", + "# Check if OPENAI_API_KEY is already set in the environment\n", + "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", + "\n", + "if not openai_api_key:\n", + " print(\"OpenAI API key not found in environment variables.\")\n", + " openai_api_key = getpass(\"Please enter your OpenAI API key: \")\n", + "\n", + " # Set the API key for the current session\n", + " os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n", + " print(\"OpenAI API key has been set for this session.\")\n", + "else:\n", + " print(\"OpenAI API key found in environment variables.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using RedisCache" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First call (not cached):\n", + "Result: \n", + "\n", + "Caching is the process of storing frequently accessed data in a temporary storage location for faster retrieval. This helps to reduce the time and resources needed to access the data from its original source. Caching is commonly used in computer systems, web browsers, and databases to improve performance and efficiency.\n", + "Time: 1.16 seconds\n", + "\n", + "Second call (cached):\n", + "Result: \n", + "\n", + "Caching is the process of storing frequently accessed data in a temporary storage location for faster retrieval. This helps to reduce the time and resources needed to access the data from its original source. Caching is commonly used in computer systems, web browsers, and databases to improve performance and efficiency.\n", + "Time: 0.05 seconds\n", + "\n", + "Speed improvement: 25.40x faster\n", + "Cache cleared\n" + ] + } + ], + "source": [ + "# Initialize RedisCache\n", + "redis_cache = RedisCache(redis_url=REDIS_URL)\n", + "\n", + "# Set the cache for LangChain to use\n", + "set_llm_cache(redis_cache)\n", + "\n", + "# Initialize the language model\n", + "llm = OpenAI(temperature=0)\n", + "\n", + "\n", + "# Function to measure execution time\n", + "def timed_completion(prompt):\n", + " start_time = time.time()\n", + " result = llm.invoke(prompt)\n", + " end_time = time.time()\n", + " return result, end_time - start_time\n", + "\n", + "\n", + "# First call (not cached)\n", + "prompt = \"Explain the concept of caching in three sentences.\"\n", + "result1, time1 = timed_completion(prompt)\n", + "print(f\"First call (not cached):\\nResult: {result1}\\nTime: {time1:.2f} seconds\\n\")\n", + "\n", + "# Second call (should be cached)\n", + "result2, time2 = timed_completion(prompt)\n", + "print(f\"Second call (cached):\\nResult: {result2}\\nTime: {time2:.2f} seconds\\n\")\n", + "\n", + "print(f\"Speed improvement: {time1 / time2:.2f}x faster\")\n", + "\n", + "# Clear the cache\n", + "redis_cache.clear()\n", + "print(\"Cache cleared\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using RedisSemanticCache" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original query:\n", + "Prompt: What is the capital of France?\n", + "Result: \n", + "\n", + "The capital of France is Paris.\n", + "Time: 1.52 seconds\n", + "\n", + "Similar query:\n", + "Prompt: Can you tell me the capital city of France?\n", + "Result: \n", + "\n", + "The capital of France is Paris.\n", + "Time: 0.29 seconds\n", + "\n", + "Speed improvement: 5.22x faster\n", + "Semantic cache cleared\n" + ] + } + ], + "source": [ + "# Initialize RedisSemanticCache\n", + "embeddings = OpenAIEmbeddings()\n", + "semantic_cache = RedisSemanticCache(\n", + " redis_url=REDIS_URL, embeddings=embeddings, distance_threshold=0.2\n", + ")\n", + "\n", + "# Set the cache for LangChain to use\n", + "set_llm_cache(semantic_cache)\n", + "\n", + "\n", + "# Function to test semantic cache\n", + "def test_semantic_cache(prompt):\n", + " start_time = time.time()\n", + " result = llm.invoke(prompt)\n", + " end_time = time.time()\n", + " return result, end_time - start_time\n", + "\n", + "\n", + "# Original query\n", + "original_prompt = \"What is the capital of France?\"\n", + "result1, time1 = test_semantic_cache(original_prompt)\n", + "print(\n", + " f\"Original query:\\nPrompt: {original_prompt}\\nResult: {result1}\\nTime: {time1:.2f} seconds\\n\"\n", + ")\n", + "\n", + "# Semantically similar query\n", + "similar_prompt = \"Can you tell me the capital city of France?\"\n", + "result2, time2 = test_semantic_cache(similar_prompt)\n", + "print(\n", + " f\"Similar query:\\nPrompt: {similar_prompt}\\nResult: {result2}\\nTime: {time2:.2f} seconds\\n\"\n", + ")\n", + "\n", + "print(f\"Speed improvement: {time1 / time2:.2f}x faster\")\n", + "\n", + "# Clear the semantic cache\n", + "semantic_cache.clear()\n", + "print(\"Semantic cache cleared\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Usage" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom TTL (Time-To-Live)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cached result: Cached response\n", + "Waiting for TTL to expire...\n", + "Result after TTL: Not found (expired)\n" + ] + } + ], + "source": [ + "# Initialize RedisCache with custom TTL\n", + "ttl_cache = RedisCache(redis_url=REDIS_URL, ttl=5) # 60 seconds TTL\n", + "\n", + "# Update a cache entry\n", + "ttl_cache.update(\"test_prompt\", \"test_llm\", [Generation(text=\"Cached response\")])\n", + "\n", + "# Retrieve the cached entry\n", + "cached_result = ttl_cache.lookup(\"test_prompt\", \"test_llm\")\n", + "print(f\"Cached result: {cached_result[0].text if cached_result else 'Not found'}\")\n", + "\n", + "# Wait for TTL to expire\n", + "print(\"Waiting for TTL to expire...\")\n", + "time.sleep(6)\n", + "\n", + "# Try to retrieve the expired entry\n", + "expired_result = ttl_cache.lookup(\"test_prompt\", \"test_llm\")\n", + "print(\n", + " f\"Result after TTL: {expired_result[0].text if expired_result else 'Not found (expired)'}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Customizing RedisSemanticCache" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original result: \n", + "\n", + "The largest planet in our solar system is Jupiter.\n", + "Similar query result: \n", + "\n", + "The largest planet in our solar system is Jupiter.\n" + ] + } + ], + "source": [ + "# Initialize RedisSemanticCache with custom settings\n", + "custom_semantic_cache = RedisSemanticCache(\n", + " redis_url=REDIS_URL,\n", + " embeddings=embeddings,\n", + " distance_threshold=0.1, # Stricter similarity threshold\n", + " ttl=3600, # 1 hour TTL\n", + " name=\"custom_cache\", # Custom cache name\n", + ")\n", + "\n", + "# Test the custom semantic cache\n", + "set_llm_cache(custom_semantic_cache)\n", + "\n", + "test_prompt = \"What's the largest planet in our solar system?\"\n", + "result, _ = test_semantic_cache(test_prompt)\n", + "print(f\"Original result: {result}\")\n", + "\n", + "# Try a slightly different query\n", + "similar_test_prompt = \"Which planet is the biggest in the solar system?\"\n", + "similar_result, _ = test_semantic_cache(similar_test_prompt)\n", + "print(f\"Similar query result: {similar_result}\")\n", + "\n", + "# Clean up\n", + "custom_semantic_cache.clear()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This notebook demonstrated the usage of `RedisCache` and `RedisSemanticCache` from the langchain-redis package. These caching mechanisms can significantly improve the performance of LLM-based applications by reducing redundant API calls and leveraging semantic similarity for intelligent caching. The Redis-based implementation provides a fast, scalable, and flexible solution for caching in distributed systems." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/docs/integrations/llm_caching.ipynb b/docs/docs/integrations/llm_caching.ipynb index 72d058e296098..cac516c24bd0e 100644 --- a/docs/docs/integrations/llm_caching.ipynb +++ b/docs/docs/integrations/llm_caching.ipynb @@ -457,7 +457,9 @@ "tags": [] }, "source": [ - "## `Redis` Cache" + "## `Redis` Cache\n", + "\n", + "See the main [Redis cache docs](/docs/integrations/caches/redis_llm_caching/) for detail." ] }, { diff --git a/docs/docs/integrations/memory/redis_chat_message_history.ipynb b/docs/docs/integrations/memory/redis_chat_message_history.ipynb index 760cdc0d09662..3b23833b0fba2 100644 --- a/docs/docs/integrations/memory/redis_chat_message_history.ipynb +++ b/docs/docs/integrations/memory/redis_chat_message_history.ipynb @@ -2,171 +2,347 @@ "cells": [ { "cell_type": "markdown", - "id": "91c6a7ef", "metadata": {}, "source": [ - "# Redis\n", + "# Redis Chat Message History\n", "\n", - ">[Redis (Remote Dictionary Server)](https://en.wikipedia.org/wiki/Redis) is an open-source in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability. Because it holds all data in memory and because of its design, `Redis` offers low-latency reads and writes, making it particularly suitable for use cases that require a cache. Redis is the most popular NoSQL database, and one of the most popular databases overall.\n", + ">[Redis (Remote Dictionary Server)](https://en.wikipedia.org/wiki/Redis) is an open-source in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability. `Redis` offers low-latency reads and writes. Redis is the most popular NoSQL database, and one of the most popular databases overall.\n", "\n", - "This notebook goes over how to use `Redis` to store chat message history." + "This notebook demonstrates how to use the `RedisChatMessageHistory` class from the langchain-redis package to store and manage chat message history using Redis." ] }, { "cell_type": "markdown", - "id": "897a4682-f9fc-488b-98f3-ae2acad84600", "metadata": {}, "source": [ "## Setup\n", - "First we need to install dependencies, and start a redis instance using commands like: `redis-server`." + "\n", + "First, we need to install the required dependencies and ensure we have a Redis instance running." ] }, { "cell_type": "code", "execution_count": null, - "id": "cda8b56d-baf7-49a2-91a2-4d424a8519cb", "metadata": {}, "outputs": [], "source": [ - "pip install -U langchain-community redis" + "%pip install -qU langchain-redis langchain-openai redis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make sure you have a Redis server running. You can start one using Docker with the following command:\n", + "\n", + "```\n", + "docker run -d -p 6379:6379 redis:latest\n", + "```\n", + "\n", + "Or install and run Redis locally according to the instructions for your operating system." ] }, { "cell_type": "code", - "execution_count": null, - "id": "b11090e7-284b-4ed2-9790-ce0d35638717", + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting to Redis at: redis://redis:6379\n" + ] + } + ], "source": [ - "from langchain_community.chat_message_histories import RedisChatMessageHistory" + "import os\n", + "\n", + "# Use the environment variable if set, otherwise default to localhost\n", + "REDIS_URL = os.getenv(\"REDIS_URL\", \"redis://localhost:6379\")\n", + "print(f\"Connecting to Redis at: {REDIS_URL}\")" ] }, { "cell_type": "markdown", - "id": "20b99474-75ea-422e-9809-fbdb9d103afc", "metadata": {}, "source": [ - "## Store and Retrieve Messages" + "## Importing Required Libraries" ] }, { "cell_type": "code", "execution_count": 3, - "id": "d15e3302", "metadata": {}, "outputs": [], "source": [ - "history = RedisChatMessageHistory(\"foo\", url=\"redis://localhost:6379\")\n", - "\n", - "history.add_user_message(\"hi!\")\n", - "\n", - "history.add_ai_message(\"whats up?\")" + "from langchain_core.chat_history import BaseChatMessageHistory\n", + "from langchain_core.messages import AIMessage, HumanMessage\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_redis import RedisChatMessageHistory" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic Usage of RedisChatMessageHistory" ] }, { "cell_type": "code", "execution_count": 4, - "id": "64fc465e", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[HumanMessage(content='hi!'), AIMessage(content='whats up?')]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Chat History:\n", + "HumanMessage: Hello, AI assistant!\n", + "AIMessage: Hello! How can I assist you today?\n" + ] } ], "source": [ - "history.messages" + "# Initialize RedisChatMessageHistory\n", + "history = RedisChatMessageHistory(session_id=\"user_123\", redis_url=REDIS_URL)\n", + "\n", + "# Add messages to the history\n", + "history.add_user_message(\"Hello, AI assistant!\")\n", + "history.add_ai_message(\"Hello! How can I assist you today?\")\n", + "\n", + "# Retrieve messages\n", + "print(\"Chat History:\")\n", + "for message in history.messages:\n", + " print(f\"{type(message).__name__}: {message.content}\")" ] }, { "cell_type": "markdown", - "id": "465fdd8c-b093-4d19-a55a-30f3b646432b", "metadata": {}, "source": [ - "## Using in the Chains" + "## Using RedisChatMessageHistory with Language Models" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "94d65d2f-e9bb-4b47-a86d-dd6b1b5e8247", + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "pip install -U langchain-openai" + "### Set OpenAI API key" ] }, { "cell_type": "code", "execution_count": 5, - "id": "ace3e7b2-5e3e-4966-b549-04952a6a9a09", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API key not found in environment variables.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Please enter your OpenAI API key: ········\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API key has been set for this session.\n" + ] + } + ], "source": [ - "from typing import Optional\n", + "from getpass import getpass\n", "\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_core.runnables.history import RunnableWithMessageHistory\n", - "from langchain_openai import ChatOpenAI" + "# Check if OPENAI_API_KEY is already set in the environment\n", + "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", + "\n", + "if not openai_api_key:\n", + " print(\"OpenAI API key not found in environment variables.\")\n", + " openai_api_key = getpass(\"Please enter your OpenAI API key: \")\n", + "\n", + " # Set the API key for the current session\n", + " os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n", + " print(\"OpenAI API key has been set for this session.\")\n", + "else:\n", + " print(\"OpenAI API key found in environment variables.\")" ] }, { "cell_type": "code", "execution_count": 6, - "id": "5c1fba0d-d06a-4695-ba14-c42a3461ada1", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "AIMessage(content='Your name is Bob, as you mentioned earlier. Is there anything specific you would like assistance with, Bob?')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "AI Response 1: Hello Alice! How can I assist you today?\n", + "AI Response 2: Your name is Alice.\n" + ] } ], "source": [ + "# Create a prompt template\n", "prompt = ChatPromptTemplate.from_messages(\n", " [\n", - " (\"system\", \"You're an assistant。\"),\n", + " (\"system\", \"You are a helpful AI assistant.\"),\n", " MessagesPlaceholder(variable_name=\"history\"),\n", - " (\"human\", \"{question}\"),\n", + " (\"human\", \"{input}\"),\n", " ]\n", ")\n", "\n", - "chain = prompt | ChatOpenAI()\n", + "# Initialize the language model\n", + "llm = ChatOpenAI()\n", + "\n", + "# Create the conversational chain\n", + "chain = prompt | llm\n", + "\n", + "\n", + "# Function to get or create a RedisChatMessageHistory instance\n", + "def get_redis_history(session_id: str) -> BaseChatMessageHistory:\n", + " return RedisChatMessageHistory(session_id, redis_url=REDIS_URL)\n", "\n", + "\n", + "# Create a runnable with message history\n", "chain_with_history = RunnableWithMessageHistory(\n", - " chain,\n", - " lambda session_id: RedisChatMessageHistory(\n", - " session_id, url=\"redis://localhost:6379\"\n", - " ),\n", - " input_messages_key=\"question\",\n", - " history_messages_key=\"history\",\n", + " chain, get_redis_history, input_messages_key=\"input\", history_messages_key=\"history\"\n", ")\n", "\n", - "config = {\"configurable\": {\"session_id\": \"foo\"}}\n", + "# Use the chain in a conversation\n", + "response1 = chain_with_history.invoke(\n", + " {\"input\": \"Hi, my name is Alice.\"},\n", + " config={\"configurable\": {\"session_id\": \"alice_123\"}},\n", + ")\n", + "print(\"AI Response 1:\", response1.content)\n", "\n", - "chain_with_history.invoke({\"question\": \"Hi! I'm bob\"}, config=config)\n", + "response2 = chain_with_history.invoke(\n", + " {\"input\": \"What's my name?\"}, config={\"configurable\": {\"session_id\": \"alice_123\"}}\n", + ")\n", + "print(\"AI Response 2:\", response2.content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Redis Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Custom History: [HumanMessage(content='This is a message with custom configuration.')]\n" + ] + } + ], + "source": [ + "# Initialize with custom Redis configuration\n", + "custom_history = RedisChatMessageHistory(\n", + " \"user_456\",\n", + " redis_url=REDIS_URL,\n", + " key_prefix=\"custom_prefix:\",\n", + " ttl=3600, # Set TTL to 1 hour\n", + " index_name=\"custom_index\",\n", + ")\n", "\n", - "chain_with_history.invoke({\"question\": \"Whats my name\"}, config=config)" + "custom_history.add_user_message(\"This is a message with custom configuration.\")\n", + "print(\"Custom History:\", custom_history.messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Searching Messages" ] }, { "cell_type": "code", - "execution_count": null, - "id": "76ce3f6b-f4c7-4d27-8031-60f7dd756695", + "execution_count": 8, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Search Results:\n", + "human: Tell me about artificial intelligence....\n", + "ai: Artificial Intelligence (AI) is a branch of comput...\n" + ] + } + ], + "source": [ + "# Add more messages\n", + "history.add_user_message(\"Tell me about artificial intelligence.\")\n", + "history.add_ai_message(\n", + " \"Artificial Intelligence (AI) is a branch of computer science...\"\n", + ")\n", + "\n", + "# Search for messages containing a specific term\n", + "search_results = history.search_messages(\"artificial intelligence\")\n", + "print(\"Search Results:\")\n", + "for result in search_results:\n", + " print(f\"{result['type']}: {result['content'][:50]}...\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Clearing History" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Messages after clearing: []\n" + ] + } + ], + "source": [ + "# Clear the chat history\n", + "history.clear()\n", + "print(\"Messages after clearing:\", history.messages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This notebook demonstrated the key features of `RedisChatMessageHistory` from the langchain-redis package. It showed how to initialize and use the chat history, integrate it with language models, and utilize advanced features like custom configurations and message searching. Redis provides a fast and scalable solution for managing chat history in AI applications." + ] } ], "metadata": { @@ -185,9 +361,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.9" } }, "nbformat": 4, - "nbformat_minor": 5 + "nbformat_minor": 4 } diff --git a/docs/docs/integrations/vectorstores/redis.ipynb b/docs/docs/integrations/vectorstores/redis.ipynb index 6230bff240acd..daf7040bc4c2d 100644 --- a/docs/docs/integrations/vectorstores/redis.ipynb +++ b/docs/docs/integrations/vectorstores/redis.ipynb @@ -1,21 +1,40 @@ { "cells": [ + { + "cell_type": "raw", + "id": "1957f5cb", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: Redis\n", + "---" + ] + }, { "cell_type": "markdown", + "id": "ef1f0986", "metadata": {}, "source": [ - "# Redis\n", + "# Redis Vector Store\n", "\n", - ">[Redis vector database](https://redis.io/docs/get-started/vector-database/) introduction and langchain integration guide.\n", + "This notebook covers how to get started with the Redis vector store.\n", "\n", - "## What is Redis?\n", + ">[Redis](https://redis.io/docs/stack/vectorsearch/) is a popular open-source, in-memory data structure store that can be used as a database, cache, message broker, and queue. It now includes vector similarity search capabilities, making it suitable for use as a vector store." + ] + }, + { + "cell_type": "markdown", + "id": "a717e10e-c8d7-41bc-9dd0-447d11f90b68", + "metadata": {}, + "source": [ + "### What is Redis?\n", "\n", - "Most developers from a web services background are familiar with `Redis`. At its core, `Redis` is an open-source key-value store that is used as a cache, message broker, and database. Developers choose `Redis` because it is fast, has a large ecosystem of client libraries, and has been deployed by major enterprises for years.\n", + "Most developers are familiar with `Redis`. At its core, `Redis` is a NoSQL Database in the key-value family that can used as a cache, message broker, stream processing and a primary database. Developers choose `Redis` because it is fast, has a large ecosystem of client libraries, and has been deployed by major enterprises for years.\n", "\n", "On top of these traditional use cases, `Redis` provides additional capabilities like the Search and Query capability that allows users to create secondary index structures within `Redis`. This allows `Redis` to be a Vector Database, at the speed of a cache. \n", "\n", "\n", - "## Redis as a Vector Database\n", + "### Redis as a Vector Database\n", "\n", "`Redis` uses compressed, inverted indexes for fast indexing with a low memory footprint. It also supports a number of advanced features such as:\n", "\n", @@ -38,9 +57,7 @@ "* Retrieve full documents, selected fields, or only the document IDs\n", "* Sorting results (for example, by creation date)\n", "\n", - "\n", - "\n", - "## Clients\n", + "### Clients\n", "\n", "Since `Redis` is much more than just a vector database, there are often use cases that demand the usage of a `Redis` client besides just the `LangChain` integration. You can use any standard `Redis` client library to run Search and Query commands, but it's easiest to use a library that wraps the Search and Query API. Below are a few examples, but you can find more client libraries [here](https://redis.io/resources/clients/).\n", "\n", @@ -54,8 +71,8 @@ "\n", "[redis-url]: https://redis.com\n", "\n", - "[redisvl-url]: https://github.com/RedisVentures/redisvl\n", - "[redisvl-stars]: https://img.shields.io/github/stars/RedisVentures/redisvl.svg?style=social&label=Star&maxAge=2592000\n", + "[redisvl-url]: https://github.com/redis/redis-vl-python\n", + "[redisvl-stars]: https://img.shields.io/github/stars/redis/redisvl.svg?style=social&label=Star&maxAge=2592000\n", "[redisvl-package]: https://pypi.python.org/pypi/redisvl\n", "\n", "[redis-py-url]: https://github.com/redis/redis-py\n", @@ -87,7 +104,7 @@ "[redisearch-api-rs-stars]: https://img.shields.io/github/stars/RediSearch/redisearch-api-rs.svg?style=social&label=Star&maxAge=2592000\n", "\n", "\n", - "## Deployment options\n", + "### Deployment options\n", "\n", "There are many ways to deploy Redis with RediSearch. The easiest way to get started is to use Docker, but there are are many potential options for deployment such as\n", "\n", @@ -96,80 +113,82 @@ "- Cloud marketplaces: [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-e6y7ork67pjwg?sr=0-2&ref_=beagle&applicationId=AWSMPContessa), [Google Marketplace](https://console.cloud.google.com/marketplace/details/redislabs-public/redis-enterprise?pli=1), or [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/garantiadata.redis_enterprise_1sp_public_preview?tab=Overview)\n", "- On-premise: [Redis Enterprise Software](https://redis.com/redis-enterprise-software/overview/)\n", "- Kubernetes: [Redis Enterprise Software on Kubernetes](https://docs.redis.com/latest/kubernetes/)\n", + " \n", + "### Redis connection Url schemas\n", "\n", + "Valid Redis Url schemas are:\n", + "1. `redis://` - Connection to Redis standalone, unencrypted\n", + "2. `rediss://` - Connection to Redis standalone, with TLS encryption\n", + "3. `redis+sentinel://` - Connection to Redis server via Redis Sentinel, unencrypted\n", + "4. `rediss+sentinel://` - Connection to Redis server via Redis Sentinel, booth connections with TLS encryption\n", "\n", - "## Additional examples\n", - "\n", - "Many examples can be found in the [Redis AI team's GitHub](https://github.com/RedisVentures/)\n", - "\n", - "- [Awesome Redis AI Resources](https://github.com/RedisVentures/redis-ai-resources) - List of examples of using Redis in AI workloads\n", - "- [Azure OpenAI Embeddings Q&A](https://github.com/ruoccofabrizio/azure-open-ai-embeddings-qna) - OpenAI and Redis as a Q&A service on Azure.\n", - "- [ArXiv Paper Search](https://github.com/RedisVentures/redis-arXiv-search) - Semantic search over arXiv scholarly papers\n", - "- [Vector Search on Azure](https://learn.microsoft.com/azure/azure-cache-for-redis/cache-tutorial-vector-similarity) - Vector search on Azure using Azure Cache for Redis and Azure OpenAI\n", - "\n", - "\n", - "## More resources\n", - "\n", - "For more information on how to use Redis as a vector database, check out the following resources:\n", - "\n", - "- [RedisVL Documentation](https://redisvl.com) - Documentation for the Redis Vector Library Client\n", - "- [Redis Vector Similarity Docs](https://redis.io/docs/stack/search/reference/vectors/) - Redis official docs for Vector Search.\n", - "- [Redis-py Search Docs](https://redis.readthedocs.io/en/latest/redismodules.html#redisearch-commands) - Documentation for redis-py client library\n", - "- [Vector Similarity Search: From Basics to Production](https://mlops.community/vector-similarity-search-from-basics-to-production/) - Introductory blog post to VSS and Redis as a VectorDB." + "More information about additional connection parameters can be found in the [redis-py documentation](https://redis-py.readthedocs.io/en/stable/connections.html)." ] }, { "cell_type": "markdown", + "id": "36fdc060", "metadata": {}, "source": [ "## Setup\n", "\n", - "`Redis-py` is the officially supported client by Redis. Recently released is the `RedisVL` client which is purpose-built for the Vector Database use cases. Both can be installed with pip." + "To use the RedisVectorStore, you'll need to install the `langchain-redis` partner package, as well as the other packages used throughout this notebook." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], + "execution_count": 1, + "id": "64e28aa6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ - "%pip install -qU redis redisvl langchain-community" + "%pip install -qU langchain-redis langchain-huggingface sentence-transformers scikit-learn" ] }, { "cell_type": "markdown", + "id": "37d388a3-1a56-453e-8f84-e24a72d682eb", "metadata": {}, "source": [ - "### Deploy Redis locally\n", + "### Credentials\n", "\n", - "To locally deploy Redis, run:\n", + "Redis connection credentials are passed as part of the Redis Connection URL. Redis Connection URLs are versatile and can accommodate various Redis server topologies and authentication methods. These URLs follow a specific format that includes the connection protocol, authentication details, host, port, and database information.\n", + "The basic structure of a Redis Connection URL is:\n", "\n", - "```console\n", - "docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack:latest\n", "```\n", - "If things are running correctly you should see a nice Redis UI at `http://localhost:8001`. See the [Deployment options](#deployment-options) section above for other ways to deploy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Redis connection Url schemas\n", + "[protocol]://[auth]@[host]:[port]/[database]\n", + "```\n", "\n", - "Valid Redis Url schemas are:\n", - "1. `redis://` - Connection to Redis standalone, unencrypted\n", - "2. `rediss://` - Connection to Redis standalone, with TLS encryption\n", - "3. `redis+sentinel://` - Connection to Redis server via Redis Sentinel, unencrypted\n", - "4. `rediss+sentinel://` - Connection to Redis server via Redis Sentinel, booth connections with TLS encryption\n", + "Where:\n", "\n", - "More information about additional connection parameters can be found in the [redis-py documentation](https://redis-py.readthedocs.io/en/stable/connections.html)." + "* protocol can be redis for standard connections, rediss for SSL/TLS connections, or redis+sentinel for Sentinel connections.\n", + "* auth includes username and password (if applicable).\n", + "* host is the Redis server hostname or IP address.\n", + "* port is the Redis server port.\n", + "* database is the Redis database number.\n", + "\n", + "Redis Connection URLs support various configurations, including:\n", + "\n", + "* Standalone Redis servers (with or without authentication)\n", + "* Redis Sentinel setups\n", + "* SSL/TLS encrypted connections\n", + "* Different authentication methods (password-only or username-password)\n", + "\n", + "Below are examples of Redis Connection URLs for different configurations:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "id": "b1b1eb90-5155-44ca-a8a7-b04b02d5e77c", "metadata": {}, "outputs": [], "source": [ @@ -196,14 +215,53 @@ }, { "cell_type": "markdown", + "id": "9695dee7", "metadata": {}, "source": [ - "If you want to get best in-class automated tracing of your model calls you can also set your [LangSmith](https://docs.smith.langchain.com/) API key by uncommenting below:" + "### Launching a Redis Instance with Docker\n", + "\n", + "To use Redis with LangChain, you need a running Redis instance. You can start one using Docker with:\n", + "\n", + "```bash\n", + "docker run -d -p 6379:6379 redis/redis-stack:latest\n", + "```\n", + "\n", + "For this example, we'll use a local Redis instance. If you're using a remote instance, you'll need to modify the Redis URL accordingly." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, + "id": "894c30e4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting to Redis at: redis://redis:6379\n" + ] + } + ], + "source": [ + "import os\n", + "\n", + "REDIS_URL = os.getenv(\"REDIS_URL\", \"redis://localhost:6379\")\n", + "print(f\"Connecting to Redis at: {REDIS_URL}\")" + ] + }, + { + "cell_type": "markdown", + "id": "7f98392b", + "metadata": {}, + "source": [ + "If you want to get automated tracing of your model calls you can also set your [LangSmith](https://docs.smith.langchain.com/) API key by uncommenting below:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e7b6a6e0", "metadata": {}, "outputs": [], "source": [ @@ -213,19 +271,96 @@ }, { "cell_type": "markdown", + "id": "63dd7f8d-df13-45d8-8e13-892b29803e96", + "metadata": {}, + "source": [ + "Let's check that Redis is up an running by pinging it:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "37fc2d36-f5bf-465f-9774-510bdc134b62", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import redis\n", + "\n", + "redis_client = redis.from_url(REDIS_URL)\n", + "redis_client.ping()" + ] + }, + { + "cell_type": "markdown", + "id": "4e388814-4188-4f29-8f24-cc67d4048ebe", + "metadata": {}, + "source": [ + "### Sample Data\n", + "\n", + "The 20 newsgroups dataset comprises around 18000 newsgroups posts on 20 topics. We'll use a subset for this demonstration and focus on two categories: 'alt.atheism' and 'sci.space':" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "869a4726-1e24-48fd-9ffd-c62a589d0bb1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "250" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain.docstore.document import Document\n", + "from sklearn.datasets import fetch_20newsgroups\n", + "\n", + "categories = [\"alt.atheism\", \"sci.space\"]\n", + "newsgroups = fetch_20newsgroups(\n", + " subset=\"train\", categories=categories, shuffle=True, random_state=42\n", + ")\n", + "\n", + "# Use only the first 250 documents\n", + "texts = newsgroups.data[:250]\n", + "metadata = [\n", + " {\"category\": newsgroups.target_names[target]} for target in newsgroups.target[:250]\n", + "]\n", + "\n", + "len(texts)" + ] + }, + { + "cell_type": "markdown", + "id": "93df377e", "metadata": {}, "source": [ "## Initialization\n", "\n", - "The Redis VectorStore instance can be initialized in a number of ways. There are multiple class methods that can be used to initialize a Redis VectorStore instance.\n", + "The RedisVectorStore instance can be initialized in several ways:\n", "\n", - "- ``Redis.__init__`` - Initialize directly\n", - "- ``Redis.from_documents`` - Initialize from a list of ``Langchain.docstore.Document`` objects\n", - "- ``Redis.from_texts`` - Initialize from a list of texts (optionally with metadata)\n", - "- ``Redis.from_texts_return_keys`` - Initialize from a list of texts (optionally with metadata) and return the keys\n", - "- ``Redis.from_existing_index`` - Initialize from an existing Redis index\n", + "- `RedisVectorStore.__init__` - Initialize directly\n", + "- `RedisVectorStore.from_texts` - Initialize from a list of texts (optionally with metadata)\n", + "- `RedisVectorStore.from_documents` - Initialize from a list of `langchain_core.documents.Document` objects\n", + "- `RedisVectorStore.from_existing_index` - Initialize from an existing Redis index\n", "\n", - "Below we will use the ``Redis.__init__`` method. \n", + "Below we will use the `RedisVectorStore.__init__` method using a `RedisConfig` instance.\n", "\n", "```{=mdx}\n", "import EmbeddingTabs from \"@theme/EmbeddingTabs\";\n", @@ -236,146 +371,114 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, + "id": "7a95c110-015b-4300-93b7-c0100d55d024", "metadata": {}, "outputs": [], "source": [ + "%%capture\n", "# | output: false\n", "# | echo: false\n", - "from langchain_openai import OpenAIEmbeddings\n", + "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n", + "from langchain_huggingface import HuggingFaceEmbeddings\n", + "from tqdm.auto import tqdm\n", "\n", - "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-large\")" + "embeddings = HuggingFaceEmbeddings(model_name=\"msmarco-distilbert-base-v4\")" + ] + }, + { + "cell_type": "markdown", + "id": "ec110186-d5e4-4eaa-a4e3-899f405f719f", + "metadata": {}, + "source": [ + "We'll use the SentenceTransformer model to create embeddings. This model runs locally and doesn't require an API key." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, + "id": "dc37144c-208d-4ab3-9f3a-0407a69fe052", "metadata": { "tags": [] }, "outputs": [], "source": [ - "from langchain_community.vectorstores.redis import Redis\n", + "from langchain_redis import RedisConfig, RedisVectorStore\n", + "\n", + "config = RedisConfig(\n", + " index_name=\"newsgroups\",\n", + " redis_url=REDIS_URL,\n", + " metadata_schema=[\n", + " {\"name\": \"category\", \"type\": \"tag\"},\n", + " ],\n", + ")\n", "\n", - "vector_store = Redis(\n", - " redis_url=\"redis://localhost:6379\",\n", - " embedding=embeddings,\n", - " index_name=\"users\",\n", - ")" + "vector_store = RedisVectorStore(embeddings, config=config)" ] }, { "cell_type": "markdown", + "id": "ac6071d4", "metadata": {}, "source": [ "## Manage vector store\n", "\n", - "Once you have created your vector store, we can interact with it by adding and deleting different items.\n", - "\n", - "### Add items to vector store\n", + "### Add items to vector store" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "17f5efc0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['newsgroups:f1e788ee61fe410daa8ef941dd166223', 'newsgroups:80b39032181f4299a359a9aaed6e2401', 'newsgroups:99a3efc1883647afba53d115b49e6e92', 'newsgroups:503a6c07cd71418eb71e11b42589efd7', 'newsgroups:7351210e32d1427bbb3c7426cf93a44f', 'newsgroups:4e79fdf67abe471b8ee98ba0e8a1a055', 'newsgroups:03559a1d574e4f9ca0479d7b3891402e', 'newsgroups:9a1c2a7879b8409a805db72feac03580', 'newsgroups:3578a1e129f5435f9743cf803413f37a', 'newsgroups:9f68baf4d6b04f1683d6b871ce8ad92d']\n" + ] + } + ], + "source": [ + "ids = vector_store.add_texts(texts, metadata)\n", "\n", - "We can add items to our vector store by using the `add_documents` function." + "print(ids[0:10])" + ] + }, + { + "cell_type": "markdown", + "id": "f8822e55-40d5-48aa-8e29-79101feb645a", + "metadata": {}, + "source": [ + "Let's inspect the first document:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, + "id": "ca27e394-ae1e-4fdb-b79a-4a6b45a953a8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['doc:users:622f5f19-9b4b-4896-9a16-e1e95f19db4b',\n", - " 'doc:users:032b489f-d37e-4bf1-85ec-4c2275be48ef',\n", - " 'doc:users:5daf0855-b352-45bd-9d29-e21ff66e38c8',\n", - " 'doc:users:b9204897-190b-4dd9-af2b-081ed4e9cbb0',\n", - " 'doc:users:9395caff-1a6a-46c1-bc5c-7c5558eadf46',\n", - " 'doc:users:28243c3d-463d-4662-936e-003a2dc0dc30',\n", - " 'doc:users:1e1cdb91-c226-4836-b38e-ee4b61444913',\n", - " 'doc:users:4005bba2-2a08-4160-a16f-5cc3cf9d4aea',\n", - " 'doc:users:8c88440a-06d2-4a68-95f1-c58d0cf99d29',\n", - " 'doc:users:cc20438f-741a-40fd-bed8-4f1cee113680']" + "('From: bil@okcforum.osrhe.edu (Bill Conner)\\nSubject: Re: Not the Omni!\\nNntp-Posting-Host: okcforum.osrhe.edu\\nOrganization: Okcforum Unix Users Group\\nX-Newsreader: TIN [version 1.1 PL6]\\nLines: 18\\n\\nCharley Wingate (mangoe@cs.umd.edu) wrote:\\n: \\n: >> Please enlighten me. How is omnipotence contradictory?\\n: \\n: >By definition, all that can occur in the universe is governed by the rules\\n: >of nature. Thus god cannot break them. Anything that god does must be allowed\\n: >in the rules somewhere. Therefore, omnipotence CANNOT exist! It contradicts\\n: >the rules of nature.\\n: \\n: Obviously, an omnipotent god can change the rules.\\n\\nWhen you say, \"By definition\", what exactly is being defined;\\ncertainly not omnipotence. You seem to be saying that the \"rules of\\nnature\" are pre-existant somehow, that they not only define nature but\\nactually cause it. If that\\'s what you mean I\\'d like to hear your\\nfurther thoughts on the question.\\n\\nBill\\n',\n", + " {'category': 'alt.atheism'})" ] }, - "execution_count": 7, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from uuid import uuid4\n", - "\n", - "from langchain_core.documents import Document\n", - "\n", - "document_1 = Document(\n", - " page_content=\"I had chocalate chip pancakes and scrambled eggs for breakfast this morning.\",\n", - " metadata={\"source\": \"tweet\"},\n", - ")\n", - "\n", - "document_2 = Document(\n", - " page_content=\"The weather forecast for tomorrow is cloudy and overcast, with a high of 62 degrees.\",\n", - " metadata={\"source\": \"news\"},\n", - ")\n", - "\n", - "document_3 = Document(\n", - " page_content=\"Building an exciting new project with LangChain - come check it out!\",\n", - " metadata={\"source\": \"tweet\"},\n", - ")\n", - "\n", - "document_4 = Document(\n", - " page_content=\"Robbers broke into the city bank and stole $1 million in cash.\",\n", - " metadata={\"source\": \"news\"},\n", - ")\n", - "\n", - "document_5 = Document(\n", - " page_content=\"Wow! That was an amazing movie. I can't wait to see it again.\",\n", - " metadata={\"source\": \"tweet\"},\n", - ")\n", - "\n", - "document_6 = Document(\n", - " page_content=\"Is the new iPhone worth the price? Read this review to find out.\",\n", - " metadata={\"source\": \"website\"},\n", - ")\n", - "\n", - "document_7 = Document(\n", - " page_content=\"The top 10 soccer players in the world right now.\",\n", - " metadata={\"source\": \"website\"},\n", - ")\n", - "\n", - "document_8 = Document(\n", - " page_content=\"LangGraph is the best framework for building stateful, agentic applications!\",\n", - " metadata={\"source\": \"tweet\"},\n", - ")\n", - "\n", - "document_9 = Document(\n", - " page_content=\"The stock market is down 500 points today due to fears of a recession.\",\n", - " metadata={\"source\": \"news\"},\n", - ")\n", - "\n", - "document_10 = Document(\n", - " page_content=\"I have a bad feeling I am going to get deleted :(\",\n", - " metadata={\"source\": \"tweet\"},\n", - ")\n", - "\n", - "documents = [\n", - " document_1,\n", - " document_2,\n", - " document_3,\n", - " document_4,\n", - " document_5,\n", - " document_6,\n", - " document_7,\n", - " document_8,\n", - " document_9,\n", - " document_10,\n", - "]\n", - "uuids = [str(uuid4()) for _ in range(len(documents))]\n", - "\n", - "vector_store.add_documents(documents=documents, ids=uuids)" + "texts[0], metadata[0]" ] }, { "cell_type": "markdown", + "id": "dcf1b905", "metadata": {}, "source": [ "### Delete items from vector store" @@ -383,26 +486,29 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, + "id": "ef61e188", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "True" + "1" ] }, - "execution_count": 8, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "vector_store.delete(ids=[uuids[-1]])" + "# Delete documents by passing one or more keys/ids\n", + "vector_store.index.drop_keys(ids[0])" ] }, { "cell_type": "markdown", + "id": "021e2e3a-8f87-4d62-a1f3-dc291c6b26be", "metadata": {}, "source": [ "### Inspecting the created Index\n", @@ -412,15 +518,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, + "id": "787d9cbf-8942-4e6f-b030-f404d4632972", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m17:24:03\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m Indices:\n", - "\u001b[32m17:24:03\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m 1. users\n" + "\u001b[32m17:54:50\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m Using Redis address from environment variable, REDIS_URL\n", + "\u001b[32m17:54:50\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m Indices:\n", + "\u001b[32m17:54:50\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m 1. newsgroups\n" ] } ], @@ -431,6 +539,7 @@ }, { "cell_type": "markdown", + "id": "b869de6f-e3da-4bfc-a267-102df1165521", "metadata": {}, "source": [ "The ``Redis`` VectorStore implementation will attempt to generate index schema (fields for filtering) for any metadata passed through the ``from_texts``, ``from_texts_return_keys``, and ``from_documents`` methods. This way, whatever metadata is passed will be indexed into the Redis search index allowing\n", @@ -441,85 +550,84 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 13, + "id": "0eb45eb1-492f-487d-a8a7-7d2d301c7bdb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "\u001b[32m17:54:50\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m Using Redis address from environment variable, REDIS_URL\n", "\n", "\n", "Index Information:\n", - "╭──────────────┬────────────────┬───────────────┬─────────────────┬────────────╮\n", - "│ Index Name │ Storage Type │ Prefixes │ Index Options │ Indexing │\n", - "├──────────────┼────────────────┼───────────────┼─────────────────┼────────────┤\n", - "│ users │ HASH │ ['doc:users'] │ [] │ 0 │\n", - "╰──────────────┴────────────────┴───────────────┴─────────────────┴────────────╯\n", + "╭──────────────┬────────────────┬────────────────┬─────────────────┬────────────╮\n", + "│ Index Name │ Storage Type │ Prefixes │ Index Options │ Indexing │\n", + "├──────────────┼────────────────┼────────────────┼─────────────────┼────────────┤\n", + "│ newsgroups │ HASH │ ['newsgroups'] │ [] │ 0 │\n", + "╰──────────────┴────────────────┴────────────────┴─────────────────┴────────────╯\n", "Index Fields:\n", - "╭────────────────┬────────────────┬────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬─────────────────┬────────────────╮\n", - "│ Name │ Attribute │ Type │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │\n", - "├────────────────┼────────────────┼────────┼────────────────┼────────────────┼────────────────┼────────────────┼────────────────┼────────────────┼─────────────────┼────────────────┤\n", - "│ content │ content │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │\n", - "│ content_vector │ content_vector │ VECTOR │ algorithm │ FLAT │ data_type │ FLOAT32 │ dim │ 3072 │ distance_metric │ COSINE │\n", - "╰────────────────┴────────────────┴────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴─────────────────┴────────────────╯\n" + "╭───────────┬─────────────┬────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────────┬─────────────────┬────────────────╮\n", + "│ Name │ Attribute │ Type │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │\n", + "├───────────┼─────────────┼────────┼────────────────┼────────────────┼────────────────┼────────────────┼────────────────┼────────────────┼─────────────────┼────────────────┤\n", + "│ text │ text │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │\n", + "│ embedding │ embedding │ VECTOR │ algorithm │ FLAT │ data_type │ FLOAT32 │ dim │ 768 │ distance_metric │ COSINE │\n", + "│ category │ category │ TAG │ SEPARATOR │ | │ │ │ │ │ │ │\n", + "╰───────────┴─────────────┴────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴────────────────┴─────────────────┴────────────────╯\n" ] } ], "source": [ - "!rvl index info -i users --port 6379" + "!rvl index info -i newsgroups --port 6379" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, + "id": "84f9a77c-41b1-4515-97f4-2635998dc0dd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "\u001b[32m17:54:51\u001b[0m \u001b[34m[RedisVL]\u001b[0m \u001b[1;30mINFO\u001b[0m Using Redis address from environment variable, REDIS_URL\n", "\n", "Statistics:\n", - "╭─────────────────────────────┬─────────────╮\n", - "│ Stat Key │ Value │\n", - "├─────────────────────────────┼─────────────┤\n", - "│ num_docs │ 10 │\n", - "│ num_terms │ 100 │\n", - "│ max_doc_id │ 10 │\n", - "│ num_records │ 116 │\n", - "│ percent_indexed │ 1 │\n", - "│ hash_indexing_failures │ 0 │\n", - "│ number_of_uses │ 1 │\n", - "│ bytes_per_record_avg │ 88.2931 │\n", - "│ doc_table_size_mb │ 0.00108719 │\n", - "│ inverted_sz_mb │ 0.00976753 │\n", - "│ key_table_size_mb │ 0.000304222 │\n", - "│ offset_bits_per_record_avg │ 8 │\n", - "│ offset_vectors_sz_mb │ 0.000102043 │\n", - "│ offsets_per_term_avg │ 0.922414 │\n", - "│ records_per_doc_avg │ 11.6 │\n", - "│ sortable_values_size_mb │ 0 │\n", - "│ total_indexing_time │ 1.373 │\n", - "│ total_inverted_index_blocks │ 100 │\n", - "│ vector_index_sz_mb │ 12.0086 │\n", - "╰─────────────────────────────┴─────────────╯\n" + "╭─────────────────────────────┬────────────╮\n", + "│ Stat Key │ Value │\n", + "├─────────────────────────────┼────────────┤\n", + "│ num_docs │ 249 │\n", + "│ num_terms │ 16178 │\n", + "│ max_doc_id │ 250 │\n", + "│ num_records │ 50394 │\n", + "│ percent_indexed │ 1 │\n", + "│ hash_indexing_failures │ 0 │\n", + "│ number_of_uses │ 2 │\n", + "│ bytes_per_record_avg │ 38.2743 │\n", + "│ doc_table_size_mb │ 0.0263586 │\n", + "│ inverted_sz_mb │ 1.83944 │\n", + "│ key_table_size_mb │ 0.00932026 │\n", + "│ offset_bits_per_record_avg │ 10.6699 │\n", + "│ offset_vectors_sz_mb │ 0.089057 │\n", + "│ offsets_per_term_avg │ 1.38937 │\n", + "│ records_per_doc_avg │ 202.386 │\n", + "│ sortable_values_size_mb │ 0 │\n", + "│ total_indexing_time │ 72.444 │\n", + "│ total_inverted_index_blocks │ 16207 │\n", + "│ vector_index_sz_mb │ 3.01776 │\n", + "╰─────────────────────────────┴────────────╯\n" ] } ], "source": [ - "!rvl stats -i users --port 6379" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It's important to note that we have not specified that the ``user``, ``job``, ``credit_score`` and ``age`` in the metadata should be fields within the index, this is because the ``Redis`` VectorStore object automatically generate the index schema from the passed metadata. For more information on the generation of index fields, see the API documentation." + "!rvl stats -i newsgroups --port 6379" ] }, { "cell_type": "markdown", + "id": "c3620501", "metadata": {}, "source": [ "## Query vector store\n", @@ -528,424 +636,481 @@ "\n", "### Query directly\n", "\n", - "#### Similarity search\n", - "\n", "Performing a simple similarity search can be done as follows:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, + "id": "aa0a16fa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "* Building an exciting new project with LangChain - come check it out! [{'id': 'doc:users:5daf0855-b352-45bd-9d29-e21ff66e38c8'}]\n", - "* LangGraph is the best framework for building stateful, agentic applications! [{'id': 'doc:users:4005bba2-2a08-4160-a16f-5cc3cf9d4aea'}]\n" + "Simple Similarity Search Results:\n", + "Content: From: aa429@freenet.carleton.ca (Terry Ford)\n", + "Subject: A flawed propulsion system: Space Shuttle\n", + "X-Ad...\n", + "Metadata: {'category': 'sci.space'}\n", + "\n", + "Content: From: nsmca@aurora.alaska.edu\n", + "Subject: Space Design Movies?\n", + "Article-I.D.: aurora.1993Apr23.124722.1\n", + "...\n", + "Metadata: {'category': 'sci.space'}\n", + "\n" ] } ], "source": [ - "results = vector_store.similarity_search(\n", - " \"LangChain provides abstractions to make working with LLMs easy\", k=2\n", - ")\n", - "for res in results:\n", - " print(f\"* {res.page_content} [{res.metadata}]\")" + "query = \"Tell me about space exploration\"\n", + "results = vector_store.similarity_search(query, k=2)\n", + "\n", + "print(\"Simple Similarity Search Results:\")\n", + "for doc in results:\n", + " print(f\"Content: {doc.page_content[:100]}...\")\n", + " print(f\"Metadata: {doc.metadata}\")\n", + " print()" ] }, { "cell_type": "markdown", + "id": "3ed9d733", "metadata": {}, "source": [ - "#### Similarity search with score\n", - "\n", - "You can also search with score:" + "If you want to execute a similarity search and receive the corresponding scores you can run:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, + "id": "5efd2eaa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "* [SIM=0.446900] The weather forecast for tomorrow is cloudy and overcast, with a high of 62 degrees. [{'id': 'doc:users:032b489f-d37e-4bf1-85ec-4c2275be48ef'}]\n" + "Similarity Search with Score Results:\n", + "Content: From: aa429@freenet.carleton.ca (Terry Ford)\n", + "Subject: A flawed propulsion system: Space Shuttle\n", + "X-Ad...\n", + "Metadata: {'category': 'sci.space'}\n", + "Score: 0.569670975208\n", + "\n", + "Content: From: nsmca@aurora.alaska.edu\n", + "Subject: Space Design Movies?\n", + "Article-I.D.: aurora.1993Apr23.124722.1\n", + "...\n", + "Metadata: {'category': 'sci.space'}\n", + "Score: 0.590400338173\n", + "\n" ] } ], "source": [ - "results = vector_store.similarity_search_with_score(\"Will it be hot tomorrow?\", k=1)\n", - "for res, score in results:\n", - " print(f\"* [SIM={score:3f}] {res.page_content} [{res.metadata}]\")" + "# Similarity search with score and filter\n", + "scored_results = vector_store.similarity_search_with_score(query, k=2)\n", + "\n", + "print(\"Similarity Search with Score Results:\")\n", + "for doc, score in scored_results:\n", + " print(f\"Content: {doc.page_content[:100]}...\")\n", + " print(f\"Metadata: {doc.metadata}\")\n", + " print(f\"Score: {score}\")\n", + " print()" ] }, { "cell_type": "markdown", + "id": "0c235cdc", "metadata": {}, "source": [ - "#### Other search methods\n", - "\n", - "For a list of all the search functions available to the `Redis` vector store, please refer to the [API reference](https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.redis.base.Redis.html)\n", - "\n", - "## Connect to an existing Index\n", - "\n", - "In order to have the same metadata indexed when using the ``Redis`` VectorStore. You will need to have the same ``index_schema`` passed in either as a path to a yaml file or as a dictionary. The following shows how to obtain the schema from an index and connect to an existing index." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "# write the schema to a yaml file\n", - "vector_store.write_schema(\"redis_schema.yaml\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'id': 'doc:users:8484c48a032d4c4cbe3cc2ed6845fabb', 'user': 'john', 'job': 'engineer', 'credit_score': 'high', 'age': '18'}\n" - ] - } - ], - "source": [ - "# now we can connect to our existing index as follows\n", + "### Query by turning into retriever\n", "\n", - "new_rds = Redis.from_existing_index(\n", - " embeddings,\n", - " index_name=\"users\",\n", - " redis_url=\"redis://localhost:6379\",\n", - " schema=\"redis_schema.yaml\",\n", - ")\n", - "results = new_rds.similarity_search(\"foo\", k=3)\n", - "print(results[0].metadata)" + "You can also transform the vector store into a retriever for easier usage in your chains." ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 17, + "id": "f3460093", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "True" + "[Document(metadata={'category': 'sci.space'}, page_content='Subject: Re: Comet in Temporary Orbit Around Jupiter?\\nFrom: Robert Coe \\nDistribution: world\\nOrganization: 1776 Enterprises, Sudbury MA\\nLines: 23\\n\\njgarland@kean.ucs.mun.ca writes:\\n\\n> >> Also, perihelions of Gehrels3 were:\\n> >> \\n> >> April 1973 83 jupiter radii\\n> >> August 1970 ~3 jupiter radii\\n> > \\n> > Where 1 Jupiter radius = 71,000 km = 44,000 mi = 0.0005 AU. So the\\n> > 1970 figure seems unlikely to actually be anything but a perijove.\\n> > Is that the case for the 1973 figure as well?\\n> > -- \\n> Sorry, _perijoves_...I\\'m not used to talking this language.\\n\\nHmmmm.... The prefix \"peri-\" is Greek, not Latin, so it\\'s usually used\\nwith the Greek form of the name of the body being orbited. (That\\'s why\\nit\\'s \"perihelion\" rather than \"perisol\", \"perigee\" rather than \"periterr\",\\nand \"pericynthion\" rather than \"perilune\".) So for Jupiter I\\'d expect it\\nto be something like \"perizeon\".) :^)\\n\\n ___ _ - Bob\\n /__) _ / / ) _ _\\n(_/__) (_)_(_) (___(_)_(/_______________________________________ bob@1776.COM\\nRobert K. Coe ** 14 Churchill St, Sudbury, Massachusetts 01776 ** 508-443-3265\\n'),\n", + " Document(metadata={'category': 'sci.space'}, page_content='From: pyron@skndiv.dseg.ti.com (Dillon Pyron)\\nSubject: Re: Why not give $1 billion to first year-long moon residents?\\nLines: 42\\nNntp-Posting-Host: skndiv.dseg.ti.com\\nReply-To: pyron@skndiv.dseg.ti.com\\nOrganization: TI/DSEG VAX Support\\n\\n\\nIn article <1qve4kINNpas@sal-sun121.usc.edu>, schaefer@sal-sun121.usc.edu (Peter Schaefer) writes:\\n>In article <1993Apr19.130503.1@aurora.alaska.edu>, nsmca@aurora.alaska.edu writes:\\n>|> In article <6ZV82B2w165w@theporch.raider.net>, gene@theporch.raider.net (Gene Wright) writes:\\n>|> > With the continuin talk about the \"End of the Space Age\" and complaints \\n>|> > by government over the large cost, why not try something I read about \\n>|> > that might just work.\\n>|> > \\n>|> > Announce that a reward of $1 billion would go to the first corporation \\n>|> > who successfully keeps at least 1 person alive on the moon for a year. \\n>|> > Then you\\'d see some of the inexpensive but not popular technologies begin \\n>|> > to be developed. THere\\'d be a different kind of space race then!\\n>|> > \\n>|> > --\\n>|> > gene@theporch.raider.net (Gene Wright)\\n>|> > theporch.raider.net 615/297-7951 The MacInteresteds of Nashville\\n>|> ====\\n>|> If that were true, I\\'d go for it.. I have a few friends who we could pool our\\n>|> resources and do it.. Maybe make it a prize kind of liek the \"Solar Car Race\"\\n>|> in Australia..\\n>|> Anybody game for a contest!\\n>|> \\n>|> ==\\n>|> Michael Adams, nsmca@acad3.alaska.edu -- I\\'m not high, just jacked\\n>\\n>\\n>Oh gee, a billion dollars! That\\'d be just about enough to cover the cost of the\\n>feasability study! Happy, Happy, JOY! JOY!\\n>\\n\\nFeasability study?? What a wimp!! While you are studying, others would be\\ndoing. Too damn many engineers doing way too little engineering.\\n\\n\"He who sits on his arse sits on his fortune\" - Sir Richard Francis Burton\\n--\\nDillon Pyron | The opinions expressed are those of the\\nTI/DSEG Lewisville VAX Support | sender unless otherwise stated.\\n(214)462-3556 (when I\\'m here) |\\n(214)492-4656 (when I\\'m home) |Texans: Vote NO on Robin Hood. We need\\npyron@skndiv.dseg.ti.com |solutions, not gestures.\\nPADI DM-54909 |\\n\\n')]" ] }, - "execution_count": 20, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# see the schemas are the same\n", - "new_rds.schema == vector_store.schema" + "retriever = vector_store.as_retriever(search_type=\"similarity\", search_kwargs={\"k\": 2})\n", + "retriever.invoke(\"What planet in the solar system has the largest number of moons?\")" ] }, { "cell_type": "markdown", + "id": "901c75dc", "metadata": {}, "source": [ - "## Custom metadata indexing\n", - "\n", - "In some cases, you may want to control what fields the metadata maps to. For example, you may want the ``credit_score`` field to be a categorical field instead of a text field (which is the default behavior for all string fields). In this case, you can use the ``index_schema`` parameter in each of the initialization methods above to specify the schema for the index. Custom index schema can either be passed as a dictionary or as a path to a YAML file.\n", - "\n", - "All arguments in the schema have defaults besides the name, so you can specify only the fields you want to change. All the names correspond to the snake/lowercase versions of the arguments you would use on the command line with ``redis-cli`` or in ``redis-py``. For more on the arguments for each field, see the [documentation](https://redis.io/docs/interact/search-and-query/basic-constructs/field-and-type-options/)\n", - "\n", - "The below example shows how to specify the schema for the ``credit_score`` field as a Tag (categorical) field instead of a text field. \n", - "\n", - "```yaml\n", - "# index_schema.yml\n", - "tag:\n", - " - name: credit_score\n", - "text:\n", - " - name: user\n", - " - name: job\n", - "numeric:\n", - " - name: age\n", - "```\n", - "\n", - "In Python, this would look like:\n", - "\n", - "```python\n", + "## Usage for retrieval-augmented generation\n", "\n", - "index_schema = {\n", - " \"tag\": [{\"name\": \"credit_score\"}],\n", - " \"text\": [{\"name\": \"user\"}, {\"name\": \"job\"}],\n", - " \"numeric\": [{\"name\": \"age\"}],\n", - "}\n", + "For guides on how to use this vector store for retrieval-augmented generation (RAG), see the following sections:\n", "\n", - "```\n", + "- [Tutorials: working with external knowledge](https://python.langchain.com/v0.2/docs/tutorials/#working-with-external-knowledge)\n", + "- [How-to: Question and answer with RAG](https://python.langchain.com/v0.2/docs/how_to/#qa-with-rag)\n", + "- [Retrieval conceptual docs](https://python.langchain.com/v0.2/docs/concepts/#retrieval)" + ] + }, + { + "cell_type": "markdown", + "id": "069f1b5f", + "metadata": {}, + "source": [ + "## Redis-specific functionality\n", "\n", - "Notice that only the ``name`` field needs to be specified. All other fields have defaults." + "Redis offers some unique features for vector search:" + ] + }, + { + "cell_type": "markdown", + "id": "8a627d3a-af78-46e2-b314-007e641b4d1d", + "metadata": {}, + "source": [ + "### Similarity search with metadata filtering\n", + "We can filter our search results based on metadata:" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 18, + "id": "23d6e6fe-8aee-4cee-bf05-59cf3fba36ae", "metadata": {}, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "`index_schema` does not match generated metadata schema.\n", - "If you meant to manually override the schema, please ignore this message.\n", - "index_schema: {'tag': [{'name': 'credit_score'}], 'text': [{'name': 'user'}, {'name': 'job'}], 'numeric': [{'name': 'age'}]}\n", - "generated_schema: {'text': [{'name': 'user'}, {'name': 'job'}, {'name': 'credit_score'}], 'numeric': [{'name': 'age'}], 'tag': []}\n", + "Filtered Similarity Search Results:\n", + "Content: From: aa429@freenet.carleton.ca (Terry Ford)\n", + "Subject: A flawed propulsion system: Space Shuttle\n", + "X-Ad...\n", + "Metadata: {'category': 'sci.space'}\n", + "\n", + "Content: From: nsmca@aurora.alaska.edu\n", + "Subject: Space Design Movies?\n", + "Article-I.D.: aurora.1993Apr23.124722.1\n", + "...\n", + "Metadata: {'category': 'sci.space'}\n", "\n" ] } ], "source": [ - "# create a new index with the new schema defined above\n", - "index_schema = {\n", - " \"tag\": [{\"name\": \"credit_score\"}],\n", - " \"text\": [{\"name\": \"user\"}, {\"name\": \"job\"}],\n", - " \"numeric\": [{\"name\": \"age\"}],\n", - "}\n", - "texts = [] # list of texts\n", - "metadata = {} # dictionary of metadata\n", - "\n", - "rds, keys = Redis.from_texts_return_keys(\n", - " texts,\n", - " embeddings,\n", - " metadatas=metadata,\n", - " redis_url=\"redis://localhost:6379\",\n", - " index_name=\"users_modified\",\n", - " index_schema=index_schema, # pass in the new index schema\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above warning is meant to notify users when they are overriding the default behavior. Ignore it if you are intentionally overriding the behavior." + "from redisvl.query.filter import Tag\n", + "\n", + "query = \"Tell me about space exploration\"\n", + "\n", + "# Create a RedisVL filter expression\n", + "filter_condition = Tag(\"category\") == \"sci.space\"\n", + "\n", + "filtered_results = vector_store.similarity_search(query, k=2, filter=filter_condition)\n", + "\n", + "print(\"Filtered Similarity Search Results:\")\n", + "for doc in filtered_results:\n", + " print(f\"Content: {doc.page_content[:100]}...\")\n", + " print(f\"Metadata: {doc.metadata}\")\n", + " print()" ] }, { "cell_type": "markdown", + "id": "f35b9ebf-6419-4402-a066-c1d5287ed38e", "metadata": {}, "source": [ - "## Hybrid filtering\n", - "\n", - "With the Redis Filter Expression language built into LangChain, you can create arbitrarily long chains of hybrid filters\n", - "that can be used to filter your search results. The expression language is derived from the [RedisVL Expression Syntax](https://redisvl.com)\n", - "and is designed to be easy to use and understand.\n", - "\n", - "The following are the available filter types:\n", - "- ``RedisText``: Filter by full-text search against metadata fields. Supports exact, fuzzy, and wildcard matching.\n", - "- ``RedisNum``: Filter by numeric range against metadata fields.\n", - "- ``RedisTag``: Filter by the exact match against string-based categorical metadata fields. Multiple tags can be specified like \"tag1,tag2,tag3\".\n", - "\n", - "The following are examples of utilizing these filters.\n", - "\n", - "```python\n", - "\n", - "from langchain_community.vectorstores.redis import RedisText, RedisNum, RedisTag\n", - "\n", - "# exact matching\n", - "has_high_credit = RedisTag(\"credit_score\") == \"high\"\n", - "does_not_have_high_credit = RedisTag(\"credit_score\") != \"low\"\n", - "\n", - "# fuzzy matching\n", - "job_starts_with_eng = RedisText(\"job\") % \"eng*\"\n", - "job_is_engineer = RedisText(\"job\") == \"engineer\"\n", - "job_is_not_engineer = RedisText(\"job\") != \"engineer\"\n", - "\n", - "# numeric filtering\n", - "age_is_18 = RedisNum(\"age\") == 18\n", - "age_is_not_18 = RedisNum(\"age\") != 18\n", - "age_is_greater_than_18 = RedisNum(\"age\") > 18\n", - "age_is_less_than_18 = RedisNum(\"age\") < 18\n", - "age_is_greater_than_or_equal_to_18 = RedisNum(\"age\") >= 18\n", - "age_is_less_than_or_equal_to_18 = RedisNum(\"age\") <= 18\n", - "\n", - "```\n", - "\n", - "The ``RedisFilter`` class can be used to simplify the import of these filters as follows\n", - "\n", - "```python\n", - "\n", - "from langchain_community.vectorstores.redis import RedisFilter\n", - "\n", - "# same examples as above\n", - "has_high_credit = RedisFilter.tag(\"credit_score\") == \"high\"\n", - "does_not_have_high_credit = RedisFilter.num(\"age\") > 8\n", - "job_starts_with_eng = RedisFilter.text(\"job\") % \"eng*\"\n", - "```\n", - "\n", - "The following are examples of using a hybrid filter for search" + "### Maximum marginal relevance search\n", + "Maximum marginal relevance search helps in getting diverse results:" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 19, + "id": "5be2afeb-d0a3-4075-bd3c-4cbe409dfb3a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Job: engineer\n", - "Engineers in the dataset: 2\n" + "Maximum Marginal Relevance Search Results:\n", + "Content: From: aa429@freenet.carleton.ca (Terry Ford)\n", + "Subject: A flawed propulsion system: Space Shuttle\n", + "X-Ad...\n", + "Metadata: {'category': 'sci.space'}\n", + "\n", + "Content: From: moroney@world.std.com (Michael Moroney)\n", + "Subject: Re: Vulcan? (No, not the guy with the ears!)\n", + "...\n", + "Metadata: {'category': 'sci.space'}\n", + "\n" ] } ], "source": [ - "from langchain_community.vectorstores.redis import RedisText\n", + "# Maximum marginal relevance search with filter\n", + "mmr_results = vector_store.max_marginal_relevance_search(\n", + " query, k=2, fetch_k=10, filter=filter_condition\n", + ")\n", + "\n", + "print(\"Maximum Marginal Relevance Search Results:\")\n", + "for doc in mmr_results:\n", + " print(f\"Content: {doc.page_content[:100]}...\")\n", + " print(f\"Metadata: {doc.metadata}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "09c3343c-6af4-4151-ba0a-50800fc34855", + "metadata": {}, + "source": [ + "## Chain usage\n", + "The code below shows how to use the vector store as a retriever in a simple RAG chain:\n", "\n", - "is_engineer = RedisText(\"job\") == \"engineer\"\n", - "results = rds.similarity_search(\"foo\", k=3, filter=is_engineer)\n", + "```{=mdx}\n", + "import ChatModelTabs from \"@theme/ChatModelTabs\";\n", "\n", - "print(\"Job:\", results[0].metadata[\"job\"])\n", - "print(\"Engineers in the dataset:\", len(results))" + "\n", + "```" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 20, + "id": "9f6658f8-45b7-4004-a0b3-893bd23bff41", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Job: doctor\n", - "Job: doctor\n", - "Jobs in dataset that start with 'doc': 2\n" + "OpenAI API key not found in environment variables.\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Please enter your OpenAI API key: ········\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI API key has been set for this session.\n" ] } ], "source": [ - "# fuzzy match\n", - "starts_with_doc = RedisText(\"job\") % \"doc*\"\n", - "results = rds.similarity_search(\"foo\", k=3, filter=starts_with_doc)\n", + "# | output: false\n", + "# | echo: false\n", + "from getpass import getpass\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "# Check if OPENAI_API_KEY is already set in the environment\n", + "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", + "\n", + "if not openai_api_key:\n", + " print(\"OpenAI API key not found in environment variables.\")\n", + " openai_api_key = getpass(\"Please enter your OpenAI API key: \")\n", + "\n", + " # Set the API key for the current session\n", + " os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n", + " print(\"OpenAI API key has been set for this session.\")\n", + "else:\n", + " print(\"OpenAI API key found in environment variables.\")\n", "\n", - "for result in results:\n", - " print(\"Job:\", result.metadata[\"job\"])\n", - "print(\"Jobs in dataset that start with 'doc':\", len(results))" + "llm = ChatOpenAI(model=\"gpt-4o-mini\")" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 21, + "id": "d0ac614c-3f80-4839-8451-d3322a870809", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "User: derrick is 45\n", - "User: nancy is 94\n", - "User: joe is 35\n" - ] + "data": { + "text/plain": [ + "'The Space Shuttle program was a NASA initiative that enabled reusable spacecraft to transport astronauts and cargo to and from low Earth orbit. It conducted a variety of missions, including satellite deployment, scientific research, and assembly of the International Space Station, and typically carried a crew of five astronauts. Although it achieved many successes, the program faced criticism for its safety concerns and the complexity of its propulsion system.'" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "from langchain_community.vectorstores.redis import RedisNum\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "\n", + "# Prompt\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"human\",\n", + " \"\"\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\n", + "Question: {question} \n", + "Context: {context} \n", + "Answer:\"\"\",\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "\n", + "def format_docs(docs):\n", + " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", + "\n", + "\n", + "rag_chain = (\n", + " {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + ")\n", "\n", - "is_over_18 = RedisNum(\"age\") > 18\n", - "is_under_99 = RedisNum(\"age\") < 99\n", - "age_range = is_over_18 & is_under_99\n", - "results = rds.similarity_search(\"foo\", filter=age_range)\n", + "rag_chain.invoke(\"Describe the Space Shuttle program?\")" + ] + }, + { + "cell_type": "markdown", + "id": "8ad3e6e4-36ef-494a-be50-4bf8e374b077", + "metadata": {}, + "source": [ + "## Connect to an existing Index\n", "\n", - "for result in results:\n", - " print(\"User:\", result.metadata[\"user\"], \"is\", result.metadata[\"age\"])" + "In order to have the same metadata indexed when using the ``Redis`` VectorStore. You will need to have the same ``index_schema`` passed in either as a path to a yaml file or as a dictionary. The following shows how to obtain the schema from an index and connect to an existing index." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "6a0e7a49-8271-44b2-abb2-0ef499546b28", + "metadata": {}, + "outputs": [], + "source": [ + "# write the schema to a yaml file\n", + "vector_store.index.schema.to_yaml(\"redis_schema.yaml\")" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 33, + "id": "e3588805-b3d9-4af8-8786-b57fc640ebb0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "User: derrick is 45\n", - "User: nancy is 94\n", - "User: joe is 35\n" + "18:19:58 redisvl.index.index INFO Index already exists, not overwriting.\n", + "page_content='From: aa429@freenet.carleton.ca (Terry Ford)\n", + "Subject: A flawed propulsion system: Space Shuttle\n", + "X-Added: Forwarded by Space Digest\n", + "Organization: [via International Space University]\n", + "Original-Sender: isu@VACATION.VENARI.CS.CMU.EDU\n", + "Distribution: sci\n", + "Lines: 13\n", + "\n", + "\n", + "\n", + "For an essay, I am writing about the space shuttle and a need for a better\n", + "propulsion system. Through research, I have found that it is rather clumsy \n", + "(i.e. all the checks/tests before launch), the safety hazards (\"sitting\n", + "on a hydrogen bomb\"), etc.. If you have any beefs about the current\n", + "space shuttle program Re: propulsion, please send me your ideas.\n", + "\n", + "Thanks a lot.\n", + "\n", + "--\n", + "Terry Ford [aa429@freenet.carleton.ca]\n", + "Nepean, Ontario, Canada.\n", + "' metadata={'category': 'sci.space'}\n" ] } ], "source": [ - "# make sure to use parenthesis around FilterExpressions\n", - "# if initializing them while constructing them\n", - "age_range = (RedisNum(\"age\") > 18) & (RedisNum(\"age\") < 99)\n", - "results = rds.similarity_search(\"foo\", filter=age_range)\n", - "\n", - "for result in results:\n", - " print(\"User:\", result.metadata[\"user\"], \"is\", result.metadata[\"age\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Query by turning into retriever\n", + "# now we can connect to our existing index as follows\n", "\n", - "You can also transform the vector store into a retriever for easier usage in your chains. Here we go over different options for using the vector store as a retriever.\n", + "new_rdvs = RedisVectorStore(\n", + " embeddings,\n", + " redis_url=REDIS_URL,\n", + " schema_path=\"redis_schema.yaml\",\n", + ")\n", "\n", - "There are three different search methods we can use to do retrieval. By default, it will use semantic similarity. To see all the options, please refer to the [API reference](https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.redis.base.Redis.html#langchain_community.vectorstores.redis.base.Redis.as_retriever)" + "results = new_rdvs.similarity_search(\"Space Shuttle Propulsion System\", k=3)\n", + "print(results[0])" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 35, + "id": "4d7ff456-de2a-4c58-9a3f-a9a3cfdca492", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[Document(metadata={'id': 'doc:users:b9204897-190b-4dd9-af2b-081ed4e9cbb0'}, page_content='Robbers broke into the city bank and stole $1 million in cash.')]" + "True" ] }, - "execution_count": 16, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "retriever = vector_store.as_retriever(\n", - " search_type=\"similarity_score_threshold\",\n", - " search_kwargs={\"k\": 1, \"score_threshold\": 0.2},\n", - ")\n", - "retriever.invoke(\"Stealing from the bank is a crime\")" + "# compare the two schemas to verify they are the same\n", + "new_rdvs.index.schema == vector_store.index.schema" ] }, { "cell_type": "markdown", + "id": "044a2a8c-cb25-453b-a439-38fcb06081ab", "metadata": {}, "source": [ - "## Usage for retrieval-augmented generation\n", - "\n", - "For guides on how to use this vector store for retrieval-augmented generation (RAG), see the following sections:\n", - "\n", - "- [Tutorials: working with external knowledge](https://python.langchain.com/v0.2/docs/tutorials/#working-with-external-knowledge)\n", - "- [How-to: Question and answer with RAG](https://python.langchain.com/v0.2/docs/how_to/#qa-with-rag)\n", - "- [Retrieval conceptual docs](https://python.langchain.com/v0.2/docs/concepts/#retrieval)" + "## Cleanup vector store" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "bb24ab8b-1040-489d-bef6-9137dd2215f3", + "metadata": {}, + "outputs": [], + "source": [ + "# Clear vector store\n", + "vector_store.index.delete(drop=True)" ] }, { "cell_type": "markdown", + "id": "8a27244f", "metadata": {}, "source": [ "## API reference\n", "\n", - "For detailed documentation of all `Redis` vector store features and configurations head to the API reference: https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.redis.base.Redis.html" + "For detailed documentation of all RedisVectorStore features and configurations head to the API reference: https://api.python.langchain.com/en/latest/vectorstores/langchain_redis.vectorstores.RedisVectorStore.html" ] } ], @@ -969,5 +1134,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 }