From 1c9b1d29c31d70f4ac90d4a794b6da5c13fa0de8 Mon Sep 17 00:00:00 2001 From: Isaac Francisco <78627776+isahers1@users.noreply.github.com> Date: Sun, 8 Sep 2024 09:58:55 -0700 Subject: [PATCH] docs: add page on creating react agent from scratch (#1626) --------- Co-authored-by: vbarda --- docs/_scripts/copy_notebooks.py | 1 + docs/docs/how-tos/index.md | 1 + docs/mkdocs.yml | 1 + examples/react-agent-from-scratch.ipynb | 344 ++++++++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 examples/react-agent-from-scratch.ipynb diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py index 6ffc889b5..96b07e560 100644 --- a/docs/_scripts/copy_notebooks.py +++ b/docs/_scripts/copy_notebooks.py @@ -72,6 +72,7 @@ _MANUAL = { "langsmith_graph_trace.png", "run-id-langsmith.ipynb", "react-agent-structured-output.ipynb", + "react-agent-from-scratch.ipynb" ], "tutorials": [ "introduction.ipynb", diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 1a537d61a..c7ca5927b 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -98,3 +98,4 @@ Please note that here will we use a **prebuilt agent**. One of the big benefits - [How to add memory to a ReAct agent](create-react-agent-memory.ipynb) - [How to add a custom system prompt to a ReAct agent](create-react-agent-system-prompt.ipynb) - [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb) +- [How to create prebuilt ReAct agent from scratch](react-agent-from-scratch.ipynb) \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 031f2781f..4d087bb49 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -183,6 +183,7 @@ nav: - Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb - Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb - Add Human-in-the-loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb + - Create prebuilt ReAct agent from scratch: how-tos/react-agent-from-scratch.ipynb - "Conceptual Guides": - "concepts/index.md" - LangGraph for Agentic Applications: concepts/high_level.md diff --git a/examples/react-agent-from-scratch.ipynb b/examples/react-agent-from-scratch.ipynb new file mode 100644 index 000000000..4fa8e7c9a --- /dev/null +++ b/examples/react-agent-from-scratch.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to create a ReAct agent from scratch\n", + "\n", + "Using the prebuilt ReAct agent (`create_react_agent`) is a great way to get started, but sometimes you might want more control and customization. In those cases, you can create a custom ReAct agent. This guide shows how to implement ReAct agent from scratch using LangGraph.\n", + "\n", + "## Setup\n", + "\n", + "First, let's install the required packages and set our API keys" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

Set up LangSmith for better debugging

\n", + "

\n", + " Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM aps built with LangGraph — read more about how to get started in the docs. \n", + "

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create ReAct agent\n", + "\n", + "Now that you have installed the required packages and set your environment variables, we can code our ReAct agent!\n", + "\n", + "### Define graph state\n", + "\n", + "We are going to define the most basic ReAct state in this example, which will just contain a list of messages.\n", + "\n", + "For your specific use case, feel free to add any other state keys that you need." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import (\n", + " Annotated,\n", + " Sequence,\n", + " TypedDict,\n", + ")\n", + "from langchain_core.messages import BaseMessage\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "class AgentState(TypedDict):\n", + " \"\"\"The state of the agent.\"\"\"\n", + "\n", + " messages: Annotated[Sequence[BaseMessage], add_messages]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define model and tools\n", + "\n", + "Next, let's define the tools and model we will use for our example." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_openai import ChatOpenAI\n", + "from langchain_core.tools import tool\n", + "\n", + "model = ChatOpenAI(model=\"gpt-4o-mini\")\n", + "\n", + "@tool\n", + "def get_weather(location: str):\n", + " \"\"\"Call to get the weather from a specific location.\"\"\"\n", + " # This is a placeholder for the actual implementation\n", + " # Don't let the LLM know this though 😊\n", + " if any([city in location.lower() for city in ['sf','san francisco']]):\n", + " return \"It's sunny in San Francisco, but you better look out if you're a Gemini 😈.\"\n", + " else:\n", + " return f\"I am not sure what the weather is in {location}\"\n", + "\n", + "tools = [get_weather]\n", + "\n", + "model = model.bind_tools(tools)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define nodes and edges\n", + "\n", + "Next let's define our nodes and edges. In our basic ReAct agent there are only two nodes, one for calling the model and one for using tools, however you can modify this basic structure to work better for your use case. The tool node we define here is a simplified version of the prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/), which has some additional features.\n", + "\n", + "Perhaps you want to add a node for [adding structured output](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output/) or a node for executing some external action (sending an email, adding a calendar event, etc.). Maybe you just want to change the way the `call_model` node works and how `should_continue` decides whether to call tools - the possibilities are endless and LangGraph makes it easy to customize this basic structure for your specific use case." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from langchain_core.messages import ToolMessage, SystemMessage\n", + "from langchain_core.runnables import RunnableConfig\n", + "\n", + "tools_by_name = {tool.name: tool for tool in tools}\n", + "# Define our tool node\n", + "def tool_node(state: AgentState):\n", + " outputs = []\n", + " for tool_call in state['messages'][-1].tool_calls:\n", + " tool_result = tools_by_name[tool_call[\"name\"]].invoke(\n", + " tool_call[\"args\"]\n", + " )\n", + " outputs.append(\n", + " ToolMessage(\n", + " content=json.dumps(tool_result),\n", + " name=tool_call[\"name\"],\n", + " tool_call_id=tool_call[\"id\"],\n", + " )\n", + " )\n", + " return {\"messages\": outputs}\n", + "\n", + "# Define the node that calls the model\n", + "def call_model(\n", + " state: AgentState,\n", + " config: RunnableConfig,\n", + "):\n", + " # this is similar to customizing the create_react_agent with state_modifier, but is a lot more flexible\n", + " system_prompt = SystemMessage(\"You are a helpful AI assistant, please respond to the users query to the best of your ability!\")\n", + " response = model.invoke([system_prompt] + state['messages'], config)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the conditional edge that determines whether to continue or not\n", + "def should_continue(state: AgentState):\n", + " messages = state[\"messages\"]\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if not last_message.tool_calls:\n", + " return \"end\"\n", + " # Otherwise if there is, we continue\n", + " else:\n", + " return \"continue\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define the graph\n", + "\n", + "Now that we have defined all of our nodes and edges, we can define and compile our graph. Depending on if you have added more nodes or different edges, you will need to edit this to fit your specific use case." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "from langgraph.constants import END\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"tools\", tool_node)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"tools\",\n", + " # Otherwise we finish.\n", + " \"end\": END,\n", + " },\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge(\"tools\", \"agent\")\n", + "\n", + "# Now we can compile and visualize our graph\n", + "graph = workflow.compile()\n", + "\n", + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "except Exception:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use ReAct agent\n", + "\n", + "Now that we have created our react agent, let's actually put it to the test!" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "what is the weather in sf\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " get_weather (call_iXNCfcUUc7rkgLYbDBYkPZYM)\n", + " Call ID: call_iXNCfcUUc7rkgLYbDBYkPZYM\n", + " Args:\n", + " location: San Francisco\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: get_weather\n", + "\n", + "\"It's sunny in San Francisco, but you better look out if you're a Gemini \\ud83d\\ude08.\"\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The weather in San Francisco is sunny. However, it seems there's a playful warning for Geminis—so keep an eye out!\n" + ] + } + ], + "source": [ + "# Helper function for formatting the stream nicely\n", + "def print_stream(stream):\n", + " for s in stream:\n", + " message = s[\"messages\"][-1]\n", + " if isinstance(message, tuple):\n", + " print(message)\n", + " else:\n", + " message.pretty_print()\n", + "\n", + "inputs = {\"messages\": [(\"user\", \"what is the weather in sf\")]}\n", + "print_stream(graph.stream(inputs, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perfect! The graph correctly calls the `get_weather` tool and responds to the user after receiving the information from the tool." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}