Introduction to LLM Agents · Introduction to LLM Agents · 5 min read
The reasoning step
In the previous lesson we built a get_weather tool and described it to the LLM. Now we add a second tool, show both to the LLM alongside a task, and inspect what the LLM decides to do.
The LLM does not execute tools. It reads their descriptions and returns a structured decision about which one to call and with what arguments.
Adding a second tool
Our agent will answer two kinds of questions: weather and news. Here is a get_news tool that fetches recent headlines for a topic:
import os
import requests
def get_news(topic: str) -> str:
"""Get the three most recent news headlines about a topic.
Args:
topic: The subject to search for, e.g. 'climate change' or 'Python'.
Returns:
A numbered list of up to three recent headlines.
"""
# Using the free GNews API (requires a free API key at gnews.io).
# Keep the key out of the source: read it from the environment instead.
url = "https://gnews.io/api/v4/search"
params = {
"q": topic,
"lang": "en",
"max": 3,
"apikey": os.environ.get("GNEWS_API_KEY"), # set GNEWS_API_KEY in your environment
}
response = requests.get(url, params=params, timeout=5)
articles = response.json().get("articles", [])
if not articles:
return f"No recent news found for: {topic}"
headlines = [f"{i+1}. {a['title']}" for i, a in enumerate(articles)]
return "\n".join(headlines)
Now add its description to the tools list:
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current temperature and weather conditions for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city, e.g. 'London' or 'Tokyo'.",
}
},
"required": ["city"],
},
},
},
{
"type": "function",
"function": {
"name": "get_news",
"description": "Get the three most recent news headlines about a topic.",
"parameters": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"description": "The subject to search for, e.g. 'climate change'.",
}
},
"required": ["topic"],
},
},
},
]
Sending a task to the LLM with tools available
We call the OpenAI API with the task as a user message and the tools list attached. The LLM does not answer the question directly. It returns a tool call: a structured decision naming the tool and the arguments to pass to it.
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY from environment
def reasoning_step(task: str) -> dict | str:
"""Ask the LLM what tool to call for this task.
Returns either a tool call dict or a plain text answer
if the LLM decides no tool is needed.
"""
response = client.chat.completions.create(
model = "gpt-4o-mini",
messages = [{"role": "user", "content": task}],
tools = TOOLS,
)
message = response.choices[0].message
if message.tool_calls:
# LLM decided to use a tool
tool_call = message.tool_calls[0]
return {
"tool_name": tool_call.function.name,
"arguments": tool_call.function.arguments, # JSON string
"call_id": tool_call.id, # needed to send the result back
}
# LLM answered directly without a tool
return message.content
What the LLM returns
Given two different tasks, it returns:
print(reasoning_step("What is the weather like in Berlin right now?"))
# {"tool_name": "get_weather", "arguments": '{"city": "Berlin"}', "call_id": "call_abc123"}
print(reasoning_step("What is the latest news about solar energy?"))
# {"tool_name": "get_news", "arguments": '{"topic": "solar energy"}', "call_id": "call_def456"}
print(reasoning_step("What is 12 multiplied by 8?"))
# "12 multiplied by 8 is 96." <-- no tool needed, LLM answers directly
The LLM read the two tool descriptions and matched each task to the right one. For a question it could answer from its own knowledge, it skipped the tools entirely.
In the next lesson, we take that tool call decision and execute it: call the Python function, collect the result, and hand it back.