Skip to content
ExponaBLOG
PRACTICAL AI & MACHINE LEARNING · DAY 050 / 100

AI agents explained in Practical AI & Machine Learning

Models that plan and use tools in a loop.

Expona Admin8 min read

What is an AI agent?

You've likely heard the term "AI agent" thrown around in product announcements and research papers, but what does it actually mean in production code? AI agents explained in Practical AI & Machine Learning comes down to a single pattern: a model that thinks through a problem, calls tools to gather information or take action, observes the results, and repeats until the task is complete. Unlike a simple prompt-response system, agents operate in a loop—they reason about what they need to do next rather than executing a fixed sequence of steps. This shift from choreography to orchestration changes how we architect systems and where we place safeguards.

The loop — Think, call a tool, observe the result, repeat until done

At its core, an agent is a state machine with four states: planning, tool selection, execution, and observation. The model sits at the center, deciding what happens next based on everything it has seen so far.

Here's a concrete example: a customer support agent needs to resolve a refund request. The model receives the customer's message, reasons that it needs three pieces of information (order history, current policy, customer lifetime value), and decides which tool to call first. It calls the order lookup tool, observes the result, then decides whether to call the refund policy tool or check customer history next. After gathering enough context, it reasons that approval is warranted and hands the decision back to a human for final sign-off.

The loop itself is straightforward to implement. Most teams use libraries like LangChain, LlamaIndex, or direct API calls to models with function calling support:

python
from anthropic import Anthropic

client = Anthropic()

tools = [
    {
        "name": "lookup_order",
        "description": "Retrieve order details by order ID",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string"}
            },
            "required": ["order_id"]
        }
    },
    {
        "name": "check_refund_policy",
        "description": "Get current refund policy details",
        "input_schema": {"type": "object", "properties": {}}
    },
    {
        "name": "approve_refund",
        "description": "Approve a refund (requires human review)",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string"},
                "amount": {"type": "number"},
                "reason": {"type": "string"}
            },
            "required": ["order_id", "amount", "reason"]
        }
    }
]

def lookup_order(order_id):
    # Simulate database lookup
    return {"order_id": order_id, "amount": 89.99, "date": "2025-01-10"}

def check_refund_policy():
    return {"window_days": 30, "restocking_fee": 0.15}

def approve_refund(order_id, amount, reason):
    # This returns a result, but actual approval requires human review
    return {"status": "pending_approval", "order_id": order_id, "amount": amount}

def process_tool_call(tool_name, tool_input):
    if tool_name == "lookup_order":
        return lookup_order(tool_input["order_id"])
    elif tool_name == "check_refund_policy":
        return check_refund_policy()
    elif tool_name == "approve_refund":
        return approve_refund(tool_input["order_id"], tool_input["amount"], tool_input["reason"])

def run_agent(user_message):
    messages = [{"role": "user", "content": user_message}]
    
    while True:
        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )
        
        # Check if model wants to use a tool
        if response.stop_reason == "tool_use":
            # Process each tool call
            tool_results = []
            for content_block in response.content:
                if content_block.type == "tool_use":
                    tool_name = content_block.name
                    tool_input = content_block.input
                    tool_use_id = content_block.id
                    
                    result = process_tool_call(tool_name, tool_input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": str(result)
                    })
            
            # Add assistant response and tool results to conversation
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_results})
        else:
            # Model has finished reasoning
            final_response = ""
            for content_block in response.content:
                if hasattr(content_block, "text"):
                    final_response = content_block.text
            return final_response

# Run the agent
result = run_agent("Customer ordered on Jan 10, order #12345. They want a refund because the item arrived damaged.")
print(result)

Notice the pattern: the model looks at the conversation history, decides what tool to call next, and we feed the result back into the same conversation thread. The model gains context from its own previous reasoning and the tool outputs. This is fundamentally different from chaining a fixed sequence of API calls.

Good fits — Multi-step tasks like research, data lookup and triage

Not every problem needs an agent. Agents shine when the task has these characteristics:

  • Unknown number of steps. You can't hardcode a workflow because the path depends on intermediate results.
  • Tool selection is non-obvious. The model needs to reason about which data source or action comes next.
  • Iteration is natural. The task involves gathering info, reassessing, then gathering more info.
  • Conditional branching. The next step depends on what the previous tool returned.

In production, we've seen agents work well for:

  • Research and due diligence. An agent searching multiple knowledge bases, APIs, and documents to answer a question about a company, product, or person. It knows when it has enough data and when to dig deeper.
  • Data triage and routing. An inbound request arrives (support ticket, expense claim, bug report). The agent gathers context, checks policies, looks up historical data, then routes to the right queue with a summary.
  • Report generation. An agent fetches data from multiple systems, transforms it, validates it against business rules, and assembles it into a structured report.
  • Customer onboarding workflows. Collect company info, verify it against external data, check compliance requirements, create accounts, send confirmations—each step informs the next.

