Files
langgraph/examples/time-travel.ipynb
T

1012 lines
84 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
"metadata": {},
"source": [
"# Get/Update State\n",
"\n",
"When running LangGraph agents, you can easily get or update the state of the agent at any point in time. This allows for several things. Firstly, it allows you to inspect the state and take actions accordingly. Second, it allows you to modify the state - this can be useful for changing or correcting potential actions.\n",
"\n",
"**Note:** this requires passing in a checkpointer."
]
},
{
"cell_type": "markdown",
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First we need to install the packages required"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n",
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
]
}
],
"source": [
"!pip install --quiet -U langchain langchain_openai tavily-python"
]
},
{
"cell_type": "markdown",
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
"metadata": {},
"source": [
"Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OpenAI API Key: ········\n",
"Tavily API Key: ········\n"
]
}
],
"source": [
"import os\n",
"import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily 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": null,
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
]
},
{
"cell_type": "markdown",
"id": "21ac643b-cb06-4724-a80c-2862ba4773f1",
"metadata": {},
"source": [
"## Set up the tools\n",
"\n",
"We will first define the tools we want to use.\n",
"For this simple example, we will use a built-in search tool via Tavily.\n",
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"\n",
"tools = [TavilySearchResults(max_results=1)]"
]
},
{
"cell_type": "markdown",
"id": "01885785-b71a-44d1-b1d6-7b5b14d53b58",
"metadata": {},
"source": [
"We can now wrap these tools in a simple ToolNode.\n",
"This is a prebuilt node that extracts tool calls from the most recent AIMessage, executes them, and returns a ToolMessage with the results.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.prebuilt import ToolNode\n",
"\n",
"tool_node = ToolNode(tools)"
]
},
{
"cell_type": "markdown",
"id": "5497ed70-fce3-47f1-9cad-46f912bad6a5",
"metadata": {},
"source": [
"## Set up the model\n",
"\n",
"Now we need to load the chat model we want to use.\n",
"Importantly, this should satisfy two criteria:\n",
"\n",
"1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them.\n",
"2. It should work with OpenAI function calling. This means it should either be an OpenAI model or a model that exposes a similar interface.\n",
"\n",
"Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"\n",
"model = ChatOpenAI(temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "a77995c0-bae2-4cee-a036-8688a90f05b9",
"metadata": {},
"source": [
"\n",
"After we've done this, we should make sure the model knows that it has these tools available to call.\n",
"We can do this using the `.bind_tools()` method, common to many of LangChain's chat models.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
"metadata": {},
"outputs": [],
"source": [
"model = model.bind_tools(tools)"
]
},
{
"cell_type": "markdown",
"id": "e03c5094-9297-4d19-a04e-3eedc75cefb4",
"metadata": {},
"source": [
"## Define the nodes\n",
"\n",
"We now need to define a few different nodes in our graph.\n",
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n",
"There are two main nodes we need for this:\n",
"\n",
"1. The agent: responsible for deciding what (if any) actions to take.\n",
"2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action.\n",
"\n",
"We will also need to define some edges.\n",
"Some of these edges may be conditional.\n",
"The reason they are conditional is that based on the output of a node, one of several paths may be taken.\n",
"The path that is taken is not known until that node is run (the LLM decides).\n",
"\n",
"1. Conditional Edge: after the agent is called, we should either:\n",
" a. If the agent said to take an action, then the function to invoke tools should be called\n",
" b. If the agent said that it was finished, then it should finish\n",
"2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n",
"\n",
"Let's define the nodes, as well as a function to decide how what conditional edge to take."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
"metadata": {},
"outputs": [],
"source": [
"# Define the function that determines whether to continue or not\n",
"def should_continue(messages):\n",
" last_message = messages[-1]\n",
" # If there is no function call, then we finish\n",
" if not last_message.tool_calls:\n",
" return \"end\"\n",
" # Otherwise if there is, we continue\n",
" else:\n",
" return \"continue\""
]
},
{
"cell_type": "markdown",
"id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b",
"metadata": {},
"source": [
"## Define the graph\n",
"\n",
"We can now put it all together and define the graph!"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "812b4e70-4956-4415-8880-db48b3dcbad2",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import MessageGraph, END\n",
"\n",
"# Define a new graph\n",
"workflow = MessageGraph()\n",
"\n",
"# Define the two nodes we will cycle between\n",
"workflow.add_node(\"agent\", model)\n",
"workflow.add_node(\"action\", tool_node)\n",
"\n",
"# Set the entrypoint as `agent`\n",
"# This means that this node is the first one called\n",
"workflow.set_entry_point(\"agent\")\n",
"\n",
"# We now add a conditional edge\n",
"workflow.add_conditional_edges(\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",
" should_continue,\n",
" # Finally we pass in a mapping.\n",
" # The keys are strings, and the values are other nodes.\n",
" # END is a special node marking that the graph should finish.\n",
" # What will happen is we will call `should_continue`, and then the output of that\n",
" # will be matched against the keys in this mapping.\n",
" # Based on which one it matches, that node will then be called.\n",
" {\n",
" # If `tools`, then we call the tool node.\n",
" \"continue\": \"action\",\n",
" # Otherwise we finish.\n",
" \"end\": END,\n",
" },\n",
")\n",
"\n",
"# We now add a normal edge from `tools` to `agent`.\n",
"# This means that after `tools` is called, `agent` node is called next.\n",
"workflow.add_edge(\"action\", \"agent\")"
]
},
{
"cell_type": "markdown",
"id": "bc9c8536-f90b-44fa-958d-5df016c66d8f",
"metadata": {},
"source": [
"**Persistence**\n",
"\n",
"To add in persistence, we pass in a checkpoint when compiling the graph"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6845ed6a-d155-4105-9160-28849877248b",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.checkpoint.sqlite import SqliteSaver\n",
"\n",
"memory = SqliteSaver.from_conn_string(\":memory:\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "79d29875-8aa8-434c-9f20-1c58346a6249",
"metadata": {},
"outputs": [],
"source": [
"# 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",
"app = workflow.compile(checkpointer=memory)"
]
},
{
"cell_type": "markdown",
"id": "e8aff75b-563e-42b1-969b-742201514fc3",
"metadata": {},
"source": [
"## Preview the graph"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "c9ab60eb-679b-4eef-9e64-5ffbf3dffc70",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<IPython.core.display.Image object>"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import Image\n",
"\n",
"Image(app.get_graph().draw_png())"
]
},
{
"cell_type": "markdown",
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
"metadata": {},
"source": [
"## Interacting with the Agent\n",
"\n",
"We can now interact with the agent. Between interactions you can get and update state.\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content='Hello Bob! How can I assist you today?' response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 86, 'total_tokens': 97}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None} id='run-f36a6fff-7732-43ed-b2a1-4d6cde4c073d-0'\n"
]
}
],
"source": [
"from langchain_core.messages import HumanMessage\n",
"\n",
"thread = {\"configurable\": {\"thread_id\": '3'}}\n",
"for event in app.stream(\"hi! I'm bob\", thread):\n",
" for v in event.values():\n",
" print(v)"
]
},
{
"cell_type": "markdown",
"id": "9230c0a7-bab8-4327-a9b4-f21e7a28d32a",
"metadata": {},
"source": [
"See LangSmith example run here https://smith.langchain.com/public/01c1d61c-6943-4db1-8afe-5366f083caf3/r\n",
"\n",
"Here you can see the \"agent\" node ran, and then \"should_continue\" returned \"end\" so the graph stopped execution there."
]
},
{
"cell_type": "markdown",
"id": "c161d511-0dc3-4824-b89e-0dbcf9a7a759",
"metadata": {},
"source": [
"Let's now get the current state"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "23433643-d35d-4df4-80fe-a3002323cd4f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content=\"hi! I'm bob\", id='8b86e367-6571-4333-86db-ed089288fd4f'),\n",
" AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 86, 'total_tokens': 97}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-f36a6fff-7732-43ed-b2a1-4d6cde4c073d-0')]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"app.get_state(thread).values"
]
},
{
"cell_type": "markdown",
"id": "b894d739-d8b1-485b-841a-5cd997c111f9",
"metadata": {},
"source": [
"The current state is the two messages we've seen above, 1. the HumanMessage we sent in, 2. the AIMessage we got back from the model."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "e2c2531f-6dda-444b-b2f5-bbd607251776",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"app.get_state(thread).next"
]
},
{
"cell_type": "markdown",
"id": "98a953a5-7c04-4210-a6ea-bd42e8abd81c",
"metadata": {},
"source": [
"The graph got to the end without interruptions, so the list of next nodes is empty."
]
},
{
"cell_type": "markdown",
"id": "7f117f21-cabc-4c6a-b89a-e673085bc067",
"metadata": {},
"source": [
"### Let's get it to execute a tool"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "73eb35a7-b705-4d7b-9e4b-28f7e2130358",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content='' additional_kwargs={'tool_calls': [{'id': 'call_S1BSXnmEYRcs1Aed2obUsi4J', 'function': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 111, 'total_tokens': 133}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-3d0caf20-5c92-4dd4-bd55-551d29fe6eb4-0' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_S1BSXnmEYRcs1Aed2obUsi4J'}]\n",
"[ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1712938752, \\'localtime\\': \\'2024-04-12 9:19\\'}, \\'current\\': {\\'last_updated_epoch\\': 1712938500, \\'last_updated\\': \\'2024-04-12 09:15\\', \\'temp_c\\': 12.2, \\'temp_f\\': 54.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 260, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1008.0, \\'pressure_in\\': 29.76, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 77, \\'cloud\\': 75, \\'feelslike_c\\': 10.5, \\'feelslike_f\\': 51.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 4.0, \\'gust_mph\\': 12.8, \\'gust_kph\\': 20.5}}\"}]', name='tavily_search_results_json', id='1ab04e35-8850-4a78-a66c-071c83c37e55', tool_call_id='call_S1BSXnmEYRcs1Aed2obUsi4J')]\n",
"content='The current weather in San Francisco is as follows:\\n- Temperature: 12.2°C (54.0°F)\\n- Condition: Partly cloudy\\n- Wind: 11.9 mph from the west\\n- Humidity: 77%\\n- Visibility: 16.0 km (9.0 miles)\\n- UV Index: 4.0\\n\\nIf you need more detailed information or have any other questions, feel free to ask!' response_metadata={'token_usage': {'completion_tokens': 91, 'prompt_tokens': 490, 'total_tokens': 581}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None} id='run-0ac4ccf0-e8a9-4e71-9349-0020baa3e266-0'\n"
]
}
],
"source": [
"for event in app.stream(\"what is the weather in sf currently\", thread):\n",
" for v in event.values():\n",
" print(v)"
]
},
{
"cell_type": "markdown",
"id": "c0ebc4b8-b2cc-4394-bb5a-8964bafa8d2d",
"metadata": {},
"source": [
"See LangSmith example run here https://smith.langchain.com/public/c33c04c5-f1f2-4977-9d7d-c48f28be7be2/r"
]
},
{
"cell_type": "markdown",
"id": "18d3141b-9f9a-4ad8-b475-400674848b16",
"metadata": {},
"source": [
"We can see it planned the tool execution (ie the \"agent\" node), then \"should_continue\" edge returned \"continue\" so we proceeded to \"action\" node, which executed the tool, and then \"agent\" node emitted the final response, which made \"should_continue\" edge return \"end\". Let's see how we can have more control over this."
]
},
{
"cell_type": "markdown",
"id": "1c5d69cf-6de5-4a52-976a-3799aa39d6d5",
"metadata": {},
"source": [
"### Pause before tools"
]
},
{
"cell_type": "markdown",
"id": "06aaa787-32fe-404b-abeb-5b2ed94309fd",
"metadata": {},
"source": [
"If you notice below, we now will add `interrupt_before=[\"action\"]` - this means that before any actions are taken we pause. This is a great moment to allow the user to correct and update the state! This is very useful when you want to have a human-in-the-loop to validate (and potentially change) the action to take. "
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "5a68afc0-606f-4294-a872-b2b563be0d69",
"metadata": {},
"outputs": [],
"source": [
"app_w_interrupt = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content='' additional_kwargs={'tool_calls': [{'id': 'call_ZzRMxOoMFp5ydcbpAQptdHbL', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 88, 'total_tokens': 109}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-1b01c302-8e24-43fe-9324-572d67bc529e-0' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_ZzRMxOoMFp5ydcbpAQptdHbL'}]\n"
]
}
],
"source": [
"thread = {\"configurable\": {\"thread_id\": '4'}}\n",
"for event in app_w_interrupt.stream(\"what is the weather in sf currently\", thread):\n",
" for v in event.values():\n",
" print(v)"
]
},
{
"cell_type": "markdown",
"id": "c53352c8-dcf7-48b5-940c-f75a279cb42c",
"metadata": {},
"source": [
"See LangSmith example run here https://smith.langchain.com/public/22402055-a50e-4d82-8b3e-733c9d752bc5/r\n",
"This time it executed the \"agent\" node same as before, and you can see in the LangSmith trace that \"should_continue\" returned \"continue\", but it paused execution per our setting above."
]
},
{
"cell_type": "markdown",
"id": "7d9fa4f0-fb7d-47a7-b77e-464e8b619273",
"metadata": {},
"source": [
"This is the function call the model produced"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "5a53df78-6c25-4176-b049-02a02a713771",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('action',)"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_values = app_w_interrupt.get_state(thread)\n",
"current_values.next"
]
},
{
"cell_type": "markdown",
"id": "a901e2d1-dcf4-470c-88c0-2e4c91307f69",
"metadata": {},
"source": [
"Because we asked to interrupt the graph before getting to the action node, the next node to execute, if we were to resume, would be the \"action\" node."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "9bad3f92-a3b9-4370-a1f5-0b78d1b67602",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'name': 'tavily_search_results_json',\n",
" 'args': {'query': 'weather in San Francisco'},\n",
" 'id': 'call_ZzRMxOoMFp5ydcbpAQptdHbL'}]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"current_values.values[-1].tool_calls"
]
},
{
"cell_type": "markdown",
"id": "b2b7af00-035c-44e6-a76a-a4b83340b32b",
"metadata": {},
"source": [
"Let's update the search string before proceeding"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "060e2e33-1f6a-40ef-850e-161b308986fb",
"metadata": {},
"outputs": [],
"source": [
"current_values.values[-1].tool_calls[0]['args']['query'] = \"weather in San Francisco today\""
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "3210d003-e20e-47fa-b4f4-402fed7409f1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'configurable': {'thread_id': '4',\n",
" 'thread_ts': '2024-04-12T16:19:26.369199+00:00'}}"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"app_w_interrupt.update_state(thread, current_values.values)"
]
},
{
"cell_type": "markdown",
"id": "0a21b87a-319c-4a93-9ac1-a3c6ad3f5400",
"metadata": {},
"source": [
"This actually produces a LangSmith run too! See it here https://smith.langchain.com/public/9d86718b-333e-4175-bec0-9a64cdd01dc3/r\n",
"\n",
"This is a shorter run that allows you to inspect the edges that reacted to the state update, you can see \"should_continue\" returned \"continue\" as before, given this is still a function call."
]
},
{
"cell_type": "markdown",
"id": "3613f445-cf46-46a0-b7c1-e83a02aae96f",
"metadata": {},
"source": [
"The current state now reflects our updated search query!"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "4a7dfaab-bfa7-47c7-8b93-2b06b3560809",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='what is the weather in sf currently', id='27b8dc9e-0e43-4c20-bb9f-53ee27c791f1'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ZzRMxOoMFp5ydcbpAQptdHbL', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 88, 'total_tokens': 109}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-1b01c302-8e24-43fe-9324-572d67bc529e-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco today'}, 'id': 'call_ZzRMxOoMFp5ydcbpAQptdHbL'}])]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"app_w_interrupt.get_state(thread).values"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "77d73f33-93cb-41db-b6bb-81697569fdb0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('action',)"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"app_w_interrupt.get_state(thread).next"
]
},
{
"cell_type": "markdown",
"id": "27304085-4e44-48bc-afa5-d7ab6da4bb8c",
"metadata": {},
"source": [
"If we start the agent again it will pick up from the state we updated."
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1712938752, \\'localtime\\': \\'2024-04-12 9:19\\'}, \\'current\\': {\\'last_updated_epoch\\': 1712938500, \\'last_updated\\': \\'2024-04-12 09:15\\', \\'temp_c\\': 12.2, \\'temp_f\\': 54.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 260, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1008.0, \\'pressure_in\\': 29.76, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 77, \\'cloud\\': 75, \\'feelslike_c\\': 10.5, \\'feelslike_f\\': 51.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 4.0, \\'gust_mph\\': 12.8, \\'gust_kph\\': 20.5}}\"}]', name='tavily_search_results_json', id='578c3d3b-f905-412e-992a-4e08c4324f8b', tool_call_id='call_ZzRMxOoMFp5ydcbpAQptdHbL')]\n",
"content='The current weather in San Francisco is partly cloudy with a temperature of 12.2°C (54.0°F). The wind speed is 11.9 mph coming from the west. The humidity is at 77%, and the visibility is 16.0 km.' response_metadata={'token_usage': {'completion_tokens': 56, 'prompt_tokens': 466, 'total_tokens': 522}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None} id='run-e28b3232-f2a0-4b22-bedd-0738788c9449-0'\n"
]
}
],
"source": [
"for event in app_w_interrupt.stream(None, thread):\n",
" for v in event.values():\n",
" print(v)"
]
},
{
"cell_type": "markdown",
"id": "14d288a5-1027-4a12-898c-1c6bfcc19bb6",
"metadata": {},
"source": [
"See this run in LangSmith here https://smith.langchain.com/public/8262c0f9-0701-4d73-95f6-2a32f6d3f96a/r\n",
"\n",
"This continues where we left off, with \"action\" node, followed by \"agent\" node, which terminates the execution."
]
},
{
"cell_type": "markdown",
"id": "1c38c505-6cee-427f-9dcd-493a2ade7ebb",
"metadata": {},
"source": [
"## Checking history\n",
"\n",
"Let's browse the history of this thread, from newest to oldest."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "8578a66d-6489-4e03-8c23-fd0530278455",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"StateSnapshot(values=[HumanMessage(content='what is the weather in sf currently', id='06076c4e-40fe-4f44-978a-612fed60849c'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 88, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'function_call', 'logprobs': None}, id='run-966f6180-3c8f-42d5-af89-906650c46ab5-0'), FunctionMessage(content='[{\\'url\\': \\'https://weather.com/weather/hourbyhour/l/USCA0987:1:US\\', \\'content\\': \"recents\\\\nSpecialty Forecasts\\\\nHourly Weather-San Francisco, CA\\\\nBeach Hazard Statement\\\\nSaturday, November 25\\\\n5 pm\\\\nClear\\\\n6 pm\\\\nClear\\\\n7 pm\\\\nClear\\\\n8 pm\\\\nMostly Clear\\\\n9 pm\\\\nPartly Cloudy\\\\n10 pm\\\\nPartly Cloudy\\\\n11 pm\\\\nPartly Cloudy\\\\nSunday, November 26\\\\n12 am\\\\nPartly Cloudy\\\\n1 am\\\\nMostly Cloudy\\\\n2 am\\\\nMostly Cloudy\\\\n3 am\\\\nMostly Cloudy\\\\n4 am\\\\nCloudy\\\\n5 am\\\\nCloudy\\\\n6 am\\\\nMostly Cloudy\\\\n7 am\\\\nMostly Cloudy\\\\n8 am\\\\nMostly Cloudy\\\\n9 am\\\\nMostly Cloudy\\\\n10 am\\\\nPartly Cloudy\\\\n11 am\\\\nPartly Cloudy\\\\n12 pm\\\\nPartly Cloudy\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nPartly Cloudy\\\\n3 pm\\\\nPartly Cloudy\\\\n4 pm\\\\nPartly Cloudy\\\\n5 pm\\\\nPartly Cloudy\\\\n6 pm\\\\nPartly Cloudy\\\\n7 pm\\\\nPartly Cloudy\\\\n8 pm\\\\nPartly Cloudy\\\\n9 pm\\\\nMostly Clear\\\\n10 pm\\\\nMostly Clear\\\\n11 pm\\\\nMostly Clear\\\\nMonday, November 27\\\\n12 am\\\\nClear\\\\n1 am\\\\nMostly Clear\\\\n2 am\\\\nMostly Clear\\\\n3 am\\\\nPartly Cloudy\\\\n4 am\\\\nMostly Clear\\\\n5 am\\\\nMostly Clear\\\\n6 am\\\\nClear\\\\n7 am\\\\nClear\\\\n8 am\\\\nSunny\\\\n9 am\\\\nSunny\\\\n10 am\\\\nSunny\\\\n11 am\\\\nSunny\\\\n12 pm\\\\nMostly Sunny\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nMostly Sunny\\\\n3 pm\\\\nMostly Sunny\\\\n4 pm\\\\nPartly Cloudy\\\\nRadar\\\\nSafety First!\\\\n Don\\'t Miss\\\\nIrresistible\\\\nWeather Wonders\\\\nOur Amazing World\\\\nCelestial Symphony\\\\nFried Turkey Fail\\\\nLook At That!\\\\n Health & Activities\\\\nSeasonal Allergies and Pollen Count Forecast\\\\nNo pollen detected in your area\\\\nCold & Flu Forecast\\\\nFlu risk is low in your area\\\\nWe recognize our responsibility to use data and technology for good. Changes For Critters\\\\nHurricane Tracker\\\\nStay Safe\\\\nAir Quality Index\\\\nAir quality is considered satisfactory, and air pollution poses little or no risk.\\\\n Take control of your data.\\\\n\"}]', name='tavily_search_results_json', id='97fa7a07-399e-4c5d-8175-624be906c574'), AIMessage(content='The current weather in San Francisco is partly cloudy. The temperature is expected to remain consistent with partly cloudy conditions throughout the day.', response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 661, 'total_tokens': 687}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-b99e4fd0-7688-4db7-a677-bf8ba9b8698e-0')], next=(), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-04-02T23:07:42.012426+00:00'}}, parent_config=None)\n",
"--\n",
"StateSnapshot(values=[HumanMessage(content='what is the weather in sf currently', id='06076c4e-40fe-4f44-978a-612fed60849c'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 88, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'function_call', 'logprobs': None}, id='run-966f6180-3c8f-42d5-af89-906650c46ab5-0')], next=('action',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-04-02T23:05:41.595778+00:00'}}, parent_config=None)\n",
"--\n",
"StateSnapshot(values=[HumanMessage(content='what is the weather in sf currently', id='06076c4e-40fe-4f44-978a-612fed60849c'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 88, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'function_call', 'logprobs': None}, id='run-966f6180-3c8f-42d5-af89-906650c46ab5-0')], next=('action',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-04-02T23:01:11.296458+00:00'}}, parent_config=None)\n",
"--\n"
]
}
],
"source": [
"for state in app_w_interrupt.get_state_history(thread):\n",
" print(state)\n",
" print('--')\n",
" if len(state.values) == 2:\n",
" to_replay = state"
]
},
{
"cell_type": "markdown",
"id": "0ec41c37-7c09-4cc7-8475-bf373fe66584",
"metadata": {},
"source": [
"We can go back to any of these states and restart the agent from there!"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "21e7fc18-6fd9-4e11-a84b-e0325c9640c8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='what is the weather in sf currently', id='06076c4e-40fe-4f44-978a-612fed60849c'),\n",
" AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 88, 'total_tokens': 110}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'function_call', 'logprobs': None}, id='run-966f6180-3c8f-42d5-af89-906650c46ab5-0')]"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"to_replay.values"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "d4b01634-0041-4632-8d1f-5464580e54f5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('action',)"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"to_replay.next"
]
},
{
"cell_type": "markdown",
"id": "29da43ea-9295-43e2-b164-0eb28d96749c",
"metadata": {},
"source": [
"### Replay a past state\n",
"\n",
"To replay from this place we just need to pass its config back to the agent."
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "e986f94f-706f-4b6f-b3c4-f95483b9e9b8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"content=\"[{'url': 'https://www.accuweather.com/en/us/san-francisco/94103/current-weather/347629', 'content': 'Get the latest weather conditions and forecast for San Francisco, CA. See the temperature, humidity, wind, pressure, cloud cover, and alerts for the current hour and the next few days.'}]\" name='tavily_search_results_json' id='82d6c2ac-260c-4d5d-9541-21ac4fab8916'\n",
"content='You can check the current weather conditions and forecast for San Francisco, CA on [AccuWeather](https://www.accuweather.com/en/us/san-francisco/94103/current-weather/347629). It will provide you with information on temperature, humidity, wind, pressure, cloud cover, and alerts for the current hour and the next few days.' response_metadata={'token_usage': {'completion_tokens': 75, 'prompt_tokens': 194, 'total_tokens': 269}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None} id='run-b4ab153a-266f-44c2-ad37-5fd2a869003a-0'\n"
]
}
],
"source": [
"for event in app_w_interrupt.stream(None, to_replay.config):\n",
" for v in event.values():\n",
" print(v)"
]
},
{
"cell_type": "markdown",
"id": "8b4b81dd-1108-4ab7-b680-3f641b3efa70",
"metadata": {},
"source": [
"See this run in LangSmith here https://smith.langchain.com/public/f26e9e1d-16df-48ae-98f7-c823d6942bf7/r\n",
"\n",
"This is similar to the previous run, this time with the original search query, instead of our modified one. "
]
},
{
"cell_type": "markdown",
"id": "59910951-fae1-4475-8511-f622439b590d",
"metadata": {},
"source": [
"### Branch off a past state"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "b084f141-5800-487b-b115-d2e58421b963",
"metadata": {},
"outputs": [],
"source": [
"branch_config = app_w_interrupt.update_state(to_replay.config, AIMessage(content='All done here!', id=to_replay.values[-1].id))"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "1a7cfcd4-289e-419e-8b49-dfaef4f88641",
"metadata": {},
"outputs": [],
"source": [
"branch_state = app_w_interrupt.get_state(branch_config)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "5198f9c1-d2d4-458a-993d-3caa55810b1e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[HumanMessage(content='what is the weather in sf currently', id='06076c4e-40fe-4f44-978a-612fed60849c'),\n",
" AIMessage(content='All done here!', id='run-966f6180-3c8f-42d5-af89-906650c46ab5-0')]"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"branch_state.values"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "5d89d55d-db84-4c2d-828b-64a29a69947b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"branch_state.next"
]
},
{
"cell_type": "markdown",
"id": "cc168c90-a374-4280-a9a6-8bc232dbb006",
"metadata": {},
"source": [
"You can see the snapshot was updated and now correctly reflects that there is no next step.\n",
"\n",
"You can see this in LangSmith update run here https://smith.langchain.com/public/65104717-6eda-4a0f-93c1-4755c6f929ed/r\n",
"\n",
"This shows the \"should_continue\" edge now reacting to this replaced message, and now changing the outcome to \"end\" which finishes the computation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ab115de-9b11-4e8b-8ace-c23e1369300b",
"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.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}