How to Code an AI Agent: A Beginner's Deep Dive into Tool-Calling Agents

Search is unavailable right now, but no worries. I have strong, up-to-date knowledge on this topic. Let me write the full draft for you now. --- # How to Code an AI Agent: A Beginner's Deep Dive into Tool-Calling Agents --- ``` Meta description: New to AI agents? This deep dive breaks down how tool-calling agents work from the ground up, with clear explanations and real Python code examples you can run today. ``` ---

How to Code an AI Agent: A Beginner's Deep Dive into Tool-Calling Agents

AI agents are everywhere right now. Every major tech company is shipping one, every developer is building one, and every blog post seems to promise they will change everything. But if you are new to the space, the word "agent" can feel frustratingly vague. Is it just a chatbot with a fancy name? Is it some kind of autonomous robot? And most importantly: how do you actually build one?

This post cuts through the noise. We will start from first principles, explain exactly what a tool-calling agent is and how it thinks, and then walk through building one from scratch in Python. No magic frameworks hiding the details. Just clean, readable code and honest explanations.

By the end, you will understand not just how to build an agent, but why each piece exists.


What Is an AI Agent, Really?

Let's start with a simple, precise definition:

An AI agent is a program that uses a language model to decide what actions to take, takes those actions, observes the results, and repeats until it completes a goal.

That's it. The key word is decide. A standard chatbot responds to your message and stops. An agent responds, then asks itself: "Do I have enough information to answer? Or do I need to do something first?" If it needs to do something, it does it, checks the result, and continues reasoning.

This loop is often called the agent loop or ReAct loop (Reason + Act), and it is the heartbeat of every agent you will ever build.

The Three Core Components of Any Agent

  1. The Brain (LLM): The language model that does the reasoning. It reads the conversation history, decides what to do next, and generates either a final answer or a request to use a tool.
  2. The Tools: Functions the agent can call to interact with the outside world. A tool could search the web, query a database, run code, send an email, or call any API.
  3. The Loop: The orchestration logic that runs the brain, checks if it wants to use a tool, executes the tool, feeds the result back to the brain, and repeats.

Think of it like hiring a smart intern. The intern (LLM) is clever but lives in a box with no internet access. You give them a set of tools: a phone, a calculator, a filing cabinet. When you ask them a question, they figure out which tools they need, use them, and come back with an answer. The loop is the back-and-forth between you and the intern.


Understanding Tool Calling (Function Calling)

Tool calling (also called function calling) is the mechanism that allows an LLM to request the execution of a specific function. It is the bridge between the model's reasoning and the real world.

Here is the key insight that trips up many beginners: the LLM does not actually execute the tool itself. It cannot. A language model only generates text. What it does is generate a structured piece of text that says: "I want to call this function with these arguments." Your code then reads that request, runs the actual function, and sends the result back to the model.

The Tool-Calling Flow, Step by Step

  1. You send the LLM a user message plus a list of available tools (described in plain text or JSON schema).
  2. The LLM reads the message and the tool descriptions and decides: "I need to call get_weather with city="London"."
  3. The LLM returns a special response indicating a tool call request, not a final answer.
  4. Your code detects this, runs the actual get_weather("London") function, and gets a result.
  5. You append the tool result to the conversation history and send everything back to the LLM.
  6. The LLM now has the data it needed and generates a final, human-readable answer.
  7. If it needs more tools, it repeats from step 2. Otherwise, it stops.

This cycle is the agent loop in action.


Defining Tools: The Schema

Before writing the loop, you need to understand how tools are described to the model. Every tool needs a schema: a structured description that tells the LLM what the tool does, what arguments it takes, and what those arguments mean.

Most modern LLM APIs (OpenAI, Anthropic, Google Gemini, etc.) accept tools in a JSON Schema format. Here is an example:

get_weather_tool = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a given city. Use this whenever the user asks about weather conditions.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The name of the city, e.g. 'London' or 'New York'."
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use."
                }
            },
            "required": ["city"]
        }
    }
}

A few things to notice here:

  • The description field is critical. The LLM uses this to decide when to call the tool. Write it clearly and include usage hints.
  • The parameters section uses standard JSON Schema. You can mark fields as required or optional.
  • The enum field constrains the model to only valid values, which reduces errors.

Good tool descriptions are half the battle in building a reliable agent. Garbage descriptions lead to the model calling the wrong tool or passing wrong arguments.


Building the Agent: The Full Code

