DEV Community

Cover image for AI Agents Explained (When an LLM Stops Talking and Starts Doing)
Syed Muhammad Ali Raza
Syed Muhammad Ali Raza

Posted on

AI Agents Explained (When an LLM Stops Talking and Starts Doing)

AI Agents Explained (When an LLM Stops Talking and Starts Doing)

Written by Syed Muhammad Ali Raza

So far in this series we covered how LLMs work, how RAG lets a model answer using your own documents, and how fine-tuning reshapes a model's behavior. All three of those have one thing in common though. At the end of the day, the model just gives you text back. You still have to be the one who actually does something with that text.

Agents are what happens when you remove that last step. Instead of the model just telling you what to do, it actually goes and does it. Calls the API. Runs the calculation. Checks the database. Sends the email. And then, based on what it finds, decides what to do next, on its own, without you sitting there feeding it each new piece of information by hand.

This is the part of the AI world that finally clicked for me and made everything else make sense in hindsight. Let's go slow though, real example first, then real code.

A real life example before any tech talk

Think about the difference between calling a friend for restaurant advice and calling a travel agent to actually book your trip.

Your friend can tell you "Italian food near downtown is usually good, try that one place on Fifth Street." That's genuinely useful information. But your friend isn't checking if the restaurant is open right now, isn't looking at today's availability, isn't placing the reservation. You still have to take that advice and go do the rest yourself. That's a regular chatbot. It gives you words, you do the work.

A travel agent works differently. You tell them what you want, and they actually check flight availability right now, compare a few options, book the one that fits, then check if your hotel dates line up, notice a conflict, adjust the dates, confirm everything, and hand you a finished itinerary. They didn't just describe what you should do. They took actions, looked at the results of those actions, and adjusted their next move based on what they found, in a loop, until the task was actually done.

That loop, take an action, look at what happened, decide the next action based on that, repeat until done, is the entire idea behind an AI agent. The "friend giving advice" version is a regular LLM chat. The "travel agent actually booking things" version is an agent.

Why this needed a new word at all

You might wonder why this isn't just "using an API." It kind of is, except for one important difference. In a normal program, you, the developer, write the exact logic of what happens next. If X, do Y. If the flight isn't available, try this next specific fallback. You hardcode every branch.

With an agent, you don't hardcode all those branches. You give the model a goal and a set of tools it's allowed to use, and the model itself decides, in the moment, which tool to reach for and what to do with the result, based on reasoning about the actual situation in front of it, not a script you wrote in advance. It's the difference between programming exactly what happens and describing what you want and letting something capable figure out the steps.

This is genuinely powerful and also why agents can be unpredictable in a way a normal program isn't. You're trading precise control for flexibility. That tradeoff matters and I'll come back to it near the end.

The building block underneath all of this, tool use

Before an LLM can act like a travel agent, it needs a way to actually reach outside of itself and do things, since on its own all it can do is generate text. This capability is usually called tool use, or function calling.

Here's the idea. You describe to the model, in a structured way, what functions or tools are available to it, what each one does, and what information each one needs. Something like "there's a function called get_weather that takes a city name and returns the current temperature." Then, when the model is generating its response, instead of just writing text, it can output a special kind of message that basically says "I want to call get_weather with the city set to Lahore."

Your code is the one that actually runs that function, since the model itself can't reach out to the internet or your database directly. You run it, get the real result, and hand that result back to the model as part of the conversation. The model then reads that result and decides what to say or do next, maybe it's done and gives you a final answer, or maybe it decides it needs to call another tool first.

That back and forth, model requests a tool call, your code executes it, result goes back to the model, model decides the next step, is the actual mechanical loop that makes an agent work. Let's build one.

Building an actual working agent, step by step

I'm going to build a small agent that can answer questions using two tools, one that checks the weather, and one that does math, since it needs both to answer certain questions properly and that forces it to actually chain tool calls together instead of just calling one and stopping.

Step 1, define what our tools actually do

These are just normal Python functions. Nothing magic here yet.

def get_weather(city):
    # in a real project this would call an actual weather API
    # hardcoding some fake data here to keep the example focused
    fake_weather_data = {
        "lahore": {"temp_c": 38, "condition": "sunny"},
        "london": {"temp_c": 17, "condition": "cloudy"},
        "new york": {"temp_c": 24, "condition": "clear"}
    }
    city_key = city.lower()
    if city_key in fake_weather_data:
        return fake_weather_data[city_key]
    return {"error": f"no weather data found for {city}"}


def calculate(expression):
    # a genuinely simple calculator, real projects would want
    # something safer than raw eval, but this keeps the example clear
    try:
        result = eval(expression, {"__builtins__": {}})
        return {"result": result}
    except Exception as e:
        return {"error": str(e)}
Enter fullscreen mode Exit fullscreen mode

Step 2, describe these tools to the model

The model can't read your Python code, so you describe each tool in a structured format it understands, basically a name, a description of what it does, and what input it expects.

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a specific city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The name of the city, for example Lahore or London"
                }
            },
            "required": ["city"]
        }
    },
    {
        "name": "calculate",
        "description": "Evaluate a basic math expression and return the numeric result.",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "A math expression like '38 * 9/5 + 32'"
                }
            },
            "required": ["expression"]
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

