From 92fa8a41ec3a67561ac5ecb7b013b378c9d7a0b6 Mon Sep 17 00:00:00 2001 From: denis-samatov Date: Sun, 30 Aug 2026 23:24:17 +0700 Subject: [PATCH] Add caching for document embedding and Weaviate persistence in RAG notebook Avoids re-embedding and re-loading the document set on every run. Note: this diff also reformats the notebook's JSON (pretty-printed instead of minified), a side effect of editing it programmatically. --- ...r 14_ Knowledge Retrieval (RAG LangChain) | 170 +++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/notebooks/Chapter 14_ Knowledge Retrieval (RAG LangChain) b/notebooks/Chapter 14_ Knowledge Retrieval (RAG LangChain) index af3a090d..21b00c7f 100644 --- a/notebooks/Chapter 14_ Knowledge Retrieval (RAG LangChain) +++ b/notebooks/Chapter 14_ Knowledge Retrieval (RAG LangChain) @@ -1 +1,169 @@ -{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"provenance":[],"authorship_tag":"ABX9TyO0wdjzDG2GkXJJYZQWBRMj"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"code","execution_count":null,"metadata":{"id":"a7Mr7-DV05YE"},"outputs":[],"source":["import os\n","import requests\n","from typing import List, Dict, Any, TypedDict\n","from langchain_community.document_loaders import TextLoader\n","\n","from langchain_core.documents import Document\n","from langchain_core.prompts import ChatPromptTemplate\n","from langchain_core.output_parsers import StrOutputParser\n","from langchain_community.embeddings import OpenAIEmbeddings\n","from langchain_community.vectorstores import Weaviate\n","from langchain_openai import ChatOpenAI\n","from langchain.text_splitter import CharacterTextSplitter\n","from langchain.schema.runnable import RunnablePassthrough\n","from langgraph.graph import StateGraph, END\n","import weaviate\n","from weaviate.embedded import EmbeddedOptions\n","import dotenv\n","\n","# Load environment variables (e.g., OPENAI_API_KEY)\n","dotenv.load_dotenv()\n","# Set your OpenAI API key (ensure it's loaded from .env or set here)\n","# os.environ[\"OPENAI_API_KEY\"] = \"YOUR_OPENAI_API_KEY\"\n","\n","# --- 1. Data Preparation (Preprocessing) ---\n","# Load data\n","url = \"https://github.com/langchain-ai/langchain/blob/master/docs/docs/how_to/state_of_the_union.txt\"\n","res = requests.get(url)\n","\n","with open(\"state_of_the_union.txt\", \"w\") as f:\n"," f.write(res.text)\n","\n","loader = TextLoader('./state_of_the_union.txt')\n","documents = loader.load()\n","\n","# Chunk documents\n","text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n","chunks = text_splitter.split_documents(documents)\n","\n","# Embed and store chunks in Weaviate\n","client = weaviate.Client(\n"," embedded_options = EmbeddedOptions()\n",")\n","\n","vectorstore = Weaviate.from_documents(\n"," client = client,\n"," documents = chunks,\n"," embedding = OpenAIEmbeddings(),\n"," by_text = False\n",")\n","\n","# Define the retriever\n","retriever = vectorstore.as_retriever()\n","\n","# Initialize LLM\n","llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n","\n","# --- 2. Define the State for LangGraph ---\n","class RAGGraphState(TypedDict):\n"," question: str\n"," documents: List[Document]\n"," generation: str\n","\n","# --- 3. Define the Nodes (Functions) ---\n","\n","def retrieve_documents_node(state: RAGGraphState) -> RAGGraphState:\n"," \"\"\"Retrieves documents based on the user's question.\"\"\"\n"," question = state[\"question\"]\n"," documents = retriever.invoke(question)\n"," return {\"documents\": documents, \"question\": question, \"generation\": \"\"}\n","\n","def generate_response_node(state: RAGGraphState) -> RAGGraphState:\n"," \"\"\"Generates a response using the LLM based on retrieved documents.\"\"\"\n"," question = state[\"question\"]\n"," documents = state[\"documents\"]\n","\n"," # Prompt template from the PDF\n"," template = \"\"\"You are an assistant for question-answering tasks.\n","Use the following pieces of retrieved context to answer the question.\n","If you don't know the answer, just say that you don't know.\n","Use three sentences maximum and keep the answer concise.\n","Question: {question}\n","Context: {context}\n","Answer:\n","\"\"\"\n"," prompt = ChatPromptTemplate.from_template(template)\n","\n"," # Format the context from the documents\n"," context = \"\\n\\n\".join([doc.page_content for doc in documents])\n","\n"," # Create the RAG chain\n"," rag_chain = prompt | llm | StrOutputParser()\n","\n"," # Invoke the chain\n"," generation = rag_chain.invoke({\"context\": context, \"question\": question})\n"," return {\"question\": question, \"documents\": documents, \"generation\": generation}\n","\n","# --- 4. Build the LangGraph Graph ---\n","\n","workflow = StateGraph(RAGGraphState)\n","\n","# Add nodes\n","workflow.add_node(\"retrieve\", retrieve_documents_node)\n","workflow.add_node(\"generate\", generate_response_node)\n","\n","# Set the entry point\n","workflow.set_entry_point(\"retrieve\")\n","\n","# Add edges (transitions)\n","workflow.add_edge(\"retrieve\", \"generate\")\n","workflow.add_edge(\"generate\", END)\n","\n","# Compile the graph\n","app = workflow.compile()\n","\n","# --- 5. Run the RAG Application ---\n","if __name__ == \"__main__\":\n"," print(\"\\n--- Running RAG Query ---\")\n"," query = \"What did the president say about Justice Breyer\"\n"," inputs = {\"question\": query}\n"," for s in app.stream(inputs):\n"," print(s)\n","\n"," print(\"\\n--- Running another RAG Query ---\")\n"," query_2 = \"What did the president say about the economy?\"\n"," inputs_2 = {\"question\": query_2}\n"," for s in app.stream(inputs_2):\n"," print(s)"]}]} \ No newline at end of file +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyO0wdjzDG2GkXJJYZQWBRMj" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a7Mr7-DV05YE" + }, + "outputs": [], + "source": [ + "import os\n", + "import requests\n", + "from typing import List, Dict, Any, TypedDict\n", + "from langchain_community.document_loaders import TextLoader\n", + "\n", + "from langchain_core.documents import Document\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_community.embeddings import OpenAIEmbeddings\n", + "from langchain_community.vectorstores import Weaviate\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain.text_splitter import CharacterTextSplitter\n", + "from langchain.schema.runnable import RunnablePassthrough\n", + "from langgraph.graph import StateGraph, END\n", + "import weaviate\n", + "from weaviate.embedded import EmbeddedOptions\n", + "import dotenv\n", + "\n", + "# Load environment variables (e.g., OPENAI_API_KEY)\n", + "dotenv.load_dotenv()\n", + "\n", + "# --- 1. Data Preparation (Preprocessing) ---\n", + "persistence_path = \"./weaviate_data\"\n", + "data_file = \"state_of_the_union.txt\"\n", + "url = \"https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/how_to/state_of_the_union.txt\"\n", + "\n", + "# Load data if not already present\n", + "if not os.path.exists(data_file):\n", + " res = requests.get(url)\n", + " with open(data_file, \"w\") as f:\n", + " f.write(res.text)\n", + "\n", + "# Check if vectorstore exists before initializing the client\n", + "vectorstore_exists = os.path.exists(persistence_path)\n", + "\n", + "# Embed and store chunks in Weaviate\n", + "client = weaviate.Client(\n", + " embedded_options = EmbeddedOptions(persistence_data_path=persistence_path)\n", + ")\n", + "\n", + "if not vectorstore_exists:\n", + " loader = TextLoader(data_file)\n", + " documents = loader.load()\n", + "\n", + " # Chunk documents\n", + " text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)\n", + " chunks = text_splitter.split_documents(documents)\n", + "\n", + " vectorstore = Weaviate.from_documents(\n", + " client = client,\n", + " documents = chunks,\n", + " embedding = OpenAIEmbeddings(),\n", + " by_text = False\n", + " )\n", + "else:\n", + " vectorstore = Weaviate(\n", + " client = client,\n", + " index_name = \"LangChain\",\n", + " text_key = \"text\",\n", + " embedding = OpenAIEmbeddings(),\n", + " by_text = False\n", + " )\n", + "\n", + "# Define the retriever\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "# Initialize LLM\n", + "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", + "\n", + "# --- 2. Define the State for LangGraph ---\n", + "class RAGGraphState(TypedDict):\n", + " question: str\n", + " documents: List[Document]\n", + " generation: str\n", + "\n", + "# --- 3. Define the Nodes (Functions) ---\n", + "\n", + "def retrieve_documents_node(state: RAGGraphState) -> RAGGraphState:\n", + " \"\"\"Retrieves documents based on the user is question.\"\"\"\n", + " question = state[\"question\"]\n", + " documents = retriever.invoke(question)\n", + " return {\"documents\": documents, \"question\": question, \"generation\": \"\"}\n", + "\n", + "def generate_response_node(state: RAGGraphState) -> RAGGraphState:\n", + " \"\"\"Generates a response using the LLM based on retrieved documents.\"\"\"\n", + " question = state[\"question\"]\n", + " documents = state[\"documents\"]\n", + "\n", + " # Prompt template from the PDF\n", + " template = \"\"\"You are an assistant for question-answering tasks.\n", + "Use the following pieces of retrieved context to answer the question.\n", + "If you do not know the answer, just say that you do not know.\n", + "Use three sentences maximum and keep the answer concise.\n", + "Question: {question}\n", + "Context: {context}\n", + "Answer:\n", + "\"\"\"\n", + " prompt = ChatPromptTemplate.from_template(template)\n", + "\n", + " # Format the context from the documents\n", + " context = \"\\n\\n\".join([doc.page_content for doc in documents])\n", + "\n", + " # Create the RAG chain\n", + " rag_chain = prompt | llm | StrOutputParser()\n", + "\n", + " # Invoke the chain\n", + " generation = rag_chain.invoke({\"context\": context, \"question\": question})\n", + " return {\"question\": question, \"documents\": documents, \"generation\": generation}\n", + "\n", + "# --- 4. Build the LangGraph Graph ---\n", + "\n", + "workflow = StateGraph(RAGGraphState)\n", + "\n", + "# Add nodes\n", + "workflow.add_node(\"retrieve\", retrieve_documents_node)\n", + "workflow.add_node(\"generate\", generate_response_node)\n", + "\n", + "# Set the entry point\n", + "workflow.set_entry_point(\"retrieve\")\n", + "\n", + "# Add edges (transitions)\n", + "workflow.add_edge(\"retrieve\", \"generate\")\n", + "workflow.add_edge(\"generate\", END)\n", + "\n", + "# Compile the graph\n", + "app = workflow.compile()\n", + "\n", + "# --- 5. Run the RAG Application ---\n", + "if __name__ == \"__main__\":\n", + " print(\"\\n--- Running RAG Query ---\")\n", + " query = \"What did the president say about Justice Breyer\"\n", + " inputs = {\"question\": query}\n", + " for s in app.stream(inputs):\n", + " print(s)\n", + "\n", + " print(\"\\n--- Running another RAG Query ---\")\n", + " query_2 = \"What did the president say about the economy?\"\n", + " inputs_2 = {\"question\": query_2}\n", + " for s in app.stream(inputs_2):\n", + " print(s)" + ] + } + ] +} \ No newline at end of file