Skip to content

Commit

Permalink
cr
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Aug 24, 2024
1 parent deb2bdc commit b6db631
Showing 1 changed file with 108 additions and 146 deletions.
254 changes: 108 additions & 146 deletions examples/how-tos/add-summary-conversation-history.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,64 +24,24 @@
"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",
"First, let's set up the packages we're going to want to use\n",
"\n",
"```bash\n",
"npm install @langchain/langgraph @langchain/anthropic uuid\n",
"```\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"Next, we need to set API keys for Anthropic (the LLM we will use)\n",
"\n",
"```typescript\n",
"process.env.ANTHROPIC_API_KEY = 'YOUR_API_KEY'\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\")"
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability.\n",
"\n",
"```typescript\n",
"process.env.LANGCHAIN_TRACING_V2 = 'true'\n",
"process.env.LANGCHAIN_API_KEY = 'YOUR_API_KEY'\n",
"```"
]
},
{
Expand All @@ -96,102 +56,104 @@
},
{
"cell_type": "code",
"execution_count": 25,
"id": "378899a9-3b9a-4748-95b6-eb00e0828677",
"execution_count": null,
"id": "1a76502d",
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_core.messages import SystemMessage, RemoveMessage\n",
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
"from langgraph.graph import MessagesState, StateGraph, START, END\n",
"\n",
"memory = SqliteSaver.from_conn_string(\":memory:\")\n",
"\n",
"\n",
"# We will add a `summary` attribute (in addition to `messages` key,\n",
"# which MessagesState already has)\n",
"class State(MessagesState):\n",
" summary: str\n",
"\n",
"\n",
"# We will use this model for both the conversation and the summarization\n",
"model = ChatAnthropic(model_name=\"claude-3-haiku-20240307\")\n",
"\n",
"\n",
"# Define the logic to call the model\n",
"def call_model(state: State):\n",
" # If a summary exists, we add this in as a system message\n",
" summary = state.get(\"summary\", \"\")\n",
" if summary:\n",
" system_message = f\"Summary of conversation earlier: {summary}\"\n",
" messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n",
" else:\n",
" messages = state[\"messages\"]\n",
" response = model.invoke(messages)\n",
" # We return a list, because this will get added to the existing list\n",
" return {\"messages\": [response]}\n",
"\n",
"\n",
"# We now define the logic for determining whether to end or summarize the conversation\n",
"def should_continue(state: State) -> Literal[\"summarize_conversation\", END]:\n",
" \"\"\"Return the next node to execute.\"\"\"\n",
" messages = state[\"messages\"]\n",
" # If there are more than six messages, then we summarize the conversation\n",
" if len(messages) > 6:\n",
" return \"summarize_conversation\"\n",
" # Otherwise we can just end\n",
" return END\n",
"\n",
"\n",
"def summarize_conversation(state: State):\n",
" # First, we summarize the conversation\n",
" summary = state.get(\"summary\", \"\")\n",
" if summary:\n",
" # If a summary already exists, we use a different system prompt\n",
" # to summarize it than if one didn't\n",
" summary_message = (\n",
" f\"This is summary of the conversation to date: {summary}\\n\\n\"\n",
" \"Extend the summary by taking into account the new messages above:\"\n",
" )\n",
" else:\n",
" summary_message = \"Create a summary of the conversation above:\"\n",
"\n",
" messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n",
" response = model.invoke(messages)\n",
" # We now need to delete messages that we no longer want to show up\n",
" # I will delete all but the last two messages, but you can change this\n",
" delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n",
" return {\"summary\": response.content, \"messages\": delete_messages}\n",
"\n",
"\n",
"# Define a new graph\n",
"workflow = StateGraph(State)\n",
"\n",
"# Define the conversation node and the summarize node\n",
"workflow.add_node(\"conversation\", call_model)\n",
"workflow.add_node(summarize_conversation)\n",
"\n",
"# Set the entrypoint as conversation\n",
"workflow.add_edge(START, \"conversation\")\n",
"\n",
"# We now add a conditional edge\n",
"workflow.add_conditional_edges(\n",
" # First, we define the start node. We use `conversation`.\n",
" # This means these are the edges taken after the `conversation` node is called.\n",
"import { ChatAnthropic } from \"@langchain/anthropic\";\n",
"import { SystemMessage, HumanMessage, AIMessage, RemoveMessage } from \"@langchain/core/messages\";\n",
"import { MemorySaver } from \"@langchain/langgraph-checkpoint\";\n",
"import { MessagesAnnotation, StateGraph, START, END, Annotation } from \"@langchain/langgraph\";\n",
"import { v4 as uuidv4 } from \"uuid\";\n",
"\n",
"const memory = new MemorySaver();\n",
"\n",
"// We will add a `summary` attribute (in addition to `messages` key,\n",
"// which MessagesState already has)\n",
"const GraphAnnotation = Annotation.Root({\n",
" ...MessagesAnnotation.spec,\n",
" summary: Annotation<string>({\n",
" default: () => \"\",\n",
" })\n",
"})\n",
"\n",
"// We will use this model for both the conversation and the summarization\n",
"const model = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" });\n",
"\n",
"// Define the logic to call the model\n",
"async function callModel(state: typeof GraphAnnotation.State) {\n",
" // If a summary exists, we add this in as a system message\n",
" const { summary } = state;\n",
" let { messages } = state;\n",
" if (summary) {\n",
" const systemMessage = new SystemMessage({\n",
" id: uuidv4(),\n",
" content: `Summary of conversation earlier: ${summary}`\n",
" });\n",
" messages = [systemMessage, ...messages];\n",
" }\n",
" const response = await model.invoke(messages);\n",
" // We return an object, because this will get added to the existing state\n",
" return { messages: [response] };\n",
"}\n",
"\n",
"// We now define the logic for determining whether to end or summarize the conversation\n",
"function shouldContinue(state: typeof GraphAnnotation.State): \"summarize_conversation\" | typeof END {\n",
" const messages = state.messages;\n",
" // If there are more than six messages, then we summarize the conversation\n",
" if (messages.length > 6) {\n",
" return \"summarize_conversation\";\n",
" }\n",
" // Otherwise we can just end\n",
" return END;\n",
"}\n",
"\n",
"async function summarizeConversation(state: typeof GraphAnnotation.State) {\n",
" // First, we summarize the conversation\n",
" const { summary, messages } = state;\n",
" let summaryMessage: string;\n",
" if (summary) {\n",
" // If a summary already exists, we use a different system prompt\n",
" // to summarize it than if one didn't\n",
" summaryMessage = `This is summary of the conversation to date: ${summary}\\n\\n` +\n",
" \"Extend the summary by taking into account the new messages above:\";\n",
" } else {\n",
" summaryMessage = \"Create a summary of the conversation above:\";\n",
" }\n",
"\n",
" const allMessages = [...messages, new HumanMessage({\n",
" id: uuidv4(),\n",
" content: summaryMessage,\n",
" })];\n",
" const response = await model.invoke(allMessages);\n",
" // We now need to delete messages that we no longer want to show up\n",
" // I will delete all but the last two messages, but you can change this\n",
" const deleteMessages = messages.slice(0, -2).map((m) => new RemoveMessage(m.id));\n",
" return { summary: response.content, messages: deleteMessages };\n",
"}\n",
"\n",
"// Define a new graph\n",
"const workflow = new StateGraph(GraphAnnotation)\n",
" // Define the conversation node and the summarize node\n",
" .addNode(\"conversation\", callModel)\n",
" .addNode(\"summarize_conversation\", summarizeConversation)\n",
" // Set the entrypoint as conversation\n",
" .addEdge(START, \"conversation\")\n",
" // We now add a conditional edge\n",
" .addConditionalEdges(\n",
" // First, we define the start node. We use `conversation`.\n",
" // This means these are the edges taken after the `conversation` node is called.\n",
" \"conversation\",\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 `summarize_conversation` to END.\n",
"# This means that after `summarize_conversation` is called, we end.\n",
"workflow.add_edge(\"summarize_conversation\", END)\n",
"\n",
"# Finally, we compile it!\n",
"app = workflow.compile(checkpointer=memory)"
" // Next, we pass in the function that will determine which node is called next.\n",
" shouldContinue\n",
" )\n",
" // We now add a normal edge from `summarize_conversation` to END.\n",
" // This means that after `summarize_conversation` is called, we end.\n",
" .addEdge(\"summarize_conversation\", END);\n",
"\n",
"// Finally, we compile it!\n",
"const app = workflow.compile({ checkpointer: memory });"
]
},
{
Expand Down

0 comments on commit b6db631

Please sign in to comment.