LLM Agents — Build Autonomous AI Agents with Tool Use (2026)
Advertisement
Introduction
What Are LLM Agents?
An LLM agent is a system where a language model drives an iterative loop: it reasons about a task, decides which tool to use, executes the tool, observes the result, and repeats until it reaches a final answer or completes a goal. Unlike a single prompt-response exchange, agents take multiple steps and interact with external systems.
Agents enable LLMs to go beyond text generation — they can browse the web, write and execute code, query databases, call APIs, and interact with file systems. The LLM acts as the "brain" while tools are its "hands."
Agent Architecture: The Core Loop
Every agent implements some version of this loop:
User Goal
↓
[Reasoning] → What is the next action?
↓
[Tool Selection] → Which tool, with which arguments?
↓
[Tool Execution] → Run the tool
↓
[Observation] → What did the tool return?
↓
[Decision] → Is the goal complete? → YES → Return answer
→ NO → Loop back to ReasoningFunction Calling: The Modern Tool Interface
OpenAI's function calling (also called "tool use") is the standard way to give LLMs access to external tools:
from openai import OpenAI
import json
import math
client = OpenAI()
# Define tools in OpenAI's schema
tools = [
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression. Use for arithmetic, percentages, and algebra.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "A valid Python math expression, e.g. '2 ** 10' or 'math.sqrt(144)'"
}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
},
"required": ["city"]
}
}
}
]
def calculate(expression: str) -> str:
"""Safely evaluate a math expression."""
try:
result = eval(expression, {"__builtins__": {}}, {"math": math})
return str(result)
except Exception as e:
return f"Error: {e}"
def get_weather(city: str, units: str = "celsius") -> str:
"""Mock weather API."""
weather_data = {
"london": {"temp": 12, "condition": "cloudy"},
"tokyo": {"temp": 22, "condition": "sunny"},
"new york": {"temp": 18, "condition": "partly cloudy"},
}
data = weather_data.get(city.lower(), {"temp": 20, "condition": "unknown"})
unit_symbol = "C" if units == "celsius" else "F"
temp = data["temp"] if units == "celsius" else data["temp"] * 9/5 + 32
return f"{temp}{unit_symbol}, {data['condition']}"
TOOL_MAP = {"calculate": calculate, "get_weather": get_weather}ReAct Agent: The Production Standard
The ReAct (Reasoning + Acting) pattern interleaves thinking and tool use in a loop:
def run_react_agent(user_query: str, max_iterations: int = 10) -> str:
"""
Run a ReAct agent that can use tools to answer questions.
"""
messages = [
{
"role": "system",
"content": """You are a helpful assistant with access to tools.
Think step by step. Use tools when you need real data.
When you have enough information, provide a clear final answer."""
},
{"role": "user", "content": user_query}
]
for iteration in range(max_iterations):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto",
temperature=0.1
)
message = response.choices[0].message
messages.append(message)
# No tool call → agent has finished reasoning
if not message.tool_calls:
return message.content
# Execute each tool call
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
print(f"[Agent] Using tool: {tool_name}({tool_args})")
if tool_name in TOOL_MAP:
result = TOOL_MAP[tool_name](**tool_args)
else:
result = f"Error: Tool '{tool_name}' not found"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
return "Max iterations reached without a final answer."
# Test the agent
answer = run_react_agent(
"What is the weather in Tokyo? And what is 15% of 3,400?"
)
print(answer)Memory-Augmented Agent
Agents that remember past interactions and user preferences across sessions:
from datetime import datetime
from collections import deque
class MemoryAgent:
"""An agent with short-term conversation memory and long-term fact storage."""
def __init__(self, max_history: int = 20):
self.conversation_history = deque(maxlen=max_history)
self.long_term_memory: dict[str, str] = {}
self.client = OpenAI()
def remember(self, key: str, value: str):
"""Store a fact in long-term memory."""
self.long_term_memory[key] = value
print(f"[Memory] Stored: {key} = {value}")
def build_system_prompt(self) -> str:
memory_block = ""
if self.long_term_memory:
facts = "\n".join(f"- {k}: {v}" for k, v in self.long_term_memory.items())
memory_block = f"\nKnown facts about this user:\n{facts}\n"
return f"""You are a personalized AI assistant.
{memory_block}
Use known facts to personalize your responses.
Current time: {datetime.now().strftime('%Y-%m-%d %H:%M')}"""
def chat(self, user_message: str) -> str:
"""Process a message with memory context."""
messages = [{"role": "system", "content": self.build_system_prompt()}]
messages.extend(list(self.conversation_history))
messages.append({"role": "user", "content": user_message})
response = self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.4
)
assistant_reply = response.choices[0].message.content
# Update short-term memory
self.conversation_history.append({"role": "user", "content": user_message})
self.conversation_history.append({"role": "assistant", "content": assistant_reply})
return assistant_reply
# Usage
agent = MemoryAgent()
agent.remember("name", "Alex")
agent.remember("preferred_language", "Python")
agent.remember("timezone", "PST")
print(agent.chat("What time should I schedule my standup?"))
print(agent.chat("Can you help me with a coding problem?"))Planning Agent: Breaking Down Complex Tasks
For multi-step tasks, have the agent produce a plan before executing:
def planning_agent(goal: str) -> dict:
"""
Two-phase agent: plan first, then execute step by step.
"""
# Phase 1: Generate a plan
plan_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "You are a planning assistant. Break down goals into concrete, ordered steps."
},
{
"role": "user",
"content": f"""Break this goal into 3-5 concrete steps:
Goal: {goal}
Format each step as:
Step N: [action]
Required tool: [tool name or 'none']
Expected output: [what this step produces]"""
}
],
temperature=0.2
)
plan = plan_response.choices[0].message.content
# Phase 2: Execute the plan
execution_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Execute the given plan, using tools as needed."},
{"role": "user", "content": f"Plan:\n{plan}\n\nExecute each step and summarize results."}
],
tools=tools,
tool_choice="auto",
temperature=0.1
)
return {
"goal": goal,
"plan": plan,
"execution": execution_response.choices[0].message.content
}Common Mistakes
- Unbounded loops — Always set a
max_iterationslimit; agents can loop indefinitely on ambiguous tasks. - No tool error handling — Tools fail; always catch exceptions and return informative error strings.
- Leaking tool results to users — Intermediate tool outputs should be processed, not shown verbatim.
- Missing tool descriptions — Vague tool descriptions cause the model to use the wrong tool or wrong arguments.
- Stateless agents in stateful applications — Users expect agents to remember context across messages.
- Executing agent-generated code without sandboxing — Never run untrusted code outside a sandbox.
Best Practices
- Use structured tool schemas with clear descriptions and parameter examples
- Log every tool call and result for debugging and cost tracking
- Set conservative
max_iterationslimits (5–10) and surface them as errors, not silent failures - Implement retry logic with exponential backoff for external API tool calls
- Test agents on adversarial inputs — users will try to make them behave unexpectedly
- Use planning agents for tasks with more than 3 steps to reduce errors from missing context
Key Takeaways
- LLM agents combine reasoning with tool use in an iterative loop: reason, act, observe, repeat
- Function calling (tool use) is the standard interface for giving LLMs access to external systems in 2026
- The ReAct (Reasoning + Acting) pattern interleaves thinking and tool use and is the production standard for agentic systems
- Always set a maximum iteration limit to prevent infinite loops on ambiguous or impossible tasks
- Memory-augmented agents maintain short-term conversation history and long-term user facts for personalized responses
- Tool descriptions must be precise — vague descriptions cause the model to misuse or ignore tools
- Never run agent-generated code outside a sandbox; agents can produce dangerous code when given that capability
- Planning agents that produce an explicit plan before execution are more reliable for multi-step complex tasks
Advertisement