{
 "cells": [
  {
   "cell_type": "raw",
   "id": "8165bd4c",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "---\n",
    "keywords: [memory]\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f47033eb",
   "metadata": {},
   "source": [
    "# How to add message history\n",
    "\n",
    ":::info Prerequisites\n",
    "\n",
    "This guide assumes familiarity with the following concepts:\n",
    "- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
    "- [Chaining runnables](/docs/how_to/sequence/)\n",
    "- [Configuring chain parameters at runtime](/docs/how_to/configure)\n",
    "- [Prompt templates](/docs/concepts/#prompt-templates)\n",
    "- [Chat Messages](/docs/concepts/#message-types)\n",
    "\n",
    ":::\n",
    "\n",
    "Passing conversation state into and out a chain is vital when building a chatbot. The [`RunnableWithMessageHistory`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html#langchain_core.runnables.history.RunnableWithMessageHistory) class lets us add message history to certain types of chains. It wraps another Runnable and manages the chat message history for it. Specifically, it loads previous messages in the conversation BEFORE passing it to the Runnable, and it saves the generated response as a message AFTER calling the runnable. This class also enables multiple conversations by saving each conversation with a `session_id` - it then expects a `session_id` to be passed in the config when calling the runnable, and uses that to look up the relevant conversation history.\n",
    "\n",
    "![index_diagram](../../static/img/message_history.png)\n",
    "\n",
    "In practice this looks something like:\n",
    "\n",
    "```python\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(\n",
    "    # The underlying runnable\n",
    "    runnable,  \n",
    "    # A function that takes in a session id and returns a memory object\n",
    "    get_session_history,  \n",
    "    # Other parameters that may be needed to align the inputs/outputs\n",
    "    # of the Runnable with the memory object\n",
    "    ...  \n",
    ")\n",
    "\n",
    "with_message_history.invoke(\n",
    "    # The same input as before\n",
    "    {\"ability\": \"math\", \"input\": \"What does cosine mean?\"},\n",
    "    # Configuration specifying the `session_id`,\n",
    "    # which controls which conversation to load\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "```\n",
    "\n",
    "\n",
    "In order to properly set this up there are two main things to consider:\n",
    "\n",
    "1. How to store and load messages? (this is `get_session_history` in the example above)\n",
    "2. What is the underlying Runnable you are wrapping and what are its inputs/outputs? (this is `runnable` in the example above, as well any additional parameters you pass to `RunnableWithMessageHistory` to align the inputs/outputs)\n",
    "\n",
    "Let's walk through these pieces (and more) below."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "734123cb",
   "metadata": {},
   "source": [
    "## How to store and load messages\n",
    "\n",
    "A key part of this is storing and loading messages.\n",
    "When constructing `RunnableWithMessageHistory` you need to pass in a `get_session_history` function.\n",
    "This function should take in a `session_id` and return a `BaseChatMessageHistory` object.\n",
    "\n",
    "**What is `session_id`?** \n",
    "\n",
    "`session_id` is an identifier for the session (conversation) thread that these input messages correspond to. This allows you to maintain several conversations/threads with the same chain at the same time.\n",
    "\n",
    "**What is `BaseChatMessageHistory`?** \n",
    "\n",
    "`BaseChatMessageHistory` is a class that can load and save message objects. It will be called by `RunnableWithMessageHistory` to do exactly that. These classes are usually initialized with a session id.\n",
    "\n",
    "Let's create a `get_session_history` object to use for this example. To keep things simple, we will use a simple SQLiteMessage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e8210560",
   "metadata": {},
   "outputs": [],
   "source": [
    "! rm memory.db"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "27f36241",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.chat_message_histories import SQLChatMessageHistory\n",
    "\n",
    "\n",
    "def get_session_history(session_id):\n",
    "    return SQLChatMessageHistory(session_id, \"sqlite:///memory.db\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c200cb3a",
   "metadata": {},
   "source": [
    "Check out the [memory integrations](https://integrations.langchain.com/memory) page for implementations of chat message histories using other providers (Redis, Postgres, etc)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a531da5e",
   "metadata": {},
   "source": [
    "## What is the runnable you are trying to wrap?\n",
    "\n",
    "`RunnableWithMessageHistory` can only wrap certain types of Runnables. Specifically, it can be used for any Runnable that takes as input one of:\n",
    "\n",
    "* a sequence of [`BaseMessages`](/docs/concepts/#message-types)\n",
    "* a dict with a key that takes a sequence of `BaseMessages`\n",
    "* a dict with a key that takes the latest message(s) as a string or sequence of `BaseMessages`, and a separate key that takes historical messages\n",
    "\n",
    "And returns as output one of\n",
    "\n",
    "* a string that can be treated as the contents of an `AIMessage`\n",
    "* a sequence of `BaseMessage`\n",
    "* a dict with a key that contains a sequence of `BaseMessage`\n",
    "\n",
    "Let's take a look at some examples to see how it works. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a4becbd-238e-4c1d-a02d-08e61fbc3763",
   "metadata": {},
   "source": [
    "### Setup\n",
    "\n",
    "First we construct a runnable (which here accepts a dict as input and returns a message as output):\n",
    "\n",
    "```{=mdx}\n",
    "import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
    "\n",
    "<ChatModelTabs\n",
    "  customVarName=\"llm\"\n",
    "/>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6489f585",
   "metadata": {},
   "outputs": [],
   "source": [
    "# | output: false\n",
    "# | echo: false\n",
    "\n",
    "# %pip install -qU langchain langchain_anthropic\n",
    "\n",
    "# import os\n",
    "# from getpass import getpass\n",
    "\n",
    "# os.environ[\"ANTHROPIC_API_KEY\"] = getpass()\n",
    "from langchain_anthropic import ChatAnthropic\n",
    "\n",
    "model = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2ed413b4-33a1-48ee-89b0-2d4917ec101a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8816b01",
   "metadata": {},
   "source": [
    "### Messages input, message(s) output\n",
    "\n",
    "The simplest form is just adding memory to a ChatModel.\n",
    "ChatModels accept a list of messages as input and output a message.\n",
    "This makes it very easy to use `RunnableWithMessageHistory` - no additional configuration is needed!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0521d551",
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable_with_history = RunnableWithMessageHistory(\n",
    "    model,\n",
    "    get_session_history,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d5142e1a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"It's nice to meet you, Bob! I'm Claude, an AI assistant created by Anthropic. How can I help you today?\", response_metadata={'id': 'msg_01UHCCMiZz9yNYjt41xUJrtk', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 32}}, id='run-55f6a451-606b-4e04-9e39-e03b81035c1f-0', usage_metadata={'input_tokens': 12, 'output_tokens': 32, 'total_tokens': 44})"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"hi - im bob!\")],\n",
    "    config={\"configurable\": {\"session_id\": \"1\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "768e0c12",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='I\\'m afraid I don\\'t actually know your name - you introduced yourself as Bob, but I don\\'t have any other information about your identity. As an AI assistant, I don\\'t have a way to independently verify people\\'s names or identities. I\\'m happy to continue our conversation, but I\\'ll just refer to you as \"Bob\" since that\\'s the name you provided.', response_metadata={'id': 'msg_018L96tAxiexMKsHBQz22CcE', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 52, 'output_tokens': 80}}, id='run-7399ddb5-bb06-444b-bfb2-2f65674105dd-0', usage_metadata={'input_tokens': 52, 'output_tokens': 80, 'total_tokens': 132})"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"whats my name?\")],\n",
    "    config={\"configurable\": {\"session_id\": \"1\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d942227",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that in this case the context is preserved via the chat history for the provided `session_id`, so the model knows the users name.\n",
    "\n",
    ":::\n",
    "\n",
    "We can now try this with a new session id and see that it does not remember."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "addddd03",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"I'm afraid I don't actually know your name. As an AI assistant, I don't have personal information about you unless you provide it to me directly.\", response_metadata={'id': 'msg_01LhbWu7mSKTvKAx7iQpMPzd', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 35}}, id='run-cf86cad2-21f2-4525-afc8-09bfd1e8af70-0', usage_metadata={'input_tokens': 12, 'output_tokens': 35, 'total_tokens': 47})"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"whats my name?\")],\n",
    "    config={\"configurable\": {\"session_id\": \"1a\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b26a0c0",
   "metadata": {},
   "source": [
    ":::info      \n",
    "\n",
    "When we pass a different `session_id`, we start a new chat history, so the model does not know what the user's name is. \n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5bb5c7c",
   "metadata": {},
   "source": [
    "### Dictionary input, message(s) output\n",
    "\n",
    "Besides just wrapping a raw model, the next step up is wrapping a prompt + LLM. This now changes the input to be a **dictionary** (because the input to a prompt is a dictionary). This adds two bits of complication.\n",
    "\n",
    "First: a dictionary can have multiple keys, but we only want to save ONE as input. In order to do this, we now now need to specify a key to save as the input.\n",
    "\n",
    "Second: once we load the messages, we need to know how to save them to the dictionary. That equates to know which key in the dictionary to save them in. Therefore, we need to specify a key to save the loaded messages in.\n",
    "\n",
    "Putting it all together, that ends up looking something like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "34edd990",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You're an assistant who speaks in {language}. Respond in 20 words or fewer\",\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "runnable = prompt | model\n",
    "\n",
    "runnable_with_history = RunnableWithMessageHistory(\n",
    "    runnable,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"input\",\n",
    "    history_messages_key=\"history\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0baa075",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that we've specified `input_messages_key` (the key to be treated as the latest input message) and `history_messages_key` (the key to add historical messages to).\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "5877544f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Ciao Bob! È un piacere conoscerti. Come stai oggi?', response_metadata={'id': 'msg_0121ADUEe4G1hMC6zbqFWofr', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 29, 'output_tokens': 23}}, id='run-246a70df-aad6-43d6-a7e8-166d96e0d67e-0', usage_metadata={'input_tokens': 29, 'output_tokens': 23, 'total_tokens': 52})"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"hi im bob!\"},\n",
    "    config={\"configurable\": {\"session_id\": \"2\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "8605c2b1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Bob, il tuo nome è Bob.', response_metadata={'id': 'msg_01EDUZG6nRLGeti9KhFN5cek', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 60, 'output_tokens': 12}}, id='run-294b4a72-81bc-4c43-b199-3aafdff87cb3-0', usage_metadata={'input_tokens': 60, 'output_tokens': 12, 'total_tokens': 72})"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"whats my name?\"},\n",
    "    config={\"configurable\": {\"session_id\": \"2\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ab7c09f",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that in this case the context is preserved via the chat history for the provided `session_id`, so the model knows the users name.\n",
    "\n",
    ":::\n",
    "\n",
    "We can now try this with a new session id and see that it does not remember."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "c7ddad6b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Mi dispiace, non so il tuo nome. Come posso aiutarti?', response_metadata={'id': 'msg_01Lyd9FAGQJTxxAZoFi3sQpQ', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 30, 'output_tokens': 23}}, id='run-19a82197-3b1c-4b5f-a68d-f91f4a2ba523-0', usage_metadata={'input_tokens': 30, 'output_tokens': 23, 'total_tokens': 53})"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"whats my name?\"},\n",
    "    config={\"configurable\": {\"session_id\": \"2a\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a05e6c12",
   "metadata": {},
   "source": [
    ":::info      \n",
    "\n",
    "When we pass a different `session_id`, we start a new chat history, so the model does not know what the user's name is. \n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "717440a9",
   "metadata": {},
   "source": [
    "### Messages input, dict output\n",
    "\n",
    "This format is useful when you are using a model to generate one key in a dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "80b8efb0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_core.runnables import RunnableParallel\n",
    "\n",
    "chain = RunnableParallel({\"output_message\": model})\n",
    "\n",
    "\n",
    "runnable_with_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    output_messages_key=\"output_message\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9040c535",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that we've specified `output_messages_key` (the key to be treated as the output to save).\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "8b26a209",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'output_message': AIMessage(content=\"It's nice to meet you, Bob! I'm Claude, an AI assistant created by Anthropic. How can I help you today?\", response_metadata={'id': 'msg_01WWJSyUyGGKuBqTs3h18ZMM', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 32}}, id='run-0f50cb43-a734-447c-b535-07c615a0984c-0', usage_metadata={'input_tokens': 12, 'output_tokens': 32, 'total_tokens': 44})}"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"hi - im bob!\")],\n",
    "    config={\"configurable\": {\"session_id\": \"3\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "743edcf8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'output_message': AIMessage(content='I\\'m afraid I don\\'t actually know your name - you introduced yourself as Bob, but I don\\'t have any other information about your identity. As an AI assistant, I don\\'t have a way to independently verify people\\'s names or identities. I\\'m happy to continue our conversation, but I\\'ll just refer to you as \"Bob\" since that\\'s the name you provided.', response_metadata={'id': 'msg_01TEGrhfLXTwo36rC7svdTy4', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 52, 'output_tokens': 80}}, id='run-178e8f3f-da21-430d-9edc-ef07797a5e2d-0', usage_metadata={'input_tokens': 52, 'output_tokens': 80, 'total_tokens': 132})}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"whats my name?\")],\n",
    "    config={\"configurable\": {\"session_id\": \"3\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81efb7f1",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that in this case the context is preserved via the chat history for the provided `session_id`, so the model knows the users name.\n",
    "\n",
    ":::\n",
    "\n",
    "We can now try this with a new session id and see that it does not remember."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "b8b04907",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'output_message': AIMessage(content=\"I'm afraid I don't actually know your name. As an AI assistant, I don't have personal information about you unless you provide it to me directly.\", response_metadata={'id': 'msg_0118ZBudDXAC9P6smf91NhCX', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 35}}, id='run-deb14a3a-0336-42b4-8ace-ad1e52ca5910-0', usage_metadata={'input_tokens': 12, 'output_tokens': 35, 'total_tokens': 47})}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    [HumanMessage(content=\"whats my name?\")],\n",
    "    config={\"configurable\": {\"session_id\": \"3a\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6716a068",
   "metadata": {},
   "source": [
    ":::info      \n",
    "\n",
    "When we pass a different `session_id`, we start a new chat history, so the model does not know what the user's name is. \n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec4187d0",
   "metadata": {},
   "source": [
    "### Dict with single key for all messages input, messages output\n",
    "\n",
    "This is a specific case of \"Dictionary input, message(s) output\". In this situation, because there is only a single key we don't need to specify as much - we only need to specify the `input_messages_key`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "7530c4ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "runnable_with_history = RunnableWithMessageHistory(\n",
    "    itemgetter(\"input_messages\") | model,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"input_messages\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "def75152",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that we've specified `input_messages_key` (the key to be treated as the latest input message).\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "659bc1bf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"It's nice to meet you, Bob! I'm Claude, an AI assistant created by Anthropic. How can I help you today?\", response_metadata={'id': 'msg_01UdD5wz1J5xwoz5D94onaQC', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 32}}, id='run-91bee6eb-0814-4557-ad71-fef9b0270358-0', usage_metadata={'input_tokens': 12, 'output_tokens': 32, 'total_tokens': 44})"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"input_messages\": [HumanMessage(content=\"hi - im bob!\")]},\n",
    "    config={\"configurable\": {\"session_id\": \"4\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "6da2835e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='I\\'m afraid I don\\'t actually know your name - you introduced yourself as Bob, but I don\\'t have any other information about your identity. As an AI assistant, I don\\'t have a way to independently verify people\\'s names or identities. I\\'m happy to continue our conversation, but I\\'ll just refer to you as \"Bob\" since that\\'s the name you provided.', response_metadata={'id': 'msg_012WUygxBKXcVJPeTW14LNrc', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 52, 'output_tokens': 80}}, id='run-fcbaaa1a-8c33-4eec-b0b0-5b800a47bddd-0', usage_metadata={'input_tokens': 52, 'output_tokens': 80, 'total_tokens': 132})"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"input_messages\": [HumanMessage(content=\"whats my name?\")]},\n",
    "    config={\"configurable\": {\"session_id\": \"4\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4c7a6f2",
   "metadata": {},
   "source": [
    ":::info\n",
    "\n",
    "Note that in this case the context is preserved via the chat history for the provided `session_id`, so the model knows the users name.\n",
    "\n",
    ":::\n",
    "\n",
    "We can now try this with a new session id and see that it does not remember."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "6cf6abd6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"I'm afraid I don't actually know your name. As an AI assistant, I don't have personal information about you unless you provide it to me directly.\", response_metadata={'id': 'msg_017xW3Ki5y4UBYzCU9Mf1pgM', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 35}}, id='run-d2f372f7-3679-4a5c-9331-a55b820ec03e-0', usage_metadata={'input_tokens': 12, 'output_tokens': 35, 'total_tokens': 47})"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "runnable_with_history.invoke(\n",
    "    {\"input_messages\": [HumanMessage(content=\"whats my name?\")]},\n",
    "    config={\"configurable\": {\"session_id\": \"4a\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9839a6d1",
   "metadata": {},
   "source": [
    ":::info      \n",
    "\n",
    "When we pass a different `session_id`, we start a new chat history, so the model does not know what the user's name is. \n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6710e65",
   "metadata": {},
   "source": [
    "## Customization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d29497be-3366-408d-bbb9-d4a8bf4ef37c",
   "metadata": {},
   "source": [
    "The configuration parameters by which we track message histories can be customized by passing in a list of ``ConfigurableFieldSpec`` objects to the ``history_factory_config`` parameter. Below, we use two parameters: a `user_id` and `conversation_id`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "1c89daee-deff-4fdf-86a3-178f7d8ef536",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Ciao Bob! È un piacere conoscerti. Come stai oggi?', response_metadata={'id': 'msg_016RJebCoiAgWaNcbv9wrMNW', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 29, 'output_tokens': 23}}, id='run-40425414-8f72-47d4-bf1d-a84175d8b3f8-0', usage_metadata={'input_tokens': 29, 'output_tokens': 23, 'total_tokens': 52})"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.runnables import ConfigurableFieldSpec\n",
    "\n",
    "\n",
    "def get_session_history(user_id: str, conversation_id: str):\n",
    "    return SQLChatMessageHistory(f\"{user_id}--{conversation_id}\", \"sqlite:///memory.db\")\n",
    "\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(\n",
    "    runnable,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"input\",\n",
    "    history_messages_key=\"history\",\n",
    "    history_factory_config=[\n",
    "        ConfigurableFieldSpec(\n",
    "            id=\"user_id\",\n",
    "            annotation=str,\n",
    "            name=\"User ID\",\n",
    "            description=\"Unique identifier for the user.\",\n",
    "            default=\"\",\n",
    "            is_shared=True,\n",
    "        ),\n",
    "        ConfigurableFieldSpec(\n",
    "            id=\"conversation_id\",\n",
    "            annotation=str,\n",
    "            name=\"Conversation ID\",\n",
    "            description=\"Unique identifier for the conversation.\",\n",
    "            default=\"\",\n",
    "            is_shared=True,\n",
    "        ),\n",
    "    ],\n",
    ")\n",
    "\n",
    "with_message_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"hi im bob!\"},\n",
    "    config={\"configurable\": {\"user_id\": \"123\", \"conversation_id\": \"1\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "4f282883",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Bob, il tuo nome è Bob.', response_metadata={'id': 'msg_01Kktiy3auFDKESY54KtTWPX', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 60, 'output_tokens': 12}}, id='run-c7768420-3f30-43f5-8834-74b1979630dd-0', usage_metadata={'input_tokens': 60, 'output_tokens': 12, 'total_tokens': 72})"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# remembers\n",
    "with_message_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"whats my name?\"},\n",
    "    config={\"configurable\": {\"user_id\": \"123\", \"conversation_id\": \"1\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "fc122c18",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Mi dispiace, non so il tuo nome. Come posso aiutarti?', response_metadata={'id': 'msg_0178FpbpPNioB7kqvyHk7rjD', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 30, 'output_tokens': 23}}, id='run-df1f1768-aab6-4aec-8bba-e33fc9e90b8d-0', usage_metadata={'input_tokens': 30, 'output_tokens': 23, 'total_tokens': 53})"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# New user_id --> does not remember\n",
    "with_message_history.invoke(\n",
    "    {\"language\": \"italian\", \"input\": \"whats my name?\"},\n",
    "    config={\"configurable\": {\"user_id\": \"456\", \"conversation_id\": \"1\"}},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ce37565",
   "metadata": {},
   "source": [
    "Note that in this case the context was preserved for the same `user_id`, but once we changed it, the new chat history was started, even though the `conversation_id` was the same."
   ]
  }
 ],
 "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.9.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
