NorthGradient
Start reading
Introduction to LLM Agents Browse lessons

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.