{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "86fc5bb2-017f-434e-8cd6-53ab214a5604",
   "metadata": {},
   "source": [
    "# How to add chat history\n",
    "\n",
    "In many Q&A applications we want to allow the user to have a back-and-forth conversation, meaning the application needs some sort of \"memory\" of past questions and answers, and some logic for incorporating those into its current thinking.\n",
    "\n",
    "In this guide we focus on **adding logic for incorporating historical messages.**\n",
    "\n",
    "This is largely a condensed version of the [Conversational RAG tutorial](/docs/tutorials/qa_chat_history).\n",
    "\n",
    "We will cover two approaches:\n",
    "1. [Chains](/docs/how_to/qa_chat_history_how_to#chains), in which we always execute a retrieval step;\n",
    "2. [Agents](/docs/how_to/qa_chat_history_how_to#agents), in which we give an LLM discretion over whether and how to execute a retrieval step (or multiple steps).\n",
    "\n",
    "For the external knowledge source, we will use the same [LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/) blog post by Lilian Weng from the [RAG tutorial](/docs/tutorials/rag)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "487d8d79-5ee9-4aa4-9fdf-cd5f4303e099",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "### Dependencies\n",
    "\n",
    "We'll use OpenAI embeddings and a Chroma vector store in this walkthrough, but everything shown here works with any [Embeddings](/docs/concepts#embedding-models), and [VectorStore](/docs/concepts#vectorstores) or [Retriever](/docs/concepts#retrievers). \n",
    "\n",
    "We'll use the following packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ede7fdc0-ef31-483d-bd67-32e4b5c5d527",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --upgrade --quiet  langchain langchain-community langchain-chroma bs4"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51ef48de-70b6-4f43-8e0b-ab9b84c9c02a",
   "metadata": {},
   "source": [
    "We need to set environment variable `OPENAI_API_KEY`, which can be done directly or loaded from a `.env` file like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "143787ca-d8e6-4dc9-8281-4374f4d71720",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "if not os.environ.get(\"OPENAI_API_KEY\"):\n",
    "    os.environ[\"OPENAI_API_KEY\"] = getpass.getpass()\n",
    "\n",
    "# import dotenv\n",
    "\n",
    "# dotenv.load_dotenv()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1665e740-ce01-4f09-b9ed-516db0bd326f",
   "metadata": {},
   "source": [
    "### LangSmith\n",
    "\n",
    "Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your chain or agent. The best way to do this is with [LangSmith](https://smith.langchain.com).\n",
    "\n",
    "Note that LangSmith is not needed, but it is helpful. If you do want to use LangSmith, after you sign up at the link above, make sure to set your environment variables to start logging traces:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "07411adb-3722-4f65-ab7f-8f6f57663d11",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "if not os.environ.get(\"LANGCHAIN_API_KEY\"):\n",
    "    os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa6ba684-26cf-4860-904e-a4d51380c134",
   "metadata": {},
   "source": [
    "## Chains {#chains}\n",
    "\n",
    "In a conversational RAG application, queries issued to the retriever should be informed by the context of the conversation. LangChain provides a [create_history_aware_retriever](https://api.python.langchain.com/en/latest/chains/langchain.chains.history_aware_retriever.create_history_aware_retriever.html) constructor to simplify this. It constructs a chain that accepts keys `input` and `chat_history` as input, and has the same output schema as a retriever. `create_history_aware_retriever` requires as inputs:  \n",
    "\n",
    "1. LLM;\n",
    "2. Retriever;\n",
    "3. Prompt.\n",
    "\n",
    "First we obtain these objects:\n",
    "\n",
    "### LLM\n",
    "\n",
    "We can use any supported chat model:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "646840fb-5212-48ea-8bc7-ec7be5ec727e",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    "import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
    "\n",
    "<ChatModelTabs customVarName=\"llm\" />\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cb58f273-2111-4a9b-8932-9b64c95030c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# | output: false\n",
    "# | echo: false\n",
    "\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bb76a36-15b1-4589-8a3d-18c6f5fdb7e0",
   "metadata": {},
   "source": [
    "### Retriever"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15f8ad59-19de-42e3-85a8-3ba95ee0bd43",
   "metadata": {},
   "source": [
    "For the retriever, we will use [WebBaseLoader](https://api.python.langchain.com/en/latest/document_loaders/langchain_community.document_loaders.web_base.WebBaseLoader.html) to load the content of a web page. Here we instantiate a `Chroma` vectorstore and then use its [.as_retriever](https://api.python.langchain.com/en/latest/vectorstores/langchain_core.vectorstores.VectorStore.html#langchain_core.vectorstores.VectorStore.as_retriever) method to build a retriever that can be incorporated into [LCEL](/docs/concepts/#langchain-expression-language) chains."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "820244ae-74b4-4593-b392-822979dd91b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "import bs4\n",
    "from langchain.chains import create_retrieval_chain\n",
    "from langchain.chains.combine_documents import create_stuff_documents_chain\n",
    "from langchain_chroma import Chroma\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n",
    "    bs_kwargs=dict(\n",
    "        parse_only=bs4.SoupStrainer(\n",
    "            class_=(\"post-content\", \"post-title\", \"post-header\")\n",
    "        )\n",
    "    ),\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "splits = text_splitter.split_documents(docs)\n",
    "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n",
    "retriever = vectorstore.as_retriever()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "776ae958-cbdc-4471-8669-c6087436f0b5",
   "metadata": {},
   "source": [
    "### Prompt\n",
    "\n",
    "We'll use a prompt that includes a `MessagesPlaceholder` variable under the name \"chat_history\". This allows us to pass in a list of Messages to the prompt using the \"chat_history\" input key, and these messages will be inserted after the system message and before the human message containing the latest question."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2b685428-8b82-4af1-be4f-7232c5d55b73",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import create_history_aware_retriever\n",
    "from langchain_core.prompts import MessagesPlaceholder\n",
    "\n",
    "contextualize_q_system_prompt = (\n",
    "    \"Given a chat history and the latest user question \"\n",
    "    \"which might reference context in the chat history, \"\n",
    "    \"formulate a standalone question which can be understood \"\n",
    "    \"without the chat history. Do NOT answer the question, \"\n",
    "    \"just reformulate it if needed and otherwise return it as is.\"\n",
    ")\n",
    "\n",
    "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", contextualize_q_system_prompt),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d2a692e-a019-4515-9625-5b0530c3c9af",
   "metadata": {},
   "source": [
    "### Assembling the chain\n",
    "\n",
    "We can then instantiate the history-aware retriever:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "4c4b1695-6217-4ee8-abaf-7cc26366d988",
   "metadata": {},
   "outputs": [],
   "source": [
    "history_aware_retriever = create_history_aware_retriever(\n",
    "    llm, retriever, contextualize_q_prompt\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42a47168-4a1f-4e39-bd2d-d5b03609a243",
   "metadata": {},
   "source": [
    "This chain prepends a rephrasing of the input query to our retriever, so that the retrieval incorporates the context of the conversation.\n",
    "\n",
    "Now we can build our full QA chain.\n",
    "\n",
    "As in the [RAG tutorial](/docs/tutorials/rag), we will use [create_stuff_documents_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.combine_documents.stuff.create_stuff_documents_chain.html) to generate a `question_answer_chain`, with input keys `context`, `chat_history`, and `input`-- it accepts the retrieved context alongside the conversation history and query to generate an answer.\n",
    "\n",
    "We build our final `rag_chain` with [create_retrieval_chain](https://api.python.langchain.com/en/latest/chains/langchain.chains.retrieval.create_retrieval_chain.html). This chain applies the `history_aware_retriever` and `question_answer_chain` in sequence, retaining intermediate outputs such as the retrieved context for convenience. It has input keys `input` and `chat_history`, and includes `input`, `chat_history`, `context`, and `answer` in its output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "afef4385-f571-4874-8f52-3d475642f579",
   "metadata": {},
   "outputs": [],
   "source": [
    "system_prompt = (\n",
    "    \"You are an assistant for question-answering tasks. \"\n",
    "    \"Use the following pieces of retrieved context to answer \"\n",
    "    \"the question. If you don't know the answer, say that you \"\n",
    "    \"don't know. Use three sentences maximum and keep the \"\n",
    "    \"answer concise.\"\n",
    "    \"\\n\\n\"\n",
    "    \"{context}\"\n",
    ")\n",
    "qa_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", system_prompt),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)\n",
    "\n",
    "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53a662c2-f38b-45f9-95c4-66de15637614",
   "metadata": {},
   "source": [
    "### Adding chat history\n",
    "\n",
    "To manage the chat history, we will need:\n",
    "\n",
    "1. An object for storing the chat history;\n",
    "2. An object that wraps our chain and manages updates to the chat history.\n",
    "\n",
    "For these we will use [BaseChatMessageHistory](https://api.python.langchain.com/en/latest/chat_history/langchain_core.chat_history.BaseChatMessageHistory.html) and [RunnableWithMessageHistory](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html). The latter is a wrapper for an LCEL chain and a `BaseChatMessageHistory` that handles injecting chat history into inputs and updating it after each invocation.\n",
    "\n",
    "For a detailed walkthrough of how to use these classes together to create a stateful conversational chain, head to the [How to add message history (memory)](/docs/how_to/message_history/) LCEL how-to guide.\n",
    "\n",
    "Below, we implement a simple example of the second option, in which chat histories are stored in a simple dict. LangChain manages memory integrations with [Redis](/docs/integrations/memory/redis_chat_message_history/) and other technologies to provide for more robust persistence.\n",
    "\n",
    "Instances of `RunnableWithMessageHistory` manage the chat history for you. They accept a config with a key (`\"session_id\"` by default) that specifies what conversation history to fetch and prepend to the input, and append the output to the same conversation history. Below is an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9c3fb176-8d6a-4dc7-8408-6a22c5f7cc72",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_core.chat_history import BaseChatMessageHistory\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "store = {}\n",
    "\n",
    "\n",
    "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
    "    if session_id not in store:\n",
    "        store[session_id] = ChatMessageHistory()\n",
    "    return store[session_id]\n",
    "\n",
    "\n",
    "conversational_rag_chain = RunnableWithMessageHistory(\n",
    "    rag_chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"input\",\n",
    "    history_messages_key=\"chat_history\",\n",
    "    output_messages_key=\"answer\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "1046c92f-21b3-4214-907d-92878d8cba23",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable and easier to accomplish. This process can be done using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down tasks effectively. Task decomposition can be facilitated by providing simple prompts to a language model, task-specific instructions, or human inputs.'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversational_rag_chain.invoke(\n",
    "    {\"input\": \"What is Task Decomposition?\"},\n",
    "    config={\n",
    "        \"configurable\": {\"session_id\": \"abc123\"}\n",
    "    },  # constructs a key \"abc123\" in `store`.\n",
    ")[\"answer\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "0e89c75f-7ad7-4331-a2fe-57579eb8f840",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Task decomposition can be achieved through various methods, including using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down tasks effectively. Common ways of task decomposition include providing simple prompts to a language model, task-specific instructions, or human inputs to break down complex tasks into smaller and more manageable steps. Additionally, task decomposition can involve utilizing resources like internet access for information gathering, long-term memory management, and GPT-3.5 powered agents for delegation of simple tasks.'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversational_rag_chain.invoke(\n",
    "    {\"input\": \"What are common ways of doing it?\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")[\"answer\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ab59258-84bc-4904-880e-2ebfebbca563",
   "metadata": {},
   "source": [
    "The conversation history can be inspected in the `store` dict:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "7686b874-3a85-499f-82b5-28a85c4c768c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "User: What is Task Decomposition?\n",
      "\n",
      "AI: Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable and easier to accomplish. This process can be done using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down tasks effectively. Task decomposition can be facilitated by providing simple prompts to a language model, task-specific instructions, or human inputs.\n",
      "\n",
      "User: What are common ways of doing it?\n",
      "\n",
      "AI: Task decomposition can be achieved through various methods, including using techniques like Chain of Thought (CoT) or Tree of Thoughts to guide the model in breaking down tasks effectively. Common ways of task decomposition include providing simple prompts to a language model, task-specific instructions, or human inputs to break down complex tasks into smaller and more manageable steps. Additionally, task decomposition can involve utilizing resources like internet access for information gathering, long-term memory management, and GPT-3.5 powered agents for delegation of simple tasks.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "\n",
    "for message in store[\"abc123\"].messages:\n",
    "    if isinstance(message, AIMessage):\n",
    "        prefix = \"AI\"\n",
    "    else:\n",
    "        prefix = \"User\"\n",
    "\n",
    "    print(f\"{prefix}: {message.content}\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ab1ded4-76d9-453f-9b9b-db9a4560c737",
   "metadata": {},
   "source": [
    "### Tying it together"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a08a5ea-df5b-4547-93c6-2a3940dd5c3e",
   "metadata": {},
   "source": [
    "![](../../static/img/conversational_retrieval_chain.png)\n",
    "\n",
    "For convenience, we tie together all of the necessary steps in a single code cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "71c32048-1a41-465f-a9e2-c4affc332fd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import bs4\n",
    "from langchain.chains import create_history_aware_retriever, create_retrieval_chain\n",
    "from langchain.chains.combine_documents import create_stuff_documents_chain\n",
    "from langchain_chroma import Chroma\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "from langchain_core.chat_history import BaseChatMessageHistory\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
    "\n",
    "\n",
    "### Construct retriever ###\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n",
    "    bs_kwargs=dict(\n",
    "        parse_only=bs4.SoupStrainer(\n",
    "            class_=(\"post-content\", \"post-title\", \"post-header\")\n",
    "        )\n",
    "    ),\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "splits = text_splitter.split_documents(docs)\n",
    "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "\n",
    "### Contextualize question ###\n",
    "contextualize_q_system_prompt = (\n",
    "    \"Given a chat history and the latest user question \"\n",
    "    \"which might reference context in the chat history, \"\n",
    "    \"formulate a standalone question which can be understood \"\n",
    "    \"without the chat history. Do NOT answer the question, \"\n",
    "    \"just reformulate it if needed and otherwise return it as is.\"\n",
    ")\n",
    "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", contextualize_q_system_prompt),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "history_aware_retriever = create_history_aware_retriever(\n",
    "    llm, retriever, contextualize_q_prompt\n",
    ")\n",
    "\n",
    "\n",
    "### Answer question ###\n",
    "system_prompt = (\n",
    "    \"You are an assistant for question-answering tasks. \"\n",
    "    \"Use the following pieces of retrieved context to answer \"\n",
    "    \"the question. If you don't know the answer, say that you \"\n",
    "    \"don't know. Use three sentences maximum and keep the \"\n",
    "    \"answer concise.\"\n",
    "    \"\\n\\n\"\n",
    "    \"{context}\"\n",
    ")\n",
    "qa_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", system_prompt),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)\n",
    "\n",
    "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)\n",
    "\n",
    "\n",
    "### Statefully manage chat history ###\n",
    "store = {}\n",
    "\n",
    "\n",
    "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
    "    if session_id not in store:\n",
    "        store[session_id] = ChatMessageHistory()\n",
    "    return store[session_id]\n",
    "\n",
    "\n",
    "conversational_rag_chain = RunnableWithMessageHistory(\n",
    "    rag_chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"input\",\n",
    "    history_messages_key=\"chat_history\",\n",
    "    output_messages_key=\"answer\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "6d0a7a73-d151-47d9-9e99-b4f3291c0322",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Task decomposition involves breaking down a complex task into smaller and simpler steps to make it more manageable. Techniques like Chain of Thought (CoT) and Tree of Thoughts help in decomposing hard tasks into multiple manageable tasks by instructing models to think step by step and explore multiple reasoning possibilities at each step. Task decomposition can be achieved through various methods such as using prompting techniques, task-specific instructions, or human inputs.'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversational_rag_chain.invoke(\n",
    "    {\"input\": \"What is Task Decomposition?\"},\n",
    "    config={\n",
    "        \"configurable\": {\"session_id\": \"abc123\"}\n",
    "    },  # constructs a key \"abc123\" in `store`.\n",
    ")[\"answer\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "17021822-896a-4513-a17d-1d20b1c5381c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Task decomposition can be done in common ways such as using prompting techniques like Chain of Thought (CoT) or Tree of Thoughts, which instruct models to think step by step and explore multiple reasoning possibilities at each step. Another way is to provide task-specific instructions, such as asking to \"Write a story outline\" for writing a novel, to guide the decomposition process. Additionally, task decomposition can also involve human inputs to break down complex tasks into smaller and simpler steps.'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversational_rag_chain.invoke(\n",
    "    {\"input\": \"What are common ways of doing it?\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")[\"answer\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "861da8ed-d890-4fdc-a3bf-30433db61e0d",
   "metadata": {},
   "source": [
    "## Agents {#agents}\n",
    "\n",
    "Agents leverage the reasoning capabilities of LLMs to make decisions during execution. Using agents allow you to offload some discretion over the retrieval process. Although their behavior is less predictable than chains, they offer some advantages in this context:\n",
    "- Agents generate the input to the retriever directly, without necessarily needing us to explicitly build in contextualization, as we did above;\n",
    "- Agents can execute multiple retrieval steps in service of a query, or refrain from executing a retrieval step altogether (e.g., in response to a generic greeting from a user).\n",
    "\n",
    "### Retrieval tool\n",
    "\n",
    "Agents can access \"tools\" and manage their execution. In this case, we will convert our retriever into a LangChain tool to be wielded by the agent:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "809cc747-2135-40a2-8e73-e4556343ee64",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.tools.retriever import create_retriever_tool\n",
    "\n",
    "tool = create_retriever_tool(\n",
    "    retriever,\n",
    "    \"blog_post_retriever\",\n",
    "    \"Searches and returns excerpts from the Autonomous Agents blog post.\",\n",
    ")\n",
    "tools = [tool]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f77e0217-28be-4b8b-b4c4-9cc4ed5ec201",
   "metadata": {},
   "source": [
    "### Agent constructor\n",
    "\n",
    "Now that we have defined the tools and the LLM, we can create the agent. We will be using [LangGraph](/docs/concepts/#langgraph) to construct the agent. \n",
    "Currently we are using a high level interface to construct the agent, but the nice thing about LangGraph is that this high-level interface is backed by a low-level, highly controllable API in case you want to modify the agent logic."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1726d151-4653-4c72-a187-a14840add526",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "agent_executor = create_react_agent(llm, tools)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d5152ca-1c3b-4f58-bb28-f31c0be7ba66",
   "metadata": {},
   "source": [
    "We can now try it out. Note that so far it is not stateful (we still need to add in memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "52ae46d9-43f7-481b-96d5-df750be3ad65",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error in LangChainTracer.on_tool_end callback: TracerException(\"Found chain run at ID 5cd28d13-88dd-4eac-a465-3770ac27eff6, but expected {'tool'} run.\")\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_TbhPPPN05GKi36HLeaN4QM90', 'function': {'arguments': '{\"query\":\"Task Decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 68, 'total_tokens': 87}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2e60d910-879a-4a2a-b1e9-6a6c5c7d7ebc-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'Task Decomposition'}, 'id': 'call_TbhPPPN05GKi36HLeaN4QM90'}])]}}\n",
      "----\n",
      "{'tools': {'messages': [ToolMessage(content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nFig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', name='blog_post_retriever', tool_call_id='call_TbhPPPN05GKi36HLeaN4QM90')]}}\n",
      "----\n",
      "{'agent': {'messages': [AIMessage(content='Task decomposition is a technique used to break down complex tasks into smaller and simpler steps. This approach helps in transforming big tasks into multiple manageable tasks, making it easier for autonomous agents to handle and interpret the thinking process. One common method for task decomposition is the Chain of Thought (CoT) technique, where models are instructed to \"think step by step\" to decompose hard tasks. Another extension of CoT is the Tree of Thoughts, which explores multiple reasoning possibilities at each step by creating a tree structure of multiple thoughts per step. Task decomposition can be facilitated through various methods such as using simple prompts, task-specific instructions, or human inputs.', response_metadata={'token_usage': {'completion_tokens': 130, 'prompt_tokens': 636, 'total_tokens': 766}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-3ef17638-65df-4030-a7fe-795e6da91c69-0')]}}\n",
      "----\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "query = \"What is Task Decomposition?\"\n",
    "\n",
    "for s in agent_executor.stream(\n",
    "    {\"messages\": [HumanMessage(content=query)]},\n",
    "):\n",
    "    print(s)\n",
    "    print(\"----\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1df703b1-aad6-48fb-b6fa-703e32ea88b9",
   "metadata": {},
   "source": [
    "LangGraph comes with built in persistence, so we don't need to use ChatMessageHistory! Rather, we can pass in a checkpointer to our LangGraph agent directly.\n",
    "\n",
    "Distinct conversations are managed by specifying a key for a conversation thread in the config dict, as shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "837a401e-9757-4d0e-a0da-24fa097d887e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.checkpoint.sqlite import SqliteSaver\n",
    "\n",
    "memory = SqliteSaver.from_conn_string(\":memory:\")\n",
    "\n",
    "agent_executor = create_react_agent(llm, tools, checkpointer=memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02026f78-338e-4d18-9f05-131e1dd59197",
   "metadata": {},
   "source": [
    "This is all we need to construct a conversational RAG agent.\n",
    "\n",
    "Let's observe its behavior. Note that if we input a query that does not require a retrieval step, the agent does not execute one:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "d6d70833-b958-4cd7-9e27-29c1c08bb1b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 67, 'total_tokens': 78}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-1cd17562-18aa-4839-b41b-403b17a0fc20-0')]}}\n",
      "----\n"
     ]
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"abc123\"}}\n",
    "\n",
    "for s in agent_executor.stream(\n",
    "    {\"messages\": [HumanMessage(content=\"Hi! I'm bob\")]}, config=config\n",
    "):\n",
    "    print(s)\n",
    "    print(\"----\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7928865-3dd6-4d36-abc6-2a30de770d09",
   "metadata": {},
   "source": [
    "Further, if we input a query that does require a retrieval step, the agent generates the input to the tool:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "e2c570ae-dd91-402c-8693-ae746de63b16",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error in LangChainTracer.on_tool_end callback: TracerException(\"Found chain run at ID c54381c0-c5d9-495a-91a0-aca4ae755663, but expected {'tool'} run.\")\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_rg7zKTE5e0ICxVSslJ1u9LMg', 'function': {'arguments': '{\"query\":\"Task Decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 91, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-122bf097-7ff1-49aa-b430-e362b51354ad-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'Task Decomposition'}, 'id': 'call_rg7zKTE5e0ICxVSslJ1u9LMg'}])]}}\n",
      "----\n",
      "{'tools': {'messages': [ToolMessage(content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nFig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', name='blog_post_retriever', tool_call_id='call_rg7zKTE5e0ICxVSslJ1u9LMg')]}}\n",
      "----\n",
      "{'agent': {'messages': [AIMessage(content='Task decomposition is a technique used to break down complex tasks into smaller and simpler steps. This approach helps in managing and solving intricate problems by dividing them into more manageable components. By decomposing tasks, agents or models can better understand the steps involved and plan their actions accordingly. Techniques like Chain of Thought (CoT) and Tree of Thoughts are examples of methods that enhance model performance on complex tasks by breaking them down into smaller steps.', response_metadata={'token_usage': {'completion_tokens': 87, 'prompt_tokens': 659, 'total_tokens': 746}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-b9166386-83e5-4b82-9a4b-590e5fa76671-0')]}}\n",
      "----\n"
     ]
    }
   ],
   "source": [
    "query = \"What is Task Decomposition?\"\n",
    "\n",
    "for s in agent_executor.stream(\n",
    "    {\"messages\": [HumanMessage(content=query)]}, config=config\n",
    "):\n",
    "    print(s)\n",
    "    print(\"----\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26eaae33-3c4e-49fc-9fc6-db8967e25579",
   "metadata": {},
   "source": [
    "Above, instead of inserting our query verbatim into the tool, the agent stripped unnecessary words like \"what\" and \"is\".\n",
    "\n",
    "This same principle allows the agent to use the context of the conversation when necessary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "570d8c68-136e-4ba5-969a-03ba195f6118",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_6kbxTU5CDWLmF9mrvR7bWSkI', 'function': {'arguments': '{\"query\":\"Common ways of task decomposition\"}', 'name': 'blog_post_retriever'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 769, 'total_tokens': 790}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2d2c8327-35cd-484a-b8fd-52436657c2d8-0', tool_calls=[{'name': 'blog_post_retriever', 'args': {'query': 'Common ways of task decomposition'}, 'id': 'call_6kbxTU5CDWLmF9mrvR7bWSkI'}])]}}\n",
      "----\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error in LangChainTracer.on_tool_end callback: TracerException(\"Found chain run at ID 29553415-e0f4-41a9-8921-ba489e377f68, but expected {'tool'} run.\")\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'tools': {'messages': [ToolMessage(content='Fig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nFig. 1. Overview of a LLM-powered autonomous agent system.\\nComponent One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\n\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\\nTask decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.', name='blog_post_retriever', tool_call_id='call_6kbxTU5CDWLmF9mrvR7bWSkI')]}}\n",
      "----\n",
      "{'agent': {'messages': [AIMessage(content='Common ways of task decomposition include:\\n1. Using LLM with simple prompting like \"Steps for XYZ\" or \"What are the subgoals for achieving XYZ?\"\\n2. Using task-specific instructions, for example, \"Write a story outline\" for writing a novel.\\n3. Involving human inputs in the task decomposition process.', response_metadata={'token_usage': {'completion_tokens': 67, 'prompt_tokens': 1339, 'total_tokens': 1406}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9ad14cde-ca75-4238-a868-f865e0fc50dd-0')]}}\n",
      "----\n"
     ]
    }
   ],
   "source": [
    "query = \"What according to the blog post are common ways of doing it? redo the search\"\n",
    "\n",
    "for s in agent_executor.stream(\n",
    "    {\"messages\": [HumanMessage(content=query)]}, config=config\n",
    "):\n",
    "    print(s)\n",
    "    print(\"----\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2724616-c106-4e15-a61a-3077c535f692",
   "metadata": {},
   "source": [
    "Note that the agent was able to infer that \"it\" in our query refers to \"task decomposition\", and generated a reasonable search query as a result-- in this case, \"common ways of task decomposition\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1cf87847-23bb-4672-b41c-12ad9cf81ed4",
   "metadata": {},
   "source": [
    "### Tying it together\n",
    "\n",
    "For convenience, we tie together all of the necessary steps in a single code cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "b1d2b4d4-e604-497d-873d-d345b808578e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import bs4\n",
    "from langchain.tools.retriever import create_retriever_tool\n",
    "from langchain_chroma import Chroma\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "from langgraph.checkpoint.sqlite import SqliteSaver\n",
    "\n",
    "memory = SqliteSaver.from_conn_string(\":memory:\")\n",
    "llm = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0)\n",
    "\n",
    "\n",
    "### Construct retriever ###\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n",
    "    bs_kwargs=dict(\n",
    "        parse_only=bs4.SoupStrainer(\n",
    "            class_=(\"post-content\", \"post-title\", \"post-header\")\n",
    "        )\n",
    "    ),\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "splits = text_splitter.split_documents(docs)\n",
    "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "\n",
    "### Build retriever tool ###\n",
    "tool = create_retriever_tool(\n",
    "    retriever,\n",
    "    \"blog_post_retriever\",\n",
    "    \"Searches and returns excerpts from the Autonomous Agents blog post.\",\n",
    ")\n",
    "tools = [tool]\n",
    "\n",
    "\n",
    "agent_executor = create_react_agent(llm, tools, checkpointer=memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd6bf4f4-74f4-419d-9e26-f0ed83cf05fa",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "We've covered the steps to build a basic conversational Q&A application:\n",
    "\n",
    "- We used chains to build a predictable application that generates search queries for each user input;\n",
    "- We used agents to build an application that \"decides\" when and how to generate search queries.\n",
    "\n",
    "To explore different types of retrievers and retrieval strategies, visit the [retrievers](/docs/how_to#retrievers) section of the how-to guides.\n",
    "\n",
    "For a detailed walkthrough of LangChain's conversation memory abstractions, visit the [How to add message history (memory)](/docs/how_to/message_history) LCEL page.\n",
    "\n",
    "To learn more about agents, head to the [Agents Modules](/docs/tutorials/agents)."
   ]
  }
 ],
 "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.11.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}