diff --git a/examples/chat_agent_executor_with_function_calling/prebuilt-tool-node.ipynb b/examples/chat_agent_executor_with_function_calling/prebuilt-tool-node.ipynb new file mode 100644 index 000000000..00eafa2ef --- /dev/null +++ b/examples/chat_agent_executor_with_function_calling/prebuilt-tool-node.ipynb @@ -0,0 +1,622 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# Chat Agent Executor using prebuilt Tool Node\n", + "\n", + "\n", + "In this example we will build a chat executor that uses tool calling and the prebuilt ToolNode." + ] + }, + { + "cell_type": "markdown", + "id": "7cbd446a-808f-4394-be92-d45ab818953c", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First we need to install the packages required" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install --quiet -U langchain langchain_openai tavily-python" + ] + }, + { + "cell_type": "markdown", + "id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d", + "metadata": {}, + "source": [ + "Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import getpass\n", + "\n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n", + "os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")" + ] + }, + { + "cell_type": "markdown", + "id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c", + "metadata": {}, + "source": [ + "Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")" + ] + }, + { + "cell_type": "markdown", + "id": "21ac643b-cb06-4724-a80c-2862ba4773f1", + "metadata": {}, + "source": [ + "## Set up the tools\n", + "\n", + "We will first define the tools we want to use.\n", + "For this simple example, we will use a built-in search tool via Tavily.\n", + "However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.\n", + "\n", + "**MODIFICATION**\n", + "\n", + "We don't need a ToolExecutor when using ToolNode.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "tools = [TavilySearchResults(max_results=1)]" + ] + }, + { + "cell_type": "markdown", + "id": "5497ed70-fce3-47f1-9cad-46f912bad6a5", + "metadata": {}, + "source": [ + "## Set up the model\n", + "\n", + "Now we need to load the chat model we want to use.\n", + "Importantly, this should satisfy two criteria:\n", + "\n", + "1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them.\n", + "2. It should work with tool calling. This means it should be a model that implements `.bind_tools()`.\n", + "\n", + "Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(temperature=0)" + ] + }, + { + "cell_type": "markdown", + "id": "a77995c0-bae2-4cee-a036-8688a90f05b9", + "metadata": {}, + "source": [ + "\n", + "After we've done this, we should make sure the model knows that it has these tools available to call.\n", + "We can do this by converting the LangChain tools into the format for OpenAI function calling, and then bind them to the model class.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_tools(tools)" + ] + }, + { + "cell_type": "markdown", + "id": "8e8b9211-93d0-4ad5-aa7a-9c09099c53ff", + "metadata": {}, + "source": [ + "## Define the agent state\n", + "\n", + "The main type of graph in `langgraph` is the `StatefulGraph`.\n", + "This graph is parameterized by a state object that it passes around to each node.\n", + "Each node then returns operations to update that state.\n", + "These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute.\n", + "Whether to set or add is denoted by annotating the state object you construct the graph with.\n", + "\n", + "For this example, the state we will track will just be a list of messages.\n", + "We want each node to just add messages to that list.\n", + "Therefore, we will use a `TypedDict` with one key (`messages`) and annotate it so that the `messages` attribute is always added to.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ea793afa-2eab-4901-910d-6eed90cd6564", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "\n", + "\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "markdown", + "id": "e03c5094-9297-4d19-a04e-3eedc75cefb4", + "metadata": {}, + "source": [ + "## Define the nodes\n", + "\n", + "We now need to define a few different nodes in our graph.\n", + "In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).\n", + "There are two main nodes we need for this:\n", + "\n", + "1. The agent: responsible for deciding what (if any) actions to take.\n", + "2. **MODIFICATION** The prebuilt ToolNode, given the list of tools. This will take tool calls from the most recent AIMessage, execute them, and return the result as ToolMessages.\n", + "\n", + "We will also need to define some edges.\n", + "Some of these edges may be conditional.\n", + "The reason they are conditional is that based on the output of a node, one of several paths may be taken.\n", + "The path that is taken is not known until that node is run (the LLM decides).\n", + "\n", + "1. Conditional Edge: after the agent is called, we should either:\n", + " a. If the agent said to take an action, then the function to invoke tools should be called\n", + " b. If the agent said that it was finished, then it should finish\n", + "2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n", + "\n", + "Let's define the nodes, as well as a function to decide how what conditional edge to take.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt import ToolNode\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state[\"messages\"]\n", + " last_message = messages[-1]\n", + " # If there are no tool calls, 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\"\n", + "\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state[\"messages\"]\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "\n", + "# Define the function to execute tools\n", + "tool_node = ToolNode(tools)" + ] + }, + { + "cell_type": "markdown", + "id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b", + "metadata": {}, + "source": [ + "## Define the graph\n", + "\n", + "We can now put it all together and define the graph!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, 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(\"action\", 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\": \"action\",\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(\"action\", \"agent\")\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "547c3931-3dae-4281-ad4e-4b51305594d4", + "metadata": {}, + "source": [ + "## Use it!\n", + "\n", + "We can now use it!\n", + "This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/messages/ai.py:52: UserWarning: New langchain packages are available that more efficiently handle tool calling. Please upgrade your packages to versions that set message tool calls. e.g., `pip install --upgrade langchain-anthropic`, pip install--upgrade langchain-openai`, etc.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "{'messages': [HumanMessage(content='what is the weather in sf'),\n", + " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Q7l0TopyJxaly7xM9Vq2aGxO', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 87, 'total_tokens': 108}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-d39a73db-e37a-46ce-9476-73fe9eb84b2e-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_Q7l0TopyJxaly7xM9Vq2aGxO'}]),\n", + " ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1712797953, \\'localtime\\': \\'2024-04-10 18:12\\'}, \\'current\\': {\\'last_updated_epoch\\': 1712797200, \\'last_updated\\': \\'2024-04-10 18:00\\', \\'temp_c\\': 21.1, \\'temp_f\\': 70.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 10.5, \\'wind_kph\\': 16.9, \\'wind_degree\\': 300, \\'wind_dir\\': \\'WNW\\', \\'pressure_mb\\': 1017.0, \\'pressure_in\\': 30.04, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 59, \\'cloud\\': 75, \\'feelslike_c\\': 21.1, \\'feelslike_f\\': 70.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 16.0, \\'gust_kph\\': 25.8}}\"}]', name='tavily_search_results_json', tool_call_id='call_Q7l0TopyJxaly7xM9Vq2aGxO'),\n", + " AIMessage(content='The current weather in San Francisco is partly cloudy with a temperature of 21.1°C (70.0°F). The wind speed is 10.5 mph (16.9 kph) coming from the west-northwest direction. The humidity is at 59% with a visibility of 16.0 km (9.0 miles).', response_metadata={'token_usage': {'completion_tokens': 72, 'prompt_tokens': 466, 'total_tokens': 538}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-091dcb4c-9424-40fc-aab6-58968d9929a2-0')]}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "\n", + "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n", + "app.invoke(inputs)" + ] + }, + { + "cell_type": "markdown", + "id": "5a9e8155-70c5-4973-912c-dc55104b2acf", + "metadata": {}, + "source": [ + "This may take a little bit - it's making a few calls behind the scenes.\n", + "In order to start seeing some intermediate results as they happen, we can use streaming - see below for more information on that.\n", + "\n", + "## Streaming\n", + "\n", + "LangGraph has support for several different types of streaming.\n", + "\n", + "### Streaming Node Output\n", + "\n", + "One of the benefits of using LangGraph is that it is easy to stream output as it's produced by each node.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/messages/ai.py:52: UserWarning: New langchain packages are available that more efficiently handle tool calling. Please upgrade your packages to versions that set message tool calls. e.g., `pip install --upgrade langchain-anthropic`, pip install--upgrade langchain-openai`, etc.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_VgzsFE5Cf3sdlrIudeijqVsp', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 87, 'total_tokens': 108}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b3acca13-ccbf-4761-9d0c-1410d5f4f0a9-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_VgzsFE5Cf3sdlrIudeijqVsp'}])]}\n", + "\n", + "---\n", + "\n", + "Output from node 'action':\n", + "---\n", + "{'messages': [ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1712797953, \\'localtime\\': \\'2024-04-10 18:12\\'}, \\'current\\': {\\'last_updated_epoch\\': 1712797200, \\'last_updated\\': \\'2024-04-10 18:00\\', \\'temp_c\\': 21.1, \\'temp_f\\': 70.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 10.5, \\'wind_kph\\': 16.9, \\'wind_degree\\': 300, \\'wind_dir\\': \\'WNW\\', \\'pressure_mb\\': 1017.0, \\'pressure_in\\': 30.04, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 59, \\'cloud\\': 75, \\'feelslike_c\\': 21.1, \\'feelslike_f\\': 70.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 16.0, \\'gust_kph\\': 25.8}}\"}]', name='tavily_search_results_json', tool_call_id='call_VgzsFE5Cf3sdlrIudeijqVsp')]}\n", + "\n", + "---\n", + "\n", + "Output from node 'agent':\n", + "---\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is partly cloudy with a temperature of 70°F (21.1°C). The wind speed is 10.5 mph (16.9 kph) coming from the west-northwest direction. The humidity is at 59%, and the visibility is 9.0 miles.', response_metadata={'token_usage': {'completion_tokens': 65, 'prompt_tokens': 466, 'total_tokens': 531}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}, id='run-fa95b6d0-34bb-41e7-8cfd-87b6af69064f-0')]}\n", + "\n", + "---\n", + "\n" + ] + } + ], + "source": [ + "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n", + "for output in app.stream(inputs):\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\")" + ] + }, + { + "cell_type": "markdown", + "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159", + "metadata": {}, + "source": [ + "### Streaming LLM Tokens\n", + "\n", + "You can also access the LLM tokens as they are produced by each node. \n", + "In this case only the \"agent\" node produces LLM tokens.\n", + "In order for this to work properly, you must be using an LLM that supports streaming as well as have set it when constructing the LLM (e.g. `ChatOpenAI(model=\"gpt-3.5-turbo-1106\", streaming=True)`)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "cfd140f0-a5a6-4697-8115-322242f197b5", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/messages/ai.py:52: UserWarning: New langchain packages are available that more efficiently handle tool calling. Please upgrade your packages to versions that set message tool calls. e.g., `pip install --upgrade langchain-anthropic`, pip install--upgrade langchain-openai`, etc.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_TyyKAi2L0Uymjr3YW2d3ZnIm', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_TyyKAi2L0Uymjr3YW2d3ZnIm', 'error': 'Malformed args.'}] tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_TyyKAi2L0Uymjr3YW2d3ZnIm', 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '{\"', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' tool_calls=[{'name': '', 'args': {}, 'id': None}] tool_call_chunks=[{'name': None, 'args': '{\"', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'query', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': 'query', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': 'query', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '\":\"', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': '\":\"', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': '\":\"', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'weather', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': 'weather', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': 'weather', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' in', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': ' in', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': ' in', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' San', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': ' San', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': ' San', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' Francisco', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': ' Francisco', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': ' Francisco', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '\"}', 'name': None}, 'type': None}]} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1' invalid_tool_calls=[{'name': None, 'args': '\"}', 'id': None, 'error': 'Malformed args.'}] tool_call_chunks=[{'name': None, 'args': '\"}', 'id': None, 'index': 0}]\n", + "content='' response_metadata={'finish_reason': 'tool_calls'} id='run-7106791e-464e-4aa1-aaee-5d4674fe49e1'\n", + "content='' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='The' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' current' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' weather' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' in' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' San' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Francisco' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' is' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' as' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' follows' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Temperature' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='21' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='1' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='°C' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' (' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='70' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='0' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='°F' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=')\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Condition' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Part' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='ly' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' cloudy' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Wind' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='10' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='5' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' mph' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' from' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' W' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='NW' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Pressure' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='101' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='7' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='0' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' mb' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Hum' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='idity' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='59' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='%\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Cloud' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Cover' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='75' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='%\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Visibility' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='16' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='0' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' km' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' (' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='9' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='0' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' miles' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=')\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='-' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' UV' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' Index' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=':' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' ' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='5' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='0' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='\\n\\n' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='For' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' more' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' details' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=',' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' you' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' can' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' visit' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' [' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='Weather' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=' API' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='](' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='https' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='://' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='www' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.weather' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='api' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='.com' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='/' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content=').' id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n", + "content='' response_metadata={'finish_reason': 'stop'} id='run-9000bee8-10fe-4712-852b-e043aecb42ec'\n" + ] + } + ], + "source": [ + "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf?\")]}\n", + "\n", + "async for output in app.astream_log(inputs, include_types=[\"llm\"]):\n", + " # astream_log() yields the requested logs (here LLMs) in JSONPatch format\n", + " for op in output.ops:\n", + " if op[\"path\"] == \"/streamed_output/-\":\n", + " # this is the output from .stream()\n", + " ...\n", + " elif op[\"path\"].startswith(\"/logs/\") and op[\"path\"].endswith(\n", + " \"/streamed_output/-\"\n", + " ):\n", + " # because we chose to only include LLMs, these are LLM tokens\n", + " print(op[\"value\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08ae8246-11d5-40e1-8567-361e5bef8917", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langgraph/prebuilt/__init__.py b/langgraph/prebuilt/__init__.py index db31f778b..4ffffd2cd 100644 --- a/langgraph/prebuilt/__init__.py +++ b/langgraph/prebuilt/__init__.py @@ -1,10 +1,12 @@ from langgraph.prebuilt import chat_agent_executor from langgraph.prebuilt.agent_executor import create_agent_executor from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation +from langgraph.prebuilt.tool_node import ToolNode __all__ = [ "create_agent_executor", "chat_agent_executor", "ToolExecutor", "ToolInvocation", + "ToolNode", ] diff --git a/langgraph/prebuilt/chat_agent_executor.py b/langgraph/prebuilt/chat_agent_executor.py index fcd4d6806..473d46cad 100644 --- a/langgraph/prebuilt/chat_agent_executor.py +++ b/langgraph/prebuilt/chat_agent_executor.py @@ -1,18 +1,24 @@ import json -import operator from typing import Annotated, Sequence, TypedDict, Union from langchain_core.language_models import LanguageModelLike -from langchain_core.messages import BaseMessage, FunctionMessage, ToolMessage +from langchain_core.messages import BaseMessage, FunctionMessage from langchain_core.runnables import RunnableLambda from langchain_core.tools import BaseTool -from langchain_core.utils.function_calling import ( - convert_to_openai_function, - convert_to_openai_tool, -) +from langchain_core.utils.function_calling import convert_to_openai_function from langgraph.graph import END, StateGraph +from langgraph.graph.message import add_messages from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation +from langgraph.prebuilt.tool_node import ToolNode + + +# We create the AgentState that we will pass around +# This simply involves a list of messages +# We want steps to return messages to append to the list +# So we annotate the messages attribute with operator.add +class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], add_messages] def create_function_calling_executor( @@ -26,13 +32,6 @@ def create_function_calling_executor( tool_classes = tools model = model.bind(functions=[convert_to_openai_function(t) for t in tool_classes]) - # We create the AgentState that we will pass around - # This simply involves a list of messages - # We want steps to return messages to append to the list - # So we annotate the messages attribute with operator.add - class AgentState(TypedDict): - messages: Annotated[Sequence[BaseMessage], operator.add] - # Define the function that determines whether to continue or not def should_continue(state: AgentState): messages = state["messages"] @@ -135,26 +134,17 @@ def create_tool_calling_executor( model: LanguageModelLike, tools: Union[ToolExecutor, Sequence[BaseTool]] ): if isinstance(tools, ToolExecutor): - tool_executor = tools tool_classes = tools.tools else: - tool_executor = ToolExecutor(tools) tool_classes = tools - model = model.bind(tools=[convert_to_openai_tool(t) for t in tool_classes]) - - # We create the AgentState that we will pass around - # This simply involves a list of messages - # We want steps to return messages to append to the list - # So we annotate the messages attribute with operator.add - class AgentState(TypedDict): - messages: Annotated[Sequence[BaseMessage], operator.add] + model = model.bind_tools(tool_classes) # Define the function that determines whether to continue or not def should_continue(state: AgentState): messages = state["messages"] last_message = messages[-1] # If there is no function call, then we finish - if "tool_calls" not in last_message.additional_kwargs: + if not last_message.tool_calls: return "end" # Otherwise if there is, we continue else: @@ -173,57 +163,12 @@ def create_tool_calling_executor( # We return a list, because this will get added to the existing list return {"messages": [response]} - # Define the function to execute tools - def _get_actions(state: AgentState): - messages = state["messages"] - # Based on the continue condition - # we know the last message involves a tool call - last_message = messages[-1] - # We construct an AgentAction from each of the tool_calls - return ( - [ - ToolInvocation( - tool=tool_call["function"]["name"], - tool_input=json.loads(tool_call["function"]["arguments"]), - ) - for tool_call in last_message.additional_kwargs["tool_calls"] - ], - [ - tool_call["id"] - for tool_call in last_message.additional_kwargs["tool_calls"] - ], - ) - - def call_tool(state: AgentState): - actions, ids = _get_actions(state) - # We call the tool_executor and get back a response - responses = tool_executor.batch(actions) - # We use the response to create a FunctionMessage - tool_messages = [ - ToolMessage(content=str(response), tool_call_id=id) - for response, id in zip(responses, ids) - ] - # We return a list, because this will get added to the existing list - return {"messages": tool_messages} - - async def acall_tool(state: AgentState): - actions, ids = _get_actions(state) - # We call the tool_executor and get back a response - responses = await tool_executor.abatch(actions) - # We use the response to create a FunctionMessage - tool_messages = [ - ToolMessage(content=str(response), tool_call_id=id) - for response, id in zip(responses, ids) - ] - # We return a list, because this will get added to the existing list - return {"messages": tool_messages} - # Define a new graph workflow = StateGraph(AgentState) # Define the two nodes we will cycle between workflow.add_node("agent", RunnableLambda(call_model, acall_model)) - workflow.add_node("action", RunnableLambda(call_tool, acall_tool)) + workflow.add_node("action", ToolNode(tools)) # Set the entrypoint as `agent` # This means that this node is the first one called diff --git a/langgraph/prebuilt/tool_node.py b/langgraph/prebuilt/tool_node.py new file mode 100644 index 000000000..b0d2da8be --- /dev/null +++ b/langgraph/prebuilt/tool_node.py @@ -0,0 +1,96 @@ +import asyncio +import json +from typing import Any, Sequence, Union + +from langchain_core.messages import AIMessage, AnyMessage, ToolCall, ToolMessage +from langchain_core.runnables import RunnableConfig +from langchain_core.runnables.config import get_executor_for_config +from langchain_core.tools import BaseTool + +from langgraph.utils import RunnableCallable + + +def str_output(output: Any) -> str: + if isinstance(output, str): + return output + else: + try: + return json.dumps(output) + except Exception: + return str(output) + + +class ToolNode(RunnableCallable): + """ + A node that runs the tols requested in the last AIMessage. It can be used + either in StateGraph with a "messages" key or in MessageGraph. If multiple + tool calls are requested, they will be run in parallel. The output will be + a list of ToolMessages, one for each tool call. + """ + + def __init__( + self, + tools: Sequence[BaseTool], + *, + name: str = "tools", + tags: list[str] | None = None, + ) -> None: + super().__init__(self._func, self._afunc, name=name, tags=tags, trace=False) + self.tools_by_name = {tool.name: tool for tool in tools} + + def _func( + self, input: Union[list[AnyMessage], dict[str, Any]], config: RunnableConfig + ) -> Any: + if isinstance(input, list): + output_type = "list" + message: AnyMessage = input[-1] + elif messages := input.get("messages", []): + output_type = "dict" + message = messages[-1] + else: + raise ValueError("No message found in input") + + if not isinstance(message, AIMessage): + raise ValueError("Last message is not an AIMessage") + + def run_one(call: ToolCall): + output = self.tools_by_name[call["name"]].invoke(call["args"], config) + return ToolMessage( + content=str_output(output), name=call["name"], tool_call_id=call["id"] + ) + + with get_executor_for_config(config) as executor: + outputs = [*executor.map(run_one, message.tool_calls)] + if output_type == "list": + return outputs + else: + return {"messages": outputs} + + async def _afunc( + self, input: Union[list[AnyMessage], dict[str, Any]], config: RunnableConfig + ) -> Any: + if isinstance(input, list): + output_type = "list" + message: AnyMessage = input[-1] + elif messages := input.get("messages", []): + output_type = "dict" + message = messages[-1] + else: + raise ValueError("No message found in input") + + if not isinstance(message, AIMessage): + raise ValueError("Last message is not an AIMessage") + + async def run_one(call: ToolCall): + output = await self.tools_by_name[call["name"]].ainvoke( + call["args"], config + ) + return ToolMessage( + content=str_output(output), name=call["name"], tool_call_id=call["id"] + ) + + outputs = await asyncio.gather(*(run_one(call) for call in message.tool_calls)) + if output_type == "list": + return outputs + else: + return {"messages": outputs} diff --git a/poetry.lock b/poetry.lock index 7e209a01b..d7bc341f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1587,13 +1587,13 @@ extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15. [[package]] name = "langchain-core" -version = "0.1.38" +version = "0.1.42rc1" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_core-0.1.38-py3-none-any.whl", hash = "sha256:d881b2754254cb4bdb0d5bb56e5c138d032b6e75e5cb21f151b01224b322e02b"}, - {file = "langchain_core-0.1.38.tar.gz", hash = "sha256:ee8da6d061c06cce7dc22fec224b6ecbc3a8de106d6dd9f409c7fe448ea41861"}, + {file = "langchain_core-0.1.42rc1-py3-none-any.whl", hash = "sha256:2b216652f61b915ae274d1228ad45e7fc99af1d5f41bb6900aafd0636e66def5"}, + {file = "langchain_core-0.1.42rc1.tar.gz", hash = "sha256:af75525f31251d8d2889671b6051a0e2afffd355b5efefb1c59fc91545805ab8"}, ] [package.dependencies] @@ -1602,7 +1602,6 @@ langsmith = ">=0.1.0,<0.2.0" packaging = ">=23.2,<24.0" pydantic = ">=1,<3" PyYAML = ">=5.3" -requests = ">=2,<3" tenacity = ">=8.1.0,<9.0.0" [package.extras] @@ -1610,20 +1609,19 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langchain-openai" -version = "0.0.2.post1" +version = "0.1.2" description = "An integration package connecting OpenAI and LangChain" optional = false -python-versions = ">=3.8.1,<4.0" +python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_openai-0.0.2.post1-py3-none-any.whl", hash = "sha256:ba468b94c23da9d8ccefe5d5a3c1c65b4b9702292523e53acc689a9110022e26"}, - {file = "langchain_openai-0.0.2.post1.tar.gz", hash = "sha256:f8e78db4a663feeac71d9f036b9422406c199ea3ef4c97d99ff392c93530e073"}, + {file = "langchain_openai-0.1.2-py3-none-any.whl", hash = "sha256:45fab91803df22c6d5fce7c010df404569898372df5ae8cd03af50bef774d2ec"}, + {file = "langchain_openai-0.1.2.tar.gz", hash = "sha256:cd391e61bd93ab72ae24d8e1f250257d6acff6d9e455e623363b8c171533050a"}, ] [package.dependencies] -langchain-core = ">=0.1.7,<0.2" -numpy = ">=1,<2" -openai = ">=1.6.1,<2.0.0" -tiktoken = ">=0.5.2,<0.6.0" +langchain-core = ">=0.1.41,<0.2.0" +openai = ">=1.10.0,<2.0.0" +tiktoken = ">=0.5.2,<1" [[package]] name = "langchain-text-splitters" @@ -3859,4 +3857,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4.0" -content-hash = "3f31fdccb53a66dc294a53d63bcef0233f38c9909170113c44b8dc215fc77d7c" +content-hash = "a3e232b85db7332e70c88b6715a0b5716bcb424fe7e8ac7cb65057a8b93b39a4" diff --git a/pyproject.toml b/pyproject.toml index d30d581b6..54e5d1245 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ repository = "https://www.github.com/langchain-ai/langgraph" [tool.poetry.dependencies] python = ">=3.9.0,<4.0" -langchain-core = "^0.1.38" +langchain-core = "0.1.42rc1" [tool.poetry.group.test.dependencies] @@ -41,7 +41,7 @@ optional = true jupyter = "^1.0.0" langchain = "^0.1.0" langchainhub = "^0.1.14" -langchain-openai = "^0.0.2" +langchain-openai = "^0.1.2" [tool.ruff] select = [ "E", "F", "I" ] diff --git a/tests/__snapshots__/test_pregel.ambr b/tests/__snapshots__/test_pregel.ambr index aec89a615..79d9337f2 100644 --- a/tests/__snapshots__/test_pregel.ambr +++ b/tests/__snapshots__/test_pregel.ambr @@ -1146,10 +1146,10 @@ ''' # --- # name: test_message_graph[end_of_run] - '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph[end_of_run].1 - '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph[end_of_run].2 ''' @@ -1182,12 +1182,12 @@ "type": "runnable", "data": { "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" + "langgraph", + "prebuilt", + "tool_node", + "ToolNode" ], - "name": "call_tool" + "name": "tools" } }, { @@ -1257,10 +1257,10 @@ ''' # --- # name: test_message_graph[end_of_step] - '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph[end_of_step].1 - '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph[end_of_step].2 ''' @@ -1293,12 +1293,12 @@ "type": "runnable", "data": { "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" + "langgraph", + "prebuilt", + "tool_node", + "ToolNode" ], - "name": "call_tool" + "name": "tools" } }, { @@ -1517,12 +1517,12 @@ "type": "runnable", "data": { "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" + "langgraph", + "prebuilt", + "tool_node", + "ToolNode" ], - "name": "call_tool" + "name": "tools" } }, { diff --git a/tests/test_pregel.py b/tests/test_pregel.py index 6be844479..16288f9d2 100644 --- a/tests/test_pregel.py +++ b/tests/test_pregel.py @@ -25,7 +25,7 @@ from langgraph.prebuilt.chat_agent_executor import ( create_function_calling_executor, create_tool_calling_executor, ) -from langgraph.prebuilt.tool_executor import ToolExecutor +from langgraph.prebuilt.tool_node import ToolNode from langgraph.pregel import Channel, GraphRecursionError, Pregel, StateSnapshot from tests.any_str import AnyStr from tests.memory_assert import MemorySaverAssertImmutable @@ -1825,7 +1825,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: from langchain_core.messages import AIMessage, HumanMessage, ToolMessage class FakeFuntionChatModel(FakeMessagesListChatModel): - def bind_functions(self, functions: list): + def bind_tools(self, functions: list): return self @tool() @@ -1840,41 +1840,28 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: responses=[ AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": json.dumps("query"), - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], ), AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": json.dumps("another"), - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], ), AIMessage(content="answer"), ] @@ -1891,50 +1878,52 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: {"messages": [HumanMessage(content="what is weather in sf")]} ) == { "messages": [ - HumanMessage(content="what is weather in sf"), + HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"query"', - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), ), - ToolMessage(content="result for query", tool_call_id="tool_call123"), AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"another"', - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], + ), + ToolMessage( + content="result for another", + name="search_api", + tool_call_id="tool_call234", + id=AnyStr(), + ), + ToolMessage( + content="result for a third one", + name="search_api", + tool_call_id="tool_call567", + id=AnyStr(), ), - ToolMessage(content="result for another", tool_call_id="tool_call234"), - ToolMessage(content="result for a third one", tool_call_id="tool_call567"), AIMessage(content="answer", id=AnyStr()), ] } @@ -1948,18 +1937,13 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "messages": [ AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"query"', - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], id=AnyStr(), ) ] @@ -1968,7 +1952,12 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: { "action": { "messages": [ - ToolMessage(content="result for query", tool_call_id="tool_call123") + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), + ) ] } }, @@ -1977,26 +1966,18 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "messages": [ AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"another"', - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], id=AnyStr(), ) ] @@ -2006,10 +1987,16 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "action": { "messages": [ ToolMessage( - content="result for another", tool_call_id="tool_call234" + content="result for another", + name="search_api", + tool_call_id="tool_call234", + id=AnyStr(), ), ToolMessage( - content="result for a third one", tool_call_id="tool_call567" + content="result for a third one", + name="search_api", + tool_call_id="tool_call567", + id=AnyStr(), ), ] } @@ -2035,18 +2022,13 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"query"', - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], ) ] } @@ -2054,7 +2036,12 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: { "action": { "messages": [ - ToolMessage(content="result for query", tool_call_id="tool_call123") + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), + ) ] } }, @@ -2064,26 +2051,18 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"another"', - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], ) ] } @@ -2092,10 +2071,16 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "action": { "messages": [ ToolMessage( - content="result for another", tool_call_id="tool_call234" + content="result for another", + name="search_api", + tool_call_id="tool_call234", + id=AnyStr(), ), ToolMessage( - content="result for a third one", tool_call_id="tool_call567" + content="result for a third one", + name="search_api", + tool_call_id="tool_call567", + id=AnyStr(), ), ] } @@ -2156,7 +2141,7 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: {"messages": [HumanMessage(content="what is weather in sf")]} ) == { "messages": [ - HumanMessage(content="what is weather in sf"), + HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( id=AnyStr(), content="", @@ -2164,7 +2149,7 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: "function_call": {"name": "search_api", "arguments": '"query"'} }, ), - FunctionMessage(content="result for query", name="search_api"), + FunctionMessage(content="result for query", name="search_api", id=AnyStr()), AIMessage( id=AnyStr(), content="", @@ -2172,7 +2157,9 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: "function_call": {"name": "search_api", "arguments": '"another"'} }, ), - FunctionMessage(content="result for another", name="search_api"), + FunctionMessage( + content="result for another", name="search_api", id=AnyStr() + ), AIMessage(content="answer", id=AnyStr()), ] } @@ -2199,7 +2186,9 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: { "action": { "messages": [ - FunctionMessage(content="result for query", name="search_api") + FunctionMessage( + content="result for query", name="search_api", id=AnyStr() + ) ] } }, @@ -2222,7 +2211,9 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: { "action": { "messages": [ - FunctionMessage(content="result for another", name="search_api") + FunctionMessage( + content="result for another", name="search_api", id=AnyStr() + ) ] } }, @@ -2242,13 +2233,12 @@ def test_message_graph( from langchain.chat_models.fake import FakeMessagesListChatModel from langchain_community.tools import tool - from langchain_core.agents import AgentAction from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.messages import ( AIMessage, BaseMessage, - FunctionMessage, HumanMessage, + ToolMessage, ) from langchain_core.outputs import ChatGeneration, ChatResult @@ -2282,63 +2272,46 @@ def test_message_graph( responses=[ AIMessage( content="", - additional_kwargs={ - "function_call": { + tool_calls=[ + { + "id": "tool_call123", "name": "search_api", - "arguments": json.dumps("query"), + "args": {"query": "query"}, } - }, + ], id="ai1", ), AIMessage( content="", - additional_kwargs={ - "function_call": { + tool_calls=[ + { + "id": "tool_call456", "name": "search_api", - "arguments": json.dumps("another"), + "args": {"query": "another"}, } - }, + ], id="ai2", ), AIMessage(content="answer", id="ai3"), ] ) - tool_executor = ToolExecutor(tools) - # Define the function that determines whether to continue or not def should_continue(messages): last_message = messages[-1] # If there is no function call, then we finish - if "function_call" not in last_message.additional_kwargs: + if not last_message.tool_calls: return "end" # Otherwise if there is, we continue else: return "continue" - def call_tool(messages): - # Based on the continue condition - # we know the last message involves a function call - last_message = messages[-1] - # We construct an AgentAction from the function_call - action = AgentAction( - tool=last_message.additional_kwargs["function_call"]["name"], - tool_input=json.loads( - last_message.additional_kwargs["function_call"]["arguments"] - ), - log="", - ) - # We call the tool_executor and get back a response - response = tool_executor.invoke(action) - # We use the response to create a FunctionMessage - return FunctionMessage(content=str(response), name=action.tool) - # Define a new graph workflow = MessageGraph() # Define the two nodes we will cycle between workflow.add_node("agent", model) - workflow.add_node("action", call_tool) + workflow.add_node("action", ToolNode(tools)) # Set the entrypoint as `agent` # This means that this node is the first one called @@ -2386,27 +2359,37 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", # respects ids passed in ), - FunctionMessage( + ToolMessage( content="result for query", name="search_api", - id="00000000-0000-4000-8000-000000000012", + tool_call_id="tool_call123", + id="00000000-0000-4000-8000-000000000011", ), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ), - FunctionMessage( + ToolMessage( content="result for another", name="search_api", - id="00000000-0000-4000-8000-000000000022", + tool_call_id="tool_call456", + id="00000000-0000-4000-8000-000000000020", ), AIMessage(content="answer", id="ai3"), ] @@ -2415,34 +2398,48 @@ def test_message_graph( { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", ) }, { - "action": FunctionMessage( - content="result for query", - name="search_api", - id="00000000-0000-4000-8000-000000000039", - ) + "action": [ + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id="00000000-0000-4000-8000-000000000036", + ) + ] }, { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ) }, { - "action": FunctionMessage( - content="result for another", - name="search_api", - id="00000000-0000-4000-8000-000000000049", - ) + "action": [ + ToolMessage( + content="result for another", + name="search_api", + tool_call_id="tool_call456", + id="00000000-0000-4000-8000-000000000045", + ) + ] }, {"agent": AIMessage(content="answer", id="ai3")}, ] @@ -2459,9 +2456,13 @@ def test_message_graph( { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", ) }, @@ -2472,9 +2473,13 @@ def test_message_graph( HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", ), ], @@ -2484,7 +2489,7 @@ def test_message_graph( # modify ai message last_message = app_w_interrupt.get_state(config).values[-1] - last_message.additional_kwargs["function_call"]["arguments"] = '"a different query"' + last_message.tool_calls[0]["args"] = {"query": "a different query"} next_config = app_w_interrupt.update_state(config, last_message) # message was replaced instead of appended @@ -2493,13 +2498,14 @@ def test_message_graph( HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), ], next=("action",), @@ -2508,18 +2514,25 @@ def test_message_graph( assert [c for c in app_w_interrupt.stream(None, config)] == [ { - "action": FunctionMessage( - content="result for a different query", - name="search_api", - id=AnyStr(), - ) + "action": [ + ToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), + ) + ] }, { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ) }, @@ -2533,24 +2546,30 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), - FunctionMessage( + ToolMessage( content="result for a different query", name="search_api", + tool_call_id="tool_call123", id=AnyStr(), ), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ), ], @@ -2572,17 +2591,19 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), - FunctionMessage( + ToolMessage( content="result for a different query", name="search_api", + tool_call_id="tool_call123", id=AnyStr(), ), AIMessage(content="answer", id="ai2"), @@ -2602,9 +2623,13 @@ def test_message_graph( { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", ) }, @@ -2618,9 +2643,13 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"query"'} - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + } + ], id="ai1", ), ], @@ -2630,7 +2659,7 @@ def test_message_graph( # modify ai message last_message = app_w_interrupt.get_state(config).values[-1] - last_message.additional_kwargs["function_call"]["arguments"] = '"a different query"' + last_message.tool_calls[0]["args"] = {"query": "a different query"} app_w_interrupt.update_state(config, last_message) # message was replaced instead of appended @@ -2642,13 +2671,14 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), ], next=("action",), @@ -2657,18 +2687,25 @@ def test_message_graph( assert [c for c in app_w_interrupt.stream(None, config)] == [ { - "action": FunctionMessage( - content="result for a different query", - name="search_api", - id=AnyStr(), - ) + "action": [ + ToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), + ) + ] }, { "agent": AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ) }, @@ -2682,24 +2719,30 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), - FunctionMessage( + ToolMessage( content="result for a different query", name="search_api", + tool_call_id="tool_call123", id=AnyStr(), ), AIMessage( content="", - additional_kwargs={ - "function_call": {"name": "search_api", "arguments": '"another"'} - }, + tool_calls=[ + { + "id": "tool_call456", + "name": "search_api", + "args": {"query": "another"}, + } + ], id="ai2", ), ], @@ -2721,17 +2764,19 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), - FunctionMessage( + ToolMessage( content="result for a different query", name="search_api", + tool_call_id="tool_call123", id=AnyStr(), ), AIMessage(content="answer", id="ai2"), @@ -2753,17 +2798,19 @@ def test_message_graph( ), AIMessage( content="", - additional_kwargs={ - "function_call": { - "name": "search_api", - "arguments": '"a different query"', - } - }, id="ai1", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + } + ], ), - FunctionMessage( + ToolMessage( content="result for a different query", name="search_api", + tool_call_id="tool_call123", id=AnyStr(), ), AIMessage(content="answer", id="ai2"), diff --git a/tests/test_pregel_async.py b/tests/test_pregel_async.py index 9d8e0e971..ecec6c625 100644 --- a/tests/test_pregel_async.py +++ b/tests/test_pregel_async.py @@ -1956,7 +1956,7 @@ async def test_prebuilt_tool_chat() -> None: from langchain_core.messages import AIMessage, HumanMessage, ToolMessage class FakeFuntionChatModel(FakeMessagesListChatModel): - def bind_functions(self, functions: list): + def bind_tools(self, functions: list): return self @tool() @@ -1971,41 +1971,28 @@ async def test_prebuilt_tool_chat() -> None: responses=[ AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": json.dumps("query"), - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], ), AIMessage( content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": json.dumps("another"), - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], ), AIMessage(content="answer"), ] @@ -2017,50 +2004,52 @@ async def test_prebuilt_tool_chat() -> None: {"messages": [HumanMessage(content="what is weather in sf")]} ) == { "messages": [ - HumanMessage(content="what is weather in sf"), + HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"query"', - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), ), - ToolMessage(content="result for query", tool_call_id="tool_call123"), AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"another"', - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], + ), + ToolMessage( + content="result for another", + name="search_api", + tool_call_id="tool_call234", + id=AnyStr(), + ), + ToolMessage( + content="result for a third one", + name="search_api", + tool_call_id="tool_call567", + id=AnyStr(), ), - ToolMessage(content="result for another", tool_call_id="tool_call234"), - ToolMessage(content="result for a third one", tool_call_id="tool_call567"), AIMessage(content="answer", id=AnyStr()), ] } @@ -2077,18 +2066,13 @@ async def test_prebuilt_tool_chat() -> None: AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call123", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"query"', - }, - } - ] - }, + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], ) ] } @@ -2096,7 +2080,12 @@ async def test_prebuilt_tool_chat() -> None: { "action": { "messages": [ - ToolMessage(content="result for query", tool_call_id="tool_call123") + ToolMessage( + content="result for query", + name="search_api", + tool_call_id="tool_call123", + id=AnyStr(), + ) ] } }, @@ -2106,26 +2095,18 @@ async def test_prebuilt_tool_chat() -> None: AIMessage( id=AnyStr(), content="", - additional_kwargs={ - "tool_calls": [ - { - "id": "tool_call234", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"another"', - }, - }, - { - "id": "tool_call567", - "type": "function", - "function": { - "name": "search_api", - "arguments": '"a third one"', - }, - }, - ] - }, + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another"}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one"}, + }, + ], ) ] } @@ -2134,10 +2115,16 @@ async def test_prebuilt_tool_chat() -> None: "action": { "messages": [ ToolMessage( - content="result for another", tool_call_id="tool_call234" + content="result for another", + tool_call_id="tool_call234", + name="search_api", + id=AnyStr(), ), ToolMessage( - content="result for a third one", tool_call_id="tool_call567" + content="result for a third one", + tool_call_id="tool_call567", + name="search_api", + id=AnyStr(), ), ] } @@ -2193,7 +2180,7 @@ async def test_prebuilt_chat() -> None: {"messages": [HumanMessage(content="what is weather in sf")]} ) == { "messages": [ - HumanMessage(content="what is weather in sf"), + HumanMessage(content="what is weather in sf", id=AnyStr()), AIMessage( id=AnyStr(), content="", @@ -2201,7 +2188,7 @@ async def test_prebuilt_chat() -> None: "function_call": {"name": "search_api", "arguments": '"query"'} }, ), - FunctionMessage(content="result for query", name="search_api"), + FunctionMessage(content="result for query", name="search_api", id=AnyStr()), AIMessage( id=AnyStr(), content="", @@ -2209,7 +2196,9 @@ async def test_prebuilt_chat() -> None: "function_call": {"name": "search_api", "arguments": '"another"'} }, ), - FunctionMessage(content="result for another", name="search_api"), + FunctionMessage( + content="result for another", name="search_api", id=AnyStr() + ), AIMessage(content="answer", id=AnyStr()), ] } @@ -2239,7 +2228,9 @@ async def test_prebuilt_chat() -> None: { "action": { "messages": [ - FunctionMessage(content="result for query", name="search_api") + FunctionMessage( + content="result for query", name="search_api", id=AnyStr() + ) ] } }, @@ -2262,7 +2253,9 @@ async def test_prebuilt_chat() -> None: { "action": { "messages": [ - FunctionMessage(content="result for another", name="search_api") + FunctionMessage( + content="result for another", name="search_api", id=AnyStr() + ) ] } },