A bad fit: a simple API call or two-step task. If you know exactly what data you need and the order you need it in, skip the agent and build a function. Agents add latency and cost.

Risks — Loops, wrong tool calls and runaway costs

Agents in production come with distinct failure modes that you won't encounter with simpler systems.

Infinite loops. A model might call the same tool repeatedly, either because the tool is returning unclear results or because the model is confused about what happened. We've seen agents retry a database lookup five times with slightly different parameters. Set a hard limit on tool calls per agent run. In the code above, add a simple counter:

python
MAX_TOOL_CALLS = 10
tool_call_count = 0

while True:
    # ... existing loop logic ...
    if response.stop_reason == "tool_use":
        tool_call_count += 1
        if tool_call_count > MAX_TOOL_CALLS:
            return "Agent exceeded maximum tool calls. Escalating to human."
        # ... process tools ...

Wrong tool calls. The model calls the correct tool but with incorrect parameters, or calls the wrong tool entirely. A model might call "refund_order" instead of "check_refund_eligibility" and accidentally process a refund. Validation and permission layers are essential. Make tools that modify state require explicit human approval.

Runaway costs. A model looping 20 times at $0.003 per call adds up fast when you're running thousands of agents. Set both a tool call budget and a token budget. Monitor actual spend in staging before rolling out to production.

Hallucinated tool calls. The model invents a tool that doesn't exist or makes up parameters. This is less common with recent models but still happens. Catch these in your tool dispatcher and return a clear error message back to the agent.

Keep humans in the loop — Require approval for actions with real-world effects

The most important safeguard is simple: any tool that modifies data, charges money, or commits to an action should require human approval before executing.

This doesn't mean disabling agents for meaningful work. It means treating them as high-confidence triage and recommendation systems, not autonomous executors. An agent can gather all the information needed to approve a refund, assess whether it meets policy, and present the case to a human. The human clicks "approve" and the tool actually executes. The agent saved 10 minutes of manual investigation; the human made the binding decision.

Design your tool set in tiers:

  • Read-only tools: Lookup, search, retrieve. The agent can call these freely.
  • Proposal tools: Prepare a refund, draft an email, generate a report. The agent proposes; a human reviews; a separate tool executes.
  • Execute tools: Actually transfer money, send the email, close the ticket. Only call these after human approval.

In code, separate the proposal phase from execution:

python
def propose_refund(order_id, amount, reason):
    return {
        "action": "refund",
        "order_id": order_id,
        "amount": amount,
        "reason": reason,
        "approval_url": f"https://dashboard.internal/approve?order_id={order_id}"
    }

def execute_refund(order_id, amount, approval_token):
    # Verify the approval token before executing
    if not verify_approval_token(approval_token, order_id):
        return {"error": "Invalid approval token"}
    
    # Now actually process the refund
    return process_payment_reversal(order_id, amount)

The agent calls propose_refund. A human reviews the proposal, clicks the approval URL, and that triggers execute_refund. The agent never directly modifies critical state.

Common mistakes

  • Building an agent when a function suffices. If you know the exact sequence of steps, hardcode it. Agents add latency and complexity for flexibility you don't need.
  • Giving agents access to production-modifying tools without approval gates. Test this thoroughly. An agent that can delete records or charge customers needs strict safeguards.
  • Not monitoring tool call depth. Add logging and alerts when an agent makes more than N calls. That's a signal something went wrong.
  • Vague tool descriptions. If a tool is poorly documented in the tool schema, the model will misuse it. Spend time on the description and input schema clarity.
  • No error handling for tool failures. A database timeout or API rate limit shouldn't crash your agent. Return structured errors and let the agent decide what to do next.
  • Trusting cost estimates. Run agents in staging with real traffic patterns and measure actual spend. Token usage varies more than you'd expect.

Key takeaways

  • An agent is a loop: the model reasons, calls a tool, observes the result, and repeats until done. This is fundamentally different from executing a fixed sequence.
  • Agents excel at multi-step tasks where the path is unknown upfront: research, triage, data enrichment, conditional workflows.
  • Protect against infinite loops with hard limits on tool calls. Validate tool calls before execution. Monitor spend closely.
  • Separate proposal from execution. Let the agent gather information and recommend actions; require human approval before modifications to critical state.
  • Start with read-only tools and well-isolated workflows. Add risk gradually as you gain confidence in the system's behavior.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles