Add prebuilt ToolNode

This commit is contained in:
Nuno Campos
2024-04-10 18:15:19 -07:00
parent 8788a6adfb
commit 7dafc09a5b
9 changed files with 1201 additions and 498 deletions
@@ -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
}
+2
View File
@@ -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",
]
+15 -70
View File
@@ -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
+96
View File
@@ -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}
Generated
+11 -13
View File
@@ -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"
+2 -2
View File
@@ -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" ]
File diff suppressed because one or more lines are too long
+332 -285
View File
@@ -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"),
+102 -109
View File
@@ -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()
)
]
}
},