Step 3, the actual agent loop

This is the core of everything. We send the conversation to the model, check if it wants to call a tool, if it does we run that tool and feed the result back in, and we keep repeating this until the model is done and just gives us a plain text answer instead of another tool request.

import anthropic
import json

client = anthropic.Anthropic(api_key="your-api-key-here")

# a lookup so our code knows which real Python function
# corresponds to each tool name the model might ask for
available_functions = {
    "get_weather": get_weather,
    "calculate": calculate
}

def run_agent(user_question):
    messages = [
        {"role": "user", "content": user_question}
    ]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )

        # add the model's response to our conversation history
        messages.append({"role": "assistant", "content": response.content})

        # check if the model wants to stop, or wants to call a tool
        if response.stop_reason == "tool_use":
            tool_results = []

            for block in response.content:
                if block.type == "tool_use":
                    tool_name = block.name
                    tool_input = block.input

                    print(f"Model wants to call: {tool_name} with {tool_input}")

                    # actually run the real python function
                    function_to_call = available_functions[tool_name]
                    result = function_to_call(**tool_input)

                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(result)
                    })

            # feed the tool results back into the conversation
            messages.append({"role": "user", "content": tool_results})
            # loop again, the model now sees the tool result and
            # decides what to do next

        else:
            # the model is done, no more tools needed, extract final text
            final_text = ""
            for block in response.content:
                if block.type == "text":
                    final_text += block.text
            return final_text
Enter fullscreen mode Exit fullscreen mode

Step 4, actually running it

answer = run_agent("What's the weather in Lahore right now, and what would that temperature be in Fahrenheit?")
print(answer)
Enter fullscreen mode Exit fullscreen mode

Here's what actually happens when this runs, step by step, and this is the part I find genuinely satisfying to trace through. The model reads the question, realizes it needs the current weather first, and requests a call to get_weather with city set to Lahore. Our code runs that function for real, gets back thirty eight degrees celsius and sunny, and sends that back into the conversation. The model reads that result, realizes it now needs to convert thirty eight celsius to fahrenheit, and requests a call to calculate with an expression like "38 * 9/5 + 32". Our code runs that too, gets back the actual number, and sends it back. Only now, with both pieces of information actually gathered through real function calls, does the model stop requesting tools and just answer you directly with something like "It's currently 38°C and sunny in Lahore, which is about 100.4°F."

Nobody hardcoded the order of those two tool calls. The model figured out on its own that it needed weather first, then math, based on genuinely understanding the question. That's the part that separates this from a normal script.

Where this gets more serious, real agent frameworks

The loop above is genuinely the whole concept, but building it by hand for every project gets repetitive fast, so most real projects use a framework that handles this loop, memory, and tool management for you. A few worth knowing the names of, LangChain and LangGraph are popular general purpose options for chaining tools and building more complex agent workflows, and Anthropic's own agent tooling and Claude Code itself are built on this same tool use loop under the hood, just with a lot more polish, safety handling, and additional capabilities layered on top.

Understanding the raw loop first, like we just did, makes these frameworks make a lot more sense, since you're no longer treating them as magic, you're recognizing the same request tool, run it, feed result back, repeat pattern just wrapped in nicer packaging.

The honest tradeoffs nobody mentions enough

Agents are genuinely exciting but I want to be straight about the downsides too since I've been bitten by them.

They can get stuck in loops, calling the same tool repeatedly without making real progress, especially on ambiguous tasks. It's worth setting a hard limit on how many loop iterations you allow, so a confused agent doesn't run forever and rack up cost.

They can also call tools you didn't expect in situations you didn't plan for, since you're not hardcoding the exact path, just describing what's available. Any tool you give an agent access to, especially ones that send emails, spend money, or delete things, needs careful thought about what happens if the model decides to use it in a way you didn't anticipate. Starting with read only tools, ones that just fetch information rather than change anything, is a much safer place to begin than giving an agent the ability to take irreversible actions right away.

And debugging an agent that went wrong is genuinely harder than debugging a normal program, because the "logic" isn't sitting in your code where you can read it line by line, it's inside the model's reasoning for that specific run. Logging every tool call and result, like the print statement in the example above, matters a lot more here than it would in typical code.

Bringing it back to the bigger picture

If you've followed this whole series, here's how all four pieces actually fit together. A plain LLM just talks, using whatever it learned during training. RAG lets it talk using your specific documents instead of just its training memory. Fine-tuning reshapes how it naturally talks and behaves. And agents let it stop just talking entirely, and actually go do things in the world, checking real data, taking real actions, adjusting based on real results, in a loop, until the task is actually finished.

That progression, from "answers from memory" to "answers using your data" to "consistently behaves the way you need" to "actually takes action," is basically the roadmap of how AI tools have gotten more capable over the past couple of years. Understanding all four, at the level we've gone through in this series, puts you ahead of a huge number of people who use these tools daily without ever knowing what's actually happening underneath.


If you build your own small agent off this example, I'd love to hear what tools you gave it and what it did that surprised you.

Top comments (0)