{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "68b24990",
   "metadata": {},
   "source": [
    "# Combine agents and vector stores\n",
    "\n",
    "This notebook covers how to combine agents and vector stores. The use case for this is that you've ingested your data into a vector store and want to interact with it in an agentic manner.\n",
    "\n",
    "The recommended method for doing so is to create a `RetrievalQA` and then use that as a tool in the overall agent. Let's take a look at doing this below. You can do this with multiple different vector DBs, and use the agent as a way to route between them. There are two different ways of doing this - you can either let the agent use the vector stores as normal tools, or you can set `return_direct=True` to really just use the agent as a router."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b22020a",
   "metadata": {},
   "source": [
    "## Create the vector store"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e8d63d14-138d-4aa5-a741-7fd3537d00aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = \"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2e87c10a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import RetrievalQA\n",
    "from langchain_chroma import Chroma\n",
    "from langchain_openai import OpenAI, OpenAIEmbeddings\n",
    "from langchain_text_splitters import CharacterTextSplitter\n",
    "\n",
    "llm = OpenAI(temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0b7b772b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "relevant_parts = []\n",
    "for p in Path(\".\").absolute().parts:\n",
    "    relevant_parts.append(p)\n",
    "    if relevant_parts[-3:] == [\"langchain\", \"docs\", \"modules\"]:\n",
    "        break\n",
    "doc_path = str(Path(*relevant_parts) / \"state_of_the_union.txt\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f2675861",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.document_loaders import TextLoader\n",
    "\n",
    "loader = TextLoader(doc_path)\n",
    "documents = loader.load()\n",
    "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
    "texts = text_splitter.split_documents(documents)\n",
    "\n",
    "embeddings = OpenAIEmbeddings()\n",
    "docsearch = Chroma.from_documents(texts, embeddings, collection_name=\"state-of-union\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "bc5403d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "state_of_union = RetrievalQA.from_chain_type(\n",
    "    llm=llm, chain_type=\"stuff\", retriever=docsearch.as_retriever()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1431cded",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders import WebBaseLoader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "915d3ff3",
   "metadata": {},
   "outputs": [],
   "source": [
    "loader = WebBaseLoader(\"https://beta.ruff.rs/docs/faq/\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "96a2edf8",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Created a chunk of size 2122, which is longer than the specified 1000\n",
      "Created a chunk of size 3187, which is longer than the specified 1000\n",
      "Created a chunk of size 1017, which is longer than the specified 1000\n",
      "Created a chunk of size 1049, which is longer than the specified 1000\n",
      "Created a chunk of size 1256, which is longer than the specified 1000\n",
      "Created a chunk of size 2321, which is longer than the specified 1000\n"
     ]
    }
   ],
   "source": [
    "docs = loader.load()\n",
    "ruff_texts = text_splitter.split_documents(docs)\n",
    "ruff_db = Chroma.from_documents(ruff_texts, embeddings, collection_name=\"ruff\")\n",
    "ruff = RetrievalQA.from_chain_type(\n",
    "    llm=llm, chain_type=\"stuff\", retriever=ruff_db.as_retriever()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0a6c031",
   "metadata": {},
   "source": [
    "## Create the Agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "eb142786",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import things that are needed generically\n",
    "from langchain.agents import Tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "850bc4e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    Tool(\n",
    "        name=\"state_of_union_qa_system\",\n",
    "        func=state_of_union.run,\n",
    "        description=\"useful for when you need to answer questions about the most recent state of the union address. Input should be a fully formed question.\",\n",
    "    ),\n",
    "    Tool(\n",
    "        name=\"ruff_qa_system\",\n",
    "        func=ruff.run,\n",
    "        description=\"useful for when you need to answer questions about ruff (a python linter). Input should be a fully formed question.\",\n",
    "    ),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "70c461d8-aaca-4f2a-9a93-bf35841cc615",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "agent = create_react_agent(\"openai:gpt-4.1-mini\", tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a6d2b911-3044-4430-a35b-75832bb45334",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What did biden say about ketanji brown jackson in the state of the union address?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  state_of_union_qa_system (call_26QlRdsptjEJJZjFsAUjEbaH)\n",
      " Call ID: call_26QlRdsptjEJJZjFsAUjEbaH\n",
      "  Args:\n",
      "    __arg1: What did Biden say about Ketanji Brown Jackson in the state of the union address?\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: state_of_union_qa_system\n",
      "\n",
      " Biden said that he nominated Ketanji Brown Jackson for the United States Supreme Court and praised her as one of the nation's top legal minds who will continue Justice Breyer's legacy of excellence.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "In the State of the Union address, Biden said that he nominated Ketanji Brown Jackson for the United States Supreme Court and praised her as one of the nation's top legal minds who will continue Justice Breyer's legacy of excellence.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\n",
    "    \"role\": \"user\",\n",
    "    \"content\": \"What did biden say about ketanji brown jackson in the state of the union address?\",\n",
    "}\n",
    "\n",
    "for step in agent.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    step[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "e836b4cd-abf7-49eb-be0e-b9ad501213f3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Why use ruff over flake8?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  ruff_qa_system (call_KqDoWeO9bo9OAXdxOsCb6msC)\n",
      " Call ID: call_KqDoWeO9bo9OAXdxOsCb6msC\n",
      "  Args:\n",
      "    __arg1: Why use ruff over flake8?\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: ruff_qa_system\n",
      "\n",
      "\n",
      "There are a few reasons why someone might choose to use Ruff over Flake8:\n",
      "\n",
      "1. Larger rule set: Ruff implements over 800 rules, while Flake8 only implements around 200. This means that Ruff can catch more potential issues in your code.\n",
      "\n",
      "2. Better compatibility with other tools: Ruff is designed to work well with other tools like Black, isort, and type checkers like Mypy. This means that you can use Ruff alongside these tools to get more comprehensive feedback on your code.\n",
      "\n",
      "3. Automatic fixing of lint violations: Unlike Flake8, Ruff is capable of automatically fixing its own lint violations. This can save you time and effort when fixing issues in your code.\n",
      "\n",
      "4. Native implementation of popular Flake8 plugins: Ruff re-implements some of the most popular Flake8 plugins natively, which means you don't have to install and configure multiple plugins to get the same functionality.\n",
      "\n",
      "Overall, Ruff offers a more comprehensive and user-friendly experience compared to Flake8, making it a popular choice for many developers.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "You might choose to use Ruff over Flake8 for several reasons:\n",
      "\n",
      "1. Ruff has a much larger rule set, implementing over 800 rules compared to Flake8's roughly 200, so it can catch more potential issues.\n",
      "2. Ruff is designed to work better with other tools like Black, isort, and type checkers like Mypy, providing more comprehensive code feedback.\n",
      "3. Ruff can automatically fix its own lint violations, which Flake8 cannot, saving time and effort.\n",
      "4. Ruff natively implements some popular Flake8 plugins, so you don't need to install and configure multiple plugins separately.\n",
      "\n",
      "Overall, Ruff offers a more comprehensive and user-friendly experience compared to Flake8.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\n",
    "    \"role\": \"user\",\n",
    "    \"content\": \"Why use ruff over flake8?\",\n",
    "}\n",
    "\n",
    "for step in agent.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    step[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "787a9b5e",
   "metadata": {},
   "source": [
    "## Use the Agent solely as a router"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9161ba91",
   "metadata": {},
   "source": [
    "You can also set `return_direct=True` if you intend to use the agent as a router and just want to directly return the result of the RetrievalQAChain.\n",
    "\n",
    "Notice that in the above examples the agent did some extra work after querying the RetrievalQAChain. You can avoid that and just return the result directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f59b377e",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    Tool(\n",
    "        name=\"state_of_union_qa_system\",\n",
    "        func=state_of_union.run,\n",
    "        description=\"useful for when you need to answer questions about the most recent state of the union address. Input should be a fully formed question.\",\n",
    "        return_direct=True,\n",
    "    ),\n",
    "    Tool(\n",
    "        name=\"ruff_qa_system\",\n",
    "        func=ruff.run,\n",
    "        description=\"useful for when you need to answer questions about ruff (a python linter). Input should be a fully formed question.\",\n",
    "        return_direct=True,\n",
    "    ),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "06f69c0f-c83d-4b7f-a1c8-7614aced3bae",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "agent = create_react_agent(\"openai:gpt-4.1-mini\", tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a6b38c12-ac25-43c0-b9c2-2b1985ab4825",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What did biden say about ketanji brown jackson in the state of the union address?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  state_of_union_qa_system (call_yjxh11OnZiauoyTAn9npWdxj)\n",
      " Call ID: call_yjxh11OnZiauoyTAn9npWdxj\n",
      "  Args:\n",
      "    __arg1: What did Biden say about Ketanji Brown Jackson in the state of the union address?\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: state_of_union_qa_system\n",
      "\n",
      " Biden said that he nominated Ketanji Brown Jackson for the United States Supreme Court and praised her as one of the nation's top legal minds who will continue Justice Breyer's legacy of excellence.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\n",
    "    \"role\": \"user\",\n",
    "    \"content\": \"What did biden say about ketanji brown jackson in the state of the union address?\",\n",
    "}\n",
    "\n",
    "for step in agent.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    step[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "88f08d86-7972-4148-8128-3ac8898ad68a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Why use ruff over flake8?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  ruff_qa_system (call_GiWWfwF6wbbRFQrHlHbhRtGW)\n",
      " Call ID: call_GiWWfwF6wbbRFQrHlHbhRtGW\n",
      "  Args:\n",
      "    __arg1: What are the advantages of using ruff over flake8 for Python linting?\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: ruff_qa_system\n",
      "\n",
      " Ruff has a larger rule set, supports automatic fixing of lint violations, and does not require the installation of additional plugins. It also has better compatibility with Black and can be used alongside a type checker for more comprehensive code analysis.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\n",
    "    \"role\": \"user\",\n",
    "    \"content\": \"Why use ruff over flake8?\",\n",
    "}\n",
    "\n",
    "for step in agent.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    step[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49a0cbbe",
   "metadata": {},
   "source": [
    "## Multi-Hop vector store reasoning\n",
    "\n",
    "Because vector stores are easily usable as tools in agents, it is easy to use answer multi-hop questions that depend on vector stores using the existing agent framework."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d397a233",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    Tool(\n",
    "        name=\"state_of_union_qa_system\",\n",
    "        func=state_of_union.run,\n",
    "        description=\"useful for when you need to answer questions about the most recent state of the union address. Input should be a fully formed question, not referencing any obscure pronouns from the conversation before.\",\n",
    "    ),\n",
    "    Tool(\n",
    "        name=\"ruff_qa_system\",\n",
    "        func=ruff.run,\n",
    "        description=\"useful for when you need to answer questions about ruff (a python linter). Input should be a fully formed question, not referencing any obscure pronouns from the conversation before.\",\n",
    "    ),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "41743f29-150d-40ba-aa8e-3a63c32216aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "agent = create_react_agent(\"openai:gpt-4.1-mini\", tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "e20e81dd-284a-4d07-9160-63a84b65cba8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What tool does ruff use to run over Jupyter Notebooks? Did the president mention that tool in the state of the union?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  ruff_qa_system (call_VOnxiOEehauQyVOTjDJkR5L2)\n",
      " Call ID: call_VOnxiOEehauQyVOTjDJkR5L2\n",
      "  Args:\n",
      "    __arg1: What tool does ruff use to run over Jupyter Notebooks?\n",
      "  state_of_union_qa_system (call_AbSsXAxwe4JtCRhga926SxOZ)\n",
      " Call ID: call_AbSsXAxwe4JtCRhga926SxOZ\n",
      "  Args:\n",
      "    __arg1: Did the president mention the tool that ruff uses to run over Jupyter Notebooks in the state of the union?\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: state_of_union_qa_system\n",
      "\n",
      " No, the president did not mention the tool that ruff uses to run over Jupyter Notebooks in the state of the union.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Ruff does not support source.organizeImports and source.fixAll code actions in Jupyter Notebooks. Additionally, the president did not mention the tool that ruff uses to run over Jupyter Notebooks in the state of the union.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\n",
    "    \"role\": \"user\",\n",
    "    \"content\": \"What tool does ruff use to run over Jupyter Notebooks? Did the president mention that tool in the state of the union?\",\n",
    "}\n",
    "\n",
    "for step in agent.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    step[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3b857d6",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
