diff --git a/docs/docs/tutorials/introduction.ipynb b/docs/docs/tutorials/introduction.ipynb index 266c83f77..aa72c90fb 100644 --- a/docs/docs/tutorials/introduction.ipynb +++ b/docs/docs/tutorials/introduction.ipynb @@ -225,12 +225,12 @@ { "cell_type": "code", "execution_count": 7, - "id": "32e4f36e-72ce-4ade-bd7e-94880e0d456b", + "id": "9403859a-f25a-46b4-a672-46a93f8a3cba", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -1171,9 +1171,11 @@ "\n", "Agents can be unreliable and may need human input to successfully accomplish tasks. Similarly, for some actions, you may want to require human approval before running to ensure that everything is running as intended.\n", "\n", - "LangGraph supports `human-in-the-loop` workflows in a number of ways. In this section, we will use LangGraph's `interrupt_before` functionality to always break the tool node.\n", + "LangGraph's [persistence](../../concepts/persistence) layer supports human-in-the-loop workflows, allowing execution to pause and resume based on user feedback. The primary interface to this functionality is the [interrupt](../../concepts/human_in_the_loop/#interrupt) function. Calling `interrupt` inside a node will pause execution. Execution can be resumed, together with new input from a human, by passing in a [Command](../../concepts/human_in_the_loop/#the-command-primitive). `interrupt` is ergonomically similar to Python's built-in `input()`, [with some caveats](../../concepts/human_in_the_loop/#interrupt). We demonstrate an example below.\n", "\n", - "First, start from our existing code. The following is copied from Part 3." + "First, start with our existing code from Part 3. We make two changes, highlighted below:\n", + "1. We add a `human_review_node` to handle the interaction with a human reviewer;\n", + "2. We use a different condition in our conditional edge that routes to the `human_review_node`, instead of directly to tools." ] }, { @@ -1183,16 +1185,18 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import Annotated\n", + "from typing import Annotated, Literal\n", "\n", "from langchain_anthropic import ChatAnthropic\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", "from typing_extensions import TypedDict\n", "\n", "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import StateGraph, START\n", + "from langgraph.graph import StateGraph, START, END\n", "from langgraph.graph.message import add_messages\n", - "from langgraph.prebuilt import ToolNode, tools_condition\n", + "from langgraph.prebuilt import ToolNode\n", + "# highlight-next-line\n", + "from langgraph.types import Command, interrupt\n", "\n", "memory = MemorySaver()\n", "\n", @@ -1214,14 +1218,64 @@ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", "\n", "\n", - "graph_builder.add_node(\"chatbot\", chatbot)\n", + "# highlight-start\n", + "# We add a node to handle the interaction with a human reviewer\n", + "def human_review_node(state: State) -> Command[Literal[\"chatbot\", \"tools\"]]:\n", + " last_message = state[\"messages\"][-1]\n", + " tool_call = last_message.tool_calls[-1]\n", + "\n", + " # this is the value we'll be providing via Command(resume=)\n", + " human_review = interrupt(\n", + " {\n", + " \"question\": \"Is this correct?\",\n", + " # Surface tool calls for review\n", + " \"tool_call\": tool_call,\n", + " }\n", + " )\n", + "\n", + " review_action = human_review[\"action\"]\n", + " review_data = human_review.get(\"data\")\n", + "\n", + " # if approved, call the tool\n", + " if review_action == \"continue\":\n", + " return Command(goto=\"tools\")\n", + "\n", + " elif review_action == \"feedback\":\n", + " # NOTE: we're adding feedback message as a ToolMessage\n", + " # to preserve the correct order in the message history\n", + " # (AI messages with tool calls need to be followed by tool call messages)\n", + " tool_message = {\n", + " \"role\": \"tool\",\n", + " # This is our natural language feedback\n", + " \"content\": review_data,\n", + " \"name\": tool_call[\"name\"],\n", + " \"tool_call_id\": tool_call[\"id\"],\n", + " }\n", + " return Command(goto=\"chatbot\", update={\"messages\": [tool_message]})\n", + "\n", + "\n", + "# Where we used tools_condition before, here we update the condition\n", + "# to route to the human review node first, instead of tools.\n", + "def route_after_llm(state) -> Literal[END, \"human_review_node\"]:\n", + " if len(state[\"messages\"][-1].tool_calls) == 0:\n", + " return END\n", + " else:\n", + " return \"human_review_node\"\n", + "\n", + "# highlight-end\n", + "\n", + "\n", + "graph_builder.add_node(chatbot)\n", "\n", "tool_node = ToolNode(tools=[tool])\n", "graph_builder.add_node(\"tools\", tool_node)\n", + "# highlight-next-line\n", + "graph_builder.add_node(human_review_node)\n", "\n", "graph_builder.add_conditional_edges(\n", " \"chatbot\",\n", - " tools_condition,\n", + " # highlight-next-line\n", + " route_after_llm,\n", ")\n", "graph_builder.add_edge(\"tools\", \"chatbot\")\n", "graph_builder.add_edge(START, \"chatbot\")" @@ -1232,28 +1286,65 @@ "id": "813505b2-18c1-46e9-b891-20a34232808b", "metadata": {}, "source": [ - "Now, compile the graph, specifying to `interrupt_before` the `tools` node." + "------\n", + "\n", + "!!! tip \"Read more\"\n", + "\n", + " Check out [this guide](../../how-tos/human_in_the_loop/review-tool-calls/) for more detail on human review of tool calls, including how to edit tool calls directly.\n", + "\n", + "---------\n", + "\n", + "We compile the graph with a checkpointer, as before:" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 5, "id": "b0883e32-1a39-4ce9-ae32-bbd66708fd84", "metadata": {}, "outputs": [], "source": [ - "graph = graph_builder.compile(\n", - " checkpointer=memory,\n", - " # This is new!\n", - " interrupt_before=[\"tools\"],\n", - " # Note: can also interrupt __after__ tools, if desired.\n", - " # interrupt_after=[\"tools\"]\n", - ")" + "graph = graph_builder.compile(checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "bc0e84db-925c-4468-b3e0-639e6b25ac3c", + "metadata": {}, + "source": [ + "Visualizing the graph, we can see that we've added a new node to mediate the interaction between the chatbot and the tools:" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 34, + "id": "8b8e61b5-f3c2-4917-a8fc-928ccbcfc6ab", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAFcCAIAAABumWMEAAAAAXNSR0IArs4c6QAAIABJREFUeJzt3XdcU9f7B/CTBQkZrLCnijgQAQXrFreiILhrwT2oq1ZRa9VWq3Vrra3WrS3YKg5AcFXEBYoDRMXBEhRFZkgCAbJ/f1y/1J+SACHJTcLz/sOX5K4HCJ+ce++55xDkcjkCAIDmI+JdAABAX0F8AABUBPEBAFARxAcAQEUQHwAAFUF8AABURMa7AABahF8h4VWIBXxJDV8iEepHNwSKEYFIJtBZZDqLxHagGlEJeFekIgL0+wD6qKxQlPe0Kj9TwDQnSyRyOotMZ5GNqAS9eDsbGRP5HEkNXyLgS6oqJUwLStsudPfuTBMmCe/SmgfiA+gZbpn4Tnw5xZhoZkVp68mwtDPCu6KWepdbm58pKC8Ssh2MeweyifpzRQHiA+iT1IucnEdVfQLZbbvS8a5F/TJucFPiywdOsO7ck4V3LU0C8QH0RvQvhd4DzN27MfAuRLNSL3LqaqT+463wLqRxEB9AD8jlaP/KvLELHW2cjfGuRRuepvCKC+qGfmWDdyGNgPgAemBfRN6sDW2MafpzVaDFnt3h52RUBc93wLsQZSA+gK6L3lXoP97aunW0Oz6WcYNbzZX0DWbjXYhCrSjOgT66m1DhM9C8FWYHQsjb34xMIWanV+NdiEIQH0B3cYpF+c8E7X0M/FqpEj6DzG6cLsW7CoUgPoDuSokv7x2ou013LTCmET37mKYlVuJdSMMgPoCOKs6vM2GSXTubaOdwmZmZQqEQr82V6DXa8k1WDdLJS5QQH0BH5T6utrTVUo/S+Pj46dOn19bW4rJ5o4xpxFdPBRraeUtAfAAd9Sqzuq2nlrqWqtxwwG5caqjdUa9NF/qrTF28gArxAXRRRZHI0taYZUlR+55fv34dHh7et2/fgICATZs2yWSy+Pj4LVu2IISGDBni6+sbHx+PEMrIyFi4cGHfvn379u07b968Fy9eYJtzuVxfX9/IyMg1a9b07dt3zpw5DW6uXu08Gbwysdp323LwwD7QRdxyMUEzT59u2LChoKBg2bJlAoHg4cOHRCKxT58+oaGhUVFRu3fvZjAYzs7OCKGioiKhUDh79mwikXj69OnFixfHx8dTqVRsJ0eOHJkwYcL+/ftJJJKNjc3nm6uXEY3IKRXV1cioJrr1eQ/xAXSRgCehszTy5iwqKurYsWNISAhCKDQ0FCFkYWHh6OiIEOrSpYuZmRm22siRIwMCArD/d+7cOTw8PCMjo2fPntgrnp6eCxYsqN/n55urHZ1FFvAkVBPderwY4gPoIgFfU/EREBBw/Pjxbdu2zZ4928LCQtFqBALh+vXrUVFR+fn5JiYmCKGKior6pT169NBEbUrQWaQavkTXRifQrbYQAB8QENlII2/OBQsWLF269N9//w0KCoqOjla02uHDh5cvX965c+ddu3YtWbIEISSTyeqX0mg0TdSmhBGN9NHxdQXEB9BFNDqpiqORi4UEAmHKlClxcXEDBgzYtm1bRkZG/aL657+EQuGxY8eCg4OXLVvm7e3t6enZlD1r9PExXpmIztK5scggPoAuorPINXyJJvaM3WSl0+nh4eEIoZcvX9a3JsrKyrB1amtrhUJhp06dsC+5XO4nrY9PfLK5Jgj4UhPNnM21hM4VBABCiGlB0dDJy8qVKxkMRs+ePZOTkxFCWEZ4eXmRSKQdO3YEBQUJhcJx48a5ubmdPHnS0tKyurr64MGDRCIxNzdX0T4/31y9NctlyMLWSAdHQoXWB9BFti7G+c+q6wRSte+5S5cumZmZmzZtevny5erVq728vLBbJ6tXr379+vWOHTuuXr2KENq0aRONRlu1alVkZOS33347a9as+Ph4sbjh86nPN1evV0+rde2WLQbG+wA6Kulkqa0rVV9G/dSoxL9LHN1MOvZg4l3Ip+DkBeiodl0Zr18oe9CDw+GMHTv289flcrlcLic2NGD5N998g/X40KjZs2c3eKbTqVOn+t6rH+vVq9fmzZuV7LCGL3X10MWhoaH1AXTXqZ2FAydaWzs1PFaQVCotKSn5/HWZTCaTycjkBj4aTU1N6XSN/x2WlZU1eJpDIDT850alUpX0QHl8k8vnSPqF6OLABRAfQHe9za59mMjR8fE+NW1fRN68LW1JZF2ciU4Xr8cAgHF0p5laUory6vAuBDdPbvF6B7J1MzsgPoCuGzjJ+sKRImGN7vW41Lz8Z4I32TXeA0zxLkQhiA+g675c4fz3ttd4V6FtlcXiW2fLRs+2w7sQZeDaB9ADwlr5P9tfh37nQjbS0Wa8ehW9qrt1tnRShDNBt79diA+gH/gV4r+3vhm7yMnaSbeeOlW7l/ernt3jjVvkiHchjYP4APok8USJSCjrHcg2s1L/QGS4e5NVc+d8uUsneq/RlnjX0iQQH0DPvHoiSIkvb+/NtHY2btuFjnS7ed8UdQLZq8zq96/qqnmSPoGWbAe9mRML4gPopZxH1TnpVa8yBZ59TIkkggmTZMIiGVGJevF2JlMIAp5UwJfUVEl55eKywro2noyO3VkO7al4l9Y8EB9Av71+UcMtFdVUSQV8iUSs5rezUCh8/vy5j4+POneKEI1BksvkJkyyCYtkZU+1baM3zY1PQHwAoND79+/nzJmTkJCAdyE6Cvp9AABUBPEBAFARxAcAChEIhHbt2uFdhe6C+ABAIblcnpeXh3cVugviAwBlWCwY7kwhiA8AlOHz+XiXoLsgPgBQiEAg2Nra4l2F7oL4AEAhuVxeXFyMdxW6C+IDAGXc3d3xLkF3QXwAoEx2djbeJeguiA8AgIogPgBQxtzcHO8SdBfEBwDKVFZW4l2C7oL4AEAZS0v9GPgLFxAfAChTUVGBdwm6C+IDAKAiiA8AlGnTpg3eJeguiA8AlMnPz8e7BN0F8QEAUBHEBwDKQKd1JSA+AFAGOq0rAfEBAFARxAcAChEIhA4dOuBdhe6C+ABAIblcnpWVhXcVugviAwCgIogPABSCiRqUg/gAQCGYqEE5iA8AgIogPgBQBuZ5UQLiAwBlYJ4XJSA+AFAGnrhVAuIDAGXgiVslID4AACqC+ABAGWtra7xL0F0QHwAoU1paincJugviAwBlYLwPJSA+AFAGxvtQAuIDAGWg9aEExAcAykDrQwmIDwCUsbe3x7sE3UWQy+V41wCAbgkNDeXxeEQiUSKRVFZWstlsAoEgEokuXbqEd2m6BVofAHxq4sSJHA7n3bt3JSUlIpGoqKjo3bt3RCL8sXwKfiIAfCooKMjZ2fnjV+Ryeffu3fGrSEdBfADQgClTphgbG9d/aWNjM23aNFwr0kUQHwA0IDAw0NHREfu/XC7v0aMHjFr4OYgPABo2depUOp2ONT3CwsLwLkcXQXwA0LBRo0Y5OTlB00MJMt4FANBUtdXSsndCUZ1Ma0cMGjyXVHdhaO+w3MfVWjsonUVm2xtTjAlaO6LKoN8H0AMSsfxqVMm7vFqnDnSRUHvxgQuhQMrniN28GP3HsvGupREQH0DXCWtlZ/e8/WKktbULFe9atOdZKreyqG7kDFu8C1EG4gPouj9/Khg2zZFh1upOtLMf8jnFtUO/ssG7EIXg0inQaU9v89x8TFthdiCE3H1Zwlp56Rsh3oUoBPEBdFpJYZ0Ji4R3FbihGBHL30N8AKASkVDOtKDgXQVuzKyNBDwJ3lUoBPEBdFqdQCo38DstyohFMqnupgfEBwBAVRAfAAAVQXwAAFQE8QEAUBHEBwBARRAfAAAVQXwAAFQE8QEAUBHEBwBARRAfAAAVQXwAAFQE8QFahZzcrIGDfe/evd2sraRS6dOnGR+/suaHZfPCQ5t79M/3YxggPgBQaPvODbt2b9Kd/egaiA8AFBIJ1TPWhrr2o2ta4yBOwLDV1dVFRh2+fv3fsvJSGxu7YUNHfTVlBrYovyDvZPRfWVnPHR2dv1m00tPTGyFUWlpy5Ni+e/dSBIJqJyeXKV/OGDJ4BEJoy7Z1129cRQgNHOyLEPr7xHk7W3uEkKBG8OO6FemP7hsZGQ8eNGLWzPnYfHQSieTY8f1X/k3g8bguLm2mT5vXt4//5/s5E33Z0lLXx0BuIogPYFCkUun3q5c8zcwYGzLZrZ17wetXhW9fk0gfxiuLOnFk4oSwkSOC/v7n+Oq1S/+OOs9gMCRSycuXz8YEjTdlmd1KTvp50xoHB6dOHT1Cp8wsKy15//7dqu9+QghZWnz4my8ped+rZ78F85c9eHD39JkT74oKf96wCyG0Y+fGxGuXQr+a6eraLvHapbU/RPz6y6GuXX0+2Y+pqRmuPyF1gvgABuXmrWuPMh4uj1gbMHLM50u/WbRy+PDRCCEX5zbzF05PS783oP9gezuH40dPEwgEhNDIkWNCxg1JSbnRqaOHo6OzqakZp7ICa6TUa9vGbcH8pQihEcMD2Wzr6NNRjx+nm5tbXPk3YWrY7OnT5iGEBvQfHDo15PifB3bt3K9oPwYA4gMYlPsP7hgbGw8fNrrBpSyWKfYfV9d2CKGyshLsy9y87ON/HsjKeo61XziciiYeLiR4UvTpqEcZD7Hzkb59B2KvEwgEP9+eVxMvquN70l1w6RQYlEpOBdvSqv5sRREikYglBUIo/dGD+QumiUWiFct/XP/jNhbLVNbk8RHZbCuEkEBQLRBUI4TMzSzqF7FYpjU1NQKBoGXfkE6D1gcwKAwGk1PZ1LYDJjLysL2946afd5PJZIQQjUr7eKnyiZC43EqEkLm5BZttjRDi83lYoCCEOJwKMplMpVKbsh89Ba0PYFB8fPxqa2uvJV2pf0UiaWSsYR6f69bOHcsOkUhUU1sjk31ofVCpNA6nov7Lz928mYgQ6tatR6dOXQgEQuq9ZOx1kUiUei/Zw6Mr1g5qdD96ClofwKAMHRIQGxe9ZeuPL18+c2vn/io/Ny393sH9J5Rs4u3te+VK/MVLcSym6emzJ6qq+AX5eXK5nEAgeHXtduny+V2/bPLs4s1ksnr37o8QynuVs3ffrnbt2mdlPY9PODeg/+COHTojhIYPG338zwNSqdTe3vHChRgOp+L7VRuwQ3y8HwcHpy5dvLT189AsiA9gUIyNjXfu2H/o0G9XEy8mXDhna2s/0H+Y8gbIzOlfcyrKf/t9O5PJGj1q7MTxobt2b3qU8bCbj9/QoQFZ2c//vXrhburtEcMDsfj4cvK0zMzHCRfO0emMCeO/mjE9HNvPkm++o9MZMbGnqqr4bVzbbdr4SzcfP2zRx/uZOeNrg4kPmOMW6LRzv7/z7Gdh60prwroG6PFNDpmMegZYNGFdHMC1DwCAiiA+AAAqgvgAAKgI4gMAoCKIDwCAiiA+gI7icrnLli17+7YQ70KAQhAfQIfIZLItW7bMnj0b67gZGBjo4OCId1FAIYgPgL+TJ0/Onj1bJBLJZLJ27dpt2LABIWRtbe3v7489Rw90E8QHwMedO3dWrVqVn5+PNTQWLFhgZGREJpMnTJhgZ2eHd3WgSaDTOtCevLy8uLi4fv36+fn55eTkDBw40MXFBSE0depUvEsDqoD4AJrF4XDOnz9va2s7YsSIe/fu2djYdO7cGSE0bdo0vEsDLQXxAdRPKpVevnxZJBKFhISkpKRUVVWNGDECITRlyhS8SwPqBPEB1CYtLe3u3bsLFy4sKiq6d+/e+PHjEUKBgYF41wU0BS6dghYpKCg4efIk9kR8ZGSkqakpQsjJyemnn37q2rVry/dvatmqP+HIRkQaXXf/SHW3MqCzhEJhUlJSZWWlVCqNiIh4+/YtNnTo7t27w8LC1HssGoNc/rZOvfvUI8X5NaZWRnhXoVCrjnbQLJmZmVQq1c3N7bvvviOTyT169CCRSGfOnNHoQV06mTy9w9foIXSXHIlqZU7uujvWCQwXBJSpqKiorq52cXHZsWPH06dP16xZ0759e80djs/ni0QioVCI/VtXV+ft7X3/CodbLu012kpzx9VN//5V5DfMzLmDCd6FKATxARrw+vVrFxeXv/76KyoqatOmTb6+vnV1dfWDhmtCUFAQkUiUSCRyuZxMJsvlcplMJpfLjY2Nz507l57ELXkjtG1jwnYwJpEMvB9qrUDKKxVl3KgYMd3Ovi01NTXV2NjYxMTExMSETqcbGxvT6XS8a/wA4gN8UF5ezmaz8/Lypk6dOm/evKlTp5aVlVlZaekzf/z48QUFBZ+8aG5uvmbNmgEDBiCE3ryozX7ErxXIKotF2inpc3K5TCCoYTAYGj0Kw5Rs5WTcbZA53ZSEEAoICKDRaNjfKZlMJpPJQqGQTCYzmczDhw9rtJJGQXy0dhKJhEgkTpkyhUajHTt2jMvlUqlUjTY0FOnZs+fHYxoTCISxY8euWrVK+5UoMXDgwLi4OBaLpbUj/vLLL//8888nkzzIZLL09HSt1aAIxEdrJJFIyGTy9u3b4+LiLl++TKfT8/Ly3Nzc8K1q1KhRJSUl9V+2adPm9OnTuFbUgEePHrm4uFhYaHXs4kmTJuXm5n789CCDwbhx44Y2a2gQ3LhtXRISEsLCwl69eoUQ6t27d2JiIoPBIBAIuGdHeHj4yJEjTUw+XCa0tLRcu3YtviU1yMfHR8vZgRBavXo1m82u/1Iul8fGxmq5hgZBfBi+zMzM9evXp6SkYHO7rlq1yt3dHSHUp08fXE5SPlZUVPT8+XOE0OLFixcuXHjr1i2EEIVCCQoKUkuvM7V78uTJqVOntHzQrl27jho1CpsHD2t6jBs37sCBA1ou43MQH4aJy+WePHkyNTUVe8f7+Pj06NEDuw6HPbGmCwoKClauXGljY4MQqq+KwWC4u7svWLAA7+oaZmlpeeKEsjnrNGTx4sXt2rXDLgndvHnz2rVrBAJh0KBBiYmJ2i+mHlz7MCgZGRkIIW9v7/3791dXV0+bNk1rt06a5fDhwyEhIVKp1NraGu9amo3H47FYLO2PY5SRkbFy5UoqlRoXF1dfyaFDh54+fbp8+fIuXbpouR6ID0Mgl8tzcnLc3d2joqKuX7++bNky3WlfNGjlypWurq5ff/013oUYiMzMzO3bt3fo0GHlypXYjNzaIwf6qbq6Wi6XZ2RkdO/e/cyZM3K5vK6uDu+ilElOTr5//75cLhcIBHjX0iInTpyIjY3Fu4pPXbhwwc/PD3snaA1c+9A/VVVVs2fP/v777xFCDg4ODx8+HDduHDY7NN6lKfTo0aNTp055eHgghOpvr+gpCwuL+/fv413FpwICAu7fv5+VlbV27dp3795p56Bw8qI3jh49euPGjb/++ovL5RYUFHh7e+NdUeMEAsGuXbvWrl3L4XC0f79TQ+RyuVAoxP2mlSIvX75csWJFcHDwzJkzNX0saH3otNzc3D179pSXl2OX3H/44QeEkJmZmV5kB0Jo1apVPj4+2Cc23rWoDYFA0NnsQAh17Njx/PnztbW1kydPLioq0uixoPWhix4/fmxubu7s7Iw94RoaGqrtS2ItExMTI5FIJkyYgHchmjJjxozVq1fj3tdOudzc3IiIiKlTp44dO1ZDh4DWhw4pLi7GBt359ddfjYyMEEIbN26cNm2afmVHQkLCs2fPQkJC8C5EgxwcHHJycvCuohFubm6xsbEvXrz47rvvNHUMbV6nBYpkZ2ePHDny9OnTcrm8qqoK73JUUVhYuG7dOrlczuPx8K5F48RisUgkwruKprp69WpoaGhhYaHa9wwnL7ipq6s7fvw4l8v97rvv3rx5Y2xsjPW/1DvYA3hLliyZNm0adqXD4AmFwtraWjMzM7wLaSqBQPDVV18tWbLE399fjbuFkxdt4/F4CQkJ2JxJZDIZm8/V2dlZT7Pj2LFj58+fx865Wkl2IITev38/a9YsvKtoBjqdHhsbGx8ff+TIETXuFuJDe6qqqhBCISEh2DUODw+P2bNnf/wkpd5JTk4WCASauzKns1xdXXW5l40iO3fuFAqFu3btUtcO4eRFG2JjY7du3RoTE2Nra4t3LWqQlZV14MCBXbt2CYVCffwrauVu3Lhx/vx5tYQItD40RSKRnDx5Mjk5GSFkZ2d38+ZNA8gOsViMENq7d+/cuXN1vJ+rphUUFGDNSb3j7+8/ZsyYxYsXt3xX0PpQv7dv3zo6Ov7++++1tbXh4eFMJhPvitTjwIEDrq6uw4cPx7sQnbB169Y2bdpMnDgR70JUlJKSEhUV9ccff7RkJ6R169apr6TWrrq6etasWXK5HBtfo0+fPgbz+ZyYmMjlcidNmoR3IbqCy+VWV1d7eXnhXYiKnJ2dsaFDunfvrvJOoPWhBmKx+M8//5wwYYJYLC4tLdXx5+WbJS8vb8+ePb/++it2dxbvcoCanThxorCwUOV+ZXDto0WwkcEnTZokFotZLBabzTaY7MAucxw+fBgbmAOy4xMSiYTH4+FdRUt99dVXBAIhOjpatc2h9aEikUi0e/fugQMH+vn54V2L+h09etTc3NywO563UElJyYwZMy5evIh3IWoQHh4+e/ZsX1/f5m4IrQ8VXbx40cXFxSCzIzU1tba2FrJDOTabXVdnIHN379+/HxsIorkbQuujeWJjY/fv33/58mW8C1G/kpKSHTt2bN++XSQSYQ/sgdYjNTU1MjJy7969zdoKWh9NhZ3ovnr1Cutybni2bNmC3ViB7GiFevbsaWVlFR8f36ytoPXRJL/99lv37t179+6NdyHql5CQUFVV9eWXX+JdiP5ZvHjxjBkzDOlJn169et28ebPpnx/Q+miEVCp9+/Ytk8k0vOyQy+XPnj17+PCh/vZ9wheNRquoqMC7CnXCzl6bvj60PpS5c+eOu7u7mZmZ4d223Lx5c0REhFAo1PR88QasurqaTCbr8sCFKggNDV2zZk3Hjh2bsjK0PhQqKCj4559/2Gy24WXH999/3759ewqFAtnREgwGw8CyAxuH8ejRo01cGVofCr148aJTp054V6FOz58/T09PDw0NxbsQAxEdHS0SiQzv5xkSEvLrr786Ozs3uia0PhqQlpYWHx9vYNnx/v37zZs3Dxs2DO9CDIdUKsWGbjEwM2bMOHbsWFPWhNbHp3Jycvbv379z5068C1Gb2NhYbIg6PRpcTy8IBAKRSGRubo53Ieq3ZMmStWvXWlpaKl8NWh+fat++vSFlBzaFspmZGWSH2tHpdIPMDmyEmmvXrjW6GsTH//Py5UvdH4C/ibDnoEaNGrV27Vq8azFMDx482LBhA95VaMTgwYMhPpotIiLCMG5G9OvXz9raGiFkb2+Pdy0GSyaTvX//Hu8qNMLX1zc3N7fRp2Dg2sd/iouL09LSRo0ahXchqnvz5o1MJnN1dYXnVrRAJpOJRCLDu3eL2bRpU4cOHbDZ1xWB1sd/bG1t9To7UlJSvvnmG6zRAdmhBUQi0VCzAyE0fPjwFy9eKF8H4uM/mZmZDx8+xLsKVTx69AgbuDgmJsbExATvclqL7OxsbJoeg+Th4XHp0iXl60B8/Ofdu3fnzp3Du4pm27lz5+3bt7HzVbxraV0IBEJ1dTXeVWgKlUp1dnbOzs5Wsg5c+/hPSUnJlClTqFQql8sVi8X379/Hu6JGZGdnu7u7p6WltWS0W6AybLzCRjtH6K/Nmze3b99+/PjxilaA1gcKCwvr2bNn9+7dR40axePxSkpKhEKhlZVVbm4u3qUpVFpaGhwcjI20CtmBFzKZbMDZgRDy9PR8+vSpkhUgPlBkZKSDgwOBQKh/RS6X0+l0Nzc3XOtS5smTJ7/99pvBDMusp969ezd58mS8q9AgiI8mmTVr1sfdBwkEQrdu3XCtqGFPnjzBZmkaMmSIk5MT3uUAVFNTg3cJGuTi4sLhcIRCoaIVID4QQiggIGD48OH1N+GYTGbPnj3xLur/KSkpQQgVFRVduXIF71rABzY2NgcOHMC7Cs2ytbUtLCxUtBTi44OIiAgvLy+ZTIaN49C+fXu8K/rPvn379u/fjxAaMWIE3rWA/5DJZDs7O7yr0CwnJ6c3b94oWgrx8Z+NGzdi1zvs7e0dHBzwLgchhMrLyxFCLBbrxx9/xLsW8KmysjJsqnAD5uzsrCQ+mjCOlhyJRfKaKoma69I9RMRYFL5y165dft4DeOVifIuRSCQ7duwYM2YMpZNp4IhJeNVjyqbgcly9IJFIDGaqF0WcnJwyMzMVLW2k38fze/zHt3i8chGNbmgD9uk4mUyKEIFIxLN5aGFr/Carul1XZu/RlixLeAN8MHfu3LS0NOwSu0wmIxKJ2B8R9qKBefz48dmzZ3/66acGlyp7T6QlckvfCv0n2jHM4K3TSsmkiFcuOrvnbfB8R3MbeBsghNCCBQtWrFiBjbGO5TuBQNCRs121MzU1ffbsmaKlCj/c7l3mcErFfUNsIDtaMyIJmdsYjV/qGrvvLZ9j+CewTeHl5eXp6flxs51EIgUGBuJalKaYm5tXVlYqWtpwfFSWisvfiXqOstJkYUCfDJxsl3rBoOY0aYmwsLCPB1JxcXEx1Hm2TE1Nq6qqsDuSn2s4PsqLhPAoDPiYmZVR3hODfTysuby8vLp06YL9n0gkBgYG0ul0vIvSFCUNkIbjo6pSYuVosAMZABWQKARHdxM+B+cbUrrjyy+/tLW1xZoeSh4qMwDNjg+JUCaqa7i5AlqtivdChAhNWLFV6Nq1a9euXSkUSmBgII1Gw7scDfL09OTz+Q0ugsuioFUQi+Rvc2p4ZZKqSolEjGqq1dCM8rabxfjC31zU5cJRNcz2YmRMMmGRWOZkc2uKo7sO5VFFRYWiYU0gPoCBe3Kbl5VeVfZWaO7AksvkFGMSxYSilne+EZ3WuStbipBUHXWKauSVHElBVi2RVMs58M6lE6ODL7O9N/6XVGg0Wm1tbYOLID6Awcq4wU2JL7dxMzNhm3d216drebbulvyymsx7tXfiy/uNYbftimeIUKlURZ1rIT6AAaooEl2JKiXTjDsPbkPQw8s1BCLB1IaOEN3EkpH6L+dlWnXADBu8ilHS+oBH5oBa1+MNAAAfb0lEQVShefmg6vyhYpuONtZuFvqYHR8zplPsPWwIJqy9y3IrS/G57UWj0RS1PiA+gEHJfVKbdqO6TQ8HEsVw3ts0llHnQa5nfyuq5uJwP5TNZpNIpAYXGc6PGICnKfzUS1yHLtZ4F6J+BCLBrbfjyZ1vuGXaboPU1dXxeLwGF0F8AANR/Fr48BrXsasBZke9dj0dT2x5reWDkkgkbFDuz0F8AEMgl6Hrp8vb+BnmY6/1CERCWz+Hi8dKtHlQEokklTZ8bxriAxiC5PPlRkwd6mqlOTRTI26F9NVTgdaOCPEBDFmdQPr8Ht/S2RTvQrTE0tXidmy51g6njfgIHOP/x/7d6tqbjnv1KjdozMDklBt4F9KIjZvWTJ2ubIZ0w5B+nWvrrqPTNf20bfSZuC3q3acxncJg03MztNQAodFoip4nhtaHKshkMoPBJJOg051OeJ7Kp5u3ijOXemSqUVZ6lXaOJRaLFd15ae1/AHK5nND8rkXOzq5/nzivmYpA85QWCilUMtm44Y4JhoppZfLyRpl2jkUgKBwRWZ3xUV1d9fPmtSkpN0xZZpMnTxsTNB4h9DDt3vIVC/b+dqxzZ09stZGj+oYET5o7Z9GZs3/fup00bOioP/86yONx27VznzVzfmLipZSUG2QKZdjQUXPnLCKRSCKR6K/IQ0lJV0rLSiwt2cOGjpo+bR7Wj2XND8ucHF3IZHLChRiJWNyzZ99vFn/HYDCUFPnrnq03b12LWLpm3/5f3r0r3LF9X/duPd4XF+3btyst/Z6RkbF7+44zZ87v2KHzyVN/HTi456/jZ52cXLBtv106r7a2Jjh44tZt6xFC27ft9e3+BXZj/PCRvdeSLotEQidHl4kTwwYNHPb8ReaChdO/X7Vh6JCR2Drfr16ya+d+bFdJ1//dsPH7E1Fx9nYN3yzIyc1atHjmlk17Dh7+LS8v28bGbt6cxX36DMCWPn+Ruf/A7qys51QqrXev/l9//S2Lyarf859/HSwpee/q0vaTQaLizp+JPh1VXl5qa2s/eNCISRPDjI2NW/xrx9m7nFqWjbLfeEvkvkq7eHVfUXE2k2Hh1sZ35NCvWUw2QmjNz4PHBa7MfHHjeVYKjcro6RcybOBsbBOpVJp440jqw1iRqLZd2+5isUaGYieSCNauzHe5dQ5ueD7Lo86Tl0uXz5NJ5G+XfO/apt3uX7c8efKo0U2ePs1ISrqy7oet361c/+ZN/vIVC4yMjHbs+CN4zMTo01GXr8RjV27S0u716t3/6/Bvu/n0iDpx9Oy5f+r3EH06qri4aNPPuxcuiLhxMzHqxJFGDyoQVB85tm/JN99t+GlHNx+/ioryRYtn8qt4CxdEzJu7WCwWf7Nkdn5+3ojhgWQyOfHaJWyrkpLijMdpgYHjfLz95s5ZVL83mUy2es23d+/e+mrKjG+XfO/m1mHDxu8vXorr3KmLjY1tyv+uj9y+nfQo4+HLrOfYlzdvJnZw76QoOzBCoXD9hu/Gj5uye9dBWxu7jZtW83hchFBBwatlEeFisXjF8h+nhc1JTr6+fv1KbJPEa5c3bPze0oK9aOFyP79eea9y6vd2/M+DBw/tGTRw2PKIH/wHDDkV/dfOX35u9Gel+4oLhQTNjEefk/fg0F+LbazbTAxe3b/3lFcFj/YfWyASfYiDk+fW29u6z5+1v5vXyH+TDj3PSsFej0nYfvXGkY7uvUNGRxhRqLV1mjrFEAnlvAqRhnb+MS21PoYNHbVyxY8IoX59B06cNPLGzatdu/o0utUPazebmZl7eHS9/+BOamryt0tWEQiEDu6d/v03IT39/qiAYBKJtG/vn/WnGEXv3966nTRxQij2paOj8/erNhAIhE4dPW4lJz14eDd83jfKjygSiSKWrunU6cNgc5FRh83NLHZu/4NMJiOEhg4JCJ0anHAxZtGCiL59/BMTL82YHo4QSrx2icFgDB40gkqlenX9bwbcW7eTnjx99M+JeDbbCiE0ZPCI2tqas+f+CRg5ZkD/IfEJZ0UikZGR0aXL5xFCCQnnOnboXFtbe//Bnalhcxr94SxauHzQwGEIodmzF84LD338JL1/v0FRJ44QicRtW39nMpgIISaTtWnLD48fp3fs6PH73h1du/ps37YXa529e1eYm5eNECovLzvx99E1q38e0H8wtmdLS6tfdm9euCCivtmipwQ8CdVCI2cusRd29vQNCRkdgX3p7vbF9j2TsnJTPTv7I4R6dAsaPGA6Qsje1v1+Wlx2bmrnDn3eFr1MfRgzeMCMkUPCEUK+PqPy8tM1URtCiEQhCXhqGSqgEVqKD1NTM+w/VCrV3t6xtKxJnVuMjD60n40oRhQKpT4m2FbW2IctQqiykvNX5KEHD1OrqvgIIezP5sOxjKn1m9jY2GVmPm70iFQqtT47EEL37qWUlpUEjO5X/4pYLC4rLUEIjR49NmL5/MzMx126eP179cLQoaPq58Gtl5qaLJFIpoQG1b8ilUrpdAZCyH/AkOjTUenp951d2jzKeBgUOO5q4sX5Xy+9dz+lrq5uwIAhjZZKo9LqvzUsBRBCGY/TfHz86n8Ifn69EEJZ2c/FEjGPxx0/bkr9EwrE//0nLe2eRCL5edOanzetwV7B3hDlZaX6Hh/CGhnTXv2X8DiV70vK8ss5hakPYz9+ncv78K42MvrwqyGRSKYsax6/DCH09PkNhFD/3v8Nm0wgaOruBIVKFvC0MfY9gUDAPlk/p6lLp0TF94qbqD7zOJyKueFf0WgmM2d8bW/vePTovsK3DffbpZApMlnjB6XRTD7+klNZ0atXv7mzF338Ivb3383Hz8HBKfHaJTKF8uZNwfoft32+t8rKCktL9q4d+z9+kUQmI4Q6Yecvd26+eJnp7Oy6cEHErdtJSdevPHyY2uiZy+ff2v/mjkICQbWZqXn9IiaThSULg8FECNna2n++eQWnHCG06efd1lb/77lve3vHptegm2QyuVym/nG9q6orEEJDB87u2nngx68zmezPVyYSydivhsstplIZdBNt9ECRy+VypI0BzeVyuaJO6xq/86LCfY1PnI8/W1nJ2fvbcRsbW4SQtbWtovhQDZPJ4vG4zs6uny8iEAijAoJPnvpLLpd37erj6tq2wc253EobG7sGL0P27zf4WtJlMpk8cUIYhUIJGDkmJvZUUdHbppy5KMJmW/P5/91Iq6zkIIQYDCaWKVxuA6PaMv/XxGjw29RrdBZZIpQiZhNWbQ4alYkQEouF1lbN+InR6eZ1ddViiYhCNlJzQZ+RCCVMc5zvnGq834e5mQVCqLziw02miopysbh5jwzy+VwzM3MsOxBCPD5X+cSazdWtW4/MzMdZ2S/qX/l4cJSRI4JqagTxCeeCAhseTbtbtx5SqfR8/JkGN/cfMITDqeDzecOHjcbOhvLz85p45qKIh0fXjMdp9UMw3Lp1DSHk6endrp07kUisv9b7MR8fPwKBEBN7qsEi9RrdlCQWqv8SgBXb2czU9kF6vFD04QcllUokkkbeuo4OHRFCj55cUXs9n5NJpAwWzvGh8cM7O7va2NhGRR0xN7Ooqa05cmSvoilnFPH29o2JjT567A8PD6/bt5Pu3UuRyWQ8Hrf+UksLTZs6NzU1efmKBRMnhJqbW9y/f0cqk278aSe21MzMvG8f/0cZD/v3G9Tg5kOHBMQnnNt/4Nf3xUXu7Tvm5mYnp1w/fvQMdpWkU6cu1tY2vt17YreT7Wzte/Toza3kNOvM5ROhU2YmJV1ZuWpR4OhxpaXFf/510Mfb19urO4FAGDki6MLFWJFQ2KNH74qK8nv3ks3NLRFCjg5OY0Mmnz33z/drvu3bx7+iojw2Lnrzpl/d23dUuQwdYetMzX2p/vggEAhjAr7985+Vvx2Y1avHWJlM+vDRxe7eIz6+rvE5L48hiTeOno3bUlzyysHOvaDwKb9KU70zyCRkZqXxNg6GQml4pnSNtz7IZPK6H7eRyOTlKxccPLRnatic5vY16N9v0NSw2bFxp3/+ebVYIt77+3FnZ9ePP0hbyMHe8fc9Rz08up74++jefTu5vMohg0d+vMLo0WMDRo5R9BOkUCjbt+4dPSokKenKrl82pT+6HxQ4vv5SE4FA6N9vcGDgfz3HxwSOb0nTA7vZtG3L72KxeNv29aeiI4cOCfhp/Q7sJHHRwuUhwRPT0u/v+2PXs+dP2rVzr99qwfylX4cvyX+V+8vuzRcuxvTrO9CKbQjPtju60/jFGpm/yrOz/8zQXSQS5fzFXxJvHDU3t23r2sidRBKJNDtst7vbF3cfnE248huRQKSbqOdD7hNSsYzzvsa2jTa67UilUkUf+Q3fkrl/mSOsQ94DLTRfG9AbZ38tGLvQkWWhcz2Vj60rcOhqZ0TTucI0h1tUTaPUDZ+qjQFQz5079+LFi9WrV3++yAB/4tXV1V9+NbrBRfPmfjN6VIjWK1Jo8ZLZ+fm5n7/eu/eAVSvX41GRXvLoZfruTZ2Ro8K+p5nPb56M+enz1ylkY7FE2OAmi+YctrFuo64KL17dd+f+2c9fp1GZivqVKS9AXCfy+kJTfW0/QSAQFD0yZ4DxYWJicvDA3w0uYjF165nuH9ZsFjd0Na6+uwdoiu6DzR6szDNXHB/t3XosnR/5+esSiZhMbvic1JSlzjO7AX2+6ukb/PnrcjlSdGdSSQG1fKG4uq5NFy3NYF9bW6uoE4YBxgeRSLRrqO+DDsI6qoIWIpEJ3YdYFL6qtGpr3uAKxkY0YyM8E5luYqrGziDlrzgDxzfQ/URDsG7TDS6CB/aBIeg50gKJRTKpNrpR4auWV2fnauzYXntpSCKRTE0bzj6ID2AgRky1fnXvLd5VaJa4Tlr0vHTIl1pttHI4HEWLID6AgWBakIdMtnqTXoR3IRqUd/dt6CoXLR+0trbWxMSkwUUQH8BwuHrQA2bYFma8x7sQ9RPVSl4kFcz5uY0xTdt/s8bGxnDyAloFtj1l8ETLrJuvhQJtPI2qHQJO3bsn72dvbEui4DDp5qtXr2CsU9Ba2LejTVvrKijmFL8sFddpY0QMzRFU1r1JL6JRamasc6UY4zNhL4/HU9T6MMAbtwBQ6cSxC+2y06puxxWxrE0oJsYsKzqRrDfzZYtrJfyyGrlELBOJhn1lZeuK54iEVlZWFhYNd0CH+AAGy7070707MzejOvuRIOtWuaUTXSyUk41IZKqRvJnPbWqBXCaXiiUSkdTIiFjFqWvrSW/vxXR0x78D4a1bt7Zv397gIogPYODcvBlu3gyEbIoLhNU8cQ1fKhbK6mp0rocIxZhgwqTSTcksc4qlvZYepW1UaWkpm80mKhhNFuIDtBa2rsYI6f3I8lpWVlb2xRdfKFracHwY0YhyvTlPBFpiaYfTtTuAn5ycHEUDnSq888I0p5S+NpDRqIBaiIWyotwapu49rQ80Ki8vr23bBsboxDQcHzZOxi0eohQYFG6pyM1b3QOKAp0nFArd3d0VLW04PhjmZCd32s0zxZosDOiTq1FFfYO195Qn0BFXrlzp2FHhiJYK26JeA8yojOrEqCKvARbmNsZkI2iNtEbVXAm/XHztn3cz1uHQXRrgKz8/38rKSsmsr8pOZTt0Z9DoxIybnKJXta02PGQyGYFAaPl0E/rI2onGqxC19WTM29KOpD99roC65OTkDBw4UMkKjVwJc+5o4tzRBCEkFurcfXLtWL169fBhw/v37493IbiQU4yhxdF6JSUlDRmibFjvpl5Ix6u/Pe76Dejl2taxtX77rfO7Bh8kJyf/+OOPSlaA+3CNCAwMxLsEAHDw+PHjYcOG0WjKes1D07QRKSkpb98a+BhWAHwuPj7e09NT+ToQH42Ii4vLysrCuwoAtO3SpUsjR45Uvg6cvDRiypQpNjbamIwHAN2RnJw8duxYbKJVJaD10Qhvb287Ozu8qwBAq44dOzZ48OBGV4P4aERmZubDhw/xrgIA7Xn27JlYLPb29m50TYiPRgiFwoMHD+JdBQDac+XKlZkzZzZlTYiPRvj4+EybNg3vKgDQkpcvX6anp/v7+zdlZYJc3kq7kwIAPjd//vxp06YpGSLoY9D6aNzDhw9///13vKsAQONSU1Pt7OyamB3Q+miqMWPG7N2719HREe9CANAgf3//+Ph4JrOpA7tAfDQJl8sVCoXQAQQYsK1bt7Zp02bixIlN3wROXprEzMyMTCZLJIYzcRkAH3vw4EFFRUWzsgPioxmEQmFISAjeVQCgfmKxeNGiRdu2bWvuhnDy0gyZmZmVlZX9+vXDuxAA1Gnu3LmLFy/u0qVLczeE+ACgVduwYYOnp2dwcLAK28LJS7NFRETcv38f7yoAUIODBw9aW1urlh3Q+lDRP//8M2jQILgRA/Ta3r17TU1NQ0NDVd4DxIfqHjx44Ofnh3cVAKji7Nmz+fn5ERERLdkJnLyo7vLly7dv38a7CgCaLTo6Oisrq4XZAa2Pljp37tzYsWPxrgKAZti+fbuxsfHixYtbvitofbQIlh1r1qypqqrCuxYAGjd9+nQnJye1ZAfEh3qEh4evXr0a7yoAUObFixe+vr4RERGTJ09W1z7h5EWd/v333yFDhhCJEMpAt5w5cyY2NjYyMlK98yXCG12d2rZtGxYWVl1djXchAPxn/fr1ubm5UVFRap9rFVof6ldeXi4QCAgEgrOzM961gFYtPz9///79ffr0CQoK0sT+IT40QiAQhIaGLlmyZMCAAXjXAlqpffv2JSUlbdu2rW3btho6BJy8aASdTo+JiWGxWNgITniXA1qXJ0+eBAUFGRsbnzlzRnPZAdNEaZaPjw92LjN69OiYmBgKhYJ3RcDwbdu27cWLF3/88YeDg4OmjwUnL9rw/v17FotVUVHx/v37pg8kCUCz3LlzZ82aNfPmzZs0aZJ2jgitD23A5qkjkUhbtmzJyclpyUNKAHyusrIyKioqOzs7JibG1NRUa8eF1oe2vXnzxtnZOTo6ul27dt27d8e7HKD3fv/999jY2FWrVjVlWkn1gkun2obdze3Vq9eBAwfy8/OlUineFQF9debMmV69etHp9MTERO1nB7Q+cFZdXU2hUObOnbt06VIvLy+8ywF64/bt2zt37uzZs+fSpUuNjIzwKgPiA3+ZmZkpKSnz5s3Lzc11c3PDuxyg07Kysn755Rcqlbps2TInJyd8i4H40CHJycnr168/ePBgmzZt8K4F6Jz8/Pzjx4/n5OQsXbrU19cX73IQxIfO4XA4paWlHTt2PHny5LBhwywsLPCuCOAvNzf30KFDeXl54eHhQ4YMwbuc/8CNW91iYWGBRQadTp80adKFCxeIRCKZDL+mVio7O/vQoUNv3ryZM2fO1q1b8S7nU9D60GkSiUQgEHz77bfz58/XkfYq0I6XL18eOnSoqKhozpw5gwYNwruchkF86IHHjx/fvXs3PDw8Pz/f1taWRqPhXRHQoKdPnx45cqSsrGzOnDn+/v54l6MMxIc+ycnJmTFjxrp163TqBBioy/Xr1yMjI0kk0tSpU/ViMkOID/3z7NkzDw+PU6dOmZqajhgxAu9ygBqcOXMmMjKyffv2YWFhetQDCOJDX71//37v3r3Dhg3r378/l8s1MzPDuyLQbDU1NZGRkZGRkaNGjQoLC3N0dMS7ouaB+NBvEomETCYvWrSITCZv374d7tHoi4KCgpMnT164cCEsLCwsLExPr2dBfBiIW7du+fn51dXVxcTETJ482cTEBO+KQMOuX79+6tSp8vLyKVOm6PskQRAfBkUmk/3xxx+FhYVbtmx59+6dFgaMAU1UXV0dHR0dHR3dpUuXSZMmGcb0phAfBishIeHQoUN79+7VuzNqA5OZmXnq1Klbt25NnDhx4sSJVlZWeFekNhAfhuzt27cSicTV1XXfvn1eXl59+vTR3LH4HMnDq5VFr2qkUlRXJdHcgVqOYW6EkNyxnUmPkeY0BklzB4qJiUlPTy8sLJw4cWJAQIDmDoQXiI9WIT09/fjx4z/88AOLxaqsrLSxsVHv/sveCi8ced9jhBXLksIwo+j6e4qIqjlifoX4bnzp+CWOZlZqHoP25cuXMTExZ8+eDQ4OnjBhQocOHdS7f90B8dGKyGQysVgcEhIyfPjwb775Rl27fZtTezumfPQ8nB8eV03s76+HT7W1djJWz95iY8+dOyeVSkNCQsaNG6f2aZl0DcRHa4R1PLt//35ycvLUqVPZbHZL9nZu77uBk+zJFL38U6kTyO6eLw4Kt2/JTl68eHHr1q3Dhw8HBQWNHTvWw8NDfQXqNOgm0Bph7+/u3bvn5OScOXMmPDw8LS3Nx8dHhdl5y9+J6qqlepodCCEqncgpFfErJCzLZv8t1NbWxsXFxcbGksnkyZMn37t3r7VNbwzx0XqRSKSvvvoK+39JSckXX3wRHx9va2vbrJ1wSkSO7emaKVBLnN0ZFe9FzYqP1NTUuLi427dvBwcHb9iwoX379posUHdBfACEEAoICAgICODz+QihL7/8Migo6Msvv2xwzeHDh1+5cqX+S4lIVluj36M911RLpBJZU9YsLi6Oi4tLTk5msVhjxozZvHmz5qvTaa2rrQWUw2bV3L59e01NDdax+vr165+sU1ZWFhYWhlOBuLly5cqCBQtmzZpFIBB27tyJPW2Ed1H4g/gAn3J0dJw1axZCyNLS8sKFCxs2bEAIcblchJC/vz+RSHzx4sWiRYvwLlMbsrOzt2/f3rdv35s3b4aFhV24cGHu3LnW1tZ416Ur4OQFKMRkMnfs2FFXV4cQunbtWkJCAp/Px64OPnjwYMWKFdu2bcO7Ro0QiURxcXFxcXFSqXTMmDGJiYlUKhXvonQR3LgFTTV8+PCKior6L42MjIKDg0f3Dy/MresdqMcfyDdPF3f0Zbh5M7BYjIuLu3bt2pgxY4KDgzt27Ih3dToNWh+gqXg83sdfikSiCxcumBI8XO30/ukvPp9/+PDJuLg4BweH4ODgjRs34l2RfoD4AE0lFouxbpRYixUbWyQtLc11tH7Hh1wm37NnT88hrgcOHLC3b1H/sdYGTl5Ak0yZMoVEItHpdDabbWVlxWaz2Wy2hYVFdZGZqMpUv09eoos7+n04eQHNAq0P0CR///13g68/T+UXVtVpvRy10tces/iDG7cAABVBfAAAVATxAQBQEcQHAEBFEB8AB89fZAqFwpbsgcfjDhzsG3f+jPqKAs0G8QG07fKV+AULp9fV1eJdCGgpiA+gbS1sdwDdAf0+gFbduJm4+9ctCKHgsUMQQitX/DhieCB2OrP/wO6srOdUKq13r/5ff/0ti8nCptE7dnz/lX8TeDyui0ub6dPm9e3TwKTzqanJBw//VlT01tbWPihw/NiQSXh8c60OtD6AVvl4+06cEIoQ2vzz7j27D3/Row9CqKDg1bKIcLFYvGL5j9PC5iQnX1+/fiW2/o6dG09FR44eFbL6+422tvZrf4h48uTRJ/usqalZ99NKI4rRsqVrevfqX1FRhsd31hpB6wNolampmb29I0KoU6cupqYfpvWOOnGESCRu2/o7k8FECDGZrE1bfnj8ON3c3OLKvwlTw2ZPnzYPITSg/+DQqSHH/zywa+f+j/dZyeUIhcJ+/QYNHTISp2+rlYL4APjLeJzm4+OHZQdCyM+vF0IoK/s5jWaCEOrbdyD2OoFA8PPteTXx4ieb29s5eHh0jTpxhEqlBY4ea2RkpPXvoJWCkxeAP4Gg2szUvP5LJpOFECovLxMIqhFC5mYW9YtYLNOamhqBQPDx5gQCYcumPcOHjd5/YPfU6WMfP07XbvmtF8QHwMfHj3qz2dZ8/n+DiVRWchBCDAaTzbZGCH28iMOpIJPJn4/9xWAwlnzz3Z/Hz9LpjDVrl2JjtQJNg/gA2kaj0rDGRf0rHh5dMx6nYaMiIoRu3bqGEPL09O7UqQuBQEi9l4y9LhKJUu8le3h0JZFIZDIFIVRVxccWYTeD7e0cxoZMrhZUFxcX4fGdtTpw7QNom0cXLxKJ9Pu+HSOHBwlFwqDAcaFTZiYlXVm5alHg6HGlpcV//nXQx9vX26s7gUAYPmz08T8PSKVSe3vHCxdiOJyK71dtQAjR6XQHe8fo01GmpmYjhgdOmzHOf8DQNq7t4uJOM+gM7Oos0DTSunXr8K4B6LGyt0I+R+LUoRkzRbGYLCsrmxs3rt69e7uqij98+GgWy9Szi8+Dh3fjE85mZb8Y6D9secQPxsbGCCE/314CQfWly3FJSVfoJvSIZWuwC6sIoU6dPV++fPbqVc6AAUPevn2TnHL9dnKSpaXVdyvWOTg0Iz5eP69m2xtZ2MIF12aD0cZAizxP5RvSUMmgWeDaBwBARRAfAAAVQXwAAFQE8QEAUBHEBwBARRAfAAAVQXwAAFQE8QEAUBHEBwBARRAfAAAVQXwAAFQE8QEAUBHEB2gRIolApen3u4hKJxGJBLyr0Ev6/YsHuGNZUkoL6/CuokVK39SyLCl4V6GXID5Ai1jaGpHI+v0uMqKSYLAP1ej3Lx7gztiE2M7T5E5cKd6FqOjm6RKPnkwiCe869BMMFwTU4FESt/iNsMdIKyOq3nwgiWplKedL2nVlePRk4l2LvoL4AOrxLJWfeYdfw5dY2BoLa6V4l6MMjUkqe1NnyqZ49jV17wbZoTqID6A2chkS8CV8jhjvQhpBIBBYFhQ6i4TgfkvLQHwAAFSkN2eqAABdA/EBAFARxAcAQEUQHwAAFUF8AABUBPEBAFDR/wEuRozPP7qmFAAAAABJRU5ErkJggg==", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "except Exception:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 6, "id": "9f318020-ab7e-415b-a5e2-eddec6d9f3a6", "metadata": {}, "outputs": [ @@ -1266,12 +1357,12 @@ "I'm learning LangGraph. Could you do some research on it for me?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01R4ZFcb5hohpiVZwr88Bxhc', 'input': {'query': 'LangGraph framework for building language model applications'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01UukDzyzhEhxMgTbshetpT4', 'input': {'query': 'LangGraph programming framework'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_01R4ZFcb5hohpiVZwr88Bxhc)\n", - " Call ID: toolu_01R4ZFcb5hohpiVZwr88Bxhc\n", + " tavily_search_results_json (toolu_01UukDzyzhEhxMgTbshetpT4)\n", + " Call ID: toolu_01UukDzyzhEhxMgTbshetpT4\n", " Args:\n", - " query: LangGraph framework for building language model applications\n" + " query: LangGraph programming framework\n" ] } ], @@ -1297,17 +1388,17 @@ }, { "cell_type": "code", - "execution_count": 31, - "id": "9bb7af46-9b4f-4bb1-b8b9-e9ddf7dbc82c", + "execution_count": 7, + "id": "9f511371-98b6-4513-b450-9143778f12dc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "('tools',)" + "('human_review_node',)" ] }, - "execution_count": 31, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -1322,12 +1413,12 @@ "id": "89326046-2b11-4812-8b6d-8780306ec275", "metadata": {}, "source": [ - "**Notice** that unlike last time, the \"next\" node is set to **'tools'**. We've interrupted here! Let's check the tool invocation." + "**Notice** that unlike last time, the \"next\" node is set to `human_review_node`. We've interrupted here! Let's check the tool invocation." ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 8, "id": "3facda0a-e6ad-4b28-b627-753ad8c90c15", "metadata": {}, "outputs": [ @@ -1335,12 +1426,12 @@ "data": { "text/plain": [ "[{'name': 'tavily_search_results_json',\n", - " 'args': {'query': 'LangGraph framework for building language model applications'},\n", - " 'id': 'toolu_01R4ZFcb5hohpiVZwr88Bxhc',\n", + " 'args': {'query': 'LangGraph programming framework'},\n", + " 'id': 'toolu_01UukDzyzhEhxMgTbshetpT4',\n", " 'type': 'tool_call'}]" ] }, - "execution_count": 32, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -1357,13 +1448,13 @@ "source": [ "This query seems reasonable. Nothing to filter here. The simplest thing the human can do is just let the graph continue executing. Let's do that below.\n", "\n", - "Next, continue the graph! Passing in `None` will just let the graph continue where it left off, without adding anything new to the state." + "To resume execution, we pass a [Command](../../concepts/human_in_the_loop/#the-command-primitive) object. Here, we specify `resume` with a dict containing the information expected by `human_review_node`. This information can be customized based on our needs." ] }, { "cell_type": "code", - "execution_count": 33, - "id": "effb95d9-b7d5-40c5-9253-253d193b23b2", + "execution_count": 9, + "id": "831d4978-b5ea-4258-b350-8e7dd2bf2b6c", "metadata": {}, "outputs": [ { @@ -1372,46 +1463,237 @@ "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01R4ZFcb5hohpiVZwr88Bxhc', 'input': {'query': 'LangGraph framework for building language model applications'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01UukDzyzhEhxMgTbshetpT4', 'input': {'query': 'LangGraph programming framework'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_01R4ZFcb5hohpiVZwr88Bxhc)\n", - " Call ID: toolu_01R4ZFcb5hohpiVZwr88Bxhc\n", + " tavily_search_results_json (toolu_01UukDzyzhEhxMgTbshetpT4)\n", + " Call ID: toolu_01UukDzyzhEhxMgTbshetpT4\n", " Args:\n", - " query: LangGraph framework for building language model applications\n", + " query: LangGraph programming framework\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: tavily_search_results_json\n", "\n", - "[{\"url\": \"https://towardsdatascience.com/from-basics-to-advanced-exploring-langgraph-e8c1cf4db787\", \"content\": \"LangChain is one of the leading frameworks for building applications powered by Lardge Language Models. With the LangChain Expression Language (LCEL), defining and executing step-by-step action sequences — also known as chains — becomes much simpler. In more technical terms, LangChain allows us to create DAGs (directed acyclic graphs). As LLM applications, particularly LLM agents, have ...\"}, {\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"Overview. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures ...\"}]\n", + "[{\"url\": \"https://www.langchain.com/langgraph\", \"content\": \"No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. LangChain provides a standard interface to interact with models and other components, useful for straight-forward chains and retrieval flows.\"}, {\"url\": \"https://academy.langchain.com/courses/intro-to-langgraph\", \"content\": \"Separate from the LangChain package, LangGraph helps developers add better precision and control into agentic workflows. Lesson 1: Motivation Lesson 2: Simple Graph Lesson 3: LangGraph Studio Lesson 4: Chain Lesson 5: Router Lesson 6: Agent Lesson 7: Agent with Memory Lesson 8: Deployment Lesson 1: State Schema Lesson 2: State Reducers Lesson 3: Multiple Schemas Lesson 1: Streaming Lesson 2: Breakpoints Lesson 3: Editing State and Human Feedback Lesson 4: Dynamic Breakpoints Lesson 1: Parallelization Lesson 2: Sub-graphs Lesson 3: Map-reduce Lesson 4: Research Assistant About this course No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. Deploy LangGraph agents at scale with LangGraph Cloud (available for Python).\"}]\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Thank you for your patience. I've found some valuable information about LangGraph for you. Let me summarize the key points:\n", + "Thank you for your patience. I've researched LangGraph for you, and I'm happy to share what I've found. LangGraph is an interesting framework in the field of language AI. Here's a summary of the key points:\n", "\n", - "1. LangGraph is a library for building stateful, multi-actor applications with Large Language Models (LLMs).\n", + "1. Purpose:\n", + " LangGraph is an orchestration framework designed for complex agentic systems. It provides more low-level control and precision compared to LangChain agents.\n", "\n", - "2. It is particularly useful for creating agent and multi-agent workflows.\n", + "2. Relationship to LangChain:\n", + " - LangGraph is separate from the LangChain package but complements it.\n", + " - While LangChain provides a standard interface for interacting with models and components (useful for straightforward chains and retrieval flows), LangGraph offers more fine-grained control for complex workflows.\n", "\n", - "3. LangGraph is built on top of LangChain, which is one of the leading frameworks for building LLM-powered applications.\n", + "3. Key Features:\n", + " - Better precision and control in agentic workflows\n", + " - Allows for the creation of more complex and controllable AI systems\n", "\n", - "4. Key benefits of LangGraph compared to other LLM frameworks include:\n", - " a) Cycles: It allows you to define flows that involve cycles, which is essential for most agent architectures.\n", - " b) Controllability: Offers more control over the application flow.\n", - " c) Persistence: Provides ways to maintain state across interactions.\n", + "4. Learning Resources:\n", + " There's a course available on the LangChain Academy that covers LangGraph in depth. The course structure includes:\n", "\n", - "5. LangGraph works well with the LangChain Expression Language (LCEL), which simplifies the process of defining and executing step-by-step action sequences (chains).\n", + " - Motivation\n", + " - Simple Graph\n", + " - LangGraph Studio\n", + " - Chain\n", + " - Router\n", + " - Agent\n", + " - Agent with Memory\n", + " - Deployment\n", + " - State Schema\n", + " - State Reducers\n", + " - Multiple Schemas\n", + " - Streaming\n", + " - Breakpoints\n", + " - Editing State and Human Feedback\n", + " - Dynamic Breakpoints\n", + " - Parallelization\n", + " - Sub-graphs\n", + " - Map-reduce\n", + " - Research Assistant\n", "\n", - "6. In technical terms, LangGraph enables the creation of Directed Acyclic Graphs (DAGs) for LLM applications.\n", + "5. Deployment:\n", + " LangGraph agents can be deployed at scale using LangGraph Cloud (available for Python).\n", "\n", - "7. It's particularly useful for building more complex LLM agents and multi-agent systems.\n", + "LangGraph seems to be particularly useful when you need more control over your AI workflows, especially for complex, multi-step processes or when building sophisticated AI agents. If you're already familiar with LangChain, learning LangGraph could be a valuable next step to enhance your ability to create more advanced AI systems.\n", "\n", - "LangGraph seems to be an advanced tool that builds upon LangChain to provide more sophisticated capabilities for creating stateful and multi-actor LLM applications. It's especially valuable if you're looking to create complex agent systems or applications that require maintaining state across interactions.\n", - "\n", - "Is there any specific aspect of LangGraph you'd like to know more about? I'd be happy to dive deeper into any particular area of interest.\n" + "Is there any specific aspect of LangGraph you'd like to know more about? Or do you have any questions about how it compares to other frameworks you might be familiar with?\n" ] } ], "source": [ - "# `None` will append nothing new to the current state, letting it resume as if it had never been interrupted\n", - "events = graph.stream(None, config, stream_mode=\"values\")\n", + "human_command = Command(resume={\"action\": \"continue\"})\n", + "\n", + "events = graph.stream(human_command, config, stream_mode=\"values\")\n", + "for event in events:\n", + " if \"messages\" in event:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "d789d4cf-b498-454b-a088-3ffc03e511b0", + "metadata": {}, + "source": [ + "Review this call's [LangSmith trace](https://smith.langchain.com/public/83aaec86-9dd8-406b-ab03-e66d2464b8ef/r) to see the exact work that was done in the above call. Notice that the state is loaded in the first step so that your chatbot can continue where it left off.\n", + "\n", + "Let's demonstrate another example, in which we direct the chatbot to revise its tool calls based on our feedback." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0ebad510-01f8-4b00-8e80-1b435b0f37fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Could you search the weather in San Francisco?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': \"Certainly! I'd be happy to search for the current weather in San Francisco for you. To get the most up-to-date and accurate information, I'll use the Tavily search engine. Let me do that for you right away.\", 'type': 'text'}, {'id': 'toolu_019rPs39MK72QMVFcnxEyFN4', 'input': {'query': 'current weather in San Francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_019rPs39MK72QMVFcnxEyFN4)\n", + " Call ID: toolu_019rPs39MK72QMVFcnxEyFN4\n", + " Args:\n", + " query: current weather in San Francisco\n" + ] + } + ], + "source": [ + "user_input = \"Could you search the weather in San Francisco?\"\n", + "config = {\"configurable\": {\"thread_id\": \"2\"}}\n", + "\n", + "events = graph.stream(\n", + " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", + ")\n", + "for event in events:\n", + " if \"messages\" in event:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "ce1e485c-0ddc-4c36-a1c1-b78f6c706fa1", + "metadata": {}, + "source": [ + "This time, we will indicate that the requested action is `\"feedback\"`, and provide feedback in natural language. The chatbot will respond by generating a second, updated tool call:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b5bbaf35-d1f2-46b3-8451-856e908fd11f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': \"Certainly! I'd be happy to search for the current weather in San Francisco for you. To get the most up-to-date and accurate information, I'll use the Tavily search engine. Let me do that for you right away.\", 'type': 'text'}, {'id': 'toolu_019rPs39MK72QMVFcnxEyFN4', 'input': {'query': 'current weather in San Francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_019rPs39MK72QMVFcnxEyFN4)\n", + " Call ID: toolu_019rPs39MK72QMVFcnxEyFN4\n", + " Args:\n", + " query: current weather in San Francisco\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "User requested changes: use format for location.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': 'I apologize for the error in my previous attempt. Thank you for the clarification on the format. Let me search again using the correct format.', 'type': 'text'}, {'id': 'toolu_01AfrULohtNjWqGf8oY1FP9v', 'input': {'query': 'current weather in San Francisco, USA'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01AfrULohtNjWqGf8oY1FP9v)\n", + " Call ID: toolu_01AfrULohtNjWqGf8oY1FP9v\n", + " Args:\n", + " query: current weather in San Francisco, USA\n" + ] + } + ], + "source": [ + "human_command = Command(\n", + " resume={\n", + " \"action\": \"feedback\",\n", + " \"data\": \"User requested changes: use format for location.\",\n", + " }\n", + ")\n", + "\n", + "events = graph.stream(human_command, config, stream_mode=\"values\")\n", + "for event in events:\n", + " if \"messages\" in event:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "b56541f9-a5d2-49e0-9389-8d6e116c58b3", + "metadata": {}, + "source": [ + "We can now continue, as before:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "24ec8471-4438-4c89-aafa-48bb6c72eca8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': 'I apologize for the error in my previous attempt. Thank you for the clarification on the format. Let me search again using the correct format.', 'type': 'text'}, {'id': 'toolu_01AfrULohtNjWqGf8oY1FP9v', 'input': {'query': 'current weather in San Francisco, USA'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01AfrULohtNjWqGf8oY1FP9v)\n", + " Call ID: toolu_01AfrULohtNjWqGf8oY1FP9v\n", + " Args:\n", + " query: current weather in San Francisco, USA\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1736966001, 'localtime': '2025-01-15 10:33'}, 'current': {'last_updated_epoch': 1736965800, 'last_updated': '2025-01-15 10:30', 'temp_c': 10.6, 'temp_f': 51.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 6.3, 'wind_kph': 10.1, 'wind_degree': 51, 'wind_dir': 'NE', 'pressure_mb': 1026.0, 'pressure_in': 30.3, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 46, 'cloud': 0, 'feelslike_c': 9.3, 'feelslike_f': 48.8, 'windchill_c': 6.6, 'windchill_f': 43.9, 'heatindex_c': 8.6, 'heatindex_f': 47.5, 'dewpoint_c': 4.6, 'dewpoint_f': 40.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 1.3, 'gust_mph': 9.4, 'gust_kph': 15.1}}\"}, {\"url\": \"https://www.meteoprog.com/weather/Sanfrancisco/month/january/\", \"content\": \"San Francisco (United States) weather in January 2025 ☀️ Accurate weather forecast for San Francisco in January ⛅ Detailed forecast By month Current temperature \\\"near me\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG. ... 15 January +14 °+7° 16 January +14\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Thank you for your patience. I've successfully searched for the current weather in San Francisco, USA. Based on the search results, I can provide you with the following information about the weather in San Francisco:\n", + "\n", + "1. Current Temperature:\n", + " - 10.6°C (51.1°F)\n", + " - Feels like: 9.3°C (48.8°F)\n", + "\n", + "2. Weather Condition: Sunny\n", + "\n", + "3. Wind:\n", + " - Speed: 10.1 km/h (6.3 mph)\n", + " - Direction: Northeast (NE)\n", + "\n", + "4. Humidity: 46%\n", + "\n", + "5. Precipitation: 0 mm (0 inches)\n", + "\n", + "6. Visibility: 16 km (9 miles)\n", + "\n", + "7. Pressure: 1026.0 mb (30.3 inches)\n", + "\n", + "8. UV Index: 1.3 (relatively low)\n", + "\n", + "The data shows that it's a pleasant, sunny day in San Francisco with comfortable temperatures. The weather is clear with no cloud cover, and there's a light breeze from the northeast. The humidity is moderate, and there's no precipitation expected.\n", + "\n", + "Is there any specific aspect of the weather you'd like more information about?\n" + ] + } + ], + "source": [ + "human_command = Command(resume={\"action\": \"continue\"})\n", + "\n", + "events = graph.stream(human_command, config,stream_mode=\"values\")\n", "for event in events:\n", " if \"messages\" in event:\n", " event[\"messages\"][-1].pretty_print()" @@ -1422,7 +1704,7 @@ "id": "21e78a97-474f-4709-b51d-9d5e8323e14c", "metadata": {}, "source": [ - "Review this call's [LangSmith trace](https://smith.langchain.com/public/4d7f8757-9d3b-43b9-88b6-aeab0595bc4c/r) to see the exact work that was done in the above call. Notice that the state is loaded in the first step so that your chatbot can continue where it left off.\n", + "In the [LangSmith trace](https://smith.langchain.com/public/1e4bf84c-3e9e-41fd-a924-21395f5cd09e/r) for the above run, we can see full sequence of alternating assistant and tool messages representing the interaction with a human reviewer.\n", "\n", "**Congrats!** You've used an `interrupt` to add human-in-the-loop execution to your chatbot, allowing for human oversight and intervention when needed. This opens up the potential UIs you can create with your AI systems. Since we have already added a **checkpointer**, the graph can be paused **indefinitely** and resumed at any time as if nothing had happened.\n", "\n", @@ -1435,17 +1717,19 @@ "
\n",
     "\n",
     "```python\n",
-    "from typing import Annotated\n",
+    "from typing import Annotated, Literal\n",
     "\n",
     "from langchain_anthropic import ChatAnthropic\n",
     "from langchain_community.tools.tavily_search import TavilySearchResults\n",
-    "from langchain_core.messages import BaseMessage\n",
     "from typing_extensions import TypedDict\n",
     "\n",
     "from langgraph.checkpoint.memory import MemorySaver\n",
-    "from langgraph.graph import StateGraph\n",
+    "from langgraph.graph import StateGraph, START, END\n",
     "from langgraph.graph.message import add_messages\n",
-    "from langgraph.prebuilt import ToolNode, tools_condition\n",
+    "from langgraph.prebuilt import ToolNode\n",
+    "from langgraph.types import Command, interrupt\n",
+    "\n",
+    "memory = MemorySaver()\n",
     "\n",
     "\n",
     "class State(TypedDict):\n",
@@ -1465,26 +1749,62 @@
     "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
     "\n",
     "\n",
-    "graph_builder.add_node(\"chatbot\", chatbot)\n",
+    "# We add a node to handle the interaction with a human reviewer\n",
+    "def human_review_node(state: State) -> Command[Literal[\"chatbot\", \"tools\"]]:\n",
+    "    last_message = state[\"messages\"][-1]\n",
+    "    tool_call = last_message.tool_calls[-1]\n",
+    "\n",
+    "    # this is the value we'll be providing via Command(resume=)\n",
+    "    human_review = interrupt(\n",
+    "        {\n",
+    "            \"question\": \"Is this correct?\",\n",
+    "            # Surface tool calls for review\n",
+    "            \"tool_call\": tool_call,\n",
+    "        }\n",
+    "    )\n",
+    "\n",
+    "    review_action = human_review[\"action\"]\n",
+    "    review_data = human_review.get(\"data\")\n",
+    "\n",
+    "    # if approved, call the tool\n",
+    "    if review_action == \"continue\":\n",
+    "        return Command(goto=\"tools\")\n",
+    "\n",
+    "    elif review_action == \"feedback\":\n",
+    "        # NOTE: we're adding feedback message as a ToolMessage\n",
+    "        # to preserve the correct order in the message history\n",
+    "        # (AI messages with tool calls need to be followed by tool call messages)\n",
+    "        tool_message = {\n",
+    "            \"role\": \"tool\",\n",
+    "            # This is our natural language feedback\n",
+    "            \"content\": review_data,\n",
+    "            \"name\": tool_call[\"name\"],\n",
+    "            \"tool_call_id\": tool_call[\"id\"],\n",
+    "        }\n",
+    "        return Command(goto=\"chatbot\", update={\"messages\": [tool_message]})\n",
+    "\n",
+    "\n",
+    "# Where we used tools_condition before, here we update the condition\n",
+    "# to route to the human review node first, instead of tools.\n",
+    "def route_after_llm(state) -> Literal[END, \"human_review_node\"]:\n",
+    "    if len(state[\"messages\"][-1].tool_calls) == 0:\n",
+    "        return END\n",
+    "    else:\n",
+    "        return \"human_review_node\"\n",
+    "\n",
+    "\n",
+    "graph_builder.add_node(chatbot)\n",
     "\n",
     "tool_node = ToolNode(tools=[tool])\n",
     "graph_builder.add_node(\"tools\", tool_node)\n",
+    "graph_builder.add_node(human_review_node)\n",
     "\n",
     "graph_builder.add_conditional_edges(\n",
     "    \"chatbot\",\n",
-    "    tools_condition,\n",
+    "    route_after_llm,\n",
     ")\n",
     "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
-    "graph_builder.set_entry_point(\"chatbot\")\n",
-    "\n",
-    "memory = MemorySaver()\n",
-    "graph = graph_builder.compile(\n",
-    "    checkpointer=memory,\n",
-    "    # This is new!\n",
-    "    interrupt_before=[\"tools\"],\n",
-    "    # Note: can also interrupt __after__ actions, if desired.\n",
-    "    # interrupt_after=[\"tools\"]\n",
-    ")\n",
+    "graph_builder.add_edge(START, \"chatbot\")\n",
     "```\n",
     "
\n", "" @@ -1506,21 +1826,24 @@ }, { "cell_type": "code", - "execution_count": 17, - "id": "faa345c6-38a2-42e8-9035-9cf56f7bb5b1", + "execution_count": null, + "id": "cab7f9c2-fc23-4b68-b84a-a7760b5403c9", "metadata": {}, "outputs": [], "source": [ - "from typing import Annotated\n", + "from typing import Annotated, Literal\n", "\n", "from langchain_anthropic import ChatAnthropic\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", "from typing_extensions import TypedDict\n", "\n", "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import StateGraph, START\n", + "from langgraph.graph import StateGraph, START, END\n", "from langgraph.graph.message import add_messages\n", - "from langgraph.prebuilt import ToolNode, tools_condition\n", + "from langgraph.prebuilt import ToolNode\n", + "from langgraph.types import Command, interrupt\n", + "\n", + "memory = MemorySaver()\n", "\n", "\n", "class State(TypedDict):\n", @@ -1540,38 +1863,129 @@ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", "\n", "\n", - "graph_builder.add_node(\"chatbot\", chatbot)\n", + "# We add a node to handle the interaction with a human reviewer\n", + "def human_review_node(state: State) -> Command[Literal[\"chatbot\", \"tools\"]]:\n", + " last_message = state[\"messages\"][-1]\n", + " tool_call = last_message.tool_calls[-1]\n", + "\n", + " # this is the value we'll be providing via Command(resume=)\n", + " human_review = interrupt(\n", + " {\n", + " \"question\": \"Is this correct?\",\n", + " # Surface tool calls for review\n", + " \"tool_call\": tool_call,\n", + " }\n", + " )\n", + "\n", + " review_action = human_review[\"action\"]\n", + " review_data = human_review.get(\"data\")\n", + "\n", + " # if approved, call the tool\n", + " if review_action == \"continue\":\n", + " return Command(goto=\"tools\")\n", + "\n", + " elif review_action == \"feedback\":\n", + " # NOTE: we're adding feedback message as a ToolMessage\n", + " # to preserve the correct order in the message history\n", + " # (AI messages with tool calls need to be followed by tool call messages)\n", + " tool_message = {\n", + " \"role\": \"tool\",\n", + " # This is our natural language feedback\n", + " \"content\": review_data,\n", + " \"name\": tool_call[\"name\"],\n", + " \"tool_call_id\": tool_call[\"id\"],\n", + " }\n", + " return Command(goto=\"chatbot\", update={\"messages\": [tool_message]})\n", + "\n", + "\n", + "# Where we used tools_condition before, here we update the condition\n", + "# to route to the human review node first, instead of tools.\n", + "def route_after_llm(state) -> Literal[END, \"human_review_node\"]:\n", + " if len(state[\"messages\"][-1].tool_calls) == 0:\n", + " return END\n", + " else:\n", + " return \"human_review_node\"\n", + "\n", + "\n", + "graph_builder.add_node(chatbot)\n", "\n", "tool_node = ToolNode(tools=[tool])\n", "graph_builder.add_node(\"tools\", tool_node)\n", + "graph_builder.add_node(human_review_node)\n", "\n", "graph_builder.add_conditional_edges(\n", " \"chatbot\",\n", - " tools_condition,\n", + " route_after_llm,\n", ")\n", "graph_builder.add_edge(\"tools\", \"chatbot\")\n", - "graph_builder.add_edge(START, \"chatbot\")\n", - "memory = MemorySaver()\n", - "graph = graph_builder.compile(\n", - " checkpointer=memory,\n", - " # This is new!\n", - " interrupt_before=[\"tools\"],\n", - " # Note: can also interrupt **after** actions, if desired.\n", - " # interrupt_after=[\"tools\"]\n", - ")\n", - "\n", + "graph_builder.add_edge(START, \"chatbot\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "64cc981c-38fd-4ea5-8be5-f077be796411", + "metadata": {}, + "outputs": [], + "source": [ + "graph = graph_builder.compile(checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "00726b4b-7661-414e-a077-5cabba597163", + "metadata": {}, + "source": [ + "We first begin a thread:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4124bf18-302a-4593-b485-247bfd36f150", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "I'm learning LangGraph. Could you do some research on it for me?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01NKHEFSGNmLeVGvBYDyrALi', 'input': {'query': 'LangGraph: what is it, features, and usage in AI development'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01NKHEFSGNmLeVGvBYDyrALi)\n", + " Call ID: toolu_01NKHEFSGNmLeVGvBYDyrALi\n", + " Args:\n", + " query: LangGraph: what is it, features, and usage in AI development\n" + ] + } + ], + "source": [ "user_input = \"I'm learning LangGraph. Could you do some research on it for me?\"\n", "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "# The config is the **second positional argument** to stream() or invoke()!\n", - "events = graph.stream({\"messages\": [(\"user\", user_input)]}, config)\n", + "\n", + "events = graph.stream(\n", + " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", + ")\n", "for event in events:\n", " if \"messages\" in event:\n", " event[\"messages\"][-1].pretty_print()" ] }, + { + "cell_type": "markdown", + "id": "e3f5b9c1-e259-43f3-bfc6-f9a166a1da71", + "metadata": {}, + "source": [ + "Note that we can read the existing state from the `graph` object:" + ] + }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 5, "id": "a6b3bcae-dd04-49da-a4ef-e05634657faf", "metadata": {}, "outputs": [ @@ -1581,12 +1995,12 @@ "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_018YcbFR37CG8RRXnavH5fxZ', 'input': {'query': 'LangGraph: what is it, how is it used in AI development'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01NKHEFSGNmLeVGvBYDyrALi', 'input': {'query': 'LangGraph: what is it, features, and usage in AI development'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_018YcbFR37CG8RRXnavH5fxZ)\n", - " Call ID: toolu_018YcbFR37CG8RRXnavH5fxZ\n", + " tavily_search_results_json (toolu_01NKHEFSGNmLeVGvBYDyrALi)\n", + " Call ID: toolu_01NKHEFSGNmLeVGvBYDyrALi\n", " Args:\n", - " query: LangGraph: what is it, how is it used in AI development\n" + " query: LangGraph: what is it, features, and usage in AI development\n" ] } ], @@ -1610,8 +2024,8 @@ }, { "cell_type": "code", - "execution_count": 36, - "id": "6a44bedc-ea91-4c22-976c-98b3d5a5e4a7", + "execution_count": 6, + "id": "f0fd4cdb-afc4-4617-b0a9-f018232d641c", "metadata": {}, "outputs": [ { @@ -1624,7 +2038,7 @@ "\n", "\n", "Last 2 messages;\n", - "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='675f7618-367f-44b7-b80e-2834afb02ac5', tool_call_id='toolu_018YcbFR37CG8RRXnavH5fxZ'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='35fd5682-0c2a-4200-b192-71c59ac6d412')]\n" + "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='12be5dbe-75eb-45d8-8d64-5ab0c2f8555f', tool_call_id='toolu_01NKHEFSGNmLeVGvBYDyrALi'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='0f389c32-6fdd-4ad9-bd89-671d38983a70')]\n" ] } ], @@ -1659,7 +2073,7 @@ "id": "584de971-6b10-4931-986e-cc35f7adbb3d", "metadata": {}, "source": [ - "Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspect the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n", + "Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspect the [LangSmith trace](https://smith.langchain.com/public/e844af02-7f45-43e2-92bc-13cd3af9b62f/r) of the `update_state` call above to see what's going on.\n", "\n", "**Notice** that our new messages are _appended_ to the messages already in the state. Remember how we defined the `State` type?\n", "\n", @@ -1675,7 +2089,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 7, "id": "d16d95c3-b465-42ac-8015-26b669d45d1f", "metadata": {}, "outputs": [ @@ -1684,10 +2098,10 @@ "text/plain": [ "{'configurable': {'thread_id': '1',\n", " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef7d134-3958-6412-8002-3f4b4112062f'}}" + " 'checkpoint_id': '1efd377d-87e6-6ab6-8003-7d963c60dd60'}}" ] }, - "execution_count": 19, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -1707,18 +2121,18 @@ "id": "5a1f0056-6b6f-425f-ac1a-0d4b0e9b85cc", "metadata": {}, "source": [ - "Check out the [LangSmith trace](https://smith.langchain.com/public/2e4d92ca-c17c-49e0-92e5-3962390ded30/r) for this update call at the provided link. **Notice** from the trace that the graph continues into the `tools_condition` edge. We just told the graph to treat the update `as_node=\"chatbot\"`. If we follow the diagram below and start from the `chatbot` node, we naturally end up in the `tools_condition` edge and then `__end__` since our updated message lacks tool calls." + "Check out the [LangSmith trace](https://smith.langchain.com/public/14edbcaf-a230-45e5-bda2-bd24a9f96cd1/r) for this update call at the provided link. **Notice** from the trace that the graph continues into the `route_after_llm` edge. We just told the graph to treat the update `as_node=\"chatbot\"`. If we follow the diagram below and start from the `chatbot` node, we naturally end up in the `route_after_llm` edge and then `__end__` since our updated message lacks tool calls." ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 8, "id": "f4009ba6-dc0b-4216-ab0c-fbb104616f73", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -1747,7 +2161,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 9, "id": "d420e813-a8c7-415d-ab31-5298d42491e4", "metadata": {}, "outputs": [ @@ -1755,7 +2169,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='675f7618-367f-44b7-b80e-2834afb02ac5', tool_call_id='toolu_018YcbFR37CG8RRXnavH5fxZ'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='35fd5682-0c2a-4200-b192-71c59ac6d412'), AIMessage(content=\"I'm an AI expert!\", additional_kwargs={}, response_metadata={}, id='288e2f74-f1cb-4082-8c3c-af4695c83117')]\n", + "[ToolMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='12be5dbe-75eb-45d8-8d64-5ab0c2f8555f', tool_call_id='toolu_01NKHEFSGNmLeVGvBYDyrALi'), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', additional_kwargs={}, response_metadata={}, id='0f389c32-6fdd-4ad9-bd89-671d38983a70'), AIMessage(content=\"I'm an AI expert!\", additional_kwargs={}, response_metadata={}, id='8ea38275-255a-4d64-be3c-c4fcd6c6e55a')]\n", "()\n" ] } @@ -1782,8 +2196,8 @@ }, { "cell_type": "code", - "execution_count": 40, - "id": "9fc99c7e-b61d-4aec-9c62-042798185ec3", + "execution_count": 10, + "id": "b5aa8029-927d-4597-88b7-c9fadd4f6f60", "metadata": {}, "outputs": [ { @@ -1795,12 +2209,12 @@ "I'm learning LangGraph. Could you do some research on it for me?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and accurate information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01TfAeisrpx4ddgJpoAxqrVh', 'input': {'query': 'LangGraph framework for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search function to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_0135SrrAV1dhbeDtvKzUovUH', 'input': {'query': 'LangGraph python library for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_01TfAeisrpx4ddgJpoAxqrVh)\n", - " Call ID: toolu_01TfAeisrpx4ddgJpoAxqrVh\n", + " tavily_search_results_json (toolu_0135SrrAV1dhbeDtvKzUovUH)\n", + " Call ID: toolu_0135SrrAV1dhbeDtvKzUovUH\n", " Args:\n", - " query: LangGraph framework for language models\n" + " query: LangGraph python library for language models\n" ] } ], @@ -1825,7 +2239,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 11, "id": "7215533a-b7e2-4b2d-bc1d-5122b1d06b8b", "metadata": {}, "outputs": [ @@ -1834,11 +2248,11 @@ "output_type": "stream", "text": [ "Original\n", - "Message ID run-342f3f54-356b-4cc1-b747-573f6aa31054-0\n", - "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph framework for language models'}, 'id': 'toolu_01TfAeisrpx4ddgJpoAxqrVh', 'type': 'tool_call'}\n", + "Message ID run-c6d77fff-fb85-49df-ad84-07ed4e8a352c-0\n", + "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph python library for language models'}, 'id': 'toolu_0135SrrAV1dhbeDtvKzUovUH', 'type': 'tool_call'}\n", "Updated\n", - "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph human-in-the-loop workflow'}, 'id': 'toolu_01TfAeisrpx4ddgJpoAxqrVh', 'type': 'tool_call'}\n", - "Message ID run-342f3f54-356b-4cc1-b747-573f6aa31054-0\n", + "{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph human-in-the-loop workflow'}, 'id': 'toolu_0135SrrAV1dhbeDtvKzUovUH', 'type': 'tool_call'}\n", + "Message ID run-c6d77fff-fb85-49df-ad84-07ed4e8a352c-0\n", "\n", "\n", "Tool calls\n" @@ -1849,11 +2263,11 @@ "text/plain": [ "[{'name': 'tavily_search_results_json',\n", " 'args': {'query': 'LangGraph human-in-the-loop workflow'},\n", - " 'id': 'toolu_01TfAeisrpx4ddgJpoAxqrVh',\n", + " 'id': 'toolu_0135SrrAV1dhbeDtvKzUovUH',\n", " 'type': 'tool_call'}]" ] }, - "execution_count": 41, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -1891,15 +2305,15 @@ "source": [ "**Notice** that we've modified the AI's tool invocation to search for \"LangGraph human-in-the-loop workflow\" instead of the simple \"LangGraph\".\n", "\n", - "Check out the [LangSmith trace](https://smith.langchain.com/public/cd7c09a6-758d-41d4-8de1-64ab838b2338/r) to see the state update call - you can see our new message has successfully updated the previous AI message.\n", + "Check out the [LangSmith trace](https://smith.langchain.com/public/0a13df28-1509-4974-9f3a-09af34671ec9/r) to see the state update call - you can see our new message has successfully updated the previous AI message.\n", "\n", - "Resume the graph by streaming with an input of `None` and the existing config." + "Resume the graph as before, by passing the appropriate `Command`:" ] }, { "cell_type": "code", - "execution_count": 42, - "id": "03a09bfc-3d90-4e54-878f-22e3cb28a418", + "execution_count": 12, + "id": "7e9ebc73-22e8-4af2-9a36-6678873c671f", "metadata": {}, "outputs": [ { @@ -1908,49 +2322,54 @@ "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and accurate information, I'll use the Tavily search engine to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_01TfAeisrpx4ddgJpoAxqrVh', 'input': {'query': 'LangGraph framework for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to research LangGraph for you. To get the most up-to-date and comprehensive information, I'll use the Tavily search function to look this up. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_0135SrrAV1dhbeDtvKzUovUH', 'input': {'query': 'LangGraph python library for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_01TfAeisrpx4ddgJpoAxqrVh)\n", - " Call ID: toolu_01TfAeisrpx4ddgJpoAxqrVh\n", + " tavily_search_results_json (toolu_0135SrrAV1dhbeDtvKzUovUH)\n", + " Call ID: toolu_0135SrrAV1dhbeDtvKzUovUH\n", " Args:\n", " query: LangGraph human-in-the-loop workflow\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: tavily_search_results_json\n", "\n", - "[{\"url\": \"https://www.youtube.com/watch?v=9BPCV5TYPmg\", \"content\": \"In this video, I'll show you how to handle persistence with LangGraph, enabling a unique Human-in-the-Loop workflow. This approach allows a human to grant an...\"}, {\"url\": \"https://medium.com/@kbdhunga/implementing-human-in-the-loop-with-langgraph-ccfde023385c\", \"content\": \"Implementing a Human-in-the-Loop (HIL) framework in LangGraph with the Streamlit app provides a robust mechanism for user engagement and decision-making. By incorporating breakpoints and ...\"}]\n", + "[{\"url\": \"https://www.youtube.com/watch?v=9BPCV5TYPmg\", \"content\": \"In this video, I'll show you how to handle persistence with LangGraph, enabling a unique Human-in-the-Loop workflow. This approach allows a human to grant an\"}, {\"url\": \"https://blog.langchain.dev/human-in-the-loop-with-opengpts-and-langgraph/\", \"content\": \"TLDR; Today we're launching two \\\"human in the loop\\\" features in OpenGPTs, Interrupt and Authorize, both powered by LangGraph. We've recently launched LangGraph, a library to help developers build multi-actor, multi-step, stateful LLM applications. That's a lot words packed into a short sentence, let's take it one at a time. Multi-actor\"}]\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Thank you for your patience. I've found some information about LangGraph, particularly focusing on its human-in-the-loop workflow capabilities. Let me summarize what I've learned for you:\n", + "Thank you for your patience. I've found some useful information about LangGraph, particularly focusing on its human-in-the-loop workflow capabilities. Let me summarize what I've learned:\n", "\n", - "1. LangGraph Overview:\n", - " LangGraph is a framework for building stateful, multi-actor applications with Large Language Models (LLMs). It's particularly useful for creating complex, interactive AI systems.\n", + "1. What is LangGraph:\n", + " LangGraph is a library designed to help developers build complex, stateful LLM (Large Language Model) applications. It's particularly useful for creating multi-actor and multi-step processes.\n", "\n", - "2. Human-in-the-Loop (HIL) Workflow:\n", - " One of the key features of LangGraph is its support for human-in-the-loop workflows. This means that it allows for human intervention and decision-making within AI-driven processes.\n", + "2. Human-in-the-Loop Workflow:\n", + " One of the key features of LangGraph is its support for human-in-the-loop workflows. This means it allows for human intervention and interaction within AI-driven processes.\n", "\n", - "3. Persistence Handling:\n", - " LangGraph offers capabilities for handling persistence, which is crucial for maintaining state across interactions in a workflow.\n", + "3. OpenGPTs Integration:\n", + " LangGraph has been integrated with OpenGPTs, introducing two human-in-the-loop features:\n", + " a) Interrupt: This likely allows a human to pause or intervene in an ongoing AI process.\n", + " b) Authorize: This feature probably enables human approval or authorization at certain steps of the AI workflow.\n", "\n", - "4. Implementation with Streamlit:\n", - " There are examples of implementing LangGraph's human-in-the-loop functionality using Streamlit, a popular Python library for creating web apps. This combination allows for the creation of interactive user interfaces for AI applications.\n", + "4. Persistence Handling:\n", + " LangGraph includes functionality for handling persistence. This is important for maintaining state across different steps of a workflow, especially when human interaction is involved.\n", "\n", - "5. Breakpoints and User Engagement:\n", - " LangGraph allows the incorporation of breakpoints in the workflow. These breakpoints are points where the system can pause and wait for human input or decision-making, enhancing user engagement and control over the AI process.\n", + "5. Use Cases:\n", + " The human-in-the-loop capabilities make LangGraph suitable for applications where you need a blend of AI efficiency and human oversight or decision-making. This could be particularly useful in fields like content moderation, complex decision processes, or scenarios where ethical considerations are important.\n", "\n", - "6. Decision-Making Mechanism:\n", - " The human-in-the-loop framework in LangGraph provides a robust mechanism for integrating user decision-making into AI workflows. This is particularly useful in scenarios where human judgment or expertise is needed to guide or validate AI actions.\n", + "6. Developer Focus:\n", + " LangGraph seems to be targeted at developers who are looking to create more sophisticated LLM applications that go beyond simple query-response models.\n", "\n", - "7. Flexibility and Customization:\n", - " From the information available, it seems that LangGraph offers flexibility in how human-in-the-loop processes are implemented, allowing developers to customize the interaction points and the nature of human involvement based on their specific use case.\n", + "To learn more about LangGraph, you might want to:\n", + "1. Check out the official documentation or GitHub repository for LangGraph.\n", + "2. Watch tutorial videos, like the one mentioned in the search results, which demonstrates how to handle persistence with LangGraph.\n", + "3. Explore the OpenGPTs platform to see practical implementations of LangGraph's human-in-the-loop features.\n", + "4. Try building a simple application using LangGraph to get hands-on experience with its capabilities.\n", "\n", - "LangGraph appears to be a powerful tool for developers looking to create more interactive and controllable AI applications, especially those that benefit from human oversight or input at crucial stages of the process.\n", - "\n", - "Would you like me to research any specific aspect of LangGraph in more detail, or do you have any questions about what I've found so far?\n" + "Would you like me to search for more specific information about any aspect of LangGraph, such as its installation process, core components, or example use cases?\n" ] } ], "source": [ - "events = graph.stream(None, config, stream_mode=\"values\")\n", + "human_command = Command(resume={\"action\": \"continue\"})\n", + "\n", + "events = graph.stream(human_command, config, stream_mode=\"values\")\n", "for event in events:\n", " if \"messages\" in event:\n", " event[\"messages\"][-1].pretty_print()" @@ -1961,15 +2380,15 @@ "id": "090b680b-f53f-4af2-a432-45f8c5a10779", "metadata": {}, "source": [ - "Check out the [trace](https://smith.langchain.com/public/2d633326-14ad-4248-a391-2757d01851c4/r/6464f2f2-edb4-4ef3-8f48-ee4e249f2ad0) to see the tool call and later LLM response. **Notice** that now the graph queries the search engine using our updated query term - we were able to manually override the LLM's search here!\n", + "Check out the [trace](https://smith.langchain.com/public/e5b0b999-b1cd-43cf-bf75-df0b35bbe366/r/1f17ded9-6958-4682-9a0d-9d7eedbf7136) to see the tool call and later LLM response. **Notice** that now the graph queries the search engine using our updated query term - we were able to manually override the LLM's search here!\n", "\n", "All of this is reflected in the graph's checkpointed memory, meaning if we continue the conversation, it will recall all the _modified_ state." ] }, { "cell_type": "code", - "execution_count": 43, - "id": "11d5b934-6d8b-4f52-a3bc-b3daa7207e00", + "execution_count": 13, + "id": "556c71d3-48ac-43d7-833c-c3f884b330a7", "metadata": {}, "outputs": [ { @@ -1981,19 +2400,27 @@ "Remember what I'm learning about?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "I apologize for my oversight. You're absolutely right to remind me. You mentioned that you're learning LangGraph. Thank you for bringing that back into focus. \n", + "Of course! You mentioned that you're learning LangGraph. I apologize for not explicitly referencing that in my response. You're right to bring that back into focus. \n", "\n", - "Since you're in the process of learning LangGraph, it would be helpful to know more about your current level of understanding and what specific aspects of LangGraph you're most interested in or finding challenging. This way, I can provide more targeted information or explanations that align with your learning journey.\n", + "Since you're in the process of learning LangGraph, it's important to tailor the information to your learning journey. Based on what we've found, here are some key points that might be particularly relevant for someone learning LangGraph:\n", "\n", - "Are there any particular areas of LangGraph you'd like to explore further? For example:\n", + "1. Core Concept: LangGraph is a library for building stateful, multi-step LLM applications. As you're learning it, focus on understanding how it manages state across different steps of a process.\n", "\n", - "1. Basic concepts and architecture of LangGraph\n", - "2. Setting up and getting started with LangGraph\n", - "3. Implementing specific features like the human-in-the-loop workflow\n", - "4. Best practices for using LangGraph in projects\n", - "5. Comparisons with other similar frameworks\n", + "2. Human-in-the-Loop: One of LangGraph's key features is its support for human-in-the-loop workflows. This could be an interesting area to explore in your learning, as it allows you to create AI systems that can interact with humans during execution.\n", "\n", - "Or if you have any specific questions about what you've learned so far, I'd be happy to help clarify or expand on those topics. Please let me know what would be most useful for your learning process.\n" + "3. Integration with OpenGPTs: Learning how LangGraph integrates with other tools like OpenGPTs could be valuable. The \"Interrupt\" and \"Authorize\" features might be good practical examples to study.\n", + "\n", + "4. Persistence Handling: As a learner, understanding how LangGraph handles persistence could be crucial. This relates to how the library maintains state across different steps of your AI workflows.\n", + "\n", + "5. Practical Application: The video mentioned in the search results about handling persistence with LangGraph could be a great resource for your learning. It seems to offer a practical demonstration, which is often very helpful when learning a new technology.\n", + "\n", + "To support your learning process, you might want to:\n", + "- Start with simple LangGraph projects and gradually increase complexity\n", + "- Focus on understanding the state management aspects of LangGraph\n", + "- Experiment with the human-in-the-loop features to see how they work in practice\n", + "- Look for tutorials or courses specifically designed for learning LangGraph\n", + "\n", + "Is there a particular aspect of LangGraph that you're finding challenging or especially interesting in your learning journey? Or would you like me to find more beginner-friendly resources for learning LangGraph?\n" ] } ], @@ -2018,9 +2445,7 @@ "id": "a5166e1b-96a6-4ac0-88a1-bf32a422134a", "metadata": {}, "source": [ - "**Congratulations!** You've used `interrupt_before` and `update_state` to manually modify the state as a part of a human-in-the-loop workflow. Interruptions and state modifications let you control how the agent behaves. Combined with persistent checkpointing, it means you can `pause` an action and `resume` at any point. Your user doesn't have to be available when the graph interrupts!\n", - "\n", - "The graph code for this section is identical to previous ones. The key snippets to remember are to add `.compile(..., interrupt_before=[...])` (or `interrupt_after`) if you want to explicitly pause the graph whenever it reaches a node. Then you can use `update_state` to modify the checkpoint and control how the graph should proceed." + "**Congratulations!** You've used `interrupt` and `update_state` to manually modify the state as a part of a human-in-the-loop workflow. Interruptions and state modifications let you control how the agent behaves. Combined with persistent checkpointing, it means you can `pause` an action and `resume` at any point. Your user doesn't have to be available when the graph interrupts!" ] }, { @@ -2041,7 +2466,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 14, "id": "3cf7e042-1718-4625-ae30-a9917f595449", "metadata": {}, "outputs": [], @@ -2087,7 +2512,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 15, "id": "e5192e54-6a28-42fe-a8a7-62d45d61f994", "metadata": {}, "outputs": [], @@ -2114,7 +2539,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 16, "id": "fa59b266-14e5-4c75-8b3d-54fac28e8290", "metadata": {}, "outputs": [], @@ -2148,7 +2573,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3f4464d2-288b-4689-aaf0-329a55dcb85c", + "id": "3bdd9122-1dd4-4049-9626-d009e0d2ee37", "metadata": {}, "outputs": [], "source": [ @@ -3164,7 +3589,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.10.4" } }, "nbformat": 4,