mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-08 07:57:07 +00:00
Summary of change: - Replace ChatMessageHistory with InMemoryChatMessageHistory Fixes #23892 --------- Co-authored-by: Eugene Yurtsev <eugene@langchain.dev> Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
1028 lines
30 KiB
Plaintext
1028 lines
30 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "raw",
|
|
"metadata": {
|
|
"vscode": {
|
|
"languageId": "raw"
|
|
}
|
|
},
|
|
"source": [
|
|
"---\n",
|
|
"sidebar_position: 1\n",
|
|
"keywords: [conversationchain]\n",
|
|
"---"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Build a Chatbot"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
":::info Prerequisites\n",
|
|
"\n",
|
|
"This guide assumes familiarity with the following concepts:\n",
|
|
"\n",
|
|
"- [Chat Models](/docs/concepts/#chat-models)\n",
|
|
"- [Prompt Templates](/docs/concepts/#prompt-templates)\n",
|
|
"- [Chat History](/docs/concepts/#chat-history)\n",
|
|
"\n",
|
|
":::\n",
|
|
"\n",
|
|
"## Overview\n",
|
|
"\n",
|
|
"We'll go over an example of how to design and implement an LLM-powered chatbot. \n",
|
|
"This chatbot will be able to have a conversation and remember previous interactions.\n",
|
|
"\n",
|
|
"\n",
|
|
"Note that this chatbot that we build will only use the language model to have a conversation.\n",
|
|
"There are several other related concepts that you may be looking for:\n",
|
|
"\n",
|
|
"- [Conversational RAG](/docs/tutorials/qa_chat_history): Enable a chatbot experience over an external source of data\n",
|
|
"- [Agents](/docs/tutorials/agents): Build a chatbot that can take actions\n",
|
|
"\n",
|
|
"This tutorial will cover the basics which will be helpful for those two more advanced topics, but feel free to skip directly to there should you choose.\n",
|
|
"\n",
|
|
"## Setup\n",
|
|
"\n",
|
|
"### Jupyter Notebook\n",
|
|
"\n",
|
|
"This guide (and most of the other guides in the documentation) uses [Jupyter notebooks](https://jupyter.org/) and assumes the reader is as well. Jupyter notebooks are perfect for learning how to work with LLM systems because oftentimes things can go wrong (unexpected output, API down, etc) and going through guides in an interactive environment is a great way to better understand them.\n",
|
|
"\n",
|
|
"This and other tutorials are perhaps most conveniently run in a Jupyter notebook. See [here](https://jupyter.org/install) for instructions on how to install.\n",
|
|
"\n",
|
|
"### Installation\n",
|
|
"\n",
|
|
"To install LangChain run:\n",
|
|
"\n",
|
|
"```{=mdx}\n",
|
|
"import Tabs from '@theme/Tabs';\n",
|
|
"import TabItem from '@theme/TabItem';\n",
|
|
"import CodeBlock from \"@theme/CodeBlock\";\n",
|
|
"\n",
|
|
"<Tabs>\n",
|
|
" <TabItem value=\"pip\" label=\"Pip\" default>\n",
|
|
" <CodeBlock language=\"bash\">pip install langchain</CodeBlock>\n",
|
|
" </TabItem>\n",
|
|
" <TabItem value=\"conda\" label=\"Conda\">\n",
|
|
" <CodeBlock language=\"bash\">conda install langchain -c conda-forge</CodeBlock>\n",
|
|
" </TabItem>\n",
|
|
"</Tabs>\n",
|
|
"\n",
|
|
"```\n",
|
|
"\n",
|
|
"\n",
|
|
"For more details, see our [Installation guide](/docs/how_to/installation).\n",
|
|
"\n",
|
|
"### LangSmith\n",
|
|
"\n",
|
|
"Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls.\n",
|
|
"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.\n",
|
|
"The best way to do this is with [LangSmith](https://smith.langchain.com).\n",
|
|
"\n",
|
|
"After you sign up at the link above, make sure to set your environment variables to start logging traces:\n",
|
|
"\n",
|
|
"```shell\n",
|
|
"export LANGCHAIN_TRACING_V2=\"true\"\n",
|
|
"export LANGCHAIN_API_KEY=\"...\"\n",
|
|
"```\n",
|
|
"\n",
|
|
"Or, if in a notebook, you can set them with:\n",
|
|
"\n",
|
|
"```python\n",
|
|
"import getpass\n",
|
|
"import os\n",
|
|
"\n",
|
|
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
|
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()\n",
|
|
"```\n",
|
|
"\n",
|
|
"## Quickstart\n",
|
|
"\n",
|
|
"First up, let's learn how to use a language model by itself. LangChain supports many different language models that you can use interchangably - select the one you want to use below!\n",
|
|
"\n",
|
|
"```{=mdx}\n",
|
|
"import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
|
|
"\n",
|
|
"<ChatModelTabs openaiParams={`model=\"gpt-3.5-turbo\"`} />\n",
|
|
"```"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# | output: false\n",
|
|
"# | echo: false\n",
|
|
"\n",
|
|
"from langchain_openai import ChatOpenAI\n",
|
|
"\n",
|
|
"model = ChatOpenAI(model=\"gpt-3.5-turbo\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's first use the model directly. `ChatModel`s are instances of LangChain \"Runnables\", which means they expose a standard interface for interacting with them. To just simply call the model, we can pass in a list of messages to the `.invoke` method."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 12, 'total_tokens': 22}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-d939617f-0c3b-45e9-a93f-13dafecbd4b5-0', usage_metadata={'input_tokens': 12, 'output_tokens': 10, 'total_tokens': 22})"
|
|
]
|
|
},
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.messages import HumanMessage\n",
|
|
"\n",
|
|
"model.invoke([HumanMessage(content=\"Hi! I'm Bob\")])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The model on its own does not have any concept of state. For example, if you ask a followup question:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"AIMessage(content=\"I'm sorry, I don't have access to personal information unless you provide it to me. How may I assist you today?\", response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 12, 'total_tokens': 38}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-47bc8c20-af7b-4fd2-9345-f0e9fdf18ce3-0', usage_metadata={'input_tokens': 12, 'output_tokens': 26, 'total_tokens': 38})"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model.invoke([HumanMessage(content=\"What's my name?\")])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's take a look at the example [LangSmith trace](https://smith.langchain.com/public/5c21cb92-2814-4119-bae9-d02b8db577ac/r)\n",
|
|
"\n",
|
|
"We can see that it doesn't take the previous conversation turn into context, and cannot answer the question.\n",
|
|
"This makes for a terrible chatbot experience!\n",
|
|
"\n",
|
|
"To get around this, we need to pass the entire conversation history into the model. Let's see what happens when we do that:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"AIMessage(content='Your name is Bob. How can I help you, Bob?', response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 35, 'total_tokens': 48}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9f90291b-4df9-41dc-9ecf-1ee1081f4490-0', usage_metadata={'input_tokens': 35, 'output_tokens': 13, 'total_tokens': 48})"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.messages import AIMessage\n",
|
|
"\n",
|
|
"model.invoke(\n",
|
|
" [\n",
|
|
" HumanMessage(content=\"Hi! I'm Bob\"),\n",
|
|
" AIMessage(content=\"Hello Bob! How can I assist you today?\"),\n",
|
|
" HumanMessage(content=\"What's my name?\"),\n",
|
|
" ]\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"And now we can see that we get a good response!\n",
|
|
"\n",
|
|
"This is the basic idea underpinning a chatbot's ability to interact conversationally.\n",
|
|
"So how do we best implement this?"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Message History\n",
|
|
"\n",
|
|
"We can use a Message History class to wrap our model and make it stateful.\n",
|
|
"This will keep track of inputs and outputs of the model, and store them in some datastore.\n",
|
|
"Future interactions will then load those messages and pass them into the chain as part of the input.\n",
|
|
"Let's see how to use this!\n",
|
|
"\n",
|
|
"First, let's make sure to install `langchain-community`, as we will be using an integration in there to store message history."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# ! pip install langchain_community"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"After that, we can import the relevant classes and set up our chain which wraps the model and adds in this message history. A key part here is the function we pass into as the `get_session_history`. This function is expected to take in a `session_id` and return a Message History object. This `session_id` is used to distinguish between separate conversations, and should be passed in as part of the config when calling the new chain (we'll show how to do that)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.chat_history import (\n",
|
|
" BaseChatMessageHistory,\n",
|
|
" InMemoryChatMessageHistory,\n",
|
|
")\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] = InMemoryChatMessageHistory()\n",
|
|
" return store[session_id]\n",
|
|
"\n",
|
|
"\n",
|
|
"with_message_history = RunnableWithMessageHistory(model, get_session_history)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We now need to create a `config` that we pass into the runnable every time. This config contains information that is not part of the input directly, but is still useful. In this case, we want to include a `session_id`. This should look like:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc2\"}}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Hi Bob! How can I assist you today?'"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"Hi! I'm Bob\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Your name is Bob. How can I help you today, Bob?'"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"What's my name?\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Great! Our chatbot now remembers things about us. If we change the config to reference a different `session_id`, we can see that it starts the conversation fresh."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"\"I'm sorry, I cannot determine your name as I am an AI assistant and do not have access to that information.\""
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc3\"}}\n",
|
|
"\n",
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"What's my name?\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"However, we can always go back to the original conversation (since we are persisting it in a database)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Your name is Bob. How can I assist you today, Bob?'"
|
|
]
|
|
},
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc2\"}}\n",
|
|
"\n",
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"What's my name?\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"This is how we can support a chatbot having conversations with many users!\n",
|
|
"\n",
|
|
"Right now, all we've done is add a simple persistence layer around the model. We can start to make the more complicated and personalized by adding in a prompt template."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Prompt templates\n",
|
|
"\n",
|
|
"Prompt Templates help to turn raw user information into a format that the LLM can work with. In this case, the raw user input is just a message, which we are passing to the LLM. Let's now make that a bit more complicated. First, let's add in a system message with some custom instructions (but still taking messages as input). Next, we'll add in more input besides just the messages.\n",
|
|
"\n",
|
|
"First, let's add in a system message. To do this, we will create a ChatPromptTemplate. We will utilize `MessagesPlaceholder` to pass all the messages in."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
|
"\n",
|
|
"prompt = ChatPromptTemplate.from_messages(\n",
|
|
" [\n",
|
|
" (\n",
|
|
" \"system\",\n",
|
|
" \"You are a helpful assistant. Answer all questions to the best of your ability.\",\n",
|
|
" ),\n",
|
|
" MessagesPlaceholder(variable_name=\"messages\"),\n",
|
|
" ]\n",
|
|
")\n",
|
|
"\n",
|
|
"chain = prompt | model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Note that this slightly changes the input type - rather than pass in a list of messages, we are now passing in a dictionary with a `messages` key where that contains a list of messages."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Hello Bob! How can I assist you today?'"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = chain.invoke({\"messages\": [HumanMessage(content=\"hi! I'm bob\")]})\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can now wrap this in the same Messages History object as before"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"with_message_history = RunnableWithMessageHistory(chain, get_session_history)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc5\"}}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Hello, Jim! How can I assist you today?'"
|
|
]
|
|
},
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"Hi! I'm Jim\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Your name is Jim.'"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" [HumanMessage(content=\"What's my name?\")],\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Awesome! Let's now make our prompt a little bit more complicated. Let's assume that the prompt template now looks something like this:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"prompt = ChatPromptTemplate.from_messages(\n",
|
|
" [\n",
|
|
" (\n",
|
|
" \"system\",\n",
|
|
" \"You are a helpful assistant. Answer all questions to the best of your ability in {language}.\",\n",
|
|
" ),\n",
|
|
" MessagesPlaceholder(variable_name=\"messages\"),\n",
|
|
" ]\n",
|
|
")\n",
|
|
"\n",
|
|
"chain = prompt | model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Note that we have added a new `language` input to the prompt. We can now invoke the chain and pass in a language of our choice."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'¡Hola, Bob! ¿En qué puedo ayudarte hoy?'"
|
|
]
|
|
},
|
|
"execution_count": 19,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = chain.invoke(\n",
|
|
" {\"messages\": [HumanMessage(content=\"hi! I'm bob\")], \"language\": \"Spanish\"}\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's now wrap this more complicated chain in a Message History class. This time, because there are multiple keys in the input, we need to specify the correct key to use to save the chat history."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"with_message_history = RunnableWithMessageHistory(\n",
|
|
" chain,\n",
|
|
" get_session_history,\n",
|
|
" input_messages_key=\"messages\",\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 21,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc11\"}}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'¡Hola Todd! ¿En qué puedo ayudarte hoy?'"
|
|
]
|
|
},
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" {\"messages\": [HumanMessage(content=\"hi! I'm todd\")], \"language\": \"Spanish\"},\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 23,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Tu nombre es Todd.'"
|
|
]
|
|
},
|
|
"execution_count": 23,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" {\"messages\": [HumanMessage(content=\"whats my name?\")], \"language\": \"Spanish\"},\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"To help you understand what's happening internally, check out [this LangSmith trace](https://smith.langchain.com/public/f48fabb6-6502-43ec-8242-afc352b769ed/r)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Managing Conversation History\n",
|
|
"\n",
|
|
"One important concept to understand when building chatbots is how to manage conversation history. If left unmanaged, the list of messages will grow unbounded and potentially overflow the context window of the LLM. Therefore, it is important to add a step that limits the size of the messages you are passing in.\n",
|
|
"\n",
|
|
"**Importantly, you will want to do this BEFORE the prompt template but AFTER you load previous messages from Message History.**\n",
|
|
"\n",
|
|
"We can do this by adding a simple step in front of the prompt that modifies the `messages` key appropriately, and then wrap that new chain in the Message History class. \n",
|
|
"\n",
|
|
"LangChain comes with a few built-in helpers for [managing a list of messages](/docs/how_to/#messages). In this case we'll use the [trim_messages](/docs/how_to/trim_messages/) helper to reduce how many messages we're sending to the model. The trimmer allows us to specify how many tokens we want to keep, along with other parameters like if we want to always keep the system message and whether to allow partial messages:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 24,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"[SystemMessage(content=\"you're a good assistant\"),\n",
|
|
" HumanMessage(content='whats 2 + 2'),\n",
|
|
" AIMessage(content='4'),\n",
|
|
" HumanMessage(content='thanks'),\n",
|
|
" AIMessage(content='no problem!'),\n",
|
|
" HumanMessage(content='having fun?'),\n",
|
|
" AIMessage(content='yes!')]"
|
|
]
|
|
},
|
|
"execution_count": 24,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.messages import SystemMessage, trim_messages\n",
|
|
"\n",
|
|
"trimmer = trim_messages(\n",
|
|
" max_tokens=65,\n",
|
|
" strategy=\"last\",\n",
|
|
" token_counter=model,\n",
|
|
" include_system=True,\n",
|
|
" allow_partial=False,\n",
|
|
" start_on=\"human\",\n",
|
|
")\n",
|
|
"\n",
|
|
"messages = [\n",
|
|
" SystemMessage(content=\"you're a good assistant\"),\n",
|
|
" HumanMessage(content=\"hi! I'm bob\"),\n",
|
|
" AIMessage(content=\"hi!\"),\n",
|
|
" HumanMessage(content=\"I like vanilla ice cream\"),\n",
|
|
" AIMessage(content=\"nice\"),\n",
|
|
" HumanMessage(content=\"whats 2 + 2\"),\n",
|
|
" AIMessage(content=\"4\"),\n",
|
|
" HumanMessage(content=\"thanks\"),\n",
|
|
" AIMessage(content=\"no problem!\"),\n",
|
|
" HumanMessage(content=\"having fun?\"),\n",
|
|
" AIMessage(content=\"yes!\"),\n",
|
|
"]\n",
|
|
"\n",
|
|
"trimmer.invoke(messages)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"To use it in our chain, we just need to run the trimmer before we pass the `messages` input to our prompt. \n",
|
|
"\n",
|
|
"Now if we try asking the model our name, it won't know it since we trimmed that part of the chat history:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 25,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"\"I'm sorry, but I don't have access to your personal information. How can I assist you today?\""
|
|
]
|
|
},
|
|
"execution_count": 25,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from operator import itemgetter\n",
|
|
"\n",
|
|
"from langchain_core.runnables import RunnablePassthrough\n",
|
|
"\n",
|
|
"chain = (\n",
|
|
" RunnablePassthrough.assign(messages=itemgetter(\"messages\") | trimmer)\n",
|
|
" | prompt\n",
|
|
" | model\n",
|
|
")\n",
|
|
"\n",
|
|
"response = chain.invoke(\n",
|
|
" {\n",
|
|
" \"messages\": messages + [HumanMessage(content=\"what's my name?\")],\n",
|
|
" \"language\": \"English\",\n",
|
|
" }\n",
|
|
")\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"But if we ask about information that is within the last few messages, it remembers:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'You asked \"what\\'s 2 + 2?\"'"
|
|
]
|
|
},
|
|
"execution_count": 26,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = chain.invoke(\n",
|
|
" {\n",
|
|
" \"messages\": messages + [HumanMessage(content=\"what math problem did i ask\")],\n",
|
|
" \"language\": \"English\",\n",
|
|
" }\n",
|
|
")\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Let's now wrap this in the Message History"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"with_message_history = RunnableWithMessageHistory(\n",
|
|
" chain,\n",
|
|
" get_session_history,\n",
|
|
" input_messages_key=\"messages\",\n",
|
|
")\n",
|
|
"\n",
|
|
"config = {\"configurable\": {\"session_id\": \"abc20\"}}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"\"I'm sorry, I don't have access to that information. How can I assist you today?\""
|
|
]
|
|
},
|
|
"execution_count": 28,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" {\n",
|
|
" \"messages\": messages + [HumanMessage(content=\"whats my name?\")],\n",
|
|
" \"language\": \"English\",\n",
|
|
" },\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"As expected, the first message where we stated our name has been trimmed. Plus there's now two new messages in the chat history (our latest question and the latest response). This means that even more information that used to be accessible in our conversation history is no longer available! In this case our initial math question has been trimmed from the history as well, so the model no longer knows about it:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 29,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"\"You haven't asked a math problem yet. Feel free to ask any math-related question you have, and I'll be happy to help you with it.\""
|
|
]
|
|
},
|
|
"execution_count": 29,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"response = with_message_history.invoke(\n",
|
|
" {\n",
|
|
" \"messages\": [HumanMessage(content=\"what math problem did i ask?\")],\n",
|
|
" \"language\": \"English\",\n",
|
|
" },\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"response.content"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"If you take a look at LangSmith, you can see exactly what is happening under the hood in the [LangSmith trace](https://smith.langchain.com/public/a64b8b7c-1fd6-4dbb-b11a-47cd09a5e4f1/r)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Streaming\n",
|
|
"\n",
|
|
"Now we've got a function chatbot. However, one *really* important UX consideration for chatbot application is streaming. LLMs can sometimes take a while to respond, and so in order to improve the user experience one thing that most application do is stream back each token as it is generated. This allows the user to see progress.\n",
|
|
"\n",
|
|
"It's actually super easy to do this!\n",
|
|
"\n",
|
|
"All chains expose a `.stream` method, and ones that use message history are no different. We can simply use that method to get back a streaming response."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 30,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"|Hi| Todd|!| Sure|,| here|'s| a| joke| for| you|:| Why| couldn|'t| the| bicycle| find| its| way| home|?| Because| it| lost| its| bearings|!| 😄||"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"config = {\"configurable\": {\"session_id\": \"abc15\"}}\n",
|
|
"for r in with_message_history.stream(\n",
|
|
" {\n",
|
|
" \"messages\": [HumanMessage(content=\"hi! I'm todd. tell me a joke\")],\n",
|
|
" \"language\": \"English\",\n",
|
|
" },\n",
|
|
" config=config,\n",
|
|
"):\n",
|
|
" print(r.content, end=\"|\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Next Steps\n",
|
|
"\n",
|
|
"Now that you understand the basics of how to create a chatbot in LangChain, some more advanced tutorials you may be interested in are:\n",
|
|
"\n",
|
|
"- [Conversational RAG](/docs/tutorials/qa_chat_history): Enable a chatbot experience over an external source of data\n",
|
|
"- [Agents](/docs/tutorials/agents): Build a chatbot that can take actions\n",
|
|
"\n",
|
|
"If you want to dive deeper on specifics, some things worth checking out are:\n",
|
|
"\n",
|
|
"- [Streaming](/docs/how_to/streaming): streaming is *crucial* for chat applications\n",
|
|
"- [How to add message history](/docs/how_to/message_history): for a deeper dive into all things related to message history\n",
|
|
"- [How to manage large message history](/docs/how_to/trim_messages/): more techniques for managing a large chat history"
|
|
]
|
|
}
|
|
],
|
|
"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.4"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|