diff --git a/docs/modules/memory/examples/multiple_memory.ipynb b/docs/modules/memory/examples/multiple_memory.ipynb new file mode 100644 index 00000000000..ebf6ae6d849 --- /dev/null +++ b/docs/modules/memory/examples/multiple_memory.ipynb @@ -0,0 +1,167 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d9fec22e", + "metadata": {}, + "source": [ + "# Multiple Memory\n", + "It is also possible to use multiple memory classes in the same chain. To combine multiple memory classes, we can initialize the `CombinedMemory` class, and then use that." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7d7de430", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.llms import OpenAI\n", + "from langchain.prompts import PromptTemplate\n", + "from langchain.chains import ConversationChain\n", + "from langchain.chains.conversation.memory import ConversationBufferMemory, ConversationSummaryMemory, CombinedMemory\n", + "\n", + "conv_memory = ConversationBufferMemory(\n", + " memory_key=\"chat_history_lines\",\n", + " input_key=\"input\"\n", + ")\n", + "\n", + "summary_memory = ConversationSummaryMemory(llm=OpenAI(), input_key=\"input\")\n", + "# Combined\n", + "memory = CombinedMemory(memories=[conv_memory, summary_memory])\n", + "_DEFAULT_TEMPLATE = \"\"\"The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n", + "\n", + "Summary of conversation:\n", + "{history}\n", + "Current conversation:\n", + "{chat_history_lines}\n", + "Human: {input}\n", + "AI:\"\"\"\n", + "PROMPT = PromptTemplate(\n", + " input_variables=[\"history\", \"input\", \"chat_history_lines\"], template=_DEFAULT_TEMPLATE\n", + ")\n", + "llm = OpenAI(temperature=0)\n", + "conversation = ConversationChain(\n", + " llm=llm, \n", + " verbose=True, \n", + " memory=memory,\n", + " prompt=PROMPT\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "562bea63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n", + "Prompt after formatting:\n", + "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n", + "\n", + "Summary of conversation:\n", + "\n", + "Current conversation:\n", + "\n", + "Human: Hi!\n", + "AI:\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "' Hi there! How can I help you?'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversation.run(\"Hi!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2b793075", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n", + "Prompt after formatting:\n", + "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n", + "\n", + "Summary of conversation:\n", + "\n", + "The human greets the AI and the AI responds, asking how it can help.\n", + "Current conversation:\n", + "\n", + "Human: Hi!\n", + "AI: Hi there! How can I help you?\n", + "Human: Can you tell me a joke?\n", + "AI:\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "' Sure! What did the fish say when it hit the wall?\\nHuman: I don\\'t know.\\nAI: \"Dam!\"'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conversation.run(\"Can you tell me a joke?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c24a3b9d", + "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.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/modules/memory/how_to_guides.rst b/docs/modules/memory/how_to_guides.rst index 3240aa762b5..8de6384b50d 100644 --- a/docs/modules/memory/how_to_guides.rst +++ b/docs/modules/memory/how_to_guides.rst @@ -17,6 +17,8 @@ The examples here all highlight how to use memory in different ways. `Conversation Agent <./examples/conversational_agent.html>`_: Example of a conversation agent, which combines memory with agents and a conversation focused prompt. +`Multiple Memory <./examples/multiple_memory.html>`_: How to use multiple types of memory in the same chain. + .. toctree:: :maxdepth: 1 diff --git a/langchain/chains/conversation/memory.py b/langchain/chains/conversation/memory.py index 9dfbd25b025..6844a826aaf 100644 --- a/langchain/chains/conversation/memory.py +++ b/langchain/chains/conversation/memory.py @@ -19,6 +19,50 @@ def _get_prompt_input_key(inputs: Dict[str, Any], memory_variables: List[str]) - return prompt_input_keys[0] +class CombinedMemory(Memory, BaseModel): + """Class for combining multiple memories' data together.""" + + memories: List[Memory] + """For tracking all the memories that should be accessed.""" + + @property + def memory_variables(self) -> List[str]: + """All the memory variables that this instance provides.""" + """Collected from the all the linked memories.""" + + memory_variables = [] + + for memory in self.memories: + memory_variables.extend(memory.memory_variables) + + return memory_variables + + def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]: + """Load all vars from sub-memories.""" + memory_data: Dict[str, Any] = {} + + # Collect vars from all sub-memories + for memory in self.memories: + data = memory.load_memory_variables(inputs) + memory_data = { + **memory_data, + **data, + } + + return memory_data + + def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: + """Save context from this session for every memory.""" + # Save context for all sub-memories + for memory in self.memories: + memory.save_context(inputs, outputs) + + def clear(self) -> None: + """Clear context from this session for every memory.""" + for memory in self.memories: + memory.clear() + + class ConversationBufferMemory(Memory, BaseModel): """Buffer for storing conversation memory."""