{ "cells": [ { "cell_type": "markdown", "id": "9e7a7c86", "metadata": {}, "source": [ "### Custom RAG Agent Workflow with Open Source LLMs Running Locally on Intel CPU" ] }, { "cell_type": "markdown", "id": "f309f56d-1db4-4e03-870e-a2a6f5ee4dc5", "metadata": {}, "source": [ "Author - Pratool Bharti (pratool.bharti@intel.com)" ] }, { "cell_type": "markdown", "id": "0af01c3c-c42a-4ba5-95fa-4b83fd77fe9d", "metadata": {}, "source": [ "This notebook demonstrates a Retrieval-Augmented Generation (RAG) agent that routes questions through two paths to find answers. The agent generates answers based on documents retrieved from either the vector database or web search. If the vector database lacks relevant information, the agent opts for web search. Open-source models for LLM and embeddings are used locally on an Intel Xeon CPU to execute this pipeline." ] }, { "cell_type": "markdown", "id": "8b50e68f", "metadata": {}, "source": [ "
\n", "
Flow chart for the Custom RAG Agent Workflow
\n", "" ] }, { "cell_type": "markdown", "id": "24f76969", "metadata": {}, "source": [ "Install required libraries in a conda or venv environment" ] }, { "cell_type": "code", "execution_count": 1, "id": "746ae008", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip available: \u001b[0m\u001b[31;49m22.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" ] } ], "source": [ "!pip install --upgrade --quiet tiktoken scikit-learn gpt4all langchain langchain-community langchain-core langchain_nomic langchain_ollama langgraph " ] }, { "cell_type": "markdown", "id": "399f7e2e", "metadata": {}, "source": [ "In Linux systems, use following commands to install Ollama and download Llama 3.1 model locally.\n", "```\n", "curl -fsSL https://ollama.com/install.sh | sh\n", "ollama run llama3.1\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "c7ea62fe-7ea0-4e98-95e5-df79599b1545", "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "This cell asks you to set up environment variables for a local RAG (Retrieval-Augmented Generation) agent.\n", "\n", "Environment Variables:\n", "- USER_AGENT: Specifies the user agent string to be used.\n", "- LANGSMITH_TRACING: Enables or disables tracing for LangChain.\n", "- LANGSMITH_API_KEY: API key for accessing LangChain services.\n", "- TAVILY_API_KEY: API key for accessing Tavily services.\n", "\"\"\"\n", "import os\n", "\n", "os.environ[\"USER_AGENT\"] = \"myagent\"\n", "os.environ[\"LANGSMITH_TRACING\"] = \"true\"\n", "os.environ[\"LANGSMITH_API_KEY\"] = \"xxxx\"\n", "os.environ[\"TAVILY_API_KEY\"] = \"tvly-xxxx\"" ] }, { "cell_type": "markdown", "id": "f4fe714b", "metadata": {}, "source": [ "Use local embedding model to store documents in vector database" ] }, { "cell_type": "code", "execution_count": 3, "id": "8d1b3be3-b150-4e39-aecf-f4a51a5eb358", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Failed to load libllamamodel-mainline-cuda-avxonly.so: dlopen: libcudart.so.11.0: cannot open shared object file: No such file or directory\n", "Failed to load libllamamodel-mainline-cuda.so: dlopen: libcudart.so.11.0: cannot open shared object file: No such file or directory\n" ] } ], "source": [ "\"\"\"\n", "This cell performs the following tasks:\n", "\n", "1. Imports necessary modules and classes from langchain and related libraries.\n", "2. Defines a list of URLs from IRS to load tax related documents from.\n", "3. Loads documents from the specified URLs using the WebBaseLoader.\n", "4. Flattens the list of loaded documents.\n", "5. Initializes a RecursiveCharacterTextSplitter with a specified chunk size and overlap.\n", "6. Splits the loaded documents into chunks using the text splitter.\n", "7. Initializes an SKLearnVectorStore with the document chunks embedded using local embeddings model \"nomic-embed-text-v1.5\" from NomicEmbeddings.\n", "8. Converts the vector store into a retriever with a specified number of nearest neighbors (k=4).\n", "\n", "Modules and Classes:\n", "- RecursiveCharacterTextSplitter: Splits text into chunks based on character count.\n", "- WebBaseLoader: Loads documents from web URLs.\n", "- SKLearnVectorStore: Stores document vectors for retrieval.\n", "- NomicEmbeddings: Generates embeddings for documents.\n", "- tool: Utility for defining tools.\n", "\n", "Variables:\n", "- urls: List of URLs to load documents from.\n", "- docs: List of loaded documents from the URLs.\n", "- docs_list: Flattened list of loaded documents.\n", "- text_splitter: Instance of RecursiveCharacterTextSplitter.\n", "- doc_splits: List of document chunks.\n", "- vectorstore: Instance of SKLearnVectorStore.\n", "- retriever: Retriever instance for querying the vector store.\n", "\"\"\"\n", "\n", "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", "from langchain_community.document_loaders import WebBaseLoader\n", "from langchain_community.vectorstores import SKLearnVectorStore\n", "from langchain_core.tools import tool\n", "from langchain_nomic.embeddings import NomicEmbeddings\n", "\n", "# List of URLs to load documents from\n", "urls = [\n", " \"https://www.irs.gov/newsroom/irs-releases-tax-inflation-adjustments-for-tax-year-2025\",\n", " \"https://www.irs.gov/newsroom/401k-limit-increases-to-23500-for-2025-ira-limit-remains-7000\",\n", " \"https://www.irs.gov/newsroom/tax-basics-understanding-the-difference-between-standard-and-itemized-deductions\",\n", "]\n", "\n", "# Load documents from the URLs\n", "docs = [WebBaseLoader(url).load() for url in urls]\n", "docs_list = [item for sublist in docs for item in sublist]\n", "\n", "# Initialize a text splitter with specified chunk size and overlap\n", "text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(\n", " chunk_size=250, chunk_overlap=0\n", ")\n", "\n", "# Split the documents into chunks\n", "doc_splits = text_splitter.split_documents(docs_list)\n", "\n", "# Add the document chunks to the \"vector store\" using NomicEmbeddings\n", "vectorstore = SKLearnVectorStore.from_documents(\n", " documents=doc_splits,\n", " embedding=NomicEmbeddings(\n", " model=\"nomic-embed-text-v1.5\", inference_mode=\"local\", device=\"cpu\"\n", " ),\n", " # embedding=OpenAIEmbeddings(),\n", ")\n", "retriever = vectorstore.as_retriever(k=4)" ] }, { "cell_type": "code", "execution_count": 4, "id": "f8d54464-37b9-4b48-877e-38fc7620c1ff", "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "This cell imports the necessary modules and initializes the web search tool for the LLM.\n", "\n", "Modules:\n", "- `Document` from `langchain.schema`: Represents a document schema.\n", "- `TavilySearchResults` from `langchain_community.tools.tavily_search`: Provides functionality to perform web search by LLM if required.\n", "\n", "Initialization:\n", "- `web_search_tool`: An instance of `TavilySearchResults` used to perform web searches.\n", "\"\"\"\n", "from langchain.schema import Document\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", "\n", "web_search_tool = TavilySearchResults()" ] }, { "cell_type": "code", "execution_count": 5, "id": "36dad7e6-3752-4939-be70-f87d23d90d6f", "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "This cell sets up a question-answering assistant using the LangChain library. \n", "1. It imports necessary modules: `ChatOllama` for the language model, `PromptTemplate` for creating prompts, and `StrOutputParser` for parsing the output.\n", "2. It defines a prompt template that instructs the assistant to answer questions concisely using provided documents.\n", "3. It initializes the `ChatOllama` language model with specific parameters.\n", "4. It creates a chain (`rag_chain`) that combines the prompt template, language model, and output parser to process and generate answers.\n", "This setup is essential for enabling the assistant to handle question-answering tasks effectively.\n", "\"\"\"\n", "from langchain.prompts import PromptTemplate\n", "from langchain_core.output_parsers import StrOutputParser\n", "from langchain_ollama import ChatOllama\n", "\n", "prompt = PromptTemplate(\n", " template=\"\"\"You are an assistant for question-answering tasks. \n", " \n", " Use the following documents to answer the question. \n", " \n", " If you don't know the answer, just say that you don't know. \n", " \n", " Use three sentences maximum and keep the answer concise:\n", " Question: {question} \n", " Documents: {documents} \n", " Answer: \n", " \"\"\",\n", " input_variables=[\"question\", \"documents\"],\n", ")\n", "\n", "llm = ChatOllama(\n", " model=\"llama3.1\",\n", " temperature=0,\n", ")\n", "\n", "rag_chain = prompt | llm | StrOutputParser()" ] }, { "cell_type": "code", "execution_count": 6, "id": "0affbee8-30c4-4dd0-a95a-d8ab571b55c6", "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "This cell sets up a prompt template and a retrieval grader for assessing the relevance of a retrieved document to a user question.\n", "\n", "Functionality:\n", "- Imports the necessary JsonOutputParser from langchain_core.output_parsers.\n", "- Defines a PromptTemplate that instructs a grader to assess the relevance of a document to a user question.\n", "- The grader uses a simple binary scoring system ('yes' or 'no') to indicate relevance.\n", "- The result is provided as a JSON object with a single key 'score'.\n", "- Combines the prompt template with a language model (llm) and the JsonOutputParser to create the retrieval_grader.\n", "\n", "The retrieval_grader can be used in the workflow to filter out erroneous document retrievals based on their relevance to user questions.\n", "\"\"\"\n", "from langchain_core.output_parsers import JsonOutputParser\n", "\n", "prompt = PromptTemplate(\n", " template=\"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n", " Here is the retrieved document: \\n\\n {document} \\n\\n\n", " Here is the user question: {question} \\n\n", " If the document contains keywords related to the user question, grade it as relevant. \\n\n", " It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \\n\n", " Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \\n\n", " Provide the binary score as a JSON with a single key 'score' and no premable or explanation.\"\"\",\n", " input_variables=[\"question\", \"document\"],\n", ")\n", "\n", "retrieval_grader = prompt | llm | JsonOutputParser()" ] }, { "cell_type": "code", "execution_count": 7, "id": "d672ffdf", "metadata": {}, "outputs": [], "source": [ "# This cell defines the state of the graph and imports necessary modules for graph visualization.\n", "# It includes a TypedDict class `GraphState` that represents the state of the graph with attributes\n", "# such as question, generation, search, documents, and steps. This state will be used to manage\n", "# the workflow of the RAG agent.\n", "\n", "from IPython.display import Image, display\n", "from langgraph.graph import END, START, StateGraph\n", "from typing_extensions import List, TypedDict\n", "\n", "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", "\n", " Attributes:\n", " question: question\n", " generation: LLM generation\n", " search: whether to add search\n", " documents: list of documents\n", " \"\"\"\n", "\n", " question: str\n", " generation: str\n", " search: str\n", " documents: List[str]\n", " steps: List[str]" ] }, { "cell_type": "code", "execution_count": 8, "id": "2f26efee", "metadata": {}, "outputs": [], "source": [ "# This cell contains the core functions for the document retrieval and answer generation pipeline.\n", "# The functions are designed to work with a state dictionary that maintains the current state of the process.\n", "\n", "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): New key added to state, documents, that contains retrieved documents\n", " \"\"\"\n", " question = state[\"question\"]\n", " documents = retriever.invoke(question)\n", " steps = state[\"steps\"]\n", " steps.append(\"retrieve_documents\")\n", " return {\"documents\": documents, \"question\": question, \"steps\": steps}\n", "\n", "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): New key added to state, generation, that contains LLM generation\n", " \"\"\"\n", "\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", " generation = rag_chain.invoke({\"documents\": documents, \"question\": question})\n", " steps = state[\"steps\"]\n", " steps.append(\"generate_answer\")\n", " return {\n", " \"documents\": documents,\n", " \"question\": question,\n", " \"generation\": generation,\n", " \"steps\": steps,\n", " }\n", "\n", "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): Updates documents key with only filtered relevant documents\n", " \"\"\"\n", "\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", " steps = state[\"steps\"]\n", " steps.append(\"grade_document_retrieval\")\n", " filtered_docs = []\n", " search = \"No\"\n", " for d in documents:\n", " score = retrieval_grader.invoke(\n", " {\"question\": question, \"document\": d.page_content}\n", " )\n", " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " filtered_docs.append(d)\n", " else:\n", " search = \"Yes\"\n", " continue\n", " return {\n", " \"documents\": filtered_docs,\n", " \"question\": question,\n", " \"search\": search,\n", " \"steps\": steps,\n", " }\n", "\n", "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): Updates documents key with appended web results\n", " \"\"\"\n", "\n", " question = state[\"question\"]\n", " documents = state.get(\"documents\", [])\n", " steps = state[\"steps\"]\n", " steps.append(\"web_search\")\n", " web_results = web_search_tool.invoke({\"query\": question})\n", " documents.extend(\n", " [\n", " Document(page_content=d[\"content\"], metadata={\"url\": d[\"url\"]})\n", " for d in web_results\n", " ]\n", " )\n", " return {\"documents\": documents, \"question\": question, \"steps\": steps}\n", "\n", "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " str: Binary decision for next node to call\n", " \"\"\"\n", " search = state[\"search\"]\n", " if search == \"Yes\":\n", " return \"search\"\n", " else:\n", " return \"generate\"" ] }, { "cell_type": "code", "execution_count": 11, "id": "e056c4c8-fb62-4524-bb38-11b8c2a20326", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Graph\n", "\"\"\"\n", "This cell defines and builds a state graph workflow for the agent pipeline described earlier.\n", "\n", "The workflow consists of the following nodes:\n", "- \"retrieve\": Retrieves documents from the vector database.\n", "- \"grade_documents\": Grades the retrieved documents.\n", "- \"generate\": Generates output based on the graded documents.\n", "- \"web_search\": Performs a web search if needed.\n", "\n", "The workflow is constructed as follows:\n", "1. The entry point is set to the \"retrieve\" node. so the first step is to retrieve similar documents from the vector database.\n", "2. An edge is added from \"retrieve\" to \"grade_documents\".\n", "3. Conditional edges are added from \"grade_documents\" to either \"web_search\" or \"generate\" based on the decision function `decide_to_generate`.\n", "4. An edge is added from \"web_search\" to \"generate\".\n", "5. An edge is added from \"generate\" to the end of the workflow.\n", "\n", "Finally, the workflow is compiled into a custom graph and displayed as a Mermaid diagram.\n", "\"\"\"\n", "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", "workflow.add_node(\"generate\", generate) # generate\n", "workflow.add_node(\"web_search\", web_search) # web search\n", "\n", "# Build graph\n", "workflow.set_entry_point(\"retrieve\")\n", "workflow.add_edge(\"retrieve\", \"grade_documents\")\n", "workflow.add_conditional_edges(\n", " \"grade_documents\",\n", " decide_to_generate,\n", " {\"search\": \"web_search\", \"generate\": \"generate\"},\n", ")\n", "workflow.add_edge(\"web_search\", \"generate\")\n", "workflow.add_edge(\"generate\", END)\n", "\n", "custom_graph = workflow.compile()\n", "\n", "display(Image(custom_graph.get_graph(xray=True).draw_mermaid_png()))" ] }, { "cell_type": "code", "execution_count": 12, "id": "f26919fb-85ac-4afc-aaf7-cbb222dcd737", "metadata": {}, "outputs": [], "source": [ "import uuid\n", "\n", "\n", "def predict_custom_agent_answer(example: dict):\n", " # This cell defines a function to predict the answer from a custom agent based on the provided example input.\n", " \"\"\"\n", " Predicts the answer from a custom agent based on the provided example input.\n", "\n", " Args:\n", " example (dict): A dictionary containing the input question under the key \"input\".\n", "\n", " Returns:\n", " dict: A dictionary containing the response generated by the custom agent under the key \"response\",\n", " and the steps taken during the generation process under the key \"steps\".\n", "\n", " The `config` dictionary is used to pass configuration settings to the custom graph.\n", " In this case, it includes a unique `thread_id` generated using `uuid.uuid4()`.\n", " The `thread_id` ensures that each invocation of the function is uniquely identifiable,\n", " which can be useful for tracing and debugging purposes.\n", " \"\"\"\n", "\n", " config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n", "\n", " state_dict = custom_graph.invoke(\n", " {\"question\": example[\"input\"], \"steps\": []}, config\n", " )\n", "\n", " return {\"response\": state_dict[\"generation\"], \"steps\": state_dict[\"steps\"]}" ] }, { "cell_type": "code", "execution_count": 13, "id": "5261f17e-3b6a-43df-ad5d-17ad9639e8dd", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'response': 'The standard deduction is a fixed amount that most taxpayers can claim, while itemized deductions are specific expenses like mortgage interest, charitable donations, and medical expenses that can be deducted from taxable income. Taxpayers choose the option that gives them the lowest overall tax.',\n", " 'steps': ['retrieve_documents',\n", " 'grade_document_retrieval',\n", " 'generate_answer']}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"\n", "# Here we define an example input question about the difference between standard deduction and itemized deduction,\n", "# and then uses the `predict_custom_agent_answer` function to generate a response based on the input and show it.\n", "# Since, this question is related to tax deductions, the agent should provide an answer based on the loaded tax documents.\n", "\"\"\"\n", "example = {\n", " \"input\": \"What is the difference between standard deduction and itemized deduction?\"\n", "}\n", "response = predict_custom_agent_answer(example)\n", "response" ] }, { "cell_type": "code", "execution_count": 14, "id": "627e38d9-3e0a-4094-b1fd-917fb89cc5bb", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'response': 'India won the 2024 cricket world cup and Virat Kohli was named Player of the Match for. The final match was played between India and South Africa on June 29, 2024. India defeated South Africa by 7 runs to win their second T20 World Cup title.',\n", " 'steps': ['retrieve_documents',\n", " 'grade_document_retrieval',\n", " 'web_search',\n", " 'generate_answer']}" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"\n", "# Here we define another example input question about the sports event,\n", "# and then uses the `predict_custom_agent_answer` function to generate a response based on the input and show it.\n", "# Since, this question is NOT related to tax deductions, the agent should provide an answer based on the documents returned from web search.\n", "\"\"\"\n", "example = {\"input\": \"Who won the 2024 cricket world cup and who was the MVP in final?\"}\n", "response = predict_custom_agent_answer(example)\n", "response" ] }, { "cell_type": "markdown", "id": "2caa78d6-f2aa-41eb-9298-f16ba6e467ba", "metadata": {}, "source": [ "As demonstrated in the previous examples, the RAG agent routes the control flow through web search to generate answers for non-TAX related questions. For TAX related queries, it uses documents retrieved from the vector database." ] }, { "cell_type": "code", "execution_count": null, "id": "87a59300-c8ab-4281-9a31-25d37a5149f3", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "test-env-langchain", "language": "python", "name": "test-env-langchain" }, "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": 5 }