{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "R2-i8jBl9GRH" }, "source": [ "![Redis](https://redis.io/wp-content/uploads/2024/04/Logotype.svg?auto=webp&quality=85,75&width=120)\n", "\n", "# RAG from scratch with the Redis Vector Library\n", "\n", "\n", "In this recipe we will cover the basic of the Redis Vector Library and build a basic RAG app from scratch.\n", "\n", "## Let's Begin!\n", "\"Open\n" ] }, { "cell_type": "markdown", "metadata": { "id": "rT9HzsnQ1uiz" }, "source": [ "## Environment Setup\n", "\n", "### Pull Github Materials\n", "Because you are likely running this notebook in **Google Colab**, we need to first\n", "pull the necessary dataset and materials directly from GitHub.\n", "\n", "**If you are running this notebook locally**, FYI you may not need to perform this\n", "step at all." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T04:41:18.607703Z", "start_time": "2025-04-24T04:41:11.664107Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "AJJ2UW6M1ui0", "outputId": "0f5773b7-a292-4ee6-f4bd-20dc40ca2aba" }, "outputs": [], "source": [ "# NBVAL_SKIP\n", "!git clone https://github.com/redis-developer/redis-ai-resources.git temp_repo\n", "!mv temp_repo/python-recipes/RAG/resources .\n", "!rm -rf temp_repo" ] }, { "cell_type": "markdown", "metadata": { "id": "Z67mf6T91ui2" }, "source": [ "### Install Python Dependencies" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T04:41:20.572419Z", "start_time": "2025-04-24T04:41:18.616143Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "DgxBQFXQ1ui2", "outputId": "c3c399d6-e294-4a3a-a0a3-82d818509991" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -q \"redisvl>=0.6.0\" langchain-community pypdf sentence-transformers langchain openai pandas langchain-text-splitters" ] }, { "cell_type": "markdown", "metadata": { "id": "peC8ThuVJkD7" }, "source": [ "### Install Redis Stack\n", "\n", "Later in this tutorial, Redis will be used to store, index, and query vector\n", "embeddings created from PDF document chunks. **We need to make sure we have a Redis\n", "instance available.**" ] }, { "cell_type": "markdown", "metadata": { "id": "zMKHJ7oWJkD8" }, "source": [ "#### For Colab\n", "Use the shell script below to download, extract, and install [Redis Stack](https://redis.io/docs/getting-started/install-stack/) directly from the Redis package archive." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "c0d5lfNxJkD8", "outputId": "f96e72fa-b9f3-476f-bc9e-328bd30d1344" }, "outputs": [], "source": [ "# NBVAL_SKIP\n", "%%sh\n", "curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg\n", "echo \"deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/redis.list\n", "sudo apt-get update > /dev/null 2>&1\n", "sudo apt-get install redis-stack-server > /dev/null 2>&1\n", "redis-stack-server --daemonize yes" ] }, { "cell_type": "markdown", "metadata": { "id": "2arb8Ic0JkD8" }, "source": [ "#### For Alternative Environments\n", "There are many ways to get the necessary redis-stack instance running\n", "1. On cloud, deploy a [FREE instance of Redis in the cloud](https://redis.com/try-free/). Or, if you have your\n", "own version of Redis Enterprise running, that works too!\n", "2. Per OS, [see the docs](https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/)\n", "3. With docker: `docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest`" ] }, { "cell_type": "markdown", "metadata": { "id": "DhP1w0R9JkD8" }, "source": [ "### Define the Redis Connection URL\n", "\n", "By default this notebook connects to the local instance of Redis Stack. **If you have your own Redis Enterprise instance** - replace REDIS_PASSWORD, REDIS_HOST and REDIS_PORT values with your own." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:45.583246Z", "start_time": "2025-04-24T16:46:45.581177Z" }, "id": "ggh5TzhkJkD9" }, "outputs": [], "source": [ "import os\n", "\n", "# Replace values below with your own if using Redis Cloud instance\n", "REDIS_HOST = os.getenv(\"REDIS_HOST\", \"localhost\") # ex: \"redis-18374.c253.us-central1-1.gce.cloud.redislabs.com\"\n", "REDIS_PORT = os.getenv(\"REDIS_PORT\", \"6379\") # ex: 18374\n", "REDIS_PASSWORD = os.getenv(\"REDIS_PASSWORD\", \"\") # ex: \"1TNxTEdYRDgIDKM2gDfasupCADXXXX\"\n", "\n", "# If SSL is enabled on the endpoint, use rediss:// as the URL prefix\n", "REDIS_URL = f\"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}\"" ] }, { "cell_type": "markdown", "metadata": { "id": "b3ErDmsIJkD9" }, "source": [ "## Simplified Vector Search with RedisVL" ] }, { "cell_type": "markdown", "metadata": { "id": "KrtWWU4I1ui3" }, "source": [ "### Dataset Preparation (PDF Documents)\n", "\n", "To best demonstrate Redis as a vector database layer, we will load a single\n", "financial (10k filings) doc and preprocess it using some helpers from LangChain:\n", "\n", "- `PyPDFLoader` is not the only document loader type that LangChain provides. Docs: https://python.langchain.com/docs/integrations/document_loaders/pypdfloader/\n", "- `RecursiveCharacterTextSplitter` is what we use to create smaller chunks of text from the doc. Docs: https://docs.langchain.com/oss/python/integrations/splitters/recursive_text_splitter" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:46.043726Z", "start_time": "2025-04-24T16:46:45.600472Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "uijl2qFH1ui3", "outputId": "a99b3fcb-7cfd-4dbd-f258-57779cfcae3c" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/joobin.lim/Desktop/redis-ai-resources/python-recipes/RAG/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Listing available documents ... ['resources/nke-10k-2023.pdf', 'resources/amzn-10k-2023.pdf', 'resources/jnj-10k-2023.pdf', 'resources/aapl-10k-2023.pdf', 'resources/testset_15.csv', 'resources/retrieval_basic_rag_test.csv', 'resources/2022-chevy-colorado-ebrochure.pdf', 'resources/nvd-10k-2023.pdf', 'resources/testset.csv', 'resources/msft-10k-2023.pdf', 'resources/propositions.json', 'resources/generation_basic_rag_test.csv']\n" ] } ], "source": [ "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", "from langchain_community.document_loaders import PyPDFLoader\n", "\n", "# Load list of pdfs from a folder\n", "data_path = \"resources/\"\n", "docs = [os.path.join(data_path, file) for file in os.listdir(data_path)]\n", "\n", "print(\"Listing available documents ...\", docs)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:50.509810Z", "start_time": "2025-04-24T16:46:46.104219Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "anya8hVnT6K_", "outputId": "a8430acc-2e6d-45fd-fc8b-601fbbd8289b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Done preprocessing. Created 211 chunks of the original pdf resources/nke-10k-2023.pdf\n" ] } ], "source": [ "# pick out the Nike doc for this exercise\n", "doc = [doc for doc in docs if \"nke\" in doc][0]\n", "\n", "# set up the file loader/extractor and text splitter to create chunks\n", "text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size=2500, chunk_overlap=0\n", ")\n", "loader = PyPDFLoader(doc, headers = None)\n", "\n", "# extract, load, and make chunks\n", "chunks = loader.load_and_split(text_splitter)\n", "\n", "print(\"Done preprocessing. Created\", len(chunks), \"chunks of the original pdf\", doc)" ] }, { "cell_type": "markdown", "metadata": { "id": "fDN4XopTJkD9" }, "source": [ "### Text embedding generation with RedisVL\n", "RedisVL has built-in extensions and utilities to aid the GenAI development process. In the following snipit we utilize the HFTextVectorizer redisvl in tandem with the **all-MiniLM-L6-v2** class to generate vector embeddings for the chunks created above. These embeddings capture the \"meaning\" of the text so that we can retrieve the relevant chunks later when a user's query is semantically related." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:55.588165Z", "start_time": "2025-04-24T16:46:50.528240Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 661, "referenced_widgets": [ "cbd44245af844dca8e568691cc1c15c5", "3109d0d320274ad0bb941608ee3df5e3", "6c902ce903bb4e25a127ec277e2b2c45", "954b76e059024b15be48fb5064ab2fb7", "160c4567015f4b1bba43dc7e1e4712fb", "712fcb54fabc430c9567240a2ddd4a76", "f96ce89375924097ab9f4cd130fd7b41", "58c687581a8d4d3a828686cd066a32b3", "df2305a9a6634dffbc08567f62047b27", "218e8977786b42e1b825a14d44164d82", "8bc8cb91c6274c08a72c91c91dddf4ef", "abee8aeb772f48dab4661dca40277788", "300b9716084a4a24bf479ae7200b87d1", "ff76433f165146f0b39d2488a33b318e", "98fe1e1e066541ec942a05ec416fa53f", "be9c6f9905fd440884261e09367fe659", "9d7bd9a50eea407eb60c41c1534f295d", "968f389c21cf469daee8284a7b14c251", "39f7677d9d8a4bdf8f4eb4756fae3ed2", "959248b437054a43a0393c71a603b35f", "6b3711002db148f790eab617f7f40eb4", "5a3363012166483d90abb10b476772bf", "92e02308d4d94725b73cc324d8cd9906", "6fe679c08e2b46dd8657160d974912e0", "61fc922ce98c4fefbebe7bb6a8ee9317", "2cc139350de742989b6e24d70e490a54", "995465a251f64f7a9c1e5541a7f28d4d", "56b8c445444b4d39b2c9fb199586ff93", "5f2ad751dab24f6aaae736c01e582c14", "54331fe70c934a7894903d5ca7a960ce", "6270fcf4772f40d59a6f6842060f36a4", "14e24b722ecf47a49ebe42e8c3492c1e", "b5e36e428e3541fd8a237d0f28a023e1", "6aa3f285fd8a4a84882b7bece1b639ac", "d20425f4a0594c319bc51ee60d773f79", "a046d9ff7e1d4577ab28315d681ac36b", "c9468d94408a4d36a20eae07624a6a09", "902551f09b44499b8c8dd88bbdf50a4a", "5477b553050e42c0b8ed7c2c8c17c025", "fcbac845d7c24db6a85e82f190e69a75", "82f4af2b827c4d98a762c2e7ebd03d6e", "146de95acc214f60b854553ab983b7ae", "a356517795234ab6abb3ffd71b05f296", "1757bba5dca64bf3b7d359cd2537e9c5", "59d890877f8b4f7aa436fa4b82e4cf8d", "9a0acbad43204038b8ca4edeeb0e0d61", "38518362236e470898cdbfb48ee0d381", "9aac56d1808d490797bbb175c5afb226", "2f848e63b87847d1a299c04052d567d6", "52395bed9f6d455897d8d489e7dcb0d3", "4e2332a6f482448597a9d4988fec7cf6", "ac55276fbd5a4404ba065a19849119c5", "fae66f22c38247ad85078f6ad2530ced", "a3fcad6db08c4f07adf4ee817afce77a", "557fb6c9f787412a8bff6f4798087bb7", "a4c7c73d90cf44acb43740b223be8101", "010e7ce97cfb43f195d1dd1811584ea2", "484f1fc0b5844726b3ac203440ddbdc8", "9368d437c3534a33b0010ea77be8a5e2", "50c576ca5f914c65aeb5b7c03f4b0fa2", "80bcb933a16c40788a3ad354e545acfe", "2bfc17a97664452787740dc202eae370", "600f4d36b66d40ecb8353db981d0f1f4", "1cb7ce33be9345e992769fb7cdeb0e75", "f1204ffea0da4058a3973e6d79a8d36c", "b91aa35f8bfb4cb29724a0cf864a3158", "b225fd0da4c24d97a502a2df731d1037", "9ed0c298163645a8a10f7704354b3d2c", "3a2d93764f7645258777f75d2a33b214", "4d21de5d79b74e7d9dc5ccfb36827358", "927cb59be15747418fba1a56d7e22e21", "4a5e1f7a57d446e980090aae0325b990", "33175a3341134f7ebba6232440e9a770", "d503a8e5ea4f4bc089c4ae3e95ce1af4", "73ffa18b349849fdb7264b748b4189e9", "316f2f8a79ad4b0aa140f149383b2eff", "1c9b5e2acf0141898ab2a0639a79d209", "dd6707fe0bae4aab842dac25bf31880d", "4682a7ebe86a4a60ab6b793718435302", "1617b257e66c409db6c4ca0d0944a933", "63825f6200a944bd8c66602a64eee67c", "6cad7dfb6dd4441fb569c5533ef044e8", "1a76918edd75460e8d572e59d3aa5413", "1b3112662eb2481087fb3af6e79a4480", "23127b47d99d406c9a53520a3697972b", "1cb27bb3b5354879b7f1a73a24df923d", "77f646bb598d471cacdf772d9799a8df", "66782c677c2040d0ae19e7c6da6186ce", "c24f6df83a0b46ecbad2be4583d3bb1b", "9101630e52a04193804e02341e38830a", "9c9441eac4fe46078709fbf9c84c4a4e", "e9ecac569557483d89b848e31b1a4f85", "a641f0330b134a48844212dd72dafa57", "9e2c06d967be46ecbb56e0e0268c9a65", "da39e3fbf61941dc9fc05d00fb44a468", "a516325f85594525aac760a5c0d1a0d2", "55529d65863a4a5fb25dca02f0e885e2", "532e6cc744b54e12a677f33af75318f0", "c9c3f643f9b0472ab9dce2649139bb6a", "26d0829f64b248ada2b0f46b746cd8b1", "448556b65d2f419ca6cd395ce6d11f3f", "c0cf7a81656c4fd98d2418fd6336c6ae", "5c88eed231d14f2da8961a4ac7837417", "b4ca94c7f8534b4e857c57a619a7f116", "c18a7f2b29e54916ba81510b2bb21902", "067c697db37d43d8b6fa3b155a794f00", "006473c1d4a247208c17d3258909adb0", "8375e9fcaa4a46d895dc074cfed92149", "56cb8feab6c047ca8afb2acfda4d35d1", "29ce854a35e94a47af82522cc9f8a92b", "8e394c924a00479ba046afb5eeacc5f3", "86148800470449979a8baeb58b5f5c88", "386648192f9e403680aa57d1444e4465", "c12d9b3dfbe045a3bfba0ecd790af191", "0dbce80382dc41429050a896f3203c4e", "90e4273246e44f7c95db4456a00755a3", "d57525fd237d4c519e52c76ee7208a30", "6db6a832f6b44c3eb82f93fd60fda7fb", "dfcbee09be344b2f8b55ef1c9ddfbd76", "0428e3d1575c4ac6b6dfca617d144b7d", "dc42c19d950943a88630242dd188c1a7", "3fb33de4563749d7827c735380453b58", "3d8d6ea4a4ef4493b8033bcc62476375", "e7693807a9154e7482b4611be6421a0d", "150b6eaa9bd64dce908775d230740038", "4b59623304314a35b030ff805e5bf699", "1bf348fa5757429790b9272f037fc93a", "470138741a50479bb930f00a060cc61e", "589f8fbac4e0492e81e35cc6424a75bc", "2d92057e09554dcdbe405aafc0f602db", "6eb2d7bb05f442519211928645384c3a", "d2206237f06a4419a7304a199dff2e8a", "40f12f8bb6a04034b8c7a95d984469f2", "98e4143c2bbb42cea2566686eff2fa6a", "981b3a05c8ae42d29ffb81156ebc1a7d", "b8513aac81224b139347dfe5011f1563", "09c487bb35b6439aaa298665873ee84b", "da636d6c421f49f48ef43db194faae5e", "958bab205e204f87bce793f79869a28b", "8e93910fca484d93ab2eddea9540d307", "0a6226f65d354c55b3370c6e87dcc246", "685026baa834438aa8060a9e681c3263", "fe189eed0a834221bd8adb0bdc44b4c8" ] }, "id": "N3iQ2aLEJkD9", "outputId": "b0f0d2c1-41dc-4932-990b-53d2912af19e" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n", "WARNING:huggingface_hub.utils._http:Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n", "Loading weights: 100%|██████████| 103/103 [00:00<00:00, 12211.35it/s]\n", "\u001b[1mBertModel LOAD REPORT\u001b[0m from: sentence-transformers/all-MiniLM-L6-v2\n", "Key | Status | | \n", "------------------------+------------+--+-\n", "embeddings.position_ids | UNEXPECTED | | \n", "\n", "Notes:\n", "- UNEXPECTED:\tcan be ignored when loading from different task/architecture; not ok if you expect identical arch.\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import warnings\n", "import pandas as pd\n", "from redisvl.utils.vectorize import HFTextVectorizer, BaseVectorizer\n", "from redisvl.extensions.cache.embeddings import EmbeddingsCache\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n", "\n", "hf = HFTextVectorizer(\n", " model=\"sentence-transformers/all-MiniLM-L6-v2\",\n", " cache=EmbeddingsCache(\n", " name=\"embedcache\",\n", " ttl=600,\n", " redis_url=REDIS_URL,\n", " )\n", ")\n", "\n", "# Embed each chunk content\n", "embeddings = hf.embed_many([chunk.page_content for chunk in chunks])\n", "\n", "# Check to make sure we've created enough embeddings, 1 per document chunk\n", "len(embeddings) == len(chunks)" ] }, { "cell_type": "markdown", "metadata": { "id": "5baI0xDQ1ui-" }, "source": [ "### Define a schema and create an index\n", "\n", "Below we connect to Redis and create an index that contains a text field, tag field, and vector field." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:55.611260Z", "start_time": "2025-04-24T16:46:55.598846Z" }, "id": "zB1EW_9n1ui-" }, "outputs": [], "source": [ "from redisvl.index import SearchIndex\n", "\n", "index_name = \"redisvl\"\n", "\n", "schema = {\n", " \"index\": {\n", " \"name\": index_name,\n", " \"prefix\": \"chunk\"\n", " },\n", " \"fields\": [\n", " {\n", " \"name\": \"chunk_id\",\n", " \"type\": \"tag\",\n", " \"attrs\": {\n", " \"sortable\": True\n", " }\n", " },\n", " {\n", " \"name\": \"content\",\n", " \"type\": \"text\"\n", " },\n", " {\n", " \"name\": \"text_embedding\",\n", " \"type\": \"vector\",\n", " \"attrs\": {\n", " \"dims\": 384,\n", " \"distance_metric\": \"cosine\",\n", " \"algorithm\": \"hnsw\",\n", " \"datatype\": \"float32\"\n", " }\n", " }\n", " ]\n", "}" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:55.630056Z", "start_time": "2025-04-24T16:46:55.620207Z" }, "id": "LKuQku2CJkD9" }, "outputs": [], "source": [ "# create an index from schema and the client\n", "index = SearchIndex.from_dict(schema, redis_url=REDIS_URL)\n", "index.create(overwrite=True, drop=True)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "L6GOqmeN1ui_", "outputId": "91a199e3-d087-4b15-9544-d59efa6033c5" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Indices:\n", "1. redisvl\n" ] } ], "source": [ "# use the RedisVL CLI tool to list all indices\n", "!rvl index listall" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:56.828176Z", "start_time": "2025-04-24T16:46:56.283831Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "C70C-UWj1ujA", "outputId": "1fb7a2d6-ae6d-4536-b4b7-702620efd128" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Index Information:\n", "╭───────────────┬───────────────┬───────────────┬───────────────┬───────────────╮\n", "│ Index Name │ Storage Type │ Prefixes │ Index Options │ Indexing │\n", "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n", "| redisvl | HASH | ['chunk'] | [] | 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 │ Field Option │ Option Value │ Field Option │ Option Value │\n", "├─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤\n", "│ chunk_id │ chunk_id │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ content │ content │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │ │ │ │ │\n", "│ text_embedding │ text_embedding │ VECTOR │ algorithm │ HNSW │ data_type │ FLOAT32 │ dim │ 384 │ distance_metric │ COSINE │ M │ 16 │ ef_construction │ 200 │\n", "╰─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────╯\n" ] } ], "source": [ "# get info about the index\n", "!rvl index info -i redisvl" ] }, { "cell_type": "markdown", "metadata": { "id": "Qrj-jeGmBRTL" }, "source": [ "### Process and load dataset\n", "Below we use the RedisVL index to simply load the list of document chunks to Redis db." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:56.895623Z", "start_time": "2025-04-24T16:46:56.836700Z" }, "id": "Zsg09Keg1ujA" }, "outputs": [], "source": [ "# load expects an iterable of dictionaries\n", "from redisvl.redis.utils import array_to_buffer\n", "\n", "data = [\n", " {\n", " 'chunk_id': i,\n", " 'content': chunk.page_content,\n", " # For HASH -- must convert embeddings to bytes\n", " 'text_embedding': array_to_buffer(embeddings[i], dtype='float32')\n", " } for i, chunk in enumerate(chunks)\n", "]\n", "\n", "# RedisVL handles batching automatically\n", "keys = index.load(data, id_field=\"chunk_id\")" ] }, { "cell_type": "markdown", "metadata": { "id": "-ZsFB-6Z1ujB" }, "source": [ "### Query the database\n", "Now we can use the RedisVL index to perform similarity search operations with Redis" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:56.991529Z", "start_time": "2025-04-24T16:46:56.903370Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 85, "referenced_widgets": [ "c75d5ab2049146e580efab9da9bbcdb0", "9ce1fb951e79468baa9d1aebfa4c4fae", "e96d1546380146078c18ec78363f7dac", "a3c36bb0d3b74c8ea56bf03521465b81", "9f306cfd66dc441aba923d4e051911fc", "9e3289444cb142c29ad7d569be2e25b8", "c20443e17308425596679c0544dab528", "f0bdd8f4d7b84bd5a1c209c591ce8787", "126743b52b254e54aa4f65bcb9e65aea", "debae380e6d24fb8ae712a6dd2226152", "aacb6f8ca39846d89e1e4e96656e3a36" ] }, "id": "BkFv-_iC1ujB", "outputId": "c398d356-6bb7-43a9-ca95-cb7f167d1f38" }, "outputs": [ { "data": { "text/plain": [ "'*=>[KNN 3 @text_embedding $vector AS vector_distance] RETURN 3 chunk_id content vector_distance SORTBY vector_distance ASC DIALECT 2 LIMIT 0 3'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from redisvl.query import VectorQuery\n", "\n", "query = \"Nike profit margins and company performance\"\n", "\n", "query_embedding = hf.embed(query)\n", "\n", "vector_query = VectorQuery(\n", " vector=query_embedding,\n", " vector_field_name=\"text_embedding\",\n", " num_results=3,\n", " return_fields=[\"chunk_id\", \"content\"],\n", " return_score=True\n", ")\n", "\n", "# show the raw redis query\n", "str(vector_query)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.008139Z", "start_time": "2025-04-24T16:46:56.999381Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 143 }, "id": "5reL5qTW1ujC", "outputId": "dd58f191-54f5-4226-c4e1-70207d58f2dc" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idvector_distancechunk_idcontent
0chunk:880.33769464492888Asia Pacific & Latin America 1,932 1,896 2 % 1...
1chunk:800.34205293655480Table of Contents\\nCONSOLIDATED OPERATING RESU...
2chunk:870.35776090621987Table of Contents\\nOPERATING SEGMENTS\\nAs disc...
\n", "
" ], "text/plain": [ " id vector_distance chunk_id \\\n", "0 chunk:88 0.337694644928 88 \n", "1 chunk:80 0.342052936554 80 \n", "2 chunk:87 0.357760906219 87 \n", "\n", " content \n", "0 Asia Pacific & Latin America 1,932 1,896 2 % 1... \n", "1 Table of Contents\\nCONSOLIDATED OPERATING RESU... \n", "2 Table of Contents\\nOPERATING SEGMENTS\\nAs disc... " ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# execute the query with RedisVL\n", "result=index.query(vector_query)\n", "\n", "# view the results\n", "pd.DataFrame(result)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.075644Z", "start_time": "2025-04-24T16:46:57.067304Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "rZrcd6n7T6LE", "outputId": "fad67a63-76bd-43b9-f62b-b1842ba47605" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "88 0.337694644928\n", "80 0.342052936554\n", "87 0.357760906219\n" ] } ], "source": [ "# paginate through results\n", "for result in index.paginate(vector_query, page_size=1):\n", " print(result[0][\"chunk_id\"], result[0][\"vector_distance\"], flush=True)" ] }, { "cell_type": "markdown", "metadata": { "id": "0Ap6WqPLT6LE" }, "source": [ "### Sort by alternative fields" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.172397Z", "start_time": "2025-04-24T16:46:57.167834Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 175 }, "id": "daLVm6OkLn9T", "outputId": "d77dfc4c-d451-4bf5-91c3-2155232570b9" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idpayloadvector_distancechunk_id
0chunk:80None0.34205293655480
1chunk:83None0.37876605987583
2chunk:87None0.35776090621987
3chunk:88None0.33769464492888
\n", "
" ], "text/plain": [ " id payload vector_distance chunk_id\n", "0 chunk:80 None 0.342052936554 80\n", "1 chunk:83 None 0.378766059875 83\n", "2 chunk:87 None 0.357760906219 87\n", "3 chunk:88 None 0.337694644928 88" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Sort by chunk_id field after vector search limits to topK\n", "vector_query = VectorQuery(\n", " vector=query_embedding,\n", " vector_field_name=\"text_embedding\",\n", " num_results=4,\n", " return_fields=[\"chunk_id\"],\n", " return_score=True\n", ")\n", "\n", "# Decompose vector_query into the core query and the params\n", "query = vector_query.query\n", "params = vector_query.params\n", "\n", "# Pass query and params direct to index.search()\n", "result = index.search(\n", " query.sort_by(\"chunk_id\", asc=True),\n", " params\n", ")\n", "\n", "pd.DataFrame([doc.__dict__ for doc in result.docs])" ] }, { "cell_type": "markdown", "metadata": { "id": "81PoXomtT6LF" }, "source": [ "### Add filters to vector queries" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.279677Z", "start_time": "2025-04-24T16:46:57.274997Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 175 }, "id": "a11G3xXJ1ujC", "outputId": "d968add5-704d-4e22-d3bd-97c1d1103a75" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idvector_distancecontent
0chunk:830.378766059875Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C...
1chunk:1290.41875731945Table of Contents\\nNIKE, INC.\\nCONSOLIDATED ST...
2chunk:730.465415358543Table of Contents\\nITEM 7. MANAGEMENT'S DISCUS...
3chunk:630.49339401722existing businesses, such as our NIKE Direct o...
\n", "
" ], "text/plain": [ " id vector_distance \\\n", "0 chunk:83 0.378766059875 \n", "1 chunk:129 0.41875731945 \n", "2 chunk:73 0.465415358543 \n", "3 chunk:63 0.49339401722 \n", "\n", " content \n", "0 Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C... \n", "1 Table of Contents\\nNIKE, INC.\\nCONSOLIDATED ST... \n", "2 Table of Contents\\nITEM 7. MANAGEMENT'S DISCUS... \n", "3 existing businesses, such as our NIKE Direct o... " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from redisvl.query.filter import Text\n", "\n", "vector_query = VectorQuery(\n", " vector=query_embedding,\n", " vector_field_name=\"text_embedding\",\n", " num_results=4,\n", " return_fields=[\"content\"],\n", " return_score=True\n", ")\n", "\n", "# Set a text filter\n", "text_filter = Text(\"content\") % \"profit\"\n", "\n", "vector_query.set_filter(text_filter)\n", "\n", "result=index.query(vector_query)\n", "pd.DataFrame(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "5XvVv8zAT6LF" }, "source": [ "### Range queries in RedisVL" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.391116Z", "start_time": "2025-04-24T16:46:57.389349Z" }, "id": "bCffoZRx1ujD" }, "outputs": [], "source": [ "from redisvl.query import RangeQuery\n", "\n", "range_query = RangeQuery(\n", " vector=query_embedding,\n", " vector_field_name=\"text_embedding\",\n", " num_results=4,\n", " return_fields=[\"content\"],\n", " return_score=True,\n", " distance_threshold=0.8 # find all items with a semantic distance of less than 0.8\n", ")" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.499232Z", "start_time": "2025-04-24T16:46:57.494328Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 175 }, "id": "0gHmam1Q1ujD", "outputId": "ac80a6ed-4eb8-44d3-881d-87c9271aa10e" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idvector_distancecontent
0chunk:880.337694644928Asia Pacific & Latin America 1,932 1,896 2 % 1...
1chunk:800.342052936554Table of Contents\\nCONSOLIDATED OPERATING RESU...
2chunk:870.357760906219Table of Contents\\nOPERATING SEGMENTS\\nAs disc...
3chunk:830.378766059875Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C...
\n", "
" ], "text/plain": [ " id vector_distance content\n", "0 chunk:88 0.337694644928 Asia Pacific & Latin America 1,932 1,896 2 % 1...\n", "1 chunk:80 0.342052936554 Table of Contents\\nCONSOLIDATED OPERATING RESU...\n", "2 chunk:87 0.357760906219 Table of Contents\\nOPERATING SEGMENTS\\nAs disc...\n", "3 chunk:83 0.378766059875 Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C..." ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result=index.query(range_query)\n", "pd.DataFrame(result)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.667013Z", "start_time": "2025-04-24T16:46:57.662153Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 175 }, "id": "YZg4U21r1ujD", "outputId": "d3db5ac3-6ae9-42c4-aaee-874cecafe3ad" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idvector_distancecontent
0chunk:880.337694644928Asia Pacific & Latin America 1,932 1,896 2 % 1...
1chunk:800.342052936554Table of Contents\\nCONSOLIDATED OPERATING RESU...
2chunk:870.357760906219Table of Contents\\nOPERATING SEGMENTS\\nAs disc...
3chunk:830.378766059875Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C...
\n", "
" ], "text/plain": [ " id vector_distance content\n", "0 chunk:88 0.337694644928 Asia Pacific & Latin America 1,932 1,896 2 % 1...\n", "1 chunk:80 0.342052936554 Table of Contents\\nCONSOLIDATED OPERATING RESU...\n", "2 chunk:87 0.357760906219 Table of Contents\\nOPERATING SEGMENTS\\nAs disc...\n", "3 chunk:83 0.378766059875 Table of Contents\\nGROSS MARGIN\\nFISCAL 2023 C..." ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Add filter to range query\n", "range_query.set_filter(text_filter)\n", "\n", "index.query(range_query)\n", "pd.DataFrame(result)" ] }, { "cell_type": "markdown", "metadata": { "id": "zYYPTQN7T6LG" }, "source": [ "## Building a basic RAG Pipeline from Scratch\n", "We're going to build a basic RAG pipeline from scratch incorporating the following components:\n", "\n", "- Standard semantic search\n", "- Integration with OpenAI for LLM\n", "- Chat completion" ] }, { "cell_type": "markdown", "metadata": { "id": "rCWlVR2OT6LG" }, "source": [ "### Setup RedisVL AsyncSearchIndex" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:46:57.734454Z", "start_time": "2025-04-24T16:46:57.732810Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "_esLGYzbT6LG", "outputId": "d3314a08-8746-4239-dcb2-e7e41b51c640" }, "outputs": [], "source": [ "from redisvl.index import AsyncSearchIndex\n", "\n", "async_index = AsyncSearchIndex.from_dict(schema, redis_url=REDIS_URL)" ] }, { "cell_type": "markdown", "metadata": { "id": "peK4C5xGJkED" }, "source": [ "### Setup OpenAI API" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:12.289527Z", "start_time": "2025-04-24T16:46:57.837857Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "EgdTvz6zJkED", "outputId": "d2ab0e8e-2ecf-458d-881d-6e4658953a71" }, "outputs": [], "source": [ "import openai\n", "import os\n", "import getpass\n", "\n", "CHAT_MODEL = \"gpt-4.1-mini\"\n", "\n", "if \"OPENAI_API_KEY\" not in os.environ:\n", " os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OPENAI_API_KEY :\")\n" ] }, { "cell_type": "markdown", "metadata": { "id": "w8Af-zneT6LH" }, "source": [ "### Baseline Retrieval Augmented Generation\n", "The code below answers a user's questions following this basic flow:\n", "\n", "1. Generate a query_vector from the user's chat question to have an apples to apples comparison against the vector database.\n", "2. Retrieve the most semantically relevant chunks to the user's query from the database.\n", "3. Pass the user query and retrieved context to the `promptify` function to generate the final prompt to be sent to the LLM along with the system prompt and necessary hyperparameters.\n", "4. Return the LLMs response to the user." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:12.308509Z", "start_time": "2025-04-24T16:47:12.303243Z" }, "id": "1V1Tio4-ZjmA" }, "outputs": [], "source": [ "async def answer_question(index: AsyncSearchIndex, query: str, **kwargs) -> str:\n", " \"\"\"Answer the user's question\"\"\"\n", "\n", " SYSTEM_PROMPT = \"\"\"You are a helpful financial analyst assistant that has access\n", " to public financial 10k documents in order to answer users questions about company\n", " performance, ethics, characteristics, and core information.\n", " \"\"\"\n", "\n", " query_vector = hf.embed(query)\n", "\n", " # Fetch context from Redis using vector search\n", " context = await retrieve_context(index, query_vector)\n", "\n", " # Generate contextualized prompt and feed to OpenAI\n", " messages=[\n", " {\"role\": \"user\", \"content\": promptify(query, context)}\n", " ]\n", " response = await openai.AsyncOpenAI().responses.create(\n", " model=CHAT_MODEL,\n", " instructions=SYSTEM_PROMPT,\n", " input=messages,\n", " temperature=0.1\n", " )\n", "\n", " # Response provided by LLM\n", " return response.output_text\n", "\n", "\n", "async def retrieve_context(async_index: AsyncSearchIndex, query_vector) -> str:\n", " \"\"\"Fetch the relevant context from Redis using vector search\"\"\"\n", " results = await async_index.query(\n", " VectorQuery(\n", " vector=query_vector,\n", " vector_field_name=\"text_embedding\",\n", " return_fields=[\"content\"],\n", " num_results=3\n", " )\n", " )\n", " content = \"\\n\".join([result[\"content\"] for result in results])\n", " return content\n", "\n", "\n", "def promptify(query: str, context: str) -> str:\n", " return f'''Use the provided context below derived from public financial\n", " documents to answer the user's question. If you can't answer the user's\n", " question, based on the context; do not guess. If there is no context at all,\n", " respond with \"I don't know\".\n", "\n", " User question:\n", "\n", " {query}\n", "\n", " Helpful context:\n", "\n", " {context}\n", "\n", " Answer:\n", " '''" ] }, { "cell_type": "markdown", "metadata": { "id": "kgVM_g01T6LP" }, "source": [ "### Let's test it out..." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:12.339354Z", "start_time": "2025-04-24T16:47:12.337769Z" }, "id": "pn-PoACdbihY" }, "outputs": [], "source": [ "# Generate a list of questions\n", "questions = [\n", " \"What is the trend in the company's revenue and profit over the past few years?\",\n", " \"What are the company's primary revenue sources?\",\n", " \"How much debt does the company have, and what are its capital expenditure plans?\",\n", " \"What does the company say about its environmental, social, and governance (ESG) practices?\",\n", " \"What is the company's strategy for growth?\"\n", "]" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:20.587275Z", "start_time": "2025-04-24T16:47:12.352722Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 264, "referenced_widgets": [ "22178a562935411f88cad67659ebb7c4", "18c7d5708c124911b214199fedb2b642", "905bc767c24447dc96998d2c5f935776", "3ad99e40e63d4443a80b2b579b32e972", "648ff789b7e640978d79bb73afb8b935", "d653f934619843e28c86c1548dfc6b58", "9845ed85170a4ca1ac53e2e662ec9aa3", "c23e1195ff58417cba20de29285b4f8d", "13c9571c73de48388ffa93f602091320", "52d9d383c841431198b7a53f14da59f1", "ef2b758d4fc241d4becf2ff611954b7e", "77c3e16292de4c0da1efe12946d59602", "f699af42ec874895beb31960b5a7db38", "df531bd2864648d3a3cd081f4395ea53", "eaea17a6fc4e4ae08e8cdb1b894a75ee", "e7653f4691f84722ac67ce2d2eea0c8c", "0296317b893f4d61ba8dcd45fb02260e", "d11dbe6f1f454b239104da75adde3ff4", "53e352c2ac614b58a76b7ea01971b51c", "6d6d0b5efd2149ada10a82e450d79a17", "14433f774cab4e70a984afee44780630", "d720cffbcc444daabf7105d7f46bb738", "083963c0130a4e0f9f8b1123495d2c94", "37f2fb1531d843ca9af8c418b156df0f", "8a9447ddaef84d18b69597c77d13cdab", "4be0f4750d7744bda6bdf9e09efc6e83", "6f77af81f9d7483eb2d9764083a28936", "a77bb82fc74643c5961ad0683719bcc7", "592ad30fe72141e099335a37f2b5d65f", "08a93f48e2ae40dd83c76c02dde1a581", "d865aa9825cc46248db4591bd7eb8202", "c06a936e3f0f4e1d98b886d7b587eb89", "d193499ece3b4e81a4deda0c843d980d", "3ca7831ca79940c9bb1a34b8ef8f763c", "db0773b8f5864b68a2ce8357a09d8012", "06ef9cbf630b445cabe4ad026642f568", "6901df439dbf4b2180d24ad62e9db4f4", "2db40294cdc8476bae1eebb1c85d86fa", "c2a875b112014ea1a88e28fb1d887ccf", "4474549702694f8e87639d19d50498fd", "92480b75b5ac45e2bf7e55ce5c89daaf", "ffd337d71aaf4e1c92c5b53987aa7c72", "21e53784d9154c0f9e0755dd7db64b01", "394450e19075459ba59f53d4f11e21c2", "9d386da534e24c7fa7f26f2c7f6a2d17", "fcda6a6a2e8b4df0b5540e707ad486eb", "37e0240a1d0c4503afd28b0072168c15", "eb4f7add5c074781b7e9d104969c3564", "ffab83c3d271402197ecc4b51225411b", "c7b5d06f461c4ce9a089851c75647544", "c7c362eaa7ea4174b1dd64377445a4b3", "38dd0aae016e4bc48026d0ee30fb807a", "b0de69c2826d4a0ba34b7d7cbce4ff6e", "1b2721602abf42e1bb4d29fb3605644f", "fe546bd8269d48eba90fb932784eea43" ] }, "id": "9M_iU6_hbv0J", "outputId": "b9fc43d9-883a-4795-8a37-8a2f4c545892" }, "outputs": [], "source": [ "import asyncio\n", "\n", "results = await asyncio.gather(*[\n", " answer_question(async_index, question) for question in questions\n", "])" ] }, { "cell_type": "markdown", "metadata": { "id": "CpQ59SRgJkED" }, "source": [ "### Let's view the results" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:20.604843Z", "start_time": "2025-04-24T16:47:20.602566Z" }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "7SZM_xg3b9Gb", "outputId": "758ae31a-2291-4191-aa57-ee941d3319cb" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Question: What is the trend in the company's revenue and profit over the past few years?\n", "Answer: \n", " The company's revenue has shown a consistent upward trend over the past three fiscal years:\n", "\n", "- Total revenues for Nike, Inc. were $44.54 billion in fiscal 2021, increased to $46.71 billion in fiscal 2022 (a 5% increase), and further rose to $51.22 billion in fiscal 2023 (a 10% increase).\n", "- Excluding currency changes, the revenue growth was 6% in 2022 and 16% in 2023.\n", "- Geographic segments such as North America, Europe/Middle East/Africa, and Asia Pacific/Latin America all showed revenue growth in 2023 compared to prior years, while Greater China saw a slight decline in 2023 compared to 2022.\n", "\n", "Regarding profit, measured by Earnings Before Interest and Taxes (EBIT):\n", "\n", "- EBIT increased from fiscal 2021 to 2022 in most regions: North America EBIT was flat at about $5.1 billion in 2021 and 2022, then increased 7% to $5.45 billion in 2023.\n", "- Europe, Middle East & Africa EBIT grew significantly by 35% in 2021 to 2022 and another 7% in 2023.\n", "- Greater China EBIT declined by 27% in 2021 to 2022 and further decreased by 3% in 2023.\n", "\n", "In summary, the company’s overall revenue and profit have generally increased over the past few years, with strong growth in most regions except for some softness in Greater China. \n", "-----------\n", "\n", "Question: What are the company's primary revenue sources?\n", "Answer: \n", " The company's primary revenue sources are:\n", "\n", "1. Footwear – This is the largest revenue source, with $35,290 million in total revenues for the year ended May 31, 2023.\n", "2. Apparel – The second largest source, generating $13,933 million in total revenues for the same period.\n", "3. Equipment – Contributing $1,755 million in revenues.\n", "4. Other revenues – Including licensing and miscellaneous revenues, totaling $239 million.\n", "\n", "These revenues are generated across various geographic segments including North America, Europe/Middle East & Africa, Greater China, Asia Pacific & Latin America, and Global Brand Divisions. Additionally, revenues come from two main distribution channels: sales to wholesale customers and sales through direct-to-consumer channels. \n", "-----------\n", "\n", "Question: How much debt does the company have, and what are its capital expenditure plans?\n", "Answer: \n", " As of May 31, 2023, the company has total long-term debt of $8,927 million, net of unamortized premiums, discounts, and debt issuance costs. There is no current portion of long-term debt reported for 2023 (compared to $500 million in 2022). The long-term debt consists of various corporate term debts with maturities ranging from 2025 to 2050 and interest rates between 2.25% and 3.88%.\n", "\n", "Regarding capital expenditure plans, the provided context does not include specific information about the company's capital expenditure plans. Therefore, I cannot provide details on their capital expenditure plans based on the given information. \n", "-----------\n", "\n", "Question: What does the company say about its environmental, social, and governance (ESG) practices?\n", "Answer: \n", " The company acknowledges an increased focus from governmental and non-governmental organizations, consumers, customers, employees, and other stakeholders on sustainability matters, including responsible sourcing, deforestation, use of plastic, energy and water consumption, recyclability or recoverability of packaging, and materials transparency. It recognizes that addressing these issues may require incurring increased costs for additional transparency, due diligence, and reporting.\n", "\n", "The company also notes that various governmental authorities worldwide are proposing and likely to continue proposing legislative and regulatory initiatives aimed at reducing or mitigating climate change impacts. These differing approaches to climate regulation across countries and regions could increase the complexity and potential costs of compliance.\n", "\n", "To respond, the company has announced sustainability-related goals and targets. However, it acknowledges that there is no assurance stakeholders will agree with its strategies, and any perception of failure to act responsibly or to meet legal or regulatory requirements related to climate change could result in adverse publicity and negatively affect its business and reputation.\n", "\n", "The company also highlights that executing these strategies and achieving its goals involves risks and uncertainties, many outside its control, including potential impacts on investments in facilities and equipment, availability and cost of raw materials, product demand, and overall business, operating results, and financial condition.\n", "\n", "In summary, the company is actively engaged in ESG practices focused on sustainability and climate change but recognizes the challenges, risks, and uncertainties involved in these efforts. \n", "-----------\n", "\n", "Question: What is the company's strategy for growth?\n", "Answer: \n", " The provided context does not explicitly state the company's strategy for growth. It includes detailed financial data such as revenues, earnings, and capital expenditures by geographic segment and product line, but it does not describe strategic initiatives or growth plans.\n", "\n", "Therefore, based on the given information, I cannot determine the company's strategy for growth. \n", "-----------\n", "\n" ] } ], "source": [ "for i, r in enumerate(results):\n", " print(f\"Question: {questions[i]}\")\n", " print(f\"Answer: \\n {r}\", \"\\n-----------\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Improve performance and cut costs with LLM caching" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:20.654925Z", "start_time": "2025-04-24T16:47:20.639324Z" } }, "outputs": [], "source": [ "from redisvl.extensions.cache.llm import SemanticCache\n", "\n", "llmcache = SemanticCache(\n", " name=\"llmcache\",\n", " vectorizer=hf,\n", " redis_url=REDIS_URL,\n", " ttl=120,\n", " distance_threshold=0.2,\n", " overwrite=True,\n", ")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:20.666720Z", "start_time": "2025-04-24T16:47:20.664080Z" } }, "outputs": [], "source": [ "from functools import wraps\n", "\n", "# Create an LLM caching decorator\n", "def cache(func):\n", " @wraps(func)\n", " async def wrapper(index, query_text, *args, **kwargs):\n", " query_vector = llmcache._vectorizer.embed(query_text)\n", "\n", " # Check the cache with the vector\n", " if result := llmcache.check(vector=query_vector):\n", " print(\"Cache hit!\")\n", " return result[0]['response']\n", "\n", " response = await func(index, query_text, query_vector=query_vector)\n", " llmcache.store(query_text, response, query_vector)\n", " return response\n", " return wrapper\n", "\n", "# Use the same answer_question function as before, but retrieve the result from the cache upon cache hit\n", "answer_question = cache(answer_question)\n" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:21.577338Z", "start_time": "2025-04-24T16:47:20.691181Z" } }, "outputs": [ { "data": { "text/plain": [ "\"Nike's total revenue for fiscal year 2023 was $51.2 billion, which represents a 10% increase compared to fiscal year 2022, when the revenue was $46.7 billion.\"" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_SKIP\n", "query = \"What was Nike's revenue last year compared to this year??\"\n", "\n", "await answer_question(async_index, query)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:21.606033Z", "start_time": "2025-04-24T16:47:21.590864Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cache hit!\n" ] }, { "data": { "text/plain": [ "\"Nike's total revenue for fiscal year 2023 was $51.2 billion, which represents a 10% increase compared to fiscal year 2022, when the revenue was $46.7 billion.\"" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_SKIP\n", "query = \"What was Nike's total revenue in the last year compared to now??\"\n", "\n", "await answer_question(async_index, query)\n", "\n", "# notice no HTTP request to OpenAI since this question is \"close enough\" to the last one" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Improve personalization by including chat session history\n", "\n", "To preserve state in the conversation, offload conversation history to a database that can handle high transaction throughput for writes/reads to limit system latency.\n" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:21.642412Z", "start_time": "2025-04-24T16:47:21.634222Z" } }, "outputs": [], "source": [ "from functools import wraps\n", "from redisvl.extensions.message_history import MessageHistory\n", "\n", "\n", "class ChatBot:\n", " def __init__(self, index: AsyncSearchIndex, vectorizer: BaseVectorizer, user: str):\n", " self.index = index\n", " self.vectorizer = vectorizer\n", " self.history = MessageHistory(\n", " name=f\"chat_session_{user}\",\n", " session_tag=user,\n", " redis_url=REDIS_URL,\n", " )\n", "\n", " @staticmethod\n", " def promptify(query: str, context: str) -> str:\n", " return f'''Use the provided context below derived from public financial\n", " documents to answer the user's question. If you can't answer the user's\n", " question, based on the context; do not guess. If there is no context at all,\n", " respond with \"I don't know\".\n", "\n", " User question:\n", "\n", " {query}\n", "\n", " Helpful context:\n", "\n", " {context}\n", "\n", " Answer:\n", " '''\n", "\n", " async def retrieve_context(self, query_vector) -> str:\n", " \"\"\"Fetch the relevant context from Redis using vector search\"\"\"\n", " results = await self.index.query(\n", " VectorQuery(\n", " vector=query_vector,\n", " vector_field_name=\"text_embedding\",\n", " return_fields=[\"content\"],\n", " num_results=3\n", " )\n", " )\n", " content = \"\\n\".join([result[\"content\"] for result in results])\n", " return content\n", "\n", " async def clear_history(self):\n", " \"\"\"Clear session chat\"\"\"\n", " self.history.clear()\n", "\n", " async def answer_question(self, query: str):\n", " \"\"\"Answer the user's question with historical context and caching baked-in\"\"\"\n", "\n", " SYSTEM_PROMPT = \"\"\"You are a helpful financial analyst assistant that has access\n", " to public financial 10k documents in order to answer users questions about company\n", " performance, ethics, characteristics, and core information.\n", " \"\"\"\n", "\n", " # Create query vector\n", " query_vector = self.vectorizer.embed(query)\n", "\n", " # Check the cache with the vector\n", " if result := llmcache.check(vector=query_vector):\n", " answer = result[0]['response']\n", " else:\n", " context = await self.retrieve_context(query_vector)\n", " messages = self.history.messages + [{\"role\": \"user\", \"content\": self.promptify(query, context)}]\n", "\n", " # Response provided by GPT-3.5\n", " response = await openai.AsyncOpenAI().responses.create(\n", " model=CHAT_MODEL,\n", " instructions=SYSTEM_PROMPT,\n", " input=messages,\n", " temperature=0.1\n", " )\n", " answer = response.output_text\n", " llmcache.store(query, answer, query_vector)\n", "\n", " # Add message history\n", " self.history.add_messages([\n", " {\"role\": \"user\", \"content\": query},\n", " {\"role\": \"assistant\", \"content\": answer}\n", " ])\n", "\n", " return answer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test the entire RAG workflow" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:21.669248Z", "start_time": "2025-04-24T16:47:21.663308Z" } }, "outputs": [], "source": [ "# Setup Session\n", "chat = ChatBot(async_index, vectorizer=hf, user=\"Andrew\")\n", "await chat.clear_history()" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:27.496044Z", "start_time": "2025-04-24T16:47:21.702428Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello! How can I assist you today?\n" ] } ], "source": [ "# Run a simple chat\n", "stopterms = [\"exit\", \"quit\", \"end\", \"cancel\"]\n", "\n", "# Simple Chat\n", "# NBVAL_SKIP\n", "while True:\n", " user_query = input()\n", " if user_query.lower() in stopterms or not user_query:\n", " break\n", " answer = await chat.answer_question(user_query)\n", " print(answer, flush=True)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:27.527276Z", "start_time": "2025-04-24T16:47:27.522755Z" } }, "outputs": [ { "data": { "text/plain": [ "[{'role': , 'content': 'hi'},\n", " {'role': ,\n", " 'content': 'Hello! How can I assist you today?'}]" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NBVAL_SKIP\n", "chat.history.messages" ] }, { "cell_type": "markdown", "metadata": { "id": "D_eiWikCJkED" }, "source": [ "# You now have a working RAG pipeline!\n", "\n", "As you can see, it is easy to get started with RAG and we were able to get decent chat results from this simple setup. To go beyond the basic example though see the [advanced_rag](./04_advanced_redisvl.ipynb) notebook.\n", "\n", "This notebook covers:\n", "\n", "- **Improving accuracy** with dense content representations and query rewriting/expansion\n", "- **Improving performance and optimizing cost** with semantic caching\n", "- **Improving personalization** with chat session memory.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "Wscs4Mvo1ujD" }, "source": [ "## Cleanup\n", "\n", "Clean up the database." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "ExecuteTime": { "end_time": "2025-04-24T16:47:34.042787Z", "start_time": "2025-04-24T16:47:34.036106Z" }, "id": "On6yNuQn1ujD" }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "await async_index.client.flushall()" ] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "T4", "provenance": [] }, "kernelspec": { "display_name": ".venv (3.12.5)", "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.12.5" } }, "nbformat": 4, "nbformat_minor": 0 }