{ "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", "# RAG with LLamaIndex\n", "\n", "This notebook uses [LLamaIndex](https://docs.llamaindex.ai/en/stable/) and [Redis](https://redis.com) to setup a basic RAG implementation.\n", "\n", "## Let's Begin!\n", "\"Open" ] }, { "cell_type": "markdown", "metadata": { "id": "ctOVb_LZ1vmk" }, "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": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UQezgPCG1vml", "outputId": "97b9bc03-da1b-439a-c37b-be6fdb58ab21" }, "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": "9eieJowO1vmo" }, "source": [ "### Install Python Dependencies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install -q llama-index \"llama-index-vector-stores-redis>=0.4.0\" llama-index-embeddings-cohere llama-index-embeddings-openai" ] }, { "cell_type": "markdown", "metadata": {}, "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": {}, "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": {}, "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": {}, "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": {}, "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": 3, "metadata": {}, "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": {}, "source": [ "## RAG with LlamaIndex" ] }, { "cell_type": "markdown", "metadata": { "id": "7MaVqU8Y1vms" }, "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", "- `UnstructuredFileLoader` is not the only document loader type that LangChain provides. Docs: https://python.langchain.com/docs/integrations/document_loaders/unstructured_file\n", "- `RecursiveCharacterTextSplitter` is what we use to create smaller chunks of text from the doc. Docs: https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "I7VS1bPr1vmt", "outputId": "72299230-26c4-4d80-d61f-138616e2173b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sample doc Doc ID: b90e8ae9-7204-4e86-87ff-16cc68f9fff4\n", "Text: 2022 COLORADO\n" ] } ], "source": [ "from llama_index.core import VectorStoreIndex, SimpleDirectoryReader\n", "from llama_index.vector_stores.redis import RedisVectorStore\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", "docs = SimpleDirectoryReader(data_path).load_data()\n", "\n", "print(f\"Sample doc {docs[0]}\")" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import getpass\n", "\n", "if \"OPENAI_API_KEY\" not in os.environ:\n", " os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OPENAI_API_KEY\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Index\n", "In the following block, Llama-index will embed the docs provide automatically with OpenAI by default and then store them in the storage_context (Redis)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from llama_index.core import StorageContext\n", "\n", "vector_store = RedisVectorStore(redis_url=REDIS_URL, overwrite=True)\n", "\n", "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", "\n", "index = VectorStoreIndex.from_documents(docs, storage_context=storage_context)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Init retriever and query_engine classes" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "query_engine = index.as_query_engine()\n", "retriever = index.as_retriever()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run vector search\n", "We can see the results of the vector search with the retrieve method" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Node ID: 023a5d47-4560-4591-ab20-37e4522863aa\n", "Text: Table of Contents FISCAL 2023 NIKE BRAND REVENUE HIGHLIGHTSThe\n", "following tables present NIKE Brand revenues disaggregated by\n", "reportable operating segment, distribution channel and major product\n", "line: FISCAL 2023 COMPARED TO FISCAL 2022 • NIKE, Inc. Revenues were\n", "$51.2 billion in fiscal 2023, which increased 10% and 16% compared to\n", "fiscal 2022 on...\n", "Score: 0.899\n", "\n", "Node ID: 10b3b6b1-112c-4279-a75a-d4d866c07f6b\n", "Text: Sales through NIKE Direct Global Brand Divisions in FISCAL 2023\n", "amounted to $21,308 million. Total NIKE Brand Wholesale Equivalent\n", "Revenues for FISCAL 2023 were $48,763 million, with a 10% rise from\n", "FISCAL 2022. NIKE Brand Wholesale Equivalent Revenues included sales\n", "from Men's, Women's, and NIKE Kids' categories. Jordan Brand revenues\n", "increased...\n", "Score: 0.883\n", "\n" ] } ], "source": [ "result_nodes = retriever.retrieve(\"What was nike's revenue in fiscal 23?\")\n", "for node in result_nodes:\n", " print(node)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run query engine\n", "Now let's get a final RAGlike response" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"NIKE's revenue in fiscal 23 was $51.2 billion.\"" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "response = query_engine.query(\"What was nike's revenue in fiscal 23?\")\n", "response.response" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Use a custom index schema\n", "\n", "In most use cases, you need the ability to customize the underling index configuration\n", "and specification. For example, this is handy in order to define specific metadata filters you wish to enable.\n", "\n", "With Redis, this is as simple as defining an index schema object\n", "(from file or dict) and passing it through to the vector store client wrapper." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from redisvl.schema import IndexSchema\n", "\n", "\n", "custom_schema = IndexSchema.from_dict(\n", " {\n", " # customize basic index specs\n", " \"index\": {\n", " \"name\": \"custom_index\",\n", " \"prefix\": \"docs\",\n", " \"key_separator\": \":\",\n", " },\n", " # customize fields that are indexed\n", " \"fields\": [\n", " # required fields for llamaindex\n", " {\"type\": \"tag\", \"name\": \"id\"},\n", " {\"type\": \"tag\", \"name\": \"doc_id\"},\n", " {\"type\": \"text\", \"name\": \"text\"},\n", " # custom metadata fields\n", " {\"type\": \"numeric\", \"name\": \"updated_at\"},\n", " {\"type\": \"tag\", \"name\": \"file_name\"},\n", " # custom vector field definition for cohere embeddings\n", " {\n", " \"type\": \"vector\",\n", " \"name\": \"vector\",\n", " \"attrs\": {\n", " \"dims\": 1536,\n", " \"algorithm\": \"hnsw\",\n", " \"distance_metric\": \"cosine\",\n", " },\n", " },\n", " ],\n", " }\n", ")" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "IndexInfo(name='custom_index', prefix='docs', key_separator=':', storage_type=)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "custom_schema.index" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'id': TagField(name='id', type='tag', path=None, attrs=TagFieldAttributes(sortable=False, separator=',', case_sensitive=False, withsuffixtrie=False)),\n", " 'doc_id': TagField(name='doc_id', type='tag', path=None, attrs=TagFieldAttributes(sortable=False, separator=',', case_sensitive=False, withsuffixtrie=False)),\n", " 'text': TextField(name='text', type='text', path=None, attrs=TextFieldAttributes(sortable=False, weight=1, no_stem=False, withsuffixtrie=False, phonetic_matcher=None)),\n", " 'updated_at': NumericField(name='updated_at', type='numeric', path=None, attrs=NumericFieldAttributes(sortable=False)),\n", " 'file_name': TagField(name='file_name', type='tag', path=None, attrs=TagFieldAttributes(sortable=False, separator=',', case_sensitive=False, withsuffixtrie=False)),\n", " 'vector': HNSWVectorField(name='vector', type='vector', path=None, attrs=HNSWVectorFieldAttributes(dims=1536, algorithm=, datatype=, distance_metric=, initial_cap=None, m=16, ef_construction=200, ef_runtime=10, epsilon=0.01))}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "custom_schema.fields" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "vector_store = RedisVectorStore(\n", " schema=custom_schema, # provide customized schema\n", " redis_url=REDIS_URL,\n", " overwrite=True,\n", ")\n", "\n", "storage_context = StorageContext.from_defaults(vector_store=vector_store)\n", "\n", "# build and load index from documents and storage context\n", "index = VectorStoreIndex.from_documents(\n", " docs, storage_context=storage_context\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Query the vector store and filter on metadata\n", "Now that we have additional metadata indexed in Redis, let's try some queries which add in filters. As an example, we'll do a search for chunks with the word \"audit\" from an exact file \"amzn-10k-2023.pdf\". " ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "from llama_index.core.vector_stores import (\n", " MetadataFilters,\n", " MetadataFilter,\n", " ExactMatchFilter,\n", ")\n", "\n", "retriever = index.as_retriever(\n", " similarity_top_k=3,\n", " filters=MetadataFilters(\n", " filters=[\n", " ExactMatchFilter(key=\"file_name\", value=\"amzn-10k-2023.pdf\"),\n", " MetadataFilter(\n", " key=\"text\",\n", " value=\"audit\",\n", " operator=\"text_match\",\n", " ),\n", " ],\n", " condition=\"and\",\n", " ),\n", ")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Node ID: 013f339e-7fda-4fc7-baf0-afbb3dadf47d\n", "Text: Table of Contents valuation. In the ordinary course of our\n", "business, there are many transactions and calculations for which the\n", "ultimate tax determination is uncertain. Significant judgment is\n", "required in evaluating and estimating our tax expense, assets, and\n", "liabilities. We are also subject to tax controversies in various\n", "jurisdictions that can...\n", "Score: 0.747\n", "\n", "Node ID: ac3f2b03-0520-4a50-ba3e-a97ad0a6f643\n", "Text: Table of Contents Included in other income (expense), net in\n", "2021 and 2022 is a marketable equity securities valuation gain (loss)\n", "of $11.8 billion and $(12.7) billion from our equity investment in\n", "Rivian Automotive, Inc. (“Rivian”). Our investment in Rivian’s\n", "preferred stock was accounted for at cost, with adjustments for\n", "observable changes in ...\n", "Score: 0.740\n", "\n", "Node ID: 62ef1673-dcfe-4ba0-a437-7b142cda4114\n", "Text: Exhibit 31.1 CERTIFICATIONS I, Andrew R. Jassy, certify that: 1.\n", "I have reviewed this Form 10-K of Amazon.com, Inc.; 2. Based on my\n", "knowledge, this report does not contain any untrue statement of a\n", "material fact or omit to state a material fact necessary to make the\n", "statements made, in light of the circumstances under which such\n", "statements were ...\n", "Score: 0.732\n", "\n" ] } ], "source": [ "result_nodes = retriever.retrieve(\"What did the author learn?\")\n", "\n", "for node in result_nodes:\n", " print(node)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "T4", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "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": 0 }