diff --git a/docs/docs/versions/migrating_memory/manage_conversation_history.ipynb b/docs/docs/versions/migrating_memory/manage_conversation_history.ipynb index 85fb2fc6287..aed400d867d 100644 --- a/docs/docs/versions/migrating_memory/manage_conversation_history.ipynb +++ b/docs/docs/versions/migrating_memory/manage_conversation_history.ipynb @@ -104,10 +104,18 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 3, "id": "8b6e1063-cf3a-456a-bf7d-830e5c1d2864", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_2459205/1840360377.py:24: LangChainDeprecationWarning: The class `LLMChain` was deprecated in LangChain 0.1.17 and will be removed in 1.0. Use RunnableSequence, e.g., `prompt | llm` instead.\n", + " legacy_chain = LLMChain(\n" + ] + }, { "name": "stdout", "output_type": "stream", @@ -155,17 +163,17 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 4, "id": "c7fa1618", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Your name is Bob. How can I assist you today, Bob?'" + "'Your name is Bob. How can I assist you further, Bob?'" ] }, - "execution_count": 40, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -192,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 5, "id": "e591965c-c4d7-4df7-966d-4d14bd46e157", "metadata": {}, "outputs": [ @@ -205,13 +213,13 @@ "hi! I'm bob\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Hello Bob! How can I assist you today?\n", + "Hello, Bob! How can I assist you today?\n", "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "what was my name?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "I'm sorry, I don't have the ability to know your name unless you tell me. Feel free to share it with me if you'd like.\n" + "I'm sorry, but I do not have the ability to know your name as I am an AI assistant.\n" ] } ], @@ -298,17 +306,25 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 6, "id": "dc2928de-d7a4-4f87-ab96-59bde9a3829f", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/eugene/.pyenv/versions/3.11.4/envs/core/lib/python3.11/site-packages/langsmith/client.py:5301: LangChainBetaWarning: The function `loads` is in beta. It is actively being worked on, so the API may change.\n", + " prompt = loads(json.dumps(prompt_object.manifest))\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "{'input': 'hi! my name is bob what is my age?', 'chat_history': [HumanMessage(content='hi! my name is bob what is my age?', additional_kwargs={}, response_metadata={}), AIMessage(content='Hello Bob! You are 42 years old. If you need any more assistance or information, feel free to ask!', additional_kwargs={}, response_metadata={})], 'output': 'Hello Bob! You are 42 years old. If you need any more assistance or information, feel free to ask!'}\n", + "{'input': 'hi! my name is bob what is my age?', 'chat_history': [HumanMessage(content='hi! my name is bob what is my age?', additional_kwargs={}, response_metadata={}), AIMessage(content='Hello Bob! You are 42 years old. If you need any more assistance or have any other questions, feel free to ask!', additional_kwargs={}, response_metadata={})], 'output': 'Hello Bob! You are 42 years old. If you need any more assistance or have any other questions, feel free to ask!'}\n", "\n", - "{'input': 'do you remember my name?', 'chat_history': [HumanMessage(content='hi! my name is bob what is my age?', additional_kwargs={}, response_metadata={}), AIMessage(content='Hello Bob! You are 42 years old. If you need any more assistance or information, feel free to ask!', additional_kwargs={}, response_metadata={}), HumanMessage(content='do you remember my name?', additional_kwargs={}, response_metadata={}), AIMessage(content='Yes, your name is Bob. How can I assist you further, Bob?', additional_kwargs={}, response_metadata={})], 'output': 'Yes, your name is Bob. How can I assist you further, Bob?'}\n" + "{'input': 'do you remember my name?', 'chat_history': [HumanMessage(content='hi! my name is bob what is my age?', additional_kwargs={}, response_metadata={}), AIMessage(content='Hello Bob! You are 42 years old. If you need any more assistance or have any other questions, feel free to ask!', additional_kwargs={}, response_metadata={}), HumanMessage(content='do you remember my name?', additional_kwargs={}, response_metadata={}), AIMessage(content='Yes, your name is Bob. How can I assist you further, Bob?', additional_kwargs={}, response_metadata={})], 'output': 'Yes, your name is Bob. How can I assist you further, Bob?'}\n" ] } ], @@ -333,7 +349,6 @@ "\n", "tools = [get_user_age]\n", "\n", - "\n", "# Get the prompt to use - you can modify this!\n", "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n", "\n", @@ -377,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 7, "id": "bdb29c9b-bc57-4512-9430-c5d5e3f91e3c", "metadata": {}, "outputs": [ @@ -390,8 +405,8 @@ "hi! I'm bob. What is my age?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " get_user_age (call_l3dpzCgvONmrcAuksHxKfcsa)\n", - " Call ID: call_l3dpzCgvONmrcAuksHxKfcsa\n", + " get_user_age (call_ewWp6keHVouwJyM4lRhk4X25)\n", + " Call ID: call_ewWp6keHVouwJyM4lRhk4X25\n", " Args:\n", " name: bob\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", @@ -464,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 8, "id": "fe63e424-1111-4f6a-a9c9-0887eb150ab0", "metadata": {}, "outputs": [ @@ -477,7 +492,7 @@ "hi! do you remember my name?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Hello! I'm sorry, but I don't have the capability to remember personal information like names. If you could remind me of your name, I'd be happy to assist you further!\n" + "Hello! Yes, I remember your name. It's great to see you again! How can I assist you today?\n" ] } ], @@ -500,9 +515,9 @@ "source": [ "\n", "\n", - "## Add Conversation History Processing\n", + "## Implementing Conversation History Processing\n", "\n", - "All the memory types below add some additional logic to process the conversation history.\n", + "Each of the following memory types applies specific logic to handle the conversation history:\n", "\n", "| Memory Type | Description |\n", "|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|\n", @@ -511,19 +526,33 @@ "| `ConversationSummaryMemory` | Continually summarizes the conversation history. The summary is updated after each conversation turn. The abstraction returns the summary of the conversation history. |\n", "| `ConversationSummaryBufferMemory` | Provides a running summary of the conversation together with the most recent messages in the conversation under the constraint that the total number of tokens in the conversation does not exceed a certain limit. |\n", "\n", + "The general approach involves writing the necessary logic for processing conversation history and integrating it at the correct point.\n", "\n", - "The general logic is to write the processing logic that you need for the conversation history and then add it in the appropriate place depending.\n", + "We’ll start by building a simple processor using LangChain's built-in [trim_messages](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function, and then demonstrate how to integrate it into your application.\n", "\n", - "We'll create a simple processor using `LangChain's` [trim_messages](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) built in functionality, and then see how to plug it into your application.\n", + "You can later replace this basic setup with more advanced logic tailored to your specific needs.\n", + "\n", + "\n", + ":::important\n", + "\n", + "We’ll begin by exploring a straightforward method that involves applying processing logic to the entire conversation history.\n", + "\n", + "While this approach is easy to test and implement, it has a downside: as the conversation grows, s\n", + "o does the latency, since the logic is re-applied to all previous exchanges at each turn.\n", + "\n", + "More advanced strategies focus on incrementally updating the conversation history to avoid redundant processing.\n", + "\n", + "For instance, the langgraph [how-to guide on summarization](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/) demonstrates\n", + "how to maintain a running summary of the conversation while discarding older messages, ensuring they aren't re-processed during later turns.\n", + ":::\n", "\n", - "You can later replace the logic with more complex logic that's better suited for your use case.\n", "\n", "### ConversationBufferWindowMemory, ConversationTokenBufferMemory" ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 12, "id": "a501806e-deac-458c-88b1-b615efde9930", "metadata": {}, "outputs": [ @@ -534,10 +563,10 @@ " AIMessage(content=\"Hmmm let me think.\\n\\nWhy, he's probably chasing after the last cup of coffee in the office!\", additional_kwargs={}, response_metadata={}),\n", " HumanMessage(content='why is 42 always the answer?', additional_kwargs={}, response_metadata={}),\n", " AIMessage(content='Because it’s the only number that’s constantly right, even when it doesn’t add up!', additional_kwargs={}, response_metadata={}),\n", - " HumanMessage(content='What is the difference between a computer and a machine?', additional_kwargs={}, response_metadata={})]" + " HumanMessage(content='What did the cow say?', additional_kwargs={}, response_metadata={})]" ] }, - "execution_count": 70, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -565,7 +594,7 @@ " AIMessage(\n", " \"Because it’s the only number that’s constantly right, even when it doesn’t add up!\"\n", " ),\n", - " HumanMessage(\"What is the difference between a computer and a machine?\"),\n", + " HumanMessage(\"What did the cow say?\"),\n", "]\n", "\n", "\n", @@ -610,34 +639,49 @@ "The obvious downside of this approach is that latency starts to increase as the conversation history grows because of two reasons:\n", "\n", "1. As the conversation gets longer, more data may need to be fetched from whatever store your'e using to store the conversation history (if not storing it in memory).\n", - "2. The pre-processing logic will end up doing a lot of redundant computation, repeating computation from previous steps of the conversation." + "2. The pre-processing logic will end up doing a lot of redundant computation, repeating computation from previous steps of the conversation.\n", + "\n", + ":::caution\n", + "\n", + "If you're using tools, remember to bind the tools to the model before adding a pre-processing step to it!\n", + "\n", + ":::" ] }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 13, "id": "5537b001-a49c-4d12-b25d-4087890f49b4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content=\"Well, a computer is a machine that can think, while a machine is a computer that can't!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 96, 'total_tokens': 117, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-0f08b428-8eb8-4422-b6d0-3ac494c0b6fe-0', usage_metadata={'input_tokens': 96, 'output_tokens': 21, 'total_tokens': 117})" + "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_aYRmth57XGNqvZq3V5vn0Urg', 'function': {'arguments': '{}', 'name': 'what_did_the_cow_say'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 126, 'total_tokens': 142, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-93b89a08-b8a1-4a7a-bedd-ab7e2ce47894-0', tool_calls=[{'name': 'what_did_the_cow_say', 'args': {}, 'id': 'call_aYRmth57XGNqvZq3V5vn0Urg', 'type': 'tool_call'}], usage_metadata={'input_tokens': 126, 'output_tokens': 16, 'total_tokens': 142})" ] }, - "execution_count": 73, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "from langchain_core.tools import tool\n", "from langchain_openai import ChatOpenAI\n", "\n", "model = ChatOpenAI()\n", "\n", - "# highlight-next-line\n", - "model_with_preprocessor = message_processor | model\n", "\n", + "@tool\n", + "def what_did_the_cow_say() -> str:\n", + " \"\"\"Check to see what the cow said.\"\"\"\n", + " return \"foo\"\n", + "\n", + "\n", + "model_with_tools = model.bind_tools([what_did_the_cow_say])\n", + "\n", + "# highlight-next-line\n", + "model_with_preprocessor = message_processor | model_with_tools\n", "\n", "# full_message_history in the previous code block.\n", "# We pass it explicity to the model_with_preprocesor for illustrative purposes.\n", @@ -646,6 +690,18 @@ "model_with_preprocessor.invoke(full_message_history)" ] }, + { + "cell_type": "markdown", + "id": "aaa4e03a-0050-4f1d-bda1-530e387966bf", + "metadata": {}, + "source": [ + "If you need to implement more efficient logic and want to use `RunnableWithMessageHistory` for now the way to achieve this\n", + "is to subclass from [BaseChatMessageHistory](https://api.python.langchain.com/en/latest/chat_history/langchain_core.chat_history.BaseChatMessageHistory.html) and\n", + "define appropriate logic for `add_messages` (that doesn't simply append the history, but instead re-writes it).\n", + "\n", + "Unless you have a good reason to implement this solution, you should instead use LangGraph," + ] + }, { "cell_type": "markdown", "id": "84229e2e-a578-4b21-840a-814223406402", @@ -659,7 +715,7 @@ "\n", "You can create a pre-built agent using: [create_react_agent](https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent).\n", "\n", - "To add memory pre-processing to the agent\n", + "To add memory pre-processing to the agent, you can do the following:\n", "\n", "```python\n", "\n", @@ -673,7 +729,7 @@ "# highlight-end \n", "\n", "app = create_react_agent(\n", - " model, \n", + " model,\n", " tools=[get_user_age], \n", " checkpointer=memory,\n", " # highlight-next-line\n", @@ -684,12 +740,12 @@ "\n", "```\n", "\n", - "Pre-built react-agent" + "At each turn of the conversation, " ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 14, "id": "f671db87-8f01-453e-81fd-4e603140a512", "metadata": {}, "outputs": [ @@ -702,8 +758,8 @@ "hi! I'm bob. What is my age?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " get_user_age (call_BVUwjmlAebA36jK6WFOtDWfi)\n", - " Call ID: call_BVUwjmlAebA36jK6WFOtDWfi\n", + " get_user_age (call_kTEpBUbRFbKE3DZolG9tFrgD)\n", + " Call ID: call_kTEpBUbRFbKE3DZolG9tFrgD\n", " Args:\n", " name: bob\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", @@ -783,6 +839,19 @@ " event[\"messages\"][-1].pretty_print()" ] }, + { + "cell_type": "markdown", + "id": "7a231ade-9f33-4ff8-9a62-20be3fc159b6", + "metadata": {}, + "source": [ + "### ConversationSummaryMemory / ConversationSummaryBufferMemory\n", + "\n", + "It’s essential to summarize conversations efficiently to prevent growing latency as the conversation history grows.\n", + "\n", + "Please follow the guide [how to add summary of the conversation history](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/) to see learn how to\n", + "handle conversation summarization efficiently with LangGraph." + ] + }, { "cell_type": "markdown", "id": "b2717810", @@ -799,13 +868,13 @@ "\n", "Add persistence with simple LCEL (favor langgraph for more complex use cases):\n", "\n", - "* [How to add message history](https://python.langchain.com/docs/how_to/message_history/)\n", + "* [How to add message history](/docs/how_to/message_history/)\n", "\n", "Working with message history:\n", "\n", - "* [How to trim messages](https://python.langchain.com/docs/how_to/trim_messages)\n", - "* [How to filter messages](https://python.langchain.com/docs/how_to/filter_messages/)\n", - "* [How to merge message runs](https://python.langchain.com/docs/how_to/merge_message_runs/)" + "* [How to trim messages](/docs/how_to/trim_messages)\n", + "* [How to filter messages](/docs/how_to/filter_messages/)\n", + "* [How to merge message runs](/docs/how_to/merge_message_runs/)" ] } ],