LLM App Development #10: Building an AI Agent

4 min read

In Part 6 we saw the flow where Claude calls a tool once and we return the result. An agent takes this one level up. Claude receives a goal, decides on its own which tools to use in what order, and works through multiple steps to finish the job. In this post we build that agent.

What an agent is #

So far we set the flow. The order was baked into the code, as in “search, then answer with that result.” An agent is different. We give only the tools and the goal, and Claude decides what to do first and what to do next. It uses a tool, looks at the result, decides the next action, and uses a tool again, repeating until the goal is met.

This repetition is called the agent loop. In fact it has the same structure as the tool-execution loop from Part 6. The difference is that there are several tools, the steps are many, and Claude decides the order.

Giving it several tools #

You usually give an agent several tools. For example, give it document search and a calculation tool together, and Claude combines the two on its own depending on the question.

agent_tools.py
tools = [
    {
        "name": "search_docs",
        "description": "Search internal documents for content related to the question.",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "calculate",
        "description": "Evaluate a math expression. e.g. '1200 * 0.1'",
        "input_schema": {
            "type": "object",
            "properties": {"expression": {"type": "string"}},
            "required": ["expression"],
        },
    },
]

def run_tool(name: str, args: dict) -> str:
    if name == "search_docs":
        return search_docs(args["query"])   # reuse the search from Part 8
    if name == "calculate":
        return str(eval(args["expression"]))  # example. In practice use a safe calculator
    return "Unknown tool."

Ask “what is the refund fee?” and Claude first finds the fee policy with search_docs, then, if calculation is needed, calls calculate next. We do not have to lay out this order; Claude decides it.

The agent loop #

The loop itself is the same as Part 6, but for safety we set a maximum number of iterations. This prevents a situation where the agent keeps calling tools forever and never stops.

agent_loop.py
import anthropic

client = anthropic.Anthropic()

def run_agent(goal: str, max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": goal}]

    for _ in range(max_steps):
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=messages,
        )

        if response.stop_reason == "end_turn":
            return next(b.text for b in response.content if b.type == "text")

        messages.append({"role": "assistant", "content": response.content})

        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = run_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })
        messages.append({"role": "user", "content": tool_results})

    return "Reached the maximum number of steps."

print(run_agent("Find the refund fee policy and calculate the fee for a 50,000 won order."))

When Claude stops calling tools and finishes with end_turn, we return the result. Otherwise we run the tools, put the results back in, and continue the loop. max_steps is a safeguard. If for any reason the agent cannot finish, it stops at the set count.

When to use an agent #

An agent is not always the right answer. For work whose steps you can know in advance, writing the order in code is faster and more predictable. An agent suits work where the steps cannot be determined in advance — cases where the needed tools and steps vary by question, and you have to decide the next action based on intermediate results.

Consider cost and risk too. An agent makes several calls, so it is more expensive and slower than a single call. It can also call the wrong tool or make a wrong judgment, so for hard-to-reverse actions (payment, deletion, etc.) it is safer to insert a human confirmation.

Where people commonly trip up #

  • No termination condition — without a cap like max_steps, the agent can repeat the same tool and never stop, blowing up cost. Always set a cap.
  • Handing over a dangerous tool as is — dangerous execution like eval, or tools like payment and deletion, must not run automatically as is. Validate input, and require human approval before execution when needed.
  • Overlapping tool descriptions — if several tools have similar descriptions, Claude gets confused about which to use. Write each tool’s purpose distinctly.

Wrapping up #

In this post we built an agent where Claude chooses its own tools and handles multiple steps.

  • Given just tools and a goal, an agent decides the order itself and repeats tool use until the goal is met.
  • The loop is the same as Part 6, but for safety it has a maximum number of iterations.
  • It suits work that is hard to lay out in advance, and since it can be expensive and risky, set termination conditions and human confirmation.

For now we defined and connected tools ourselves. In the next post, “LLM App Development #11: Connecting Tools with MCP,” we will cover MCP, the standard for connecting tools — connecting Claude to ready-made tool servers instead of writing tools by hand each time.

X