Skip to content

Commit

Permalink
Updated manage conversation history doc
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Aug 19, 2024
1 parent 49130c7 commit ce0596f
Showing 1 changed file with 81 additions and 87 deletions.
168 changes: 81 additions & 87 deletions examples/how-tos/manage-conversation-history.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 1,
"id": "2ca8a0a7",
"metadata": {},
"outputs": [],
Expand Down Expand Up @@ -72,22 +72,24 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"id": "378899a9-3b9a-4748-95b6-eb00e0828677",
"metadata": {},
"outputs": [],
"source": [
"import { ChatAnthropic } from \"@langchain/anthropic\";\n",
"import { tool } from \"@langchain/core/tools\";\n",
"import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n",
"import { StateGraph, START, END } from \"@langchain/langgraph\";\n",
"import { StateGraph, Annotation, START, END } from \"@langchain/langgraph\";\n",
"import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n",
"import { MemorySaver } from \"@langchain/langgraph\";\n",
"import { z } from \"zod\";\n",
"\n",
"interface MessagesState {\n",
" messages: BaseMessage[];\n",
"}\n",
"const AgentState = Annotation.Root({\n",
" messages: Annotation<BaseMessage[]>({\n",
" reducer: (x, y) => x.concat(y),\n",
" }),\n",
"});\n",
"\n",
"const memory = new MemorySaver();\n",
"\n",
Expand All @@ -105,35 +107,29 @@
"\n",
"\n",
"const tools = [searchTool]\n",
"const toolNode = new ToolNode<MessagesState>(tools)\n",
"const toolNode = new ToolNode<typeof AgentState.State>(tools)\n",
"const model = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n",
"const boundModel = model.bindTools(tools)\n",
"\n",
"async function shouldContinue(state: MessagesState): Promise<\"action\" | typeof END> {\n",
" const lastMessage = state.messages[state.messages.length - 1];\n",
" // If there is no function call, then we finish\n",
" if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n",
" return END;\n",
" }\n",
" // Otherwise if there is, we continue\n",
" return \"action\";\n",
"function shouldContinue(state: typeof AgentState.State): \"action\" | typeof END {\n",
" const lastMessage = state.messages[state.messages.length - 1];\n",
" // If there is no function call, then we finish\n",
" if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n",
" return END;\n",
" }\n",
" // Otherwise if there is, we continue\n",
" return \"action\";\n",
"}\n",
"\n",
"// Define the function that calls the model\n",
"async function callModel(state: MessagesState) {\n",
" const response = await model.invoke(state.messages);\n",
" // We return an object, because this will get merged with the existing state\n",
" return { messages: [response] };\n",
"async function callModel(state: typeof AgentState.State) {\n",
" const response = await model.invoke(state.messages);\n",
" // We return an object, because this will get merged with the existing state\n",
" return { messages: [response] };\n",
"}\n",
"\n",
"// Define a new graph\n",
"const workflow = new StateGraph<MessagesState>({\n",
" channels: {\n",
" messages: {\n",
" reducer: (a: any, b: any) => a.concat(b)\n",
" },\n",
" }\n",
"})\n",
"const workflow = new StateGraph(AgentState)\n",
" // Define the two nodes we will cycle between\n",
" .addNode(\"agent\", callModel)\n",
" .addNode(\"action\", toolNode)\n",
Expand Down Expand Up @@ -162,7 +158,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 3,
"id": "57b27553-21be-43e5-ac48-d1d0a3aa0dca",
"metadata": {},
"outputs": [
Expand All @@ -173,7 +169,7 @@
"================================ human Message (1) =================================\n",
"hi! I'm bob\n",
"================================ ai Message (1) =================================\n",
"It's nice to meet you, Bob! As an AI assistant, I'm here to help out however I can. Feel free to ask me anything, and I'll do my best to provide useful information or assistance. How can I help you today?\n",
"Hello Bob! It's nice to meet you. I'm an AI assistant created by Anthropic. I'm here to help with any questions or tasks you may have. Please let me know if there's anything I can assist you with.\n",
"\n",
"\n",
"================================= END =================================\n",
Expand All @@ -182,7 +178,7 @@
"================================ human Message (2) =================================\n",
"what's my name?\n",
"================================ ai Message (2) =================================\n",
"You said your name is Bob, so your name is Bob.\n"
"Your name is Bob, as you introduced yourself earlier.\n"
]
}
],
Expand Down Expand Up @@ -224,26 +220,28 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"id": "eb20430f",
"metadata": {},
"outputs": [],
"source": [
"import { ChatAnthropic } from \"@langchain/anthropic\";\n",
"import { tool } from \"@langchain/core/tools\";\n",
"import { BaseMessage, AIMessage } from \"@langchain/core/messages\";\n",
"import { StateGraph, START, END } from \"@langchain/langgraph\";\n",
"import { StateGraph, Annotation, START, END } from \"@langchain/langgraph\";\n",
"import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n",
"import { MemorySaver } from \"@langchain/langgraph\";\n",
"import { z } from \"zod\";\n",
"\n",
"interface MessagesState {\n",
" messages: BaseMessage[];\n",
"}\n",
"const MessageFilteringAgentState = Annotation.Root({\n",
" messages: Annotation<BaseMessage[]>({\n",
" reducer: (x, y) => x.concat(y),\n",
" }),\n",
"});\n",
"\n",
"const memory = new MemorySaver();\n",
"const messageFilteringMemory = new MemorySaver();\n",
"\n",
"const searchTool = tool((_): string => {\n",
"const messageFilteringSearchTool = tool((_): string => {\n",
" // This is a placeholder for the actual implementation\n",
" // Don't let the LLM know this though 😊\n",
" return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n",
Expand All @@ -255,14 +253,14 @@
" })\n",
"})\n",
"\n",
"// We can re-use the same search tool as above as we don't need to change it for this example.\n",
"const messageFilteringTools = [messageFilteringSearchTool]\n",
"const messageFilteringToolNode = new ToolNode<typeof MessageFilteringAgentState.State>(messageFilteringTools)\n",
"const messageFilteringModel = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n",
"const boundMessageFilteringModel = messageFilteringModel.bindTools(messageFilteringTools)\n",
"\n",
"const tools = [searchTool]\n",
"const toolNode = new ToolNode<MessagesState>(tools)\n",
"const model = new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n",
"const boundModel = model.bindTools(tools)\n",
"\n",
"\n",
"async function shouldContinue(state: MessagesState): Promise<\"action\" | typeof END> {\n",
"async function shouldContinueMessageFiltering(state: typeof MessageFilteringAgentState.State): Promise<\"action\" | typeof END> {\n",
" const lastMessage = state.messages[state.messages.length - 1];\n",
" // If there is no function call, then we finish\n",
" if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) {\n",
Expand All @@ -273,56 +271,49 @@
"}\n",
"\n",
"const filterMessages = (messages: BaseMessage[]): BaseMessage[] => {\n",
" // This is very simple helper function which only ever uses the last message\n",
" return messages.slice(-1);\n",
" // This is very simple helper function which only ever uses the last message\n",
" return messages.slice(-1);\n",
"}\n",
"\n",
"\n",
"// Define the function that calls the model\n",
"async function callModel(state: MessagesState) {\n",
" const response = await model.invoke(filterMessages(state.messages));\n",
" // We return an object, because this will get merged with the existing state\n",
" return { messages: [response] };\n",
"async function callModelMessageFiltering(state: typeof MessageFilteringAgentState.State) {\n",
" const response = await boundMessageFilteringModel.invoke(filterMessages(state.messages));\n",
" // We return an object, because this will get merged with the existing state\n",
" return { messages: [response] };\n",
"}\n",
"\n",
"\n",
"// Define a new graph\n",
"const workflow = new StateGraph<MessagesState>({\n",
" channels: {\n",
" messages: {\n",
" reducer: (a: any, b: any) => a.concat(b)\n",
" },\n",
" }\n",
"})\n",
" // Define the two nodes we will cycle between\n",
" .addNode(\"agent\", callModel)\n",
" .addNode(\"action\", toolNode)\n",
" // We now add a conditional edge\n",
" .addConditionalEdges(\n",
" // First, we define the start node. We use `agent`.\n",
" // This means these are the edges taken after the `agent` node is called.\n",
" \"agent\",\n",
" // 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 `action` to `agent`.\n",
" // This means that after `action` is called, `agent` node is called next.\n",
" .addEdge(\"action\", \"agent\")\n",
" // Set the entrypoint as `agent`\n",
" // This means that this node is the first one called\n",
" .addEdge(START, \"agent\");\n",
"const messageFilteringWorkflow = new StateGraph(MessageFilteringAgentState)\n",
" // Define the two nodes we will cycle between\n",
" .addNode(\"agent\", callModelMessageFiltering)\n",
" .addNode(\"action\", messageFilteringToolNode)\n",
" // We now add a conditional edge\n",
" .addConditionalEdges(\n",
" // First, we define the start node. We use `agent`.\n",
" // This means these are the edges taken after the `agent` node is called.\n",
" \"agent\",\n",
" // Next, we pass in the function that will determine which node is called next.\n",
" shouldContinueMessageFiltering\n",
" )\n",
" // We now add a normal edge from `action` to `agent`.\n",
" // This means that after `action` is called, `agent` node is called next.\n",
" .addEdge(\"action\", \"agent\")\n",
" // Set the entrypoint as `agent`\n",
" // This means that this node is the first one called\n",
" .addEdge(START, \"agent\");\n",
"\n",
"// Finally, we compile it!\n",
"// This compiles it into a LangChain Runnable,\n",
"// meaning you can use it as you would any other runnable\n",
"const app = workflow.compile({\n",
" checkpointer: memory,\n",
"const messageFilteringApp = messageFilteringWorkflow.compile({\n",
" checkpointer: messageFilteringMemory,\n",
"});"
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"id": "52468ebb-4b23-45ac-a98e-b4439f37740a",
"metadata": {},
"outputs": [
Expand All @@ -333,7 +324,7 @@
"================================ human Message (1) =================================\n",
"hi! I'm bob\n",
"================================ ai Message (1) =================================\n",
"Hello Bob! It's nice to meet you. As an AI assistant, I'm here to help with any questions or tasks you might have. Please feel free to ask me anything, and I'll do my best to assist you.\n",
"Hello, nice to meet you Bob! I'm an AI assistant here to help out. Feel free to let me know if you have any questions or if there's anything I can assist with.\n",
"\n",
"\n",
"================================= END =================================\n",
Expand All @@ -342,30 +333,33 @@
"================================ human Message (2) =================================\n",
"what's my name?\n",
"================================ ai Message (2) =================================\n",
"I'm afraid I don't actually know your name. As an AI assistant, I don't have personal information about you unless you choose to provide it to me. Could you please tell me your name?\n"
"I'm afraid I don't actually know your name, since you haven't provided that information to me. As an AI assistant, I don't have access to personal details about you unless you share them with me directly. I'm happy to continue our conversation, but I don't have enough context to know your specific name. Please feel free to introduce yourself if you'd like me to address you by name.\n"
]
}
],
"source": [
"import { HumanMessage } from \"@langchain/core/messages\";\n",
"\n",
"const config = { configurable: { thread_id: \"2\"}, streamMode: \"values\" as const }\n",
"const messageFilteringConfig = { configurable: { thread_id: \"2\"}, streamMode: \"values\" as const }\n",
"\n",
"const inputMessage = new HumanMessage(\"hi! I'm bob\");\n",
"for await (const event of await app.stream({\n",
" messages: [inputMessage]\n",
"}, config)) {\n",
"const messageFilteringInput = new HumanMessage(\"hi! I'm bob\");\n",
"for await (const event of await messageFilteringApp.stream({\n",
" messages: [messageFilteringInput]\n",
"}, messageFilteringConfig)) {\n",
" const recentMsg = event.messages[event.messages.length - 1];\n",
" console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
" console.log(recentMsg.content);\n",
"}\n",
"\n",
"console.log(\"\\n\\n================================= END =================================\\n\\n\")\n",
"\n",
"const inputMessage2 = new HumanMessage(\"what's my name?\");\n",
"for await (const event of await app.stream({\n",
" messages: [inputMessage2]\n",
"}, config)) {\n",
"const messageFilteringInput2 = new HumanMessage(\"what's my name?\");\n",
"for await (const event of await messageFilteringApp.stream(\n",
" {\n",
" messages: [messageFilteringInput2]\n",
" },\n",
" messageFilteringConfig\n",
")) {\n",
" const recentMsg = event.messages[event.messages.length - 1];\n",
" console.log(`================================ ${recentMsg._getType()} Message (2) =================================`)\n",
" console.log(recentMsg.content);\n",
Expand Down

0 comments on commit ce0596f

Please sign in to comment.