Fasttrack· 05· 25 min

Tools: Let the LLM Call Functions

What you'll learn
  • Define Python functions as LLM-callable tools with @tool
  • Understand the tool execution loop and why it needs multiple LLM calls
  • Know what ToolMessage is and when to use it

LLMs only know their training data. If you ask "What is the weather in Tokyo right now?", the model can only guess — it has no access to live data. Tools give the LLM real-time access to the outside world: weather APIs, databases, your own code, file systems, calculators, search engines. The LLM decides when to call a tool and what arguments to pass, but YOUR code actually executes it.

Diagram showing a Python function decorated with @tool becoming an LLM-callable tool
Click to zoom
The @tool decorator turns any Python function into an LLM-callable tool

Define a tool

tools_demo.py
from langchain_core.tools import tool

@tool                                                    # ①
def get_current_weather(city: str) -> str:               # ②
    """Get the current weather for a city.              # ③
    Returns temperature in Celsius and conditions.
    """
    # In a real app, call a weather API here
    weather_data = {
        'Tokyo': '18°C, partly cloudy',
        'Paris': '14°C, rainy',
        'London': '12°C, overcast',
    }
    return weather_data.get(city, f'No data for {city}')

@tool
def get_current_time(timezone: str) -> str:              # ④
    """Get the current time in a timezone (e.g. Asia/Tokyo)."""
    from datetime import datetime
    import pytz
    tz = pytz.timezone(timezone)
    return datetime.now(tz).strftime('%H:%M %Z')
① @tool decorator from langchain_core.tools — converts any function into a Tool object
② Python type hints become the parameter schema the LLM sees — city: str is required
③ The docstring becomes the tool description the LLM uses to decide when to call it
④ Multiple tools — the LLM can call any combination depending on the question

Bind tools to the model

tools_demo.py
model_with_tools = model.bind_tools([get_current_weather, get_current_time])

# When you invoke, the model may return tool_calls instead of text
response = model_with_tools.invoke('What is the weather in Tokyo?')

if response.tool_calls:
    print('Model wants to call:', response.tool_calls)
    # [{'name': 'get_current_weather', 'args': {'city': 'Tokyo'}, 'id': 'call_abc123'}]
else:
    print(response.content)

When you bind tools and invoke, the model may return a response with tool_calls — a list of tool invocations the model wants to make. The model does NOT call the functions itself. It tells you which function to call and with what arguments. Your code executes the function, then sends the result back so the model can formulate its final answer.

The tool execution loop

Diagram showing the LLM tool execution loop: think, act, observe, repeat
Click to zoom
The LLM requests tools, your code executes them, results go back — repeat until done
tool_loop.py
from langchain_core.messages import ToolMessage

messages = [HumanMessage(content='What time is it in Tokyo?')]

while True:
    response = model_with_tools.invoke(messages)         # ①
    messages.append(response)                            # ②

    if not response.tool_calls:                          # ③
        print(response.content)                          # Final answer
        break

    for tool_call in response.tool_calls:                # ④
        tool_name = tool_call['name']
        tool_args = tool_call['args']

        if tool_name == 'get_current_time':
            result = get_current_time.invoke(tool_args)  # ⑤
        elif tool_name == 'get_current_weather':
            result = get_current_weather.invoke(tool_args)

        messages.append(ToolMessage(                     # ⑥
            content=str(result),
            tool_call_id=tool_call['id']
        ))
① Invoke the model with the current message history
② Always append the model's response to history before the next turn
③ If there are no tool_calls, the model has a final answer — we are done
④ Loop through each tool call — the model may request multiple tools at once
⑤ Execute the actual Python function with the arguments the model provided
⑥ Wrap the result in a ToolMessage and append it — the model needs this to continue
This manual loop is exactly what create_agent() automates — lesson 07 shows you the easy way. Writing the loop once by hand is valuable because it makes the agent's behavior transparent. You will never be surprised by what an agent is doing once you understand this loop.
Knowledge Check
What is a ToolMessage and why does the model need it?
Recap — what you just learned
  • @tool turns any Python function into an LLM-callable tool — docstring becomes the description
  • model.bind_tools([...]) attaches tools to a model instance
  • The model does not execute tools — it requests them, YOUR code runs them
  • ToolMessage wraps execution results and sends them back to the model
Next up: 06 — RAG: Ask Questions About Your PDFs