Now let's put it all together. We will build a simple but complete agent that can answer questions about the weather and do basic math. We will use the OpenAI API, but the pattern applies to any provider.

Step 1: Set Up and Define Your Tools

import json
import math
from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

# --- Tool Implementations ---
# These are the real Python functions that do the actual work.

def get_weather(city: str, unit: str = "celsius") -> str:
    """Simulated weather lookup. In production, call a real weather API."""
    # Replace this with a real API call, e.g. to OpenWeatherMap
    fake_data = {
        "london": {"temp": 15, "condition": "Cloudy"},
        "new york": {"temp": 22, "condition": "Sunny"},
        "tokyo": {"temp": 28, "condition": "Humid"},
    }
    data = fake_data.get(city.lower(), {"temp": 20, "condition": "Clear"})
    temp = data["temp"] if unit == "celsius" else (data["temp"] * 9/5) + 32
    return f"The weather in {city} is {data['condition']} with a temperature of {temp}°{'C' if unit == 'celsius' else 'F'}."


def calculate(expression: str) -> str:
    """Safely evaluate a basic math expression."""
    try:
        # Using a restricted eval for safety. In production, use a proper parser.
        allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return f"The result of {expression} is {result}."
    except Exception as e:
        return f"Error evaluating expression: {str(e)}"


# --- Tool Registry ---
# A mapping from tool name to its Python function.
# The agent loop uses this to look up and execute tools by name.

TOOL_REGISTRY = {
    "get_weather": get_weather,
    "calculate": calculate,
}

Step 2: Define the Tool Schemas

# These schemas are what we send to the LLM so it knows what tools exist.

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a given city. Use this when the user asks about weather or temperature.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "The city name, e.g. 'London'."
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit. Defaults to celsius."
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Evaluate a mathematical expression and return the result. Use this for any arithmetic or math questions.",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "A valid Python math expression, e.g. '2 ** 10' or 'sqrt(144)'."
                    }
                },
                "required": ["expression"]
            }
        }
    }
]

Step 3: Build the Agent Loop

This is the core of the agent. Read this carefully, because this pattern is the foundation of nearly every agent in production today.

def run_agent(user_message: str) -> str:
    """
    Run the agent loop for a given user message.
    Returns the final answer as a string.
    """
    print(f"\nUser: {user_message}")

    # The conversation history. We start with a system prompt and the user message.
    messages = [
        {
            "role": "system",
            "content": (
                "You are a helpful assistant with access to tools. "
                "Use the available tools when you need real-time or calculated information. "
                "Always reason carefully about which tool to use and what arguments to pass."
            )
        },
        {
            "role": "user",
            "content": user_message
        }
    ]

    # The agent loop. We keep going until the model stops requesting tools.
    while True:
        print("  [Agent] Calling LLM...")

        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto"  # Let the model decide when to use tools
        )

        response_message = response.choices[0].message

        # Check if the model wants to call a tool
        if response_message.tool_calls:
            print(f"  [Agent] Model requested {len(response_message.tool_calls)} tool call(s).")

            # Append the assistant's tool-call request to the history
            messages.append(response_message)

            # Execute each requested tool call
            for tool_call in response_message.tool_calls:
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)

                print(f"  [Tool] Calling '{tool_name}' with args: {tool_args}")

                # Look up and execute the real function
                if tool_name in TOOL_REGISTRY:
                    tool_result = TOOL_REGISTRY[tool_name](**tool_args)
                else:
                    tool_result = f"Error: Tool '{tool_name}' not found."

                print(f"  [Tool] Result: {tool_result}")

                # Append the tool result to the history
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": tool_result
                })

            # Loop back: send updated history to LLM so it can continue reasoning

        else:
            # No tool calls: the model has generated a final answer
            final_answer = response_message.content
            print(f"\nAssistant: {final_answer}")
            return final_answer


# --- Run it ---
run_agent("What's the weather in Tokyo, and what is 15 squared?")

What the Output Looks Like

User: What's the weather in Tokyo, and what is 15 squared?

  [Agent] Calling LLM...
  [Agent] Model requested 2 tool call(s).
  [Tool] Calling 'get_weather' with args: {'city': 'Tokyo', 'unit': 'celsius'}
  [Tool] Result: The weather in Tokyo is Humid with a temperature of 28°C.
  [Tool] Calling 'calculate' with args: {'expression': '15 ** 2'}
  [Tool] Result: The result of 15 ** 2 is 225.
  [Agent] Calling LLM...