{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
   "metadata": {},
   "source": [
    "# How to manage conversation history\n",
    "\n",
    "One of the most common use cases for persistence is to use it to keep track of conversation history. This is great - it makes it easy to continue conversations. As conversations get longer and longer, however, this conversation history can build up and take up more and more of the context window. This can often be undesirable as it leads to more expensive and longer calls to the LLM, and potentially ones that error. In order to prevent this from happening, you need to probably manage the conversation history.\n",
    "\n",
    "Note: this guide focuses on how to do this in LangGraph, where you can fully customize how this is done. If you want a more off-the-shelf solution, you can look into functionality provided in LangChain:\n",
    "\n",
    "- [How to filter messages](https://python.langchain.com/v0.2/docs/how_to/filter_messages/)\n",
    "- [How to trim messages](https://python.langchain.com/v0.2/docs/how_to/trim_messages/)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cbd446a-808f-4394-be92-d45ab818953c",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "First, let's set up the packages we're going to want to use"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph langchain_anthropic"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
   "metadata": {},
   "source": [
    "Next, we need to set API keys for Anthropic (the LLM we will use)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "_set_env(\"ANTHROPIC_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
   "metadata": {},
   "source": [
    "Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "_set_env(\"LANGCHAIN_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4767ef1c-a7cf-41f8-a301-558988cb7ac5",
   "metadata": {},
   "source": [
    "## Build the agent\n",
    "Let's now build a simple ReAct style agent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "378899a9-3b9a-4748-95b6-eb00e0828677",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import MessagesState, StateGraph, START\n",
    "from langgraph.prebuilt import ToolNode\n",
    "\n",
    "memory = MemorySaver()\n",
    "\n",
    "\n",
    "@tool\n",
    "def search(query: str):\n",
    "    \"\"\"Call to surf the web.\"\"\"\n",
    "    # This is a placeholder for the actual implementation\n",
    "    # Don't let the LLM know this though 😊\n",
    "    return [\n",
    "        \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
    "    ]\n",
    "\n",
    "\n",
    "tools = [search]\n",
    "tool_node = ToolNode(tools)\n",
    "model = ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n",
    "bound_model = model.bind_tools(tools)\n",
    "\n",
    "\n",
    "def should_continue(state: MessagesState) -> Literal[\"action\", \"__end__\"]:\n",
    "    \"\"\"Return the next node to execute.\"\"\"\n",
    "    last_message = state[\"messages\"][-1]\n",
    "    # If there is no function call, then we finish\n",
    "    if not last_message.tool_calls:\n",
    "        return \"__end__\"\n",
    "    # Otherwise if there is, we continue\n",
    "    return \"action\"\n",
    "\n",
    "\n",
    "# Define the function that calls the model\n",
    "def call_model(state: MessagesState):\n",
    "    response = bound_model.invoke(state[\"messages\"])\n",
    "    # We return a list, because this will get added to the existing list\n",
    "    return {\"messages\": response}\n",
    "\n",
    "\n",
    "# Define a new graph\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "# Define the two nodes we will cycle between\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"action\", tool_node)\n",
    "\n",
    "# Set the entrypoint as `agent`\n",
    "# This means that this node is the first one called\n",
    "workflow.add_edge(START, \"agent\")\n",
    "\n",
    "# We now add a conditional edge\n",
    "workflow.add_conditional_edges(\n",
    "    # First, we define the start node. We use `agent`.\n",
    "    # This means these are the edges taken after the `agent` node is called.\n",
    "    \"agent\",\n",
    "    # Next, we pass in the function that will determine which node is called next.\n",
    "    should_continue,\n",
    ")\n",
    "\n",
    "# We now add a normal edge from `tools` to `agent`.\n",
    "# This means that after `tools` is called, `agent` node is called next.\n",
    "workflow.add_edge(\"action\", \"agent\")\n",
    "\n",
    "# Finally, we compile it!\n",
    "# This compiles it into a LangChain Runnable,\n",
    "# meaning you can use it as you would any other runnable\n",
    "app = workflow.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "57b27553-21be-43e5-ac48-d1d0a3aa0dca",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "hi! I'm bob\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Nice to meet you, Bob! As an AI assistant, I don't have a physical form, but I'm happy to chat with you and try my best to help out however I can. Please feel free to ask me anything, and I'll do my best to provide useful information or assistance.\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "what's my name?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "You said your name is Bob, so that is the name I have for you.\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
    "input_message = HumanMessage(content=\"hi! I'm bob\")\n",
    "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()\n",
    "\n",
    "\n",
    "input_message = HumanMessage(content=\"what's my name?\")\n",
    "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d5da4c9-ba8b-46cb-a860-63fe585d15c5",
   "metadata": {},
   "source": [
    "## Filtering messages\n",
    "\n",
    "The most straight-forward thing to do to prevent conversation history from blowing up is to filter the list of messages before they get passed to the LLM. This involves two parts: defining a function to filter messages, and then adding it to the graph. See the example below which defines a really simple `filter_messages` function and then uses it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "eb20430f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import MessagesState, StateGraph, START\n",
    "from langgraph.prebuilt import ToolNode\n",
    "\n",
    "memory = MemorySaver()\n",
    "\n",
    "\n",
    "@tool\n",
    "def search(query: str):\n",
    "    \"\"\"Call to surf the web.\"\"\"\n",
    "    # This is a placeholder for the actual implementation\n",
    "    # Don't let the LLM know this though 😊\n",
    "    return [\n",
    "        \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
    "    ]\n",
    "\n",
    "\n",
    "tools = [search]\n",
    "tool_node = ToolNode(tools)\n",
    "model = ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n",
    "bound_model = model.bind_tools(tools)\n",
    "\n",
    "\n",
    "def should_continue(state: MessagesState) -> Literal[\"action\", \"__end__\"]:\n",
    "    \"\"\"Return the next node to execute.\"\"\"\n",
    "    last_message = state[\"messages\"][-1]\n",
    "    # If there is no function call, then we finish\n",
    "    if not last_message.tool_calls:\n",
    "        return \"__end__\"\n",
    "    # Otherwise if there is, we continue\n",
    "    return \"action\"\n",
    "\n",
    "\n",
    "def filter_messages(messages: list):\n",
    "    # This is very simple helper function which only ever uses the last two messages\n",
    "    return messages[-2:]\n",
    "\n",
    "\n",
    "# Define the function that calls the model\n",
    "def call_model(state: MessagesState):\n",
    "    messages = filter_messages(state[\"messages\"])\n",
    "    response = bound_model.invoke(messages)\n",
    "    # We return a list, because this will get added to the existing list\n",
    "    return {\"messages\": response}\n",
    "\n",
    "\n",
    "# Define a new graph\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "# Define the two nodes we will cycle between\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"action\", tool_node)\n",
    "\n",
    "# Set the entrypoint as `agent`\n",
    "# This means that this node is the first one called\n",
    "workflow.add_edge(START, \"agent\")\n",
    "\n",
    "# We now add a conditional edge\n",
    "workflow.add_conditional_edges(\n",
    "    # First, we define the start node. We use `agent`.\n",
    "    # This means these are the edges taken after the `agent` node is called.\n",
    "    \"agent\",\n",
    "    # Next, we pass in the function that will determine which node is called next.\n",
    "    should_continue,\n",
    ")\n",
    "\n",
    "# We now add a normal edge from `tools` to `agent`.\n",
    "# This means that after `tools` is called, `agent` node is called next.\n",
    "workflow.add_edge(\"action\", \"agent\")\n",
    "\n",
    "# Finally, we compile it!\n",
    "# This compiles it into a LangChain Runnable,\n",
    "# meaning you can use it as you would any other runnable\n",
    "app = workflow.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "52468ebb-4b23-45ac-a98e-b4439f37740a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "hi! I'm bob\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Nice to meet you, Bob! I'm Claude, an AI assistant created by Anthropic. It's a pleasure to chat with you. Feel free to ask me anything, I'm here to help!\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "what's my name?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "I'm afraid I don't actually know your name. As an AI assistant, I don't have information about the specific identities of the people I talk to. I only know what is provided to me during our conversation.\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
    "input_message = HumanMessage(content=\"hi! I'm bob\")\n",
    "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()\n",
    "\n",
    "# This will now not remember the previous messages\n",
    "# (because we set `messages[-1:]` in the filter messages argument)\n",
    "input_message = HumanMessage(content=\"what's my name?\")\n",
    "for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "454102b6-7112-4710-aa08-ba675e8be14c",
   "metadata": {},
   "source": [
    "In the above example we defined the `filter_messages` function ourselves. We also provide off-the-shelf ways to trim and filter messages in LangChain. \n",
    "\n",
    "- [How to filter messages](https://python.langchain.com/v0.2/docs/how_to/filter_messages/)\n",
    "- [How to trim messages](https://python.langchain.com/v0.2/docs/how_to/trim_messages/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "686861bb-ec32-46f3-b7b3-fdac106f22f6",
   "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.11.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
