diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py index 684bac1d2..5954e7ef5 100644 --- a/docs/_scripts/copy_notebooks.py +++ b/docs/_scripts/copy_notebooks.py @@ -40,6 +40,7 @@ _MANUAL = { "pass-run-time-values-to-tools.ipynb", "tool-calling.ipynb", "tool-calling-errors.ipynb", + "pass-config-to-tools.ipynb", "dynamic-returning-direct.ipynb", "managing-agent-steps.ipynb", "respond-in-format.ipynb", diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 280ee167b..d98b9e255 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -59,6 +59,7 @@ These guides show how to use different streaming modes. - [How to call tools using ToolNode](tool-calling.ipynb) - [How to handle tool calling errors](tool-calling-errors.ipynb) - [How to pass graph state to tools](pass-run-time-values-to-tools.ipynb) +- [How to pass config to tools](pass-config-to-tools.ipynb) ## Other diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 998d78fbc..daf74c894 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -156,6 +156,7 @@ nav: - Call tools using ToolNode: how-tos/tool-calling.ipynb - Handle tool calling errors: how-tos/tool-calling-errors.ipynb - Pass graph state to tools: how-tos/pass-run-time-values-to-tools.ipynb + - Pass config to tools: how-tos/pass-config-to-tools.ipynb - Other: - Run graph asynchronously: how-tos/async.ipynb - Visualize your graph: how-tos/visualization.ipynb diff --git a/examples/pass-config-to-tools.ipynb b/examples/pass-config-to-tools.ipynb new file mode 100644 index 000000000..c36344fcb --- /dev/null +++ b/examples/pass-config-to-tools.ipynb @@ -0,0 +1,417 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to pass config to tools" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You may need to pass values to a tool that are only known at runtime. For example, the tool logic may require using the ID of the user who made the request.\n", + "\n", + "Most of the time, such values should not be controlled by the LLM. In fact, allowing the LLM to control the user ID may lead to a security risk.\n", + "\n", + "Instead, the LLM should only control the parameters of the tool that are meant to be controlled by the LLM, while other parameters (such as user ID) should be fixed by the application logic.\n", + "\n", + "To pass run time information, we will use tools that leverage the LangChain Runnable interface. The standard runnables methods (invoke, batch, stream etc.) accept a 2nd argument which is a RunnableConfig. RunnableConfig has a few standard fields, but allows users to use other fields for run time information.\n", + "\n", + "Here, we will show how to set up a simple agent that has access to three tools for saving, reading, and deleting a list of the user's favorite pets." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph langchain_anthropic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "ANTHROPIC_API_KEY: ········\n" + ] + } + ], + "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(\"ANTHROPIC_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define tools and model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "from langchain_core.tools import tool\n", + "from langchain_core.runnables.config import RunnableConfig\n", + "\n", + "from langgraph.prebuilt import ToolNode\n", + "\n", + "user_to_pets = {}\n", + "\n", + "\n", + "@tool(parse_docstring=True)\n", + "def update_favorite_pets(\n", + " # NOTE: config arg does not need to be added to docstring, as we don't want it to be included in the function signature attached to the LLM\n", + " pets: List[str], config: RunnableConfig\n", + ") -> None:\n", + " \"\"\"Add the list of favorite pets.\n", + "\n", + " Args:\n", + " pets: List of favorite pets to set.\n", + " \"\"\"\n", + " user_id = config.get(\"configurable\", {}).get(\"user_id\")\n", + " user_to_pets[user_id] = pets\n", + "\n", + "\n", + "@tool\n", + "def delete_favorite_pets(config: RunnableConfig) -> None:\n", + " \"\"\"Delete the list of favorite pets.\"\"\"\n", + " user_id = config.get(\"configurable\", {}).get(\"user_id\")\n", + " if user_id in user_to_pets:\n", + " del user_to_pets[user_id]\n", + "\n", + "\n", + "@tool\n", + "def list_favorite_pets(config: RunnableConfig) -> None:\n", + " \"\"\"List favorite pets if any.\"\"\"\n", + " user_id = config.get(\"configurable\", {}).get(\"user_id\")\n", + " return \", \".join(user_to_pets.get(user_id, []))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "tools = [update_favorite_pets, delete_favorite_pets, list_favorite_pets]\n", + "tool_node = ToolNode(tools)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll be using a small chat model from Anthropic in our example. To use chat models with tool calling, we need to first ensure that the model is aware of the available tools. We do this by calling `.bind_tools` method on `ChatAnthropic` moodel" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_anthropic import ChatAnthropic\n", + "from langgraph.graph import StateGraph, MessagesState\n", + "from langgraph.prebuilt import ToolNode\n", + "\n", + "\n", + "model_with_tools = ChatAnthropic(\n", + " model=\"claude-3-haiku-20240307\", temperature=0\n", + ").bind_tools(tools)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ReAct Agent" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's set up a graph implementation of the [ReAct agent](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#react-agent). This agent takes some query as input, then repeatedly call tools until it has enough information to resolve the query. We'll be using prebuilt `ToolNode` and the Anthropic model with tools we just defined" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal\n", + "\n", + "from langgraph.graph import StateGraph, MessagesState\n", + "\n", + "\n", + "def should_continue(state: MessagesState) -> Literal[\"tools\", \"__end__\"]:\n", + " messages = state[\"messages\"]\n", + " last_message = messages[-1]\n", + " if last_message.tool_calls:\n", + " return \"tools\"\n", + " return \"__end__\"\n", + "\n", + "\n", + "def call_model(state: MessagesState):\n", + " messages = state[\"messages\"]\n", + " response = model_with_tools.invoke(messages)\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "workflow = StateGraph(MessagesState)\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", + "workflow.add_edge(\"__start__\", \"agent\")\n", + "workflow.add_conditional_edges(\n", + " \"agent\",\n", + " should_continue,\n", + ")\n", + "workflow.add_edge(\"tools\", \"agent\")\n", + "\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.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 it!" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User information prior to run: {}\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content=[{'text': \"Okay, let's update your favorite pets:\", 'type': 'text'}, {'id': 'toolu_01LQK6fgtAyEo3xBfzg1fSuv', 'input': {'pets': ['cats', 'dogs']}, 'name': 'update_favorite_pets', 'type': 'tool_use'}], response_metadata={'id': 'msg_014bUFindzuzqqGmNVPX67zH', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 438, 'output_tokens': 70}}, id='run-2c77cfe0-ba1f-4cd5-922c-614330368ca3-0', tool_calls=[{'name': 'update_favorite_pets', 'args': {'pets': ['cats', 'dogs']}, 'id': 'toolu_01LQK6fgtAyEo3xBfzg1fSuv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 438, 'output_tokens': 70, 'total_tokens': 508})]}\n", + "\n", + "---\n", + "\n", + "Output from node 'tools':\n", + "---\n", + "{'messages': [ToolMessage(content='null', name='update_favorite_pets', tool_call_id='toolu_01LQK6fgtAyEo3xBfzg1fSuv')]}\n", + "\n", + "---\n", + "\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content='Your favorite pets have been updated to cats and dogs.', response_metadata={'id': 'msg_01JyfYdPiFHEPyE5PGeBXxqu', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 521, 'output_tokens': 15}}, id='run-c78b8fce-9358-4823-ac6c-896714860af2-0', usage_metadata={'input_tokens': 521, 'output_tokens': 15, 'total_tokens': 536})]}\n", + "\n", + "---\n", + "\n", + "User information after the run: {'123': ['cats', 'dogs']}\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "\n", + "user_to_pets.clear() # Clear the state\n", + "\n", + "print(f\"User information prior to run: {user_to_pets}\")\n", + "\n", + "inputs = {\"messages\": [HumanMessage(content=\"my favorite pets are cats and dogs\")]}\n", + "for output in app.stream(inputs, {\"configurable\": {\"user_id\": \"123\"}}):\n", + " # stream() yields dictionaries with output keyed by node name\n", + " for key, value in output.items():\n", + " print(f\"Output from node '{key}':\")\n", + " print(\"---\")\n", + " print(value)\n", + " print(\"\\n---\\n\")\n", + "\n", + "print(f\"User information after the run: {user_to_pets}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User information prior to run: {'123': ['cats', 'dogs']}\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content=[{'id': 'toolu_01EsSgrDZ8aRZsg9y7ngroiu', 'input': {}, 'name': 'list_favorite_pets', 'type': 'tool_use'}], response_metadata={'id': 'msg_01Dp1VYH5RssYbReL6KzPfNM', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 437, 'output_tokens': 38}}, id='run-c620472c-ac52-488a-90f5-141fa65f1ce9-0', tool_calls=[{'name': 'list_favorite_pets', 'args': {}, 'id': 'toolu_01EsSgrDZ8aRZsg9y7ngroiu', 'type': 'tool_call'}], usage_metadata={'input_tokens': 437, 'output_tokens': 38, 'total_tokens': 475})]}\n", + "\n", + "---\n", + "\n", + "Output from node 'tools':\n", + "---\n", + "{'messages': [ToolMessage(content='cats, dogs', name='list_favorite_pets', tool_call_id='toolu_01EsSgrDZ8aRZsg9y7ngroiu')]}\n", + "\n", + "---\n", + "\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content='Based on the output, your favorite pets are cats and dogs.', response_metadata={'id': 'msg_017heQczfgTMCzAo5qcYdYWW', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 490, 'output_tokens': 17}}, id='run-c0cb9626-61a1-4151-b194-be0e7d655a8d-0', usage_metadata={'input_tokens': 490, 'output_tokens': 17, 'total_tokens': 507})]}\n", + "\n", + "---\n", + "\n", + "User information after the run: {'123': ['cats', 'dogs']}\n" + ] + } + ], + "source": [ + "print(f\"User information prior to run: {user_to_pets}\")\n", + "\n", + "\n", + "inputs = {\"messages\": [HumanMessage(content=\"what are my favorite pets?\")]}\n", + "for output in app.stream(inputs, {\"configurable\": {\"user_id\": \"123\"}}):\n", + " # stream() yields dictionaries with output keyed by node name\n", + " for key, value in output.items():\n", + " print(f\"Output from node '{key}':\")\n", + " print(\"---\")\n", + " print(value)\n", + " print(\"\\n---\\n\")\n", + "\n", + "\n", + "print(f\"User information after the run: {user_to_pets}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User information prior to run: {'123': ['cats', 'dogs']}\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content=[{'id': 'toolu_01EcVWNpWQnoRuRtXXbndeWn', 'input': {}, 'name': 'delete_favorite_pets', 'type': 'tool_use'}], response_metadata={'id': 'msg_01PfMPkCHuV1UvcCKdqT5jXH', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 441, 'output_tokens': 38}}, id='run-eeac69b6-812e-4630-ba6f-9b22b672493b-0', tool_calls=[{'name': 'delete_favorite_pets', 'args': {}, 'id': 'toolu_01EcVWNpWQnoRuRtXXbndeWn', 'type': 'tool_call'}], usage_metadata={'input_tokens': 441, 'output_tokens': 38, 'total_tokens': 479})]}\n", + "\n", + "---\n", + "\n", + "Output from node 'tools':\n", + "---\n", + "{'messages': [ToolMessage(content='null', name='delete_favorite_pets', tool_call_id='toolu_01EcVWNpWQnoRuRtXXbndeWn')]}\n", + "\n", + "---\n", + "\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content='I have deleted the information about your favorite pets. The list of favorite pets has been cleared.', response_metadata={'id': 'msg_01PvNPmzfgSvGdWQp6ATWs6Q', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 492, 'output_tokens': 23}}, id='run-0cb06dbb-7d6c-4aa1-9ba7-685b0de62e06-0', usage_metadata={'input_tokens': 492, 'output_tokens': 23, 'total_tokens': 515})]}\n", + "\n", + "---\n", + "\n", + "User information prior to run: {}\n" + ] + } + ], + "source": [ + "print(f\"User information prior to run: {user_to_pets}\")\n", + "\n", + "\n", + "inputs = {\n", + " \"messages\": [\n", + " HumanMessage(content=\"please forget what i told you about my favorite animals\")\n", + " ]\n", + "}\n", + "for output in app.stream(inputs, {\"configurable\": {\"user_id\": \"123\"}}):\n", + " # stream() yields dictionaries with output keyed by node name\n", + " for key, value in output.items():\n", + " print(f\"Output from node '{key}':\")\n", + " print(\"---\")\n", + " print(value)\n", + " print(\"\\n---\\n\")\n", + "\n", + "\n", + "print(f\"User information prior to run: {user_to_pets}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "langgraph", + "language": "python", + "name": "langgraph" + }, + "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": 4 +}