mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-30 03:39:38 +02:00
Add prebuilt ToolNode
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user