diff --git a/docs/docs/tutorials/introduction.ipynb b/docs/docs/tutorials/introduction.ipynb index aa72c90fb..8658d90f7 100644 --- a/docs/docs/tutorials/introduction.ipynb +++ b/docs/docs/tutorials/introduction.ipynb @@ -1218,7 +1218,6 @@ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", "\n", "\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", @@ -1262,8 +1261,6 @@ " else:\n", " return \"human_review_node\"\n", "\n", - "# highlight-end\n", - "\n", "\n", "graph_builder.add_node(chatbot)\n", "\n", @@ -1288,7 +1285,7 @@ "source": [ "------\n", "\n", - "!!! tip \"Read more\"\n", + "!!! tip\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", @@ -2459,33 +2456,35 @@ "\n", "In the examples above, we involved a human deterministically: the graph __always__ interrupted whenever a tool was invoked. Suppose we wanted our chat bot to have the choice of relying on a human.\n", "\n", - "One way to do this is to create a passthrough \"human\" node, before which the graph will always stop. We will only execute this node if the LLM invokes a \"human\" tool. For our convenience, we will include an \"ask_human\" flag in our graph state that we will flip if the LLM calls this tool.\n", + "One way to do this is route to the `human_review_node` only if the LLM invokes a \"human\" tool. We will include an `ask_human` flag in our graph state that we will flip if the LLM calls this tool. This is not needed for the routing process, but conveniently indicates if a run required human assistance in the output.\n", "\n", - "Below, define this new graph, with an updated `State`" + "Below, define this new graph, with an updated `State`:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "id": "3cf7e042-1718-4625-ae30-a9917f595449", "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", "\n", "class State(TypedDict):\n", " messages: Annotated[list, add_messages]\n", " # This flag is new\n", + " # highlight-next-line\n", " ask_human: bool" ] }, @@ -2512,7 +2511,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "e5192e54-6a28-42fe-a8a7-62d45d61f994", "metadata": {}, "outputs": [], @@ -2531,16 +2530,16 @@ }, { "cell_type": "markdown", - "id": "2b19c61b-2087-463b-adf8-96dbc193f41c", + "id": "338c1bf4-8d00-49c3-9809-ecea290fa152", "metadata": {}, "source": [ - "Next, define the chatbot node. The primary modification here is flip the `ask_human` flag if we see that the chat bot has invoked the `RequestAssistance` flag." + "We include this as a new tool accessible to the model:" ] }, { "cell_type": "code", - "execution_count": 16, - "id": "fa59b266-14e5-4c75-8b3d-54fac28e8290", + "execution_count": 18, + "id": "75cf03b1-c546-4585-8d2f-4a8be296fd89", "metadata": {}, "outputs": [], "source": [ @@ -2548,18 +2547,69 @@ "tools = [tool]\n", "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n", "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n", - "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n", - "\n", - "\n", + "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])" + ] + }, + { + "cell_type": "markdown", + "id": "2b19c61b-2087-463b-adf8-96dbc193f41c", + "metadata": {}, + "source": [ + "Otherwise, we just update the `human_review_node` to short-circuit and route directly to `tools` if assistance is not requested. If it is requested, we flip the new flag in the state." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d7da1212-dc9c-4385-a0a0-c4c3bc30e633", + "metadata": {}, + "outputs": [], + "source": [ "def chatbot(state: State):\n", - " response = llm_with_tools.invoke(state[\"messages\"])\n", - " ask_human = False\n", - " if (\n", - " response.tool_calls\n", - " and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n", - " ):\n", - " ask_human = True\n", - " return {\"messages\": [response], \"ask_human\": ask_human}" + " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", + "\n", + "\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", + " # highlight-next-line\n", + " if not tool_call[\"name\"] == RequestAssistance.__name__:\n", + " return Command(goto=\"tools\")\n", + "\n", + " human_review = interrupt(\n", + " {\n", + " \"question\": \"Is this correct?\",\n", + " \"tool_call\": tool_call,\n", + " }\n", + " )\n", + "\n", + " review_action = human_review[\"action\"]\n", + " review_data = human_review.get(\"data\")\n", + "\n", + " if review_action == \"continue\":\n", + " # highlight-next-line\n", + " return Command(goto=\"tools\", update={\"ask_human\": True})\n", + "\n", + " elif review_action == \"feedback\":\n", + " tool_message = {\n", + " \"role\": \"tool\",\n", + " \"content\": review_data,\n", + " \"name\": tool_call[\"name\"],\n", + " \"tool_call_id\": tool_call[\"id\"],\n", + " }\n", + " return Command(\n", + " goto=\"chatbot\",\n", + " # highlight-next-line\n", + " update={\"messages\": [tool_message], \"ask_human\": True},\n", + " )\n", + "\n", + "\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\"" ] }, { @@ -2573,152 +2623,36 @@ { "cell_type": "code", "execution_count": null, - "id": "3bdd9122-1dd4-4049-9626-d009e0d2ee37", + "id": "8db38d3e-f2ff-4d9d-b267-27729be8e060", "metadata": {}, "outputs": [], "source": [ "graph_builder = StateGraph(State)\n", "\n", - "graph_builder.add_node(\"chatbot\", chatbot)\n", - "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))" - ] - }, - { - "cell_type": "markdown", - "id": "7f7a0ff3-b671-45c8-8157-ce5db411d370", - "metadata": {}, - "source": [ - "Next, create the \"human\" `node`. This `node` function is mostly a placeholder in our graph that will trigger an interrupt. If the human does __not__ manually update the state during the `interrupt`, it inserts a tool message so the LLM knows the user was requested but didn't respond. This node also unsets the `ask_human` flag so the graph knows not to revisit the node unless further requests are made." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d70b5a4-ce50-47dc-aa43-ffb5c48c46fc", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import AIMessage, ToolMessage\n", - "\n", - "\n", - "def create_response(response: str, ai_message: AIMessage):\n", - " return ToolMessage(\n", - " content=response,\n", - " tool_call_id=ai_message.tool_calls[0][\"id\"],\n", - " )\n", - "\n", - "\n", - "def human_node(state: State):\n", - " new_messages = []\n", - " if not isinstance(state[\"messages\"][-1], ToolMessage):\n", - " # Typically, the user will have updated the state during the interrupt.\n", - " # If they choose not to, we will include a placeholder ToolMessage to\n", - " # let the LLM continue.\n", - " new_messages.append(\n", - " create_response(\"No response from human.\", state[\"messages\"][-1])\n", - " )\n", - " return {\n", - " # Append the new messages\n", - " \"messages\": new_messages,\n", - " # Unset the flag\n", - " \"ask_human\": False,\n", - " }\n", - "\n", - "\n", - "graph_builder.add_node(\"human\", human_node)" - ] - }, - { - "cell_type": "markdown", - "id": "d56e5c65-f7b7-48bd-b0b5-fc8e590eca7d", - "metadata": {}, - "source": [ - "Next, define the conditional logic. The `select_next_node` will route to the `human` node if the flag is set. Otherwise, it lets the prebuilt `tools_condition` function choose the next node.\n", - "\n", - "Recall that the `tools_condition` function simply checks to see if the `chatbot` has responded with any `tool_calls` in its response message. If so, it routes to the `action` node. Otherwise, it ends the graph." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "586a0d07-8303-47f4-b3cf-3bdd043e762b", - "metadata": {}, - "outputs": [], - "source": [ - "def select_next_node(state: State):\n", - " if state[\"ask_human\"]:\n", - " return \"human\"\n", - " # Otherwise, we can route as before\n", - " return tools_condition(state)\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", - " select_next_node,\n", - " {\"human\": \"human\", \"tools\": \"tools\", END: END},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "66cd0bb1-b13e-477e-a08a-a7e657e2c19e", - "metadata": {}, - "source": [ - "Finally, add the simple directed edges and compile the graph. These edges instruct the graph to **always** flow from node `a`->`b` whenever `a` finishes executing." + " route_after_llm,\n", + ")\n", + "graph_builder.add_edge(\"tools\", \"chatbot\")\n", + "graph_builder.add_edge(START, \"chatbot\")" ] }, { "cell_type": "code", - "execution_count": 60, - "id": "84101737-0048-4635-9f68-45b0c508b6b6", + "execution_count": 21, + "id": "d9d77f37-63cd-4ed3-86c6-ddaac62b39e9", "metadata": {}, "outputs": [], "source": [ - "# The rest is the same\n", - "graph_builder.add_edge(\"tools\", \"chatbot\")\n", - "graph_builder.add_edge(\"human\", \"chatbot\")\n", - "graph_builder.add_edge(START, \"chatbot\")\n", "memory = MemorySaver()\n", - "graph = graph_builder.compile(\n", - " checkpointer=memory,\n", - " # We interrupt before 'human' here instead.\n", - " interrupt_before=[\"human\"],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "7f855593-8690-4a18-9ef8-7f3ccdc335bf", - "metadata": {}, - "source": [ - "If you have the visualization dependencies installed, you can see the graph structure below:" - ] - }, - { - "cell_type": "code", - "execution_count": 61, - "id": "b3220ae2-cba0-4447-96d1-eb0be4684e59", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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fp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=", - "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" + "graph = graph_builder.compile(checkpointer=memory)" ] }, { @@ -2728,13 +2662,106 @@ "source": [ "The chat bot can either request help from a human (chatbot->select->human), invoke the search engine tool (chatbot->select->action), or directly respond (chatbot->select->__end__). Once an action or request has been made, the graph will transition back to the `chatbot` node to continue operations.\n", "\n", - "Let's see this graph in action. We will request for expert assistance to illustrate our graph." + "Let's see this graph in action. We first ask it a question that requires no human intervention:" ] }, { "cell_type": "code", - "execution_count": 62, - "id": "c1955d79-a1e4-47d0-ba79-b45bd5752a23", + "execution_count": 22, + "id": "00a11e0c-937d-4f11-a623-bf16ee1f6d4e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Can you search for the weather in San Francisco?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': \"Certainly! I'd be happy to search for the weather in San Francisco for you. To get the most up-to-date and accurate information, I'll use the search function to look this up. Let me do that for you right away.\", 'type': 'text'}, {'id': 'toolu_01QvcSUFnMXDrK4dWJ4g34P7', 'input': {'query': 'current weather in San Francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01QvcSUFnMXDrK4dWJ4g34P7)\n", + " Call ID: toolu_01QvcSUFnMXDrK4dWJ4g34P7\n", + " Args:\n", + " query: current weather in San Francisco\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': 1736974398, 'localtime': '2025-01-15 12:53'}, 'current': {'last_updated_epoch': 1736973900, 'last_updated': '2025-01-15 12:45', 'temp_c': 11.7, 'temp_f': 53.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 5.8, 'wind_kph': 9.4, 'wind_degree': 45, 'wind_dir': 'NE', 'pressure_mb': 1025.0, 'pressure_in': 30.27, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 41, 'cloud': 0, 'feelslike_c': 10.8, 'feelslike_f': 51.4, 'windchill_c': 9.6, 'windchill_f': 49.3, 'heatindex_c': 11.1, 'heatindex_f': 51.9, 'dewpoint_c': 6.3, 'dewpoint_f': 43.3, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 2.4, 'gust_mph': 7.7, 'gust_kph': 12.4}}\"}, {\"url\": \"https://www.yahoo.com/news/january-15-2025-san-francisco-135157305.html\", \"content\": \"January 15, 2025 San Francisco Bay Area weather forecast Search query Search the web News Finance Sports Manage your account Add or switch accounts Search the web Wildfire damage from space L.A. fires underlying causes Rent hikes amid wildfires Photos: Wildfire devastation L.A. wildfires live updates KRON San Francisco January 15, 2025 San Francisco Bay Area weather forecast KRON San Francisco Wed, January 15, 2025 at 1:51 PM UTC KRON4 Meteorologist Gayle Ong has the latest Bay Area weather outlook. https://www.kron4.com/weather/san-francisco-bay-area-weather-forecast/ Solve the daily Crossword 28,373 people played the daily Crossword recently. Can you solve it faster than others?28,373 people played the daily Crossword recently. Can you solve it faster than others? Crossword Play on Yahoo Yahoo! © 2024 Yahoo.\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Thank you for your patience. I've searched for the current weather in San Francisco, and I have the latest information for you. Here's the weather report for San Francisco:\n", + "\n", + "Date and Time: January 15, 2025, 12:45 PM local time\n", + "Temperature: 11.7°C (53.1°F)\n", + "Condition: Sunny\n", + "Wind: 5.8 mph (9.4 km/h), coming from the Northeast\n", + "Humidity: 41%\n", + "Precipitation: 0 mm (0 inches)\n", + "Visibility: 16 km (9 miles)\n", + "UV Index: 2.4 (low)\n", + "\n", + "It's a nice day in San Francisco with clear, sunny skies. The temperature is mild, typical for a winter day in the city. The wind is light, and there's no precipitation expected. It's a good day for outdoor activities, but you might want to bring a light jacket as it's not too warm.\n", + "\n", + "Is there anything specific about the weather you'd like to know more about?\n" + ] + } + ], + "source": [ + "user_input = \"Can you search for the weather in San Francisco?\"\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\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": "0589f4c9-5ad9-42f0-818a-7e1fcad75517", + "metadata": {}, + "source": [ + "This works as expected, and the state indicates that no human assistance was requested:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "5d6e14ce-4687-48e2-a488-dca48aa9722b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.get_state(config).values.get(\"ask_human\", False)" + ] + }, + { + "cell_type": "markdown", + "id": "a06c0ce3-dc78-40a2-8ae2-7f4429d4bf9d", + "metadata": {}, + "source": [ + "We next request for expert assistance:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b789d334-9222-412c-83f2-0298571ce5c2", "metadata": {}, "outputs": [ { @@ -2746,19 +2773,19 @@ "I need some expert guidance for building this AI agent. Could you request assistance for me?\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"Certainly! I understand that you need expert guidance for building an AI agent. I'll use the RequestAssistance function to escalate your request to an expert who can provide you with the specialized knowledge and support you need. Let me do that for you right away.\", 'type': 'text'}, {'id': 'toolu_01Mo3N2c1byuSZwT1vyJWRia', 'input': {'request': 'The user needs expert guidance for building an AI agent. They require specialized knowledge and support in AI development and implementation.'}, 'name': 'RequestAssistance', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I understand that you need expert guidance for building an AI agent. This is a complex topic that would benefit from specialized knowledge. I'll use the RequestAssistance function to escalate your request to an expert who can provide more in-depth support.\", 'type': 'text'}, {'id': 'toolu_01LruhQDWhxozxHJ94qDndEF', 'input': {'request': 'The user needs expert guidance for building an AI agent. They are looking for specialized knowledge and support in this area.'}, 'name': 'RequestAssistance', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " RequestAssistance (toolu_01Mo3N2c1byuSZwT1vyJWRia)\n", - " Call ID: toolu_01Mo3N2c1byuSZwT1vyJWRia\n", + " RequestAssistance (toolu_01LruhQDWhxozxHJ94qDndEF)\n", + " Call ID: toolu_01LruhQDWhxozxHJ94qDndEF\n", " Args:\n", - " request: The user needs expert guidance for building an AI agent. They require specialized knowledge and support in AI development and implementation.\n" + " request: The user needs expert guidance for building an AI agent. They are looking for specialized knowledge and support in this area.\n" ] } ], "source": [ "user_input = \"I need some expert guidance for building this AI agent. Could you request assistance for me?\"\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "# The config is the **second positional argument** to stream() or invoke()!\n", + "config = {\"configurable\": {\"thread_id\": \"2\"}}\n", + "\n", "events = graph.stream(\n", " {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n", ")\n", @@ -2767,75 +2794,66 @@ " event[\"messages\"][-1].pretty_print()" ] }, - { - "cell_type": "markdown", - "id": "b3945ea4-8dbd-4e14-ae2a-34da7f05a0c1", - "metadata": {}, - "source": [ - "**Notice:** the LLM has invoked the \"`RequestAssistance`\" tool we provided it, and the interrupt has been set. Let's inspect the graph state to confirm." - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "id": "5320ba05-5696-4194-8278-5385c571264d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('human',)" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "snapshot = graph.get_state(config)\n", - "snapshot.next" - ] - }, { "cell_type": "markdown", "id": "ed2dd02e-f0a6-4f63-a7d6-e49ecf40db21", "metadata": {}, "source": [ - "The graph state is indeed **interrupted** before the `'human'` node. We can act as the \"expert\" in this scenario and manually update the state by adding a new ToolMessage with our input.\n", - "\n", - "Next, respond to the chatbot's request by:\n", - "1. Creating a `ToolMessage` with our response. This will be passed back to the `chatbot`.\n", - "2. Calling `update_state` to manually update the graph state." + "We can respond as before, by constructing an appropriate `Command`:" ] }, { "cell_type": "code", - "execution_count": 64, - "id": "2cbac924-61ce-4282-9b1c-77f9090ea1f5", + "execution_count": 28, + "id": "a91f333b-8c4d-44cb-ae6a-0a5e8ed4f23b", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{'configurable': {'thread_id': '1',\n", - " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef7d092-bb30-6bee-8002-015e7e1c56c0'}}" - ] - }, - "execution_count": 64, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': \"Certainly! I understand that you need expert guidance for building an AI agent. This is a complex topic that would benefit from specialized knowledge. I'll use the RequestAssistance function to escalate your request to an expert who can provide more in-depth support.\", 'type': 'text'}, {'id': 'toolu_01LruhQDWhxozxHJ94qDndEF', 'input': {'request': 'The user needs expert guidance for building an AI agent. They are looking for specialized knowledge and support in this area.'}, 'name': 'RequestAssistance', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " RequestAssistance (toolu_01LruhQDWhxozxHJ94qDndEF)\n", + " Call ID: toolu_01LruhQDWhxozxHJ94qDndEF\n", + " Args:\n", + " request: The user needs expert guidance for building an AI agent. They are looking for specialized knowledge and support in this area.\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: RequestAssistance\n", + "\n", + "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Thank you for your patience. I've escalated your request, and an expert has provided some initial guidance. Here's what they recommend:\n", + "\n", + "The experts suggest that you look into LangGraph for building your AI agent. They mention that LangGraph is a more reliable and extensible option compared to simple autonomous agents.\n", + "\n", + "LangGraph is likely a framework or library designed specifically for creating advanced AI agents. It seems to offer benefits in terms of reliability and extensibility, which are crucial factors when developing complex AI systems.\n", + "\n", + "To follow up on this recommendation, you might want to:\n", + "\n", + "1. Research LangGraph to understand its features, capabilities, and how it compares to other agent-building frameworks.\n", + "2. Look for documentation, tutorials, or guides on how to get started with LangGraph.\n", + "3. Consider any specific requirements or goals you have for your AI agent and how LangGraph might address them.\n", + "\n", + "Do you have any specific questions about LangGraph or particular aspects of AI agent development that you'd like me to try to find more information about? I'd be happy to help you dig deeper into this topic.\n" + ] } ], "source": [ - "ai_message = snapshot.values[\"messages\"][-1]\n", "human_response = (\n", " \"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.\"\n", " \" It's much more reliable and extensible than simple autonomous agents.\"\n", ")\n", - "tool_message = create_response(human_response, ai_message)\n", - "graph.update_state(config, {\"messages\": [tool_message]})" + "\n", + "human_command = Command(resume={\"action\": \"feedback\", \"data\": human_response})\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()" ] }, { @@ -2843,87 +2861,28 @@ "id": "79492363-7fc6-4ec7-977d-9030648029bc", "metadata": {}, "source": [ - "You can inspect the state to confirm our response was added." + "You can inspect the state to it's been flagged:" ] }, { "cell_type": "code", - "execution_count": 65, - "id": "4b986c66-1c65-4da8-a404-db7e28f8364e", + "execution_count": 29, + "id": "d2c94b9d-fbbd-4131-bd49-6c95d8c3708b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[HumanMessage(content='I need some expert guidance for building this AI agent. Could you request assistance for me?', additional_kwargs={}, response_metadata={}, id='3f28f959-9ab7-489a-9c58-7ed1b49cedf3'),\n", - " AIMessage(content=[{'text': \"Certainly! I understand that you need expert guidance for building an AI agent. I'll use the RequestAssistance function to escalate your request to an expert who can provide you with the specialized knowledge and support you need. Let me do that for you right away.\", 'type': 'text'}, {'id': 'toolu_01Mo3N2c1byuSZwT1vyJWRia', 'input': {'request': 'The user needs expert guidance for building an AI agent. They require specialized knowledge and support in AI development and implementation.'}, 'name': 'RequestAssistance', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01VRnZvVbgsVRbQaQuvsziDx', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 516, 'output_tokens': 130}}, id='run-4e3f7906-5887-40d9-9267-5beefe7b3b76-0', tool_calls=[{'name': 'RequestAssistance', 'args': {'request': 'The user needs expert guidance for building an AI agent. They require specialized knowledge and support in AI development and implementation.'}, 'id': 'toolu_01Mo3N2c1byuSZwT1vyJWRia', 'type': 'tool_call'}], usage_metadata={'input_tokens': 516, 'output_tokens': 130, 'total_tokens': 646}),\n", - " ToolMessage(content=\"We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\", id='8583b899-d898-4051-9f36-f5e5d11e9a37', tool_call_id='toolu_01Mo3N2c1byuSZwT1vyJWRia')]" + "True" ] }, - "execution_count": 65, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "graph.get_state(config).values[\"messages\"]" - ] - }, - { - "cell_type": "markdown", - "id": "ea6b8616-de10-44d6-a8f0-3ac73c3c3680", - "metadata": {}, - "source": [ - "Next, **resume** the graph by invoking it with `None` as the inputs." - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "6b32914d-4d60-491f-8e11-1e6867e38ffd", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "\n", - "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "\n", - "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. It's much more reliable and extensible than simple autonomous agents.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Thank you for your patience. I've escalated your request to our expert team, and they have provided some initial guidance. Here's what they suggest:\n", - "\n", - "The experts recommend that you check out LangGraph for building your AI agent. They mention that LangGraph is a more reliable and extensible option compared to simple autonomous agents.\n", - "\n", - "LangGraph is likely a framework or tool designed specifically for creating complex AI agents. It seems to offer advantages in terms of reliability and extensibility, which are crucial factors when developing sophisticated AI systems.\n", - "\n", - "To further assist you, I can provide some additional context and next steps:\n", - "\n", - "1. Research LangGraph: Look up documentation, tutorials, and examples of LangGraph to understand its features and how it can help you build your AI agent.\n", - "\n", - "2. Compare with other options: While the experts recommend LangGraph, it might be useful to understand how it compares to other AI agent development frameworks or tools you might have been considering.\n", - "\n", - "3. Assess your requirements: Consider your specific needs for the AI agent you want to build. Think about the tasks it needs to perform, the level of complexity required, and how LangGraph's features align with these requirements.\n", - "\n", - "4. Start with a small project: If you decide to use LangGraph, consider beginning with a small, manageable project to familiarize yourself with the framework.\n", - "\n", - "5. Seek community support: Look for LangGraph user communities, forums, or discussion groups where you can ask questions and get additional support as you build your agent.\n", - "\n", - "6. Consider additional training: Depending on your current skill level, you might want to look into courses or workshops that focus on AI agent development, particularly those that cover LangGraph.\n", - "\n", - "Do you have any specific questions about LangGraph or AI agent development that you'd like me to try to answer? Or would you like me to search for more detailed information about LangGraph and its features?\n" - ] - } - ], - "source": [ - "events = graph.stream(None, config, stream_mode=\"values\")\n", - "for event in events:\n", - " if \"messages\" in event:\n", - " event[\"messages\"][-1].pretty_print()" + "graph.get_state(config).values[\"ask_human\"]" ] }, { @@ -2933,8 +2892,6 @@ "source": [ "**Notice** that the chat bot has incorporated the updated state in its final response. Since **everything** was checkpointed, the \"expert\" human in the loop could perform the update at any time without impacting the graph's execution.\n", "\n", - "**Congratulations!** you've now added an additional node to your assistant graph to let the chat bot decide for itself whether or not it needs to interrupt execution. You did so by updating the graph `State` with a new `ask_human` field and modifying the interruption logic when compiling the graph. This lets you dynamically include a human in the loop while maintaining full **memory** every time you execute the graph.\n", - "\n", "We're almost done with the tutorial, but there is one more concept we'd like to review before finishing that connects `checkpointing` and `state updates`. \n", "\n", "This section's code is reproduced below for your reference.\n", @@ -2944,19 +2901,18 @@ "
\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",
-    "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n",
     "from pydantic import BaseModel\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",
     "\n",
     "class State(TypedDict):\n",
@@ -2977,74 +2933,70 @@
     "tool = TavilySearchResults(max_results=2)\n",
     "tools = [tool]\n",
     "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n",
-    "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n",
     "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n",
     "\n",
     "\n",
     "def chatbot(state: State):\n",
-    "    response = llm_with_tools.invoke(state[\"messages\"])\n",
-    "    ask_human = False\n",
-    "    if (\n",
-    "        response.tool_calls\n",
-    "        and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n",
-    "    ):\n",
-    "        ask_human = True\n",
-    "    return {\"messages\": [response], \"ask_human\": ask_human}\n",
+    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
+    "\n",
+    "\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",
+    "    if not tool_call[\"name\"] == RequestAssistance.__name__:\n",
+    "        return Command(goto=\"tools\")\n",
+    "\n",
+    "    human_review = interrupt(\n",
+    "        {\n",
+    "            \"question\": \"Is this correct?\",\n",
+    "            \"tool_call\": tool_call,\n",
+    "        }\n",
+    "    )\n",
+    "\n",
+    "    review_action = human_review[\"action\"]\n",
+    "    review_data = human_review.get(\"data\")\n",
+    "\n",
+    "    if review_action == \"continue\":\n",
+    "        return Command(goto=\"tools\", update={\"ask_human\": True})\n",
+    "\n",
+    "    elif review_action == \"feedback\":\n",
+    "        tool_message = {\n",
+    "            \"role\": \"tool\",\n",
+    "            \"content\": review_data,\n",
+    "            \"name\": tool_call[\"name\"],\n",
+    "            \"tool_call_id\": tool_call[\"id\"],\n",
+    "        }\n",
+    "        return Command(\n",
+    "            goto=\"chatbot\",\n",
+    "            update={\"messages\": [tool_message], \"ask_human\": True},\n",
+    "        )\n",
+    "\n",
+    "\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 = StateGraph(State)\n",
     "\n",
-    "graph_builder.add_node(\"chatbot\", chatbot)\n",
-    "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))\n",
-    "\n",
-    "\n",
-    "def create_response(response: str, ai_message: AIMessage):\n",
-    "    return ToolMessage(\n",
-    "        content=response,\n",
-    "        tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
-    "    )\n",
-    "\n",
-    "\n",
-    "def human_node(state: State):\n",
-    "    new_messages = []\n",
-    "    if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
-    "        # Typically, the user will have updated the state during the interrupt.\n",
-    "        # If they choose not to, we will include a placeholder ToolMessage to\n",
-    "        # let the LLM continue.\n",
-    "        new_messages.append(\n",
-    "            create_response(\"No response from human.\", state[\"messages\"][-1])\n",
-    "        )\n",
-    "    return {\n",
-    "        # Append the new messages\n",
-    "        \"messages\": new_messages,\n",
-    "        # Unset the flag\n",
-    "        \"ask_human\": False,\n",
-    "    }\n",
-    "\n",
-    "\n",
-    "graph_builder.add_node(\"human\", human_node)\n",
-    "\n",
-    "\n",
-    "def select_next_node(state: State):\n",
-    "    if state[\"ask_human\"]:\n",
-    "        return \"human\"\n",
-    "    # Otherwise, we can route as before\n",
-    "    return tools_condition(state)\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",
-    "    select_next_node,\n",
-    "    {\"human\": \"human\", \"tools\": \"tools\", \"__end__\": \"__end__\"},\n",
+    "    route_after_llm,\n",
     ")\n",
     "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
-    "graph_builder.add_edge(\"human\", \"chatbot\")\n",
-    "graph_builder.set_entry_point(\"chatbot\")\n",
+    "graph_builder.add_edge(START, \"chatbot\")\n",
+    "\n",
     "memory = MemorySaver()\n",
-    "graph = graph_builder.compile(\n",
-    "    checkpointer=memory,\n",
-    "    interrupt_before=[\"human\"],\n",
-    ")\n",
+    "graph = graph_builder.compile(checkpointer=memory)\n",
     "```\n",
     "
\n", "" @@ -3070,25 +3022,23 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 1, "id": "bb8a02de-a21b-4ef6-a714-7d6e44435e3a", "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 langchain_core.messages import AIMessage, ToolMessage\n", - "\n", - "# NOTE: you must use langchain-core >= 0.3 with Pydantic v2\n", "from pydantic import BaseModel\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", "\n", "class State(TypedDict):\n", @@ -3109,85 +3059,81 @@ "tool = TavilySearchResults(max_results=2)\n", "tools = [tool]\n", "llm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\n", - "# We can bind the llm to a tool definition, a pydantic model, or a json schema\n", "llm_with_tools = llm.bind_tools(tools + [RequestAssistance])\n", "\n", "\n", "def chatbot(state: State):\n", - " response = llm_with_tools.invoke(state[\"messages\"])\n", - " ask_human = False\n", - " if (\n", - " response.tool_calls\n", - " and response.tool_calls[0][\"name\"] == RequestAssistance.__name__\n", - " ):\n", - " ask_human = True\n", - " return {\"messages\": [response], \"ask_human\": ask_human}\n", + " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", + "\n", + "\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", + " if not tool_call[\"name\"] == RequestAssistance.__name__:\n", + " return Command(goto=\"tools\")\n", + "\n", + " human_review = interrupt(\n", + " {\n", + " \"question\": \"Is this correct?\",\n", + " \"tool_call\": tool_call,\n", + " }\n", + " )\n", + "\n", + " review_action = human_review[\"action\"]\n", + " review_data = human_review.get(\"data\")\n", + "\n", + " if review_action == \"continue\":\n", + " return Command(goto=\"tools\", update={\"ask_human\": True})\n", + "\n", + " elif review_action == \"feedback\":\n", + " tool_message = {\n", + " \"role\": \"tool\",\n", + " \"content\": review_data,\n", + " \"name\": tool_call[\"name\"],\n", + " \"tool_call_id\": tool_call[\"id\"],\n", + " }\n", + " return Command(\n", + " goto=\"chatbot\",\n", + " update={\"messages\": [tool_message], \"ask_human\": True},\n", + " )\n", + "\n", + "\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 = StateGraph(State)\n", "\n", - "graph_builder.add_node(\"chatbot\", chatbot)\n", - "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))\n", - "\n", - "\n", - "def create_response(response: str, ai_message: AIMessage):\n", - " return ToolMessage(\n", - " content=response,\n", - " tool_call_id=ai_message.tool_calls[0][\"id\"],\n", - " )\n", - "\n", - "\n", - "def human_node(state: State):\n", - " new_messages = []\n", - " if not isinstance(state[\"messages\"][-1], ToolMessage):\n", - " # Typically, the user will have updated the state during the interrupt.\n", - " # If they choose not to, we will include a placeholder ToolMessage to\n", - " # let the LLM continue.\n", - " new_messages.append(\n", - " create_response(\"No response from human.\", state[\"messages\"][-1])\n", - " )\n", - " return {\n", - " # Append the new messages\n", - " \"messages\": new_messages,\n", - " # Unset the flag\n", - " \"ask_human\": False,\n", - " }\n", - "\n", - "\n", - "graph_builder.add_node(\"human\", human_node)\n", - "\n", - "\n", - "def select_next_node(state: State):\n", - " if state[\"ask_human\"]:\n", - " return \"human\"\n", - " # Otherwise, we can route as before\n", - " return tools_condition(state)\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", - " select_next_node,\n", - " {\"human\": \"human\", \"tools\": \"tools\", END: END},\n", + " route_after_llm,\n", ")\n", "graph_builder.add_edge(\"tools\", \"chatbot\")\n", - "graph_builder.add_edge(\"human\", \"chatbot\")\n", "graph_builder.add_edge(START, \"chatbot\")\n", + "\n", "memory = MemorySaver()\n", - "graph = graph_builder.compile(\n", - " checkpointer=memory,\n", - " interrupt_before=[\"human\"],\n", - ")" + "graph = graph_builder.compile(checkpointer=memory)" ] }, { "cell_type": "code", - "execution_count": 68, - "id": "a7debb4a-2a3a-40b9-a48c-7052ec2c2726", + "execution_count": 2, + "id": "88faedd2-d12f-4084-9942-491f5ad8e6e7", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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fp6km0/mspg8vpeLCsuYii+22vZZdfIRMGAmNpZKDzu83zT16INwREQFV8ftiNeZGizZtfJVW5BjB6JmOEcx/sIdB0HpDj3lWhVgjx3iUxzNy3HYlzJDt03sTNLRv69qxJHo3HrC6ML+6J0W/wDOdlhZ0RFzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCv5mhYx2VbncfD4xL2QguVWnzp4WkuaWejtGFztgehDnDpuCPDJ4jS/FPT3iuToY/UmIdIHOrXYGzRtkb6HMePNe3fqCAQe8BWNQmV0bistdN18MlXIEAG7SmfXmcB3BzmEF4HqduOp6LfFVNcRFerX1XTpVNvg3cKWBwbw40u0PGzgMTB1G4Ox831gf4Lv0/wM4d6TzFbLYXQ+n8Tk6xJhuU8bFFLGSC08rmtBG4JH9hKkDoiYABmp88xo9HbxO/5mMlPImx7VZ766H+Er+Hh7/KS0bVoRVfyJse1We+uh/hKp8VcfldG6ByuYx2qcwblYRmMTywlnnSsadx2Y9Dj6U/Dw9/lJaNrVF+EBwII3B7wqx5E2ParPfXQ/wk8ibHtVnvrof4Sfh4e/yktG1Xv/Vr4T/9W2lf+EQfur9Pg2cJ3Ek8N9LEnvJxMBJ/8KsHkTY9qs99dD/CX6NDueOWfUWdsM67t8bEW4P542tP+BTIw9/lJaNrvy2eq4PsKMDG2MjK3arjoSA9wHTcj+jGPS89B/aQD5adwz8TWnksvZNkbkvjFuZgPK6QgDZu/Xla1rWj8zRv1JXswunMbp6OVmPqMrmUh0su5dJKR0Be9xLnnb0uJKkljVVTEZNGj9zyERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnvH8gcIs/uSBtB3f9vH+cLQlnvH/f8EWf2232g+Ntt/p4/Wg0JERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFnnhAjfhDqDqG9IOpHT/TxrQ1nnhA7fgh1Bv0G0Ho3/18aDQ0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERE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D4czPsZm/raX3lPhzM+xmb+tpfeVf0TvUbkc+pfgqGExGQymbrZXJU3YuKm17a1SSRr5XPeNi95Y4tADdwACT5xJI7lb0Rc2JiTiTeSZuIiLUgiIgIiICIiAiIgIiICIiAiIgIiII7UcL7GnspFG0ukfVla1o9JLCAq1pd7ZNNYlzTu11SEg+scgV2VTtcPm9vI/GZvJYOF7i81aYgfCHHqS1ssT+Xc9dmkDck7dV24OJTFM0VTbWuqzpRcHkBkPbPN/UUvu6eQGQ9s839RS+7rffD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7unkBkPbPN/UUvu6Xw9+OfQtxd6Lg8gMh7Z5v6il93TyAyHtnm/qKX3dL4e/HPoW4u9FweQGQ9s839RS+7p5AZD2zzf1FL7ul8Pfjn0LcXei4PIDIe2eb+opfd08gMh7Z5v6il93S+Hvxz6FuLvRcHkBkPbPN/UUvu6eQGQ9s839RS+7pfD3459C3F3ouDyAyHtnm/qKX3dPIDIe2eb+opfd0vh78c+hbi70XB5AZD2zzf1FL7uvJmgbm5Eurs1Mw97ezqM36+tsAI/uPpUvh78c+hbi/eH7CL2rZQd2S5cFp2PoqVmH/wATXD+5W9cmKxVXCY+GlShEFaIENbuXEkkkuJO5c4kklxJJJJJJJXWuLGrjErmqNHTMTnERFpQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=", + "image/png": "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", "text/plain": [ "" ] @@ -3216,7 +3162,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 3, "id": "69071b02-c011-4b7f-90b1-8e89e032322d", "metadata": {}, "outputs": [ @@ -3229,45 +3175,44 @@ "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 function to gather details about LangGraph. Let me do that for you now.\", 'type': 'text'}, {'id': 'toolu_019HPZEw6v1eSLBXnwxk6MZm', 'input': {'query': 'LangGraph framework for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"Certainly! I'd be happy to help you research LangGraph. To provide you with the most up-to-date and accurate information, I'll use the Tavily search engine to gather some data for you. Let me do that now.\", 'type': 'text'}, {'id': 'toolu_01FuxELYf5Jn1iXcEeduiTDs', 'input': {'query': 'LangGraph framework for language models'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_019HPZEw6v1eSLBXnwxk6MZm)\n", - " Call ID: toolu_019HPZEw6v1eSLBXnwxk6MZm\n", + " tavily_search_results_json (toolu_01FuxELYf5Jn1iXcEeduiTDs)\n", + " Call ID: toolu_01FuxELYf5Jn1iXcEeduiTDs\n", " Args:\n", " query: LangGraph framework for language models\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: tavily_search_results_json\n", "\n", - "[{\"url\": \"https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141\", \"content\": \"LangGraph is a powerful tool for building stateful, multi-actor applications with Large Language Models (LLMs). It extends the LangChain library, allowing you to coordinate multiple chains (or ...\"}, {\"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).\"}]\n", + "[{\"url\": \"https://www.datacamp.com/tutorial/langgraph-tutorial\", \"content\": \"LangGraph provides a framework for defining, coordinating, and executing multiple LLM agents (or chains) in a structured manner. ... LangChain is a framework for including AI from large language models inside data pipelines and applications. This tutorial provides an overview of what you can do with LangChain, including the problems that\"}, {\"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.\"}]\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "Thank you for your patience. I've gathered some information about LangGraph for you. Let me summarize the key points:\n", "\n", - "1. What is LangGraph?\n", - " LangGraph is a powerful tool designed for building stateful, multi-actor applications using Large Language Models (LLMs). It's an extension of the LangChain library, which is already a popular framework for developing LLM-powered applications.\n", + "1. Purpose:\n", + " LangGraph is a framework designed for defining, coordinating, and executing multiple Language Model (LLM) agents or chains in a structured manner.\n", "\n", - "2. Purpose and Functionality:\n", - " - LangGraph allows developers to coordinate multiple chains or actors within a single application.\n", - " - It enhances the capabilities of LangChain by introducing more complex, stateful workflows.\n", + "2. Relation to LangChain:\n", + " While LangGraph and LangChain are related, they serve different purposes:\n", + " - LangChain is a framework for integrating AI from large language models into data pipelines and applications.\n", + " - LangGraph is more focused on orchestrating complex agentic systems.\n", "\n", - "3. Relation to LangChain:\n", - " - LangGraph builds upon LangChain, which is one of the leading frameworks for creating LLM-powered applications.\n", - " - LangChain itself uses the LangChain Expression Language (LCEL) to define and execute step-by-step action sequences, also known as chains.\n", - " - LangChain allows the creation of DAGs (Directed Acyclic Graphs), which represent the flow of operations in an application.\n", + "3. Level of Control:\n", + " LangGraph is described as more low-level and controllable compared to LangChain agents. This suggests that it offers more fine-grained control over the interaction between different components in a language model system.\n", "\n", - "4. Key Features:\n", - " - Stateful Applications: Unlike simple query-response models, LangGraph allows the creation of applications that maintain state across interactions.\n", - " - Multi-Actor Systems: It supports coordinating multiple AI \"actors\" or components within a single application, enabling more complex interactions and workflows.\n", + "4. Use Cases:\n", + " LangGraph seems particularly useful for scenarios where you need to coordinate multiple AI agents or create more complex workflows involving language models.\n", "\n", - "5. Use Cases:\n", - " While not explicitly mentioned in the search results, LangGraph is typically used for creating more sophisticated AI applications such as:\n", - " - Multi-turn conversational agents\n", - " - Complex task-planning systems\n", - " - Applications requiring memory and context management across multiple steps or actors\n", + "5. Comparison to LangChain Agents:\n", + " While LangChain provides a standard interface for interacting with models and components (useful for straightforward chains and retrieval flows), LangGraph appears to be more suited for building and managing more complex, multi-agent systems.\n", "\n", - "Learning LangGraph can be a valuable skill, especially if you're interested in developing advanced applications with LLMs that go beyond simple question-answering or text generation tasks. It allows for the creation of more dynamic, interactive, and stateful AI systems.\n", + "If you're learning LangGraph, it might be helpful to understand:\n", + "1. How to define and structure multiple agents or chains\n", + "2. The ways to coordinate these agents in a cohesive system\n", + "3. The types of complex workflows you can create with LangGraph\n", + "4. How it differs from and complements LangChain in practical applications\n", "\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 or works with LangChain?\n" + "Would you like me to search for more specific information about any particular aspect of LangGraph, such as its key features, getting started guides, or specific use cases?\n" ] } ], @@ -3289,7 +3234,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 4, "id": "acbec099-e5d2-497f-929e-c548d7bcbf77", "metadata": {}, "outputs": [ @@ -3302,54 +3247,56 @@ "Ya that's helpful. Maybe I'll build an autonomous agent with it!\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"That's an excellent idea! Building an autonomous agent with LangGraph is a great way to explore its capabilities and learn about advanced AI application development. LangGraph's features make it well-suited for creating autonomous agents. Let me provide some additional insights and encouragement for your project.\", 'type': 'text'}, {'id': 'toolu_017t6BS5rNCzFWcpxRizDKjE', 'input': {'query': 'building autonomous agents with LangGraph examples and tutorials'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"That's an exciting idea! Building an autonomous agent with LangGraph could be a great way to dive deep into the framework and explore its capabilities. LangGraph's focus on orchestrating complex agentic systems makes it well-suited for such a project. Let me gather some more specific information about using LangGraph for building autonomous agents.\", 'type': 'text'}, {'id': 'toolu_01TeAyyVo8fYBYApftT7yjTS', 'input': {'query': 'Building autonomous agents with LangGraph examples and tutorials'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_017t6BS5rNCzFWcpxRizDKjE)\n", - " Call ID: toolu_017t6BS5rNCzFWcpxRizDKjE\n", + " tavily_search_results_json (toolu_01TeAyyVo8fYBYApftT7yjTS)\n", + " Call ID: toolu_01TeAyyVo8fYBYApftT7yjTS\n", " Args:\n", - " query: building autonomous agents with LangGraph examples and tutorials\n", + " query: Building autonomous agents with LangGraph examples and tutorials\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: tavily_search_results_json\n", "\n", - "[{\"url\": \"https://medium.com/@lucas.dahan/hands-on-langgraph-building-a-multi-agent-assistant-06aa68ed942f\", \"content\": \"Building the Graph. With our agents defined, we'll create a graph.py file to orchestrate their interactions. The basic graph structure in LangGraph is really simple, here we are going to use ...\"}, {\"url\": \"https://medium.com/@cplog/building-tool-calling-conversational-ai-with-langchain-and-langgraph-a-beginners-guide-8d6986cc589e\", \"content\": \"Introduction to AI Agent with LangChain and LangGraph: A Beginner’s Guide Two powerful tools revolutionizing this field are LangChain and LangGraph. In this guide, we’ll explore how these technologies can be combined to build a sophisticated AI assistant capable of handling complex conversations and tasks. Tool calling is a standout feature in agentic design, allowing the LLM to interact with external systems or perform specific tasks via the @tool decorator. While the Assistant class presented here is one approach, the flexibility of tool calling and LangGraph allows for a wide range of designs. With LangChain and LangGraph, you can build a powerful, flexible AI assistant capable of handling complex tasks and conversations. Tool calling significantly enhances the AI’s capabilities by enabling interaction with external systems.\"}]\n", + "[{\"url\": \"https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d\", \"content\": \"Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user’s question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow\"}, {\"url\": \"https://blog.futuresmart.ai/langgraph-agent-with-rag-and-nl2sql\", \"content\": \"In this blog post, we will walk you through the process of creating a custom AI agent with three powerful tools: Web Search, Retrieval-Augmented Generation (RAG), and Natural Language to SQL (NL2SQL), all integrated within the LangGraph framework. This guide is designed to provide you with a practical, step-by-step approach to building a fully functional AI agent capable of performing complex tasks such as retrieving real-time data from the web, generating responses based on retrieved information from the knowledge base, and translating natural language queries into SQL database queries. By following this tutorial, you've built an AI agent capable of performing diverse tasks such as retrieving real-time data, answering questions based on document-based knowledge, and executing SQL queries directly from natural language commands.\"}]\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Your enthusiasm for building an autonomous agent with LangGraph is fantastic! This project will not only help you learn more about LangGraph but also give you hands-on experience with cutting-edge AI development. Here are some insights and tips to get you started:\n", + "Thank you for sharing your interest in building an autonomous agent with LangGraph! That's an excellent way to learn and apply the framework. Based on the information I've gathered, I can provide you with some insights and guidance on how to approach this project:\n", "\n", - "1. Multi-Agent Systems:\n", - " LangGraph excels at creating multi-agent systems. You could design your autonomous agent as a collection of specialized sub-agents, each handling different aspects of tasks or knowledge domains.\n", + "1. Multi-Tool Agents:\n", + " LangGraph allows you to create autonomous agents that can use multiple tools. This is particularly powerful for creating versatile agents that can handle a variety of tasks.\n", "\n", - "2. Graph Structure:\n", - " The basic graph structure in LangGraph is straightforward. You'll create a graph.py file to orchestrate the interactions between your agents or components.\n", + "2. Integration with Advanced Language Models:\n", + " There's an example of building autonomous multi-tool agents using Gemini 2.0 (Google's advanced language model) and LangGraph. This suggests that LangGraph is compatible with state-of-the-art language models, which can enhance your agent's capabilities.\n", "\n", - "3. Tool Calling:\n", - " A key feature you can incorporate is tool calling. This allows your LLM-based agent to interact with external systems or perform specific tasks. You can implement this using the @tool decorator in your code.\n", + "3. Practical Tutorial Available:\n", + " There's a practical tutorial available with full code examples for building and running multi-tool agents. This could be an excellent starting point for your project.\n", "\n", - "4. Flexibility in Design:\n", - " LangGraph offers great flexibility in designing your agent. While there are example structures like the Assistant class, you have the freedom to create a wide range of designs tailored to your specific needs.\n", + "4. Diverse Tool Integration:\n", + " You can equip your agent with various tools. For example, one tutorial mentions creating an agent with four different tools to answer user questions.\n", "\n", - "5. Complex Conversations and Tasks:\n", - " Your autonomous agent can be designed to handle sophisticated conversations and complex tasks. This is where LangGraph's stateful nature really shines, allowing your agent to maintain context over extended interactions.\n", + "5. Complex Task Handling:\n", + " LangGraph enables the creation of agents capable of performing complex tasks such as:\n", + " - Web searching for real-time data retrieval\n", + " - Implementing Retrieval-Augmented Generation (RAG) for enhanced knowledge access\n", + " - Using Natural Language to SQL (NL2SQL) for database interactions\n", "\n", - "6. Integration with LangChain:\n", - " Since LangGraph builds upon LangChain, you can leverage features from both. This combination allows for powerful, flexible AI assistants capable of managing intricate workflows.\n", + "6. Step-by-Step Guides:\n", + " There are resources available that provide step-by-step approaches to building fully functional AI agents using LangGraph.\n", "\n", - "7. External System Interaction:\n", - " Consider incorporating external APIs or databases to enhance your agent's capabilities. This could include accessing real-time data, performing calculations, or interacting with other services.\n", + "7. Versatile Applications:\n", + " Your autonomous agent could potentially handle tasks like:\n", + " - Answering questions based on retrieved information from a knowledge base\n", + " - Executing SQL queries from natural language commands\n", + " - Performing web searches and integrating the results into responses\n", "\n", - "8. Tutorial Resources:\n", - " There are tutorials available that walk through the process of building AI assistants with LangChain and LangGraph. These can be excellent starting points for your project.\n", + "To get started with your project, you might want to:\n", "\n", - "To get started, you might want to:\n", - "1. Set up your development environment with LangChain and LangGraph.\n", - "2. Define the core functionalities you want your autonomous agent to have.\n", - "3. Design the overall structure of your agent, possibly as a multi-agent system.\n", - "4. Implement basic interactions and gradually add more complex features like tool calling and state management.\n", - "5. Test your agent thoroughly with various scenarios to ensure robust performance.\n", + "1. Familiarize yourself with the LangGraph documentation and basic concepts.\n", + "2. Follow one of the available tutorials to build a simple multi-tool agent.\n", + "3. Define the specific tasks and capabilities you want your autonomous agent to have.\n", + "4. Incrementally add and test new tools and functionalities to your agent.\n", + "5. Experiment with different language models to see which works best for your use case.\n", "\n", - "Remember, building an autonomous agent is an iterative process. Start with a basic version and progressively enhance its capabilities. This approach will help you understand the intricacies of LangGraph while creating a sophisticated AI application.\n", - "\n", - "Do you have any specific ideas about what kind of tasks or domain you want your autonomous agent to specialize in? This could help guide the design and implementation process.\n" + "Would you like more information on any specific aspect of building your autonomous agent with LangGraph, such as setting up the environment, choosing tools, or handling particular types of tasks?\n" ] } ], @@ -3378,8 +3325,8 @@ }, { "cell_type": "code", - "execution_count": 71, - "id": "6c0dbed5-210d-40ad-b002-0bc52ef28fac", + "execution_count": 5, + "id": "c3e5875d-5612-41cb-8109-9a45f0282783", "metadata": {}, "outputs": [ { @@ -3392,6 +3339,8 @@ "--------------------------------------------------------------------------------\n", "Num Messages: 6 Next: ('tools',)\n", "--------------------------------------------------------------------------------\n", + "Num Messages: 6 Next: ('human_review_node',)\n", + "--------------------------------------------------------------------------------\n", "Num Messages: 5 Next: ('chatbot',)\n", "--------------------------------------------------------------------------------\n", "Num Messages: 4 Next: ('__start__',)\n", @@ -3402,6 +3351,8 @@ "--------------------------------------------------------------------------------\n", "Num Messages: 2 Next: ('tools',)\n", "--------------------------------------------------------------------------------\n", + "Num Messages: 2 Next: ('human_review_node',)\n", + "--------------------------------------------------------------------------------\n", "Num Messages: 1 Next: ('chatbot',)\n", "--------------------------------------------------------------------------------\n", "Num Messages: 0 Next: ('__start__',)\n", @@ -3431,16 +3382,16 @@ }, { "cell_type": "code", - "execution_count": 72, - "id": "de8d5521-8d71-4093-a657-4920c790802f", + "execution_count": 6, + "id": "fdcf00af-8459-4132-85cc-742199391d4f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "('tools',)\n", - "{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef7d094-2634-687c-8006-49ddde5b2f1c'}}\n" + "('human_review_node',)\n", + "{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efd3840-87a4-6760-8007-bd412fc24064'}}\n" ] } ], @@ -3459,8 +3410,8 @@ }, { "cell_type": "code", - "execution_count": 73, - "id": "85f17be3-eaf6-495e-a846-49436916b4ab", + "execution_count": 7, + "id": "c5382e81-bfcd-4508-b02a-099e3d9627fd", "metadata": {}, "outputs": [ { @@ -3469,59 +3420,49 @@ "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'text': \"That's an excellent idea! Building an autonomous agent with LangGraph is a great way to explore its capabilities and learn about advanced AI application development. LangGraph's features make it well-suited for creating autonomous agents. Let me provide some additional insights and encouragement for your project.\", 'type': 'text'}, {'id': 'toolu_017t6BS5rNCzFWcpxRizDKjE', 'input': {'query': 'building autonomous agents with LangGraph examples and tutorials'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "[{'text': \"That's an exciting idea! Building an autonomous agent with LangGraph could be a great way to dive deep into the framework and explore its capabilities. LangGraph's focus on orchestrating complex agentic systems makes it well-suited for such a project. Let me gather some more specific information about using LangGraph for building autonomous agents.\", 'type': 'text'}, {'id': 'toolu_01TeAyyVo8fYBYApftT7yjTS', 'input': {'query': 'Building autonomous agents with LangGraph examples and tutorials'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " tavily_search_results_json (toolu_017t6BS5rNCzFWcpxRizDKjE)\n", - " Call ID: toolu_017t6BS5rNCzFWcpxRizDKjE\n", + " tavily_search_results_json (toolu_01TeAyyVo8fYBYApftT7yjTS)\n", + " Call ID: toolu_01TeAyyVo8fYBYApftT7yjTS\n", " Args:\n", - " query: building autonomous agents with LangGraph examples and tutorials\n", + " query: Building autonomous agents with LangGraph examples and tutorials\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: tavily_search_results_json\n", "\n", - "[{\"url\": \"https://blog.langchain.dev/how-to-build-the-ultimate-ai-automation-with-multi-agent-collaboration/\", \"content\": \"Learn how to create an autonomous research assistant using LangGraph, an extension of LangChain for agent and multi-agent flows. Follow the steps to define the graph state, initialize the graph, and run the agents for planning, research, review, writing and publishing.\"}, {\"url\": \"https://medium.com/@lucas.dahan/hands-on-langgraph-building-a-multi-agent-assistant-06aa68ed942f\", \"content\": \"Building the Graph. With our agents defined, we'll create a graph.py file to orchestrate their interactions. The basic graph structure in LangGraph is really simple, here we are going to use ...\"}]\n", + "[{\"url\": \"https://towardsdatascience.com/building-autonomous-multi-tool-agents-with-gemini-2-0-and-langgraph-ad3d7bd5e79d\", \"content\": \"Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph | by Youness Mansar | Jan, 2025 | Towards Data Science Building Autonomous Multi-Tool Agents with Gemini 2.0 and LangGraph A practical tutorial with full code examples for building and running multi-tool agents Towards Data Science LLMs are remarkable — they can memorize vast amounts of information, answer general knowledge questions, write code, generate stories, and even fix your grammar. In this tutorial, we are going to build a simple LLM agent that is equipped with four tools that it can use to answer a user’s question. This Agent will have the following specifications: Follow Published in Towards Data Science --------------------------------- Your home for data science and AI. Follow Follow Follow\"}, {\"url\": \"https://github.com/anmolaman20/Tools_and_Agents\", \"content\": \"GitHub - anmolaman20/Tools_and_Agents: This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository provides resources for building AI agents using Langchain and Langgraph. This repository serves as a comprehensive guide for building AI-powered agents using Langchain and Langgraph. It provides hands-on examples, practical tutorials, and resources for developers and AI enthusiasts to master building intelligent systems and workflows. AI Agent Development: Gain insights into creating intelligent systems that think, reason, and adapt in real time. This repository is ideal for AI practitioners, developers exploring language models, or anyone interested in building intelligent systems. This repository provides resources for building AI agents using Langchain and Langgraph.\"}]\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Great choice! Building an autonomous agent with LangGraph is an excellent way to dive deep into its capabilities. Based on the additional information I've found, here are some insights and steps to help you get started:\n", + "Great idea! Building an autonomous agent with LangGraph can be an excellent way to learn the framework and explore its capabilities. Based on the information I've found, here are some insights and resources that could help you get started:\n", "\n", - "1. LangGraph for Autonomous Agents:\n", - " LangGraph is particularly well-suited for creating autonomous agents, especially those involving multi-agent collaboration. It allows you to create complex, stateful workflows that can simulate autonomous behavior.\n", + "1. Multi-Tool Agents:\n", + " LangGraph is well-suited for building autonomous agents that can use multiple tools. This allows your agent to have a variety of capabilities and choose the appropriate tool based on the task at hand.\n", "\n", - "2. Example Project: Autonomous Research Assistant\n", - " One popular example is building an autonomous research assistant. This type of project can help you understand the core concepts of LangGraph while creating something useful.\n", + "2. Integration with Other Technologies:\n", + " There's an example of building autonomous multi-tool agents using LangGraph in combination with Gemini 2.0 (Google's large language model). This suggests that LangGraph can be integrated with various LLMs and tools to create powerful agents.\n", "\n", - "3. Key Steps in Building an Autonomous Agent:\n", - " a. Define the Graph State: This involves setting up the structure that will hold the agent's state and context.\n", - " b. Initialize the Graph: Set up the initial conditions and parameters for your agent.\n", - " c. Create Multiple Agents: For a complex system, you might create several specialized agents, each with a specific role (e.g., planning, research, review, writing).\n", - " d. Orchestrate Interactions: Use LangGraph to manage how these agents interact and collaborate.\n", + "3. Practical Tutorials:\n", + " There are tutorials available that provide full code examples for building and running multi-tool agents. These can be extremely helpful as you start your project.\n", "\n", - "4. Components of an Autonomous Agent:\n", - " - Planning Agent: Determines the overall strategy and steps.\n", - " - Research Agent: Gathers necessary information.\n", - " - Review Agent: Evaluates and refines the work.\n", - " - Writing Agent: Produces the final output.\n", - " - Publishing Agent: Handles the final distribution or application of results.\n", + "4. GitHub Resources:\n", + " There's a GitHub repository (by user anmolaman20) that provides resources for building AI agents using both LangChain and LangGraph. This could be a valuable reference as you develop your agent.\n", "\n", - "5. Implementation Tips:\n", - " - Start with a simple graph structure in LangGraph.\n", - " - Define clear roles and responsibilities for each agent or component.\n", - " - Use LangGraph's features to manage state and context across the different stages of your agent's workflow.\n", + "5. Capabilities:\n", + " The agents you can build with LangGraph can potentially memorize information, answer questions, write code, generate stories, and perform various other tasks depending on how you design them and what tools you integrate.\n", "\n", - "6. Learning Resources:\n", - " - Look for tutorials and examples specifically on building multi-agent systems with LangGraph.\n", - " - The LangChain documentation and community forums can be valuable resources, as LangGraph builds upon LangChain.\n", + "6. Real-Time Adaptation:\n", + " LangGraph seems to support building agents that can think, reason, and adapt in real-time, which is crucial for truly autonomous behavior.\n", "\n", - "7. Potential Applications:\n", - " - Autonomous research assistants\n", - " - Complex task automation systems\n", - " - Interactive storytelling agents\n", - " - Autonomous problem-solving systems\n", + "To get started with your project, you might want to:\n", "\n", - "Building an autonomous agent with LangGraph is an exciting project that will give you hands-on experience with advanced concepts in AI application development. It's a great way to learn about state management, multi-agent coordination, and complex workflow design in AI systems.\n", + "1. Set up your development environment with LangGraph and any necessary dependencies.\n", + "2. Start with a simple agent that uses one or two tools, then gradually increase complexity.\n", + "3. Explore the GitHub resources and tutorials to understand best practices and common patterns in building autonomous agents with LangGraph.\n", + "4. Consider what specific tasks or domains you want your agent to specialize in, and research appropriate tools or APIs to integrate.\n", + "5. Experiment with different LLMs to find the one that best suits your agent's needs.\n", "\n", - "As you embark on this project, remember to start small and gradually increase complexity. You might begin with a simple autonomous agent that performs a specific task, then expand its capabilities and add more agents or components as you become more comfortable with LangGraph.\n", + "Remember to consider ethical implications and potential limitations as you develop your autonomous agent. It's important to build in safeguards and ensure your agent behaves responsibly.\n", "\n", - "Do you have a specific type of autonomous agent in mind, or would you like some suggestions for beginner-friendly autonomous agent projects to start with?\n" + "Would you like more information on any specific aspect of building your autonomous agent with LangGraph, such as setting up the environment, choosing tools, or designing the agent's decision-making process?\n" ] } ],