Learning LangGraph from Scratch: Using ToolNode (Part 3)

Previous Content

Learning LangGraph from Scratch – (1) Introduction to LangGraph

Learning LangGraph – Tool Invocation in LangGraph (Part 2)

In the previous chapters, we defined a tool invocation class ourselves. However, through the official documentation, we found that there is a ToolNode class that can simplify the tool invocation process. In this article, we will look at how this class is used.

class BasicToolNode:  
    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}  

    def __call__(self, inputs: State):
        messages = inputs.get("messages", [])  
        if messages:  
            message = messages[-1]  
        else:  
            raise ValueError("No message found in input")  
        outputs = []  
        for tool_call in message.tool_calls:  
            tool_result = self.tools_by_name[tool_call["name"].invoke(  
                tool_call["args"]  
            )  
            outputs.append(  
                ToolMessage(  
                    content=json.dumps(tool_result),  
                    name=tool_call["name"],  
                    tool_call_id=tool_call["id"],  
                )  
            )  
        return {"messages": outputs}  

This class needs to be implemented by us. LangGraph considers the large number of tool invocation scenarios, so it provides the ToolNode class to simplify client tool invocation. Let’s see how it works. The tools used are still the Gaode weather query tool from the previous chapter.

from langchain_core.tools import BaseTool
import requests

class GaoDeWeather(BaseTool):
    """  
    Gaode Weather Query  
    """    
    name: str = "Gaode Weather Query"
    description: str = "Gaode weather query, input city name, returns the weather conditions of that city, e.g., Beijing"
    args_schema: Type[BaseModel] = GaoDeWeatherInput  
    return_direct: bool = True
    api_key: str  

    def _run(self, city):
        s = requests.session()  
        api_domain = 'https://restapi.amap.com/v3'
        url = f"{api_domain}/config/district?keywords={city}"f"&subdistrict=0&extensions=base&key={self.api_key}"
        headers = {"Content-Type": "application/json; charset=utf-8"}  
        city_response = s.request(method='GET', headers=headers, url=url)  
        City_data = city_response.json()  
        if city_response.status_code != 200or City_data.get('info') != 'OK':  
            return"City code not found"
        if len(City_data.get('districts')) <= 0:  
            return"City code not found"
        CityCode = City_data['districts'][0]['adcode']  
        weather_url = f"{api_domain}/weather/weatherInfo?city={CityCode}&extensions=all&key={self.api_key}"
        weatherInfo_response = s.request(method='GET', url=weather_url)  
        weatherInfo_data = weatherInfo_response.json()  
        if weatherInfo_response.status_code != 200or weatherInfo_data.get('info') != 'OK':  
            return"Failed to query weather information"
        contents = []  
        if len(weatherInfo_data.get('forecasts')) <= 0:  
            return"No weather information found for this city"
        for item in weatherInfo_data['forecasts'][0]['casts']:  
            content = {  
                'date': item.get('date'),  
                'week': item.get('week'),  
                'dayweather': item.get('dayweather'),  
                'daytemp_float': item.get('daytemp_float'),  
                'daywind': item.get('daywind'),  
                'nightweather': item.get('nightweather'),  
                'nighttemp_float': item.get('nighttemp_float')  
            }  
            contents.append(content)  
        s.close()  
        return contents

1. Manual Tool Invocation

from langgraph.prebuilt import ToolNode
weather_tool = GaoDeWeather(api_key='xxxx')
tools = [weather_tool]
toolnode = ToolNode(tools)

ai_message = AIMessage(  
    content="",  
    tool_calls=[  
        {  
            "name": "Gaode Weather Query",  
            "args": {  
                "city": "Shanghai"
            },  
            "id": "call_e8c34802b3904571bfeee7",  
            "type": "tool_call"
        }  
    ]  
)  

result = toolnode.invoke({"messages": [ai_message]})  
print(result)

First, use<span>weather_tool = GaoDeWeather(api_key='xxxx')</span> to define a weather query tool object, and then put this object into a list,<span>tools = [weather_tool]</span>. Currently, this list only has one tool; we can add more tools later, such as the tavily search tool used in the previous article.

Then use<span>toolnode = ToolNode(tools)</span> to define a<span>ToolNode</span> object, which requires a list of tools to be passed in during initialization.

Next, we manually define an<span>AIMessage</span> object, passing in the<span>tool_calls</span> attribute, which is a list containing the names and parameters needed for tool invocation. Currently, this<span>AIMessage</span> is user-defined and will later be generated by a large model.

After defining the<span>AIMessage</span> object, use<span>toolnode.invoke({"messages": [ai_message]})</span> to actually call the tool. At this point, we do not need to manually parse the tool_calls parameter or call the tools ourselves, as these operations are fully implemented by the<span>ToolNode</span> class.

The final result is

{'messages': [ToolMessage(content='[{"date": "2024-12-17", "week": "2", "dayweather": "Sunny", "daytemp_float": "13.0", "daywind": "North", "nightweather": "Sunny", "nighttemp_float": "4.0"}, {"date": "2024-12-18", "week": "3", "dayweather": "Sunny", "daytemp_float": "8.0", "daywind": "North", "nightweather": "Sunny", "nighttemp_float": "4.0"}, {"date": "2024-12-19", "week": "4", "dayweather": "Cloudy", "daytemp_float": "8.0", "daywind": "North", "nightweather": "Overcast", "nighttemp_float": "5.0"}, {"date": "2024-12-20", "week": "5", "dayweather": "Cloudy", "daytemp_float": "10.0", "daywind": "North", "nightweather": "Sunny", "nighttemp_float": "4.0"}]', name='Gaode Weather Query', tool_call_id='call_e8c34802b3904571bfeee7')]}<br/>

2. Multiple Tool Invocation

The above code shows how ToolNode performs tool invocation. Note that the<span>tool_calls</span> in the AIMessage class is a list, which means that the actual large model’s returned tool calls may be multiple. Let’s combine it with the tavily tool to see how multiple tools are invoked.

from langchain_community.tools import TavilySearchResults

# Initialize two tools
weather_tool = GaoDeWeather(api_key='xxxx')  
tavily_tool = TavilySearchResults(max_results=2)  

tools = [weather_tool, tavily_tool]  

# Initialize ToolNode object
toolnode = ToolNode(tools)  

# Define two tool calls in the tool_calls of AIMessage
ai_message = AIMessage(  
    content="",  
    tool_calls=[  
        {  
            "name": "Gaode Weather Query",  
            "args": {  
                "city": "Shanghai"
            },  
            "id": "call_e8c34802b3904571bfeee7",  
            "type": "tool_call"
        },  
        {  
            "name": "tavily_search_results_json",  
            "args": {  
                "query": "New features of Tesla Model Y"
            },  
            "id": "call_e8c34802b3904571bfeee7",  
            "type": "tool_call"
        }  
    ]  
)  

result = toolnode.invoke({"messages": [ai_message]})  
print(result)

The code above defines two tools, weather_tool and tavily_tool, and defines two tool calls in the tool_calls of AIMessage. Finally, using<span>toolnode.invoke({"messages": [ai_message]})</span> to actually perform the tool calls, at which point ToolNode will automatically invoke based on the tool call information in tool_calls. This returns two results, the code above returns

{'messages': [ToolMessage(content='[{"date": "2024-12-17", "week": "2", "dayweather": "Sunny", "daytemp_float": "13.0", "daywind": "North", ....}]', name='Gaode Weather Query', tool_call_id='call_e8c34802b3904571bfeee7'),  
 
 ToolMessage(content='[{"url": "https://news.qq.com/rain/a/20241025A05IIC00", "content": "The new Model Y brings seven killer features! Can it meet the tastes of the Chinese people?_Tencent News More browsing: The new Model Y brings seven killer features! Can it meet the tastes of the Chinese people? ....'response_time': 2.75})]}<br/>

3. Large Model Tool Invocation

The above AIMessage is manually written, where the tool_calls field is simulated according to the definition of tool_calls in AIMessage. The actual system should be generated by a large model, and the structure is the same. In this section, we will use the large model to perform tool_calls.

# Initialize two tools
weather_tool = GaoDeWeather(api_key='xxx')  
tavily_tool = TavilySearchResults(max_results=2)  

tools = [weather_tool, tavily_tool]

# Initialize ToolNode
toolnode = ToolNode(tools)  

# Initialize llm  
llm = ChatOpenAI(  
    model_name="qwen-turbo",  
    temperature=0.7,  
    max_tokens=1024,  
    top_p=1,  
    openai_api_key="sk-xxxx",  
    openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)  

# Bind tools to the large model
llm_with_tools = llm.bind_tools(tools) 

# Initialize user question
messages = [  
    HumanMessage(content="I need to go to Hangzhou tonight, what should I wear?")  
]  
ai_message = llm_with_tools.invoke(messages)  
messages.append(ai_message)  
if ai_message.tool_calls:  
    tool_result = toolnode.invoke({"messages": [ai_message]})  
    messages.extend(tool_result.get("messages"))  
    # Call the large model again  
    final_result = llm_with_tools.invoke(messages)  
    print(final_result.content)  
else:
 print(ai_message.content)

The code above uses the large model to perform analysis and invocation.

  1. First, use<span>ai_message = llm_with_tools.invoke(messages)</span> to obtain the large model’s tool invocation analysis
  2. If there are tool_calls in the large model’s return value, use<span>toolnode.invoke({"messages": [ai_message]})</span> to perform the tool calls
  3. Obtain the tool invocation result, and request the large model again for the final summary answer

The output obtained from the code above is

The weather forecast for tonight in Hangzhou shows that the temperature will be relatively low in the next few days. Specifically, today’s temperature will be around 3°C, while the nighttime temperatures over the next few days will range between 1°C and 7°C. Therefore, it is recommended that you wear warm clothes, such as sweaters and thick coats, and consider pairing them with a scarf and gloves to cope with the lower temperatures. It is also best to bring rain gear in case of sudden rain or fog.

4. Using ToolNode in LangGraph

Having understood the operation principle of ToolNode, we can quickly use LangGraph to build a running graph.

from typing import Type, TypedDict, Annotated
from langgraph.graph import add_messages, StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import ToolMessage, HumanMessage, AIMessage

class State(TypedDict):
    messages: Annotated[list, add_messages]

weather_tool = GaoDeWeather(api_key='xxxx')  
tavily_tool = TavilySearchResults(max_results=2)

tools = [weather_tool, tavily_tool]  
toolnode = ToolNode(tools)

llm = ChatOpenAI(  
    model_name="qwen-turbo",  
    temperature=0.7,  
    max_tokens=1024,  
    top_p=1,  
    openai_api_key="sk-xxxx",  
    openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)  

llm_with_tools = llm.bind_tools(tools)

def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}  


def condition_tools(state: State):
    ai_message = state["messages"][-1]  
    if ai_message.tool_calls:  
        print(json.dumps(ai_message.tool_calls, indent=4, ensure_ascii=False))  
        return"tools"
    return END  


graph_builder = StateGraph(State)

# Add nodes  
graph_builder.add_node("chatbot", chatbot)  
graph_builder.add_node("tools", toolnode)  

# Add edges  
graph_builder.add_edge(START, "chatbot")  
graph_builder.add_conditional_edges("chatbot", condition_tools)  
graph_builder.add_edge("tools", "chatbot")  

app = graph_builder.compile() 

inputs = {"messages": [HumanMessage(content="I am going to Hangzhou today, how is the weather? What fun things are there?")]}  

result = app.invoke(inputs)  
print(result.get("messages")[-1].content)

Here, adding a tool node only requires<span>graph_builder.add_node("tools", toolnode)</span>, and the rest of the code is almost the same as before.

Learning LangGraph from Scratch: Using ToolNode (Part 3)

Here, the tools and chatbot call each other repeatedly, forming a self-reflective ReAct intelligent agent.

The question I asked is

I am going to Hangzhou today, how is the weather? What fun things are there?

Clearly, it is necessary to check the weather and perform a search, but the tool calls are greatly influenced by the capabilities of the large model. For the<span>qwen-turbo</span> model used in the code, it still cannot return multiple tool invocation parameters at once; it returns them in two stages.

[
    {
        "name": "Gaode Weather Query",
        "args": {
            "city": "Hangzhou"
        },
        "id": "call_bf011943321e4b77a82320",
        "type": "tool_call"
    }
]
[
    {
        "name": "tavily_search_results_json",
        "args": {
            "query": "Hangzhou travel"
        },
        "id": "call_02ecadd5d148420c9bb0e0",
        "type": "tool_call"
    }
]
### Hangzhou Today's Weather
According to the weather forecast for the next few days, Hangzhou will have cloudy weather today (December 17, 2024), with a maximum temperature of 13°C and a minimum temperature of 3°C. It is recommended to prepare warm clothing, and since it is cloudy, consider bringing rain gear just in case.

### Future Weather Forecast:
- **December 18**: All day cloudy turning to overcast, maximum temperature 9°C, minimum temperature 1°C.
- **December 19**: Daytime overcast to partly cloudy, maximum temperature 7°C, nighttime cloudy, minimum temperature 1°C.
- **December 20**: Sunny, temperatures gradually rising, maximum temperature 10°C, minimum temperature 1°C.

### Recommendations for Hangzhou Travel
According to the search results, no direct information about tourist attractions was found. However, Hangzhou, as a famous historical and cultural city in China, has many famous tourist attractions such as West Lake and Lingyin Temple. You might consider visiting these places to experience Hangzhou's historical and cultural heritage and natural scenery. At the same time, you can explore some emerging popular spots promoted by companies such as "Hangzhou Travel Network Technology Co., Ltd." However, more detailed travel information may require further searching or reference to professional travel guides.

5. Conclusion

This article introduces the process of using ToolNode as a LangGraph node to perform tool invocation. It first uses manually simulated tool_calls data to demonstrate how ToolNode performs tool invocation, and then combines it with a large model to perform actual model invocation.

Leave a Comment