mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-30 11:49:38 +02:00
Also, remove comment from bash script that makes insertion of `uv` harder
464 lines
16 KiB
Plaintext
464 lines
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# How to create a ReAct agent from scratch (Functional API)\n",
|
|
"\n",
|
|
"!!! info \"Prerequisites\"\n",
|
|
" This guide assumes familiarity with the following:\n",
|
|
" \n",
|
|
" - [Chat Models](https://python.langchain.com/docs/concepts/chat_models)\n",
|
|
" - [Messages](https://python.langchain.com/docs/concepts/messages)\n",
|
|
" - [Tool Calling](https://python.langchain.com/docs/concepts/tool_calling/)\n",
|
|
" - [Entrypoints](../../concepts/functional_api/#entrypoint) and [Tasks](../../concepts/functional_api/#task)\n",
|
|
"\n",
|
|
"This guide demonstrates how to implement a ReAct agent using the LangGraph [Functional API](../../concepts/functional_api).\n",
|
|
"\n",
|
|
"The ReAct agent is a [tool-calling agent](../../concepts/agentic_concepts/#tool-calling-agent) that operates as follows:\n",
|
|
"\n",
|
|
"1. Queries are issued to a chat model;\n",
|
|
"2. If the model generates no [tool calls](../../concepts/agentic_concepts/#tool-calling), we return the model response.\n",
|
|
"3. If the model generates tool calls, we execute the tool calls with available tools, append them as [tool messages](https://python.langchain.com/docs/concepts/messages/) to our message list, and repeat the process.\n",
|
|
"\n",
|
|
"This is a simple and versatile set-up that can be extended with memory, human-in-the-loop capabilities, and other features. See the dedicated [how-to guides](../../how-tos/#prebuilt-react-agent) for examples.\n",
|
|
"\n",
|
|
"## Setup\n",
|
|
"\n",
|
|
"First, let's install the required packages and set our API keys:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%capture --no-stderr\n",
|
|
"%pip install -U langgraph langchain-openai"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"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": [
|
|
"<div class=\"admonition tip\">\n",
|
|
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for better debugging</p>\n",
|
|
" <p style=\"padding-top: 5px;\">\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 <a href=\"https://docs.smith.langchain.com\">docs</a>. \n",
|
|
" </p>\n",
|
|
" </div>"
|
|
]
|
|
},
|
|
{
|
|
"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 create our agent.\n",
|
|
"\n",
|
|
"### Define model and tools\n",
|
|
"\n",
|
|
"Let's first define the tools and model we will use for our example. Here we will use a single place-holder tool that gets a description of the weather for a location.\n",
|
|
"\n",
|
|
"We will use an [OpenAI](https://python.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://python.langchain.com/docs/integrations/chat/) will suffice."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"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",
|
|
"\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",
|
|
" if any([city in location.lower() for city in [\"sf\", \"san francisco\"]]):\n",
|
|
" return \"It's sunny!\"\n",
|
|
" elif \"boston\" in location.lower():\n",
|
|
" return \"It's rainy!\"\n",
|
|
" else:\n",
|
|
" return f\"I am not sure what the weather is in {location}\"\n",
|
|
"\n",
|
|
"\n",
|
|
"tools = [get_weather]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Define tasks\n",
|
|
"\n",
|
|
"We next define the [tasks](../../concepts/functional_api/#task) we will execute. Here there are two different tasks:\n",
|
|
"\n",
|
|
"1. **Call model**: We want to query our chat model with a list of messages.\n",
|
|
"2. **Call tool**: If our model generates tool calls, we want to execute them."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.messages import ToolMessage\n",
|
|
"from langgraph.func import entrypoint, task\n",
|
|
"\n",
|
|
"tools_by_name = {tool.name: tool for tool in tools}\n",
|
|
"\n",
|
|
"\n",
|
|
"@task\n",
|
|
"def call_model(messages):\n",
|
|
" \"\"\"Call model with a sequence of messages.\"\"\"\n",
|
|
" response = model.bind_tools(tools).invoke(messages)\n",
|
|
" return response\n",
|
|
"\n",
|
|
"\n",
|
|
"@task\n",
|
|
"def call_tool(tool_call):\n",
|
|
" tool = tools_by_name[tool_call[\"name\"]]\n",
|
|
" observation = tool.invoke(tool_call[\"args\"])\n",
|
|
" return ToolMessage(content=observation, tool_call_id=tool_call[\"id\"])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Define entrypoint\n",
|
|
"\n",
|
|
"Our [entrypoint](../../concepts/functional_api/#entrypoint) will handle the orchestration of these two tasks. As described above, when our `call_model` task generates tool calls, the `call_tool` task will generate responses for each. We append all messages to a single messages list.\n",
|
|
"\n",
|
|
"!!! tip\n",
|
|
" Note that because tasks return future-like objects, the below implementation executes tools in parallel."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langgraph.graph.message import add_messages\n",
|
|
"\n",
|
|
"\n",
|
|
"@entrypoint()\n",
|
|
"def agent(messages):\n",
|
|
" llm_response = call_model(messages).result()\n",
|
|
" while True:\n",
|
|
" if not llm_response.tool_calls:\n",
|
|
" break\n",
|
|
"\n",
|
|
" # Execute tools\n",
|
|
" tool_result_futures = [\n",
|
|
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
|
|
" ]\n",
|
|
" tool_results = [fut.result() for fut in tool_result_futures]\n",
|
|
"\n",
|
|
" # Append to message list\n",
|
|
" messages = add_messages(messages, [llm_response, *tool_results])\n",
|
|
"\n",
|
|
" # Call model again\n",
|
|
" llm_response = call_model(messages).result()\n",
|
|
"\n",
|
|
" return llm_response"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Usage\n",
|
|
"\n",
|
|
"To use our agent, we invoke it with a messages list. Based on our implementation, these can be LangChain [message](https://python.langchain.com/docs/concepts/messages/) objects or OpenAI-style dicts:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"{'role': 'user', 'content': \"What's the weather in san francisco?\"}\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"Tool Calls:\n",
|
|
" get_weather (call_tNnkrjnoz6MNfCHJpwfuEQ0v)\n",
|
|
" Call ID: call_tNnkrjnoz6MNfCHJpwfuEQ0v\n",
|
|
" Args:\n",
|
|
" location: san francisco\n",
|
|
"\n",
|
|
"call_tool:\n",
|
|
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
|
"\n",
|
|
"It's sunny!\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"The weather in San Francisco is sunny!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"user_message = {\"role\": \"user\", \"content\": \"What's the weather in san francisco?\"}\n",
|
|
"print(user_message)\n",
|
|
"\n",
|
|
"for step in agent.stream([user_message]):\n",
|
|
" for task_name, message in step.items():\n",
|
|
" if task_name == \"agent\":\n",
|
|
" continue # Just print task updates\n",
|
|
" print(f\"\\n{task_name}:\")\n",
|
|
" message.pretty_print()"
|
|
]
|
|
},
|
|
{
|
|
"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. Check out the LangSmith trace [here](https://smith.langchain.com/public/d5a0d5ea-bdaa-4032-911e-7db177c8141b/r)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Add thread-level persistence\n",
|
|
"\n",
|
|
"Adding [thread-level persistence](../../concepts/persistence#threads) lets us support conversational experiences with our agent: subsequent invocations will append to the prior messages list, retaining the full conversational context.\n",
|
|
"\n",
|
|
"To add thread-level persistence to our agent:\n",
|
|
"\n",
|
|
"1. Select a [checkpointer](../../concepts/persistence#checkpointer-libraries): here we will use [InMemorySaver](../../reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver), a simple in-memory checkpointer.\n",
|
|
"2. Update our entrypoint to accept the previous messages state as a second argument. Here, we simply append the message updates to the previous sequence of messages.\n",
|
|
"3. Choose which values will be returned from the workflow and which will be saved by the checkpointer as `previous` using `entrypoint.final` (optional)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
|
"\n",
|
|
"# highlight-next-line\n",
|
|
"checkpointer = InMemorySaver()\n",
|
|
"\n",
|
|
"\n",
|
|
"# highlight-next-line\n",
|
|
"@entrypoint(checkpointer=checkpointer)\n",
|
|
"# highlight-next-line\n",
|
|
"def agent(messages, previous):\n",
|
|
" # highlight-next-line\n",
|
|
" if previous is not None:\n",
|
|
" # highlight-next-line\n",
|
|
" messages = add_messages(previous, messages)\n",
|
|
"\n",
|
|
" llm_response = call_model(messages).result()\n",
|
|
" while True:\n",
|
|
" if not llm_response.tool_calls:\n",
|
|
" break\n",
|
|
"\n",
|
|
" # Execute tools\n",
|
|
" tool_result_futures = [\n",
|
|
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
|
|
" ]\n",
|
|
" tool_results = [fut.result() for fut in tool_result_futures]\n",
|
|
"\n",
|
|
" # Append to message list\n",
|
|
" messages = add_messages(messages, [llm_response, *tool_results])\n",
|
|
"\n",
|
|
" # Call model again\n",
|
|
" llm_response = call_model(messages).result()\n",
|
|
"\n",
|
|
" # Generate final response\n",
|
|
" messages = add_messages(messages, llm_response)\n",
|
|
" # highlight-next-line\n",
|
|
" return entrypoint.final(value=llm_response, save=messages)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We will now need to pass in a config when running our application. The config will specify an identifier for the conversational thread.\n",
|
|
"\n",
|
|
"!!! tip\n",
|
|
"\n",
|
|
" Read more about thread-level persistence in our [concepts page](../../concepts/persistence/) and [how-to guides](../../how-tos/#persistence)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We start a thread the same way as before, this time passing in the config:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"{'role': 'user', 'content': \"What's the weather in san francisco?\"}\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"Tool Calls:\n",
|
|
" get_weather (call_lubbUSdDofmOhFunPEZLBz3g)\n",
|
|
" Call ID: call_lubbUSdDofmOhFunPEZLBz3g\n",
|
|
" Args:\n",
|
|
" location: San Francisco\n",
|
|
"\n",
|
|
"call_tool:\n",
|
|
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
|
"\n",
|
|
"It's sunny!\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"The weather in San Francisco is sunny!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"user_message = {\"role\": \"user\", \"content\": \"What's the weather in san francisco?\"}\n",
|
|
"print(user_message)\n",
|
|
"\n",
|
|
"# highlight-next-line\n",
|
|
"for step in agent.stream([user_message], config):\n",
|
|
" for task_name, message in step.items():\n",
|
|
" if task_name == \"agent\":\n",
|
|
" continue # Just print task updates\n",
|
|
" print(f\"\\n{task_name}:\")\n",
|
|
" message.pretty_print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"When we ask a follow-up conversation, the model uses the prior context to infer that we are asking about the weather:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"{'role': 'user', 'content': 'How does it compare to Boston, MA?'}\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"Tool Calls:\n",
|
|
" get_weather (call_8sTKYAhSIHOdjLD5d6gaswuV)\n",
|
|
" Call ID: call_8sTKYAhSIHOdjLD5d6gaswuV\n",
|
|
" Args:\n",
|
|
" location: Boston, MA\n",
|
|
"\n",
|
|
"call_tool:\n",
|
|
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
|
"\n",
|
|
"It's rainy!\n",
|
|
"\n",
|
|
"call_model:\n",
|
|
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
|
"\n",
|
|
"Compared to San Francisco, which is sunny, Boston, MA is experiencing rainy weather.\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"user_message = {\"role\": \"user\", \"content\": \"How does it compare to Boston, MA?\"}\n",
|
|
"print(user_message)\n",
|
|
"\n",
|
|
"for step in agent.stream([user_message], config):\n",
|
|
" for task_name, message in step.items():\n",
|
|
" if task_name == \"agent\":\n",
|
|
" continue # Just print task updates\n",
|
|
" print(f\"\\n{task_name}:\")\n",
|
|
" message.pretty_print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"In the [LangSmith trace](https://smith.langchain.com/public/20a1116b-bb3b-44c1-8765-7a28663439d9/r), we can see that the full conversational context is retained in each model call."
|
|
]
|
|
}
|
|
],
|
|
"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.12.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|