{ "cells": [ { "cell_type": "markdown", "id": "457cdc67-1893-4653-8b0c-b185a5947e74", "metadata": {}, "source": [ "# How to migrate from legacy LangChain agents to LangGraph\n", "\n", "Here we focus on how to move from legacy LangChain agents to LangGraph agents.\n", "LangChain agents (the AgentExecutor in particular) have multiple configuration parameters.\n", "In this notebook we will show how those parameters map to the LangGraph `chat_agent_executor`." ] }, { "cell_type": "markdown", "id": "8e50635c-1671-46e6-be65-ce95f8167c2f", "metadata": {}, "source": [ "## Basic Usage\n", "\n", "First, let's define a model and tool." ] }, { "cell_type": "code", "execution_count": 20, "id": "1e425fea-2796-4b99-bee6-9a6ffe73f756", "metadata": {}, "outputs": [], "source": [ "from langchain_core.tools import tool\n", "from langchain_openai import ChatOpenAI\n", "\n", "model = ChatOpenAI()\n", "\n", "\n", "@tool\n", "def magic_function(input: int) -> int:\n", " \"\"\"Applies a magic function to an input.\"\"\"\n", " return input + 2\n", "\n", "\n", "tools = [magic_function]\n", "\n", "\n", "query = \"what is the value of magic_function(3)?\"" ] }, { "cell_type": "markdown", "id": "af002033-fe51-4d14-b47c-3e9b483c8395", "metadata": {}, "source": [ "For AgentExecutor, we define a prompt with a placeholder for the agent's scratchpad. The agent can be invoked as follows:" ] }, { "cell_type": "code", "execution_count": 21, "id": "03ea357c-9c36-4464-b2cc-27bd150e1554", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'The value of `magic_function(3)` is 5.'}" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langchain.agents import AgentExecutor, create_tool_calling_agent\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "\n", "prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", \"You are a helpful assistant\"),\n", " (\"human\", \"{input}\"),\n", " MessagesPlaceholder(\"agent_scratchpad\"),\n", " ]\n", ")\n", "\n", "\n", "agent = create_tool_calling_agent(model, tools, prompt)\n", "agent_executor = AgentExecutor(agent=agent, tools=tools)\n", "\n", "agent_executor.invoke({\"input\": query})" ] }, { "cell_type": "markdown", "id": "94205f3b-fd2b-4fd7-af69-0a3fc313dc88", "metadata": {}, "source": [ "LangGraph's `chat_agent_executor` manages a state that is defined by a list of messages. It will continue to process the list until there are no tool calls in the agent's output. To kick it off, we input a list of messages. The output will contain the entire state of the graph-- in this case, the conversation history.\n", "\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "53a3737a-d167-4255-89bf-20ac37f89a3e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'The value of the magic function with input 3 is 5.'}" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langgraph.prebuilt import chat_agent_executor\n", "\n", "app = chat_agent_executor.create_tool_calling_executor(model, tools)\n", "\n", "\n", "messages = app.invoke({\"messages\": [(\"human\", query)]})\n", "{\n", " \"input\": query,\n", " \"output\": messages[\"messages\"][-1].content,\n", "}" ] }, { "cell_type": "code", "execution_count": 23, "id": "74ecebe3-512e-409c-a661-bdd5b0a2b782", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'Pardon?',\n", " 'output': 'The value of the magic function with input 3 is 5.'}" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "message_history = messages[\"messages\"]\n", "\n", "new_query = \"Pardon?\"\n", "\n", "messages = app.invoke({\"messages\": message_history + [(\"human\", new_query)]})\n", "{\n", " \"input\": new_query,\n", " \"output\": messages[\"messages\"][-1].content,\n", "}" ] }, { "cell_type": "markdown", "id": "f4466a4d-e55e-4ece-bee8-2269a0b5677b", "metadata": {}, "source": [ "## Prompt Templates\n", "\n", "With legacy LangChain agents you have to pass in a prompt template. You can use this to control the agent.\n", "\n", "With LangGraph `chat_agent_executor`, by default there is no prompt. You can achieve similar control over the agent in a few ways:\n", "\n", "1. Pass in a system message as input\n", "2. Initialize the agent with a system message\n", "3. Initialize the agent with a function to transform messages before passing to the model.\n", "\n", "Let's take a look at all of these below. We will pass in custom instructions to get the agent to respond in Spanish.\n", "\n", "First up, using AgentExecutor:" ] }, { "cell_type": "code", "execution_count": 24, "id": "a9a11ccd-75e2-4c11-844d-a34870b0ff91", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'El valor de `magic_function(3)` es 5.'}" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", \"You are a helpful assistant. Respond only in Spanish.\"),\n", " (\"human\", \"{input}\"),\n", " MessagesPlaceholder(\"agent_scratchpad\"),\n", " ]\n", ")\n", "\n", "\n", "agent = create_tool_calling_agent(model, tools, prompt)\n", "agent_executor = AgentExecutor(agent=agent, tools=tools)\n", "\n", "agent_executor.invoke({\"input\": query})" ] }, { "cell_type": "markdown", "id": "bd5f5500-5ae4-4000-a9fd-8c5a2cc6404d", "metadata": {}, "source": [ "Now, let's pass a custom system message to `chat_agent_executor`. This can either be a string or a LangChain SystemMessage." ] }, { "cell_type": "code", "execution_count": 26, "id": "a9486805-676a-4d19-a5c4-08b41b172989", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'El valor de magic_function(3) es 5.'}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langchain_core.messages import SystemMessage\n", "\n", "system_message = \"Respond only in Spanish\"\n", "# This could also be a SystemMessage object\n", "# system_message = SystemMessage(content=\"Respond only in Spanish\")\n", "\n", "app = chat_agent_executor.create_tool_calling_executor(\n", " model, tools, messages_modifier=system_message\n", ")\n", "\n", "\n", "messages = app.invoke({\"messages\": [(\"human\", query)]})\n", "{\n", " \"input\": query,\n", " \"output\": messages[\"messages\"][-1].content,\n", "}" ] }, { "cell_type": "markdown", "id": "fc6059fd-0df7-4b6f-a84c-b5874e983638", "metadata": {}, "source": [ "We can also pass in an arbitrary function. This function should take in a list of messages and output a list of messages.\n", "We can do all types of arbitrary formatting of messages here. In this cases, let's just add a SystemMessage to the start of the list of messages." ] }, { "cell_type": "code", "execution_count": 27, "id": "d369ab45-0c82-45f4-9d3e-8efb8dd47e2c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'El valor de magic_function(3) es 5.'}" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def _modify_messages(messages):\n", " return [SystemMessage(content=\"Respond only in spanish\")] + messages\n", "\n", "\n", "app = chat_agent_executor.create_tool_calling_executor(\n", " model, tools, messages_modifier=_modify_messages\n", ")\n", "\n", "\n", "messages = app.invoke({\"messages\": [(\"human\", query)]})\n", "{\n", " \"input\": query,\n", " \"output\": messages[\"messages\"][-1].content,\n", "}" ] }, { "cell_type": "markdown", "id": "6898ccbc-42b1-4373-954a-2c7b3849fbb0", "metadata": {}, "source": [ "## `return_intermediate_steps`\n", "\n", "Setting this parameter on AgentExecutor allows users to access intermediate_steps, which pairs agent actions (e.g., tool invocations) with their outcomes.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "4eff44bc-a620-4c8a-97b1-268692a842bb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(ToolAgentAction(tool='magic_function', tool_input={'input': 3}, log=\"\\nInvoking: `magic_function` with `{'input': 3}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL', 'function': {'arguments': '{\"input\":3}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-0602a2dd-c4d9-4050-b851-3e2b838c6773', tool_calls=[{'name': 'magic_function', 'args': {'input': 3}, 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL'}], tool_call_chunks=[{'name': 'magic_function', 'args': '{\"input\":3}', 'id': 'call_qckwqZI7p2LGYhMnQI5r6qsL', 'index': 0}])], tool_call_id='call_qckwqZI7p2LGYhMnQI5r6qsL'), 5)]\n" ] } ], "source": [ "agent_executor = AgentExecutor(agent=agent, tools=tools, return_intermediate_steps=True)\n", "result = agent_executor.invoke({\"input\": query})\n", "print(result[\"intermediate_steps\"])" ] }, { "cell_type": "markdown", "id": "594f7567-302f-4fa8-85bb-025ac8322162", "metadata": {}, "source": [ "By default the `chat_agent_executor` in LangGraph appends all messages to the central state. Therefore, it is easy to see any intermediate steps by just looking at the full state." ] }, { "cell_type": "code", "execution_count": 6, "id": "4f4364ea-dffe-4d25-bdce-ef7d0020b880", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'messages': [HumanMessage(content='what is the value of magic_function(3)?', id='408451ee-d65b-498b-abf1-788aaadfbeff'),\n", " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_eF7WussX7KgpGdoJFj6cWTxR', 'function': {'arguments': '{\"input\":3}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-a07e5d11-9319-4e27-85fb-253b75c5d7c3-0', tool_calls=[{'name': 'magic_function', 'args': {'input': 3}, 'id': 'call_eF7WussX7KgpGdoJFj6cWTxR'}]),\n", " ToolMessage(content='5', name='magic_function', id='35045a27-a301-474b-b321-5f93da671fb1', tool_call_id='call_eF7WussX7KgpGdoJFj6cWTxR'),\n", " AIMessage(content='The value of magic_function(3) is 5.', response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 88, 'total_tokens': 101}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'stop', 'logprobs': None}, id='run-18a36a26-2477-4fc6-be51-7a675a6e10e8-0')]}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langgraph.prebuilt import chat_agent_executor\n", "\n", "app = chat_agent_executor.create_tool_calling_executor(model, tools)\n", "\n", "\n", "messages = app.invoke({\"messages\": [(\"human\", query)]})\n", "\n", "messages" ] }, { "cell_type": "markdown", "id": "45b528e5-57e1-450e-8d91-513eab53b543", "metadata": {}, "source": [ "## `max_iterations`\n", "\n", "`AgentExecutor` implements a `max_iterations` parameter, whereas this is controlled via `recursion_limit` in LangGraph.\n", "\n", "Note that in AgentExecutor, an \"iteration\" includes a full turn of tool invocation and execution. In LangGraph, each step contributes to the recursion limit, so we will need to multiply by two (and add one) to get equivalent results.\n", "\n", "If the recursion limit is reached, LangGraph raises a specific exception type, that we can catch and manage similarly to AgentExecutor." ] }, { "cell_type": "code", "execution_count": 7, "id": "16f189a7-fc78-4cb5-aa16-a94ca06401a6", "metadata": {}, "outputs": [], "source": [ "@tool\n", "def magic_function(input: str) -> str:\n", " \"\"\"Applies a magic function to an input.\"\"\"\n", " return \"Sorry, there was an error. Please try again.\"\n", "\n", "\n", "tools = [magic_function]" ] }, { "cell_type": "code", "execution_count": 8, "id": "c96aefd7-6f6e-4670-aca6-1ac3d4e7871f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m\n", "Invoking: `magic_function` with `{'input': '3'}`\n", "\n", "\n", "\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\n", "Invoking: `magic_function` with `{'input': '3'}`\n", "responded: I encountered an error while trying to determine the value of the magic function for the input \"3\". Let me try again.\n", "\n", "\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\n", "Invoking: `magic_function` with `{'input': '3'}`\n", "responded: I apologize for the inconvenience. It seems there is still an error in calculating the value of the magic function for the input \"3\". Let me attempt to resolve the issue by trying a different approach.\n", "\n", "\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] }, { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'Agent stopped due to max iterations.'}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "agent = create_tool_calling_agent(model, tools, prompt)\n", "agent_executor = AgentExecutor(\n", " agent=agent,\n", " tools=tools,\n", " verbose=True,\n", " max_iterations=3,\n", ")\n", "\n", "agent_executor.invoke({\"input\": query})" ] }, { "cell_type": "code", "execution_count": 10, "id": "b974a91f-6ae8-4644-83d9-73666258a6db", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_VkrswGIkIUKJQyVF0AvMaU3p', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2dd5504b-9386-4b35-aed1-a2a267f883fd-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_VkrswGIkIUKJQyVF0AvMaU3p'}])]}}\n", "------\n", "{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='85d7e845-f4ef-40a6-828d-c48c93b02b97', tool_call_id='call_VkrswGIkIUKJQyVF0AvMaU3p')]}}\n", "------\n", "{'agent': {'messages': [AIMessage(content='It seems there was an error when trying to calculate the value of the magic function for the input 3. Let me try again.', additional_kwargs={'tool_calls': [{'id': 'call_i5ZWsDhQvzgKs2bCroMB4JSL', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 42, 'prompt_tokens': 98, 'total_tokens': 140}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6224c33b-0d3a-4925-9050-cb2a844dfe62-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_i5ZWsDhQvzgKs2bCroMB4JSL'}])]}}\n", "------\n", "{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='f846363c-b143-402c-949d-40d84b19d979', tool_call_id='call_i5ZWsDhQvzgKs2bCroMB4JSL')]}}\n", "------\n", "{'agent': {'messages': [AIMessage(content='Unfortunately, there seems to be an issue with calculating the value of the magic function for the input 3. Let me attempt to resolve this issue by using a different approach.', additional_kwargs={'tool_calls': [{'id': 'call_I26nZWbe4iVnagUh4GVePwig', 'function': {'arguments': '{\"input\": \"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 65, 'prompt_tokens': 162, 'total_tokens': 227}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0512509d-201e-4fbb-ac96-fdd68400810a-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_I26nZWbe4iVnagUh4GVePwig'}])]}}\n", "------\n", "{'action': {'messages': [ToolMessage(content='Sorry, there was an error. Please try again.', name='magic_function', id='fb19299f-de26-4659-9507-4bf4fb53bff4', tool_call_id='call_I26nZWbe4iVnagUh4GVePwig')]}}\n", "------\n", "{'input': 'what is the value of magic_function(3)?', 'output': 'Agent stopped due to max iterations.'}\n" ] } ], "source": [ "from langgraph.pregel import GraphRecursionError\n", "\n", "RECURSION_LIMIT = 2 * 3 + 1\n", "\n", "app = chat_agent_executor.create_tool_calling_executor(model, tools)\n", "\n", "try:\n", " for chunk in app.stream(\n", " {\"messages\": [(\"human\", query)]}, {\"recursion_limit\": RECURSION_LIMIT}\n", " ):\n", " print(chunk)\n", " print(\"------\")\n", "except GraphRecursionError:\n", " print({\"input\": query, \"output\": \"Agent stopped due to max iterations.\"})" ] }, { "cell_type": "markdown", "id": "3a527158-ada5-4774-a98b-8272c6b6b2c0", "metadata": {}, "source": [ "## `max_execution_time`\n", "\n", "`AgentExecutor` implements a `max_execution_time` parameter, allowing users to abort a run that exceeds a total time limit." ] }, { "cell_type": "code", "execution_count": 17, "id": "4b8498fc-a7af-4164-a401-d8714f082306", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m\n", "Invoking: `magic_function` with `{'input': '3'}`\n", "\n", "\n", "\u001b[0m\u001b[36;1m\u001b[1;3mSorry, there was an error. Please try again.\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] }, { "data": { "text/plain": [ "{'input': 'what is the value of magic_function(3)?',\n", " 'output': 'Agent stopped due to max iterations.'}" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import time\n", "\n", "\n", "@tool\n", "def magic_function(input: str) -> str:\n", " \"\"\"Applies a magic function to an input.\"\"\"\n", " time.sleep(2.5)\n", " return \"Sorry, there was an error. Please try again.\"\n", "\n", "\n", "tools = [magic_function]\n", "\n", "agent = create_tool_calling_agent(model, tools, prompt)\n", "agent_executor = AgentExecutor(\n", " agent=agent,\n", " tools=tools,\n", " max_execution_time=2,\n", " verbose=True,\n", ")\n", "\n", "agent_executor.invoke({\"input\": query})" ] }, { "cell_type": "code", "execution_count": 18, "id": "a2b29113-e6be-4f91-aa4c-5c63dea3e423", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_lp2tuTmBpulORJr4FJp9za4E', 'function': {'arguments': '{\"input\":\"3\"}', 'name': 'magic_function'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 65, 'total_tokens': 79}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3b956da36b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-4070a5d8-c2ea-46f3-a3a2-dfcd2ebdadc2-0', tool_calls=[{'name': 'magic_function', 'args': {'input': '3'}, 'id': 'call_lp2tuTmBpulORJr4FJp9za4E'}])]}}\n", "------\n", "{'input': 'what is the value of magic_function(3)?', 'output': 'Agent stopped due to max iterations.'}\n" ] } ], "source": [ "app = chat_agent_executor.create_tool_calling_executor(model, tools)\n", "# Set the max timeout for each step here\n", "app.step_timeout = 2\n", "\n", "try:\n", " for chunk in app.stream({\"messages\": [(\"human\", query)]}):\n", " print(chunk)\n", " print(\"------\")\n", "except TimeoutError:\n", " print({\"input\": query, \"output\": \"Agent stopped due to max iterations.\"})" ] }, { "cell_type": "code", "execution_count": null, "id": "e9eb55f4-a321-4bac-b52d-9e43b411cf92", "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.1" } }, "nbformat": 4, "nbformat_minor": 5 }