Building Your First Single-Agent System in Python
In the world of artificial intelligence, an autonomous agent is a system that can perceive its environment, make decisions, and take actions to achieve a specific goal. Unlike traditional programs that follow rigid, pre-defined paths, an AI agent uses a Large Language Model (LLM) as its "brain" to dynamically decide how to solve a problem.
In this guide, you will learn how to build your very first single-agent system from scratch using Python. We will demystify the core mechanics of autonomous agents, explore the classic Reasoning and Acting (ReAct) loop, and write clean, runnable Python code to bring your agent to life.
Understanding Single-Agent Architecture
A single-agent system consists of one autonomous entity working through a cycle of reasoning, tool execution, and observation. To understand how this works, let us look at the fundamental flow of a single-agent system:
[User Input / Goal]
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Autonomous Agent โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ LLM Brain (Reasoning & Decision Making) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ (Decides Action) โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Tools Execution (e.g., Calculator, Web) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ (Returns Result) โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Memory / Context Update โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ (Final Answer)
โผ
[User Output]
The agent operates in a continuous loop called the Execution Loop. It takes the user input, reasons about what to do, selects a tool, executes that tool, observes the result, and decides whether it has enough information to provide the final answer or if it needs to repeat the process.
Core Components of an AI Agent
To build our agent in Python, we need to implement three core components:
- The Brain (LLM): The reasoning engine that processes instructions, analyzes observations, and decides on actions.
- Tools: Functions or APIs that the agent can execute to interact with the external world (e.g., a calculator, a database query tool, or a web search function).
- The Agent Loop: The control flow logic that manages the transition between reasoning, action execution, and observation.
Step-by-Step Implementation in Python
Let us build a simple, lightweight single-agent system. To keep this example accessible and dependency-free, we will implement a mock LLM brain that simulates the decision-making process, alongside real Python tools. This will help you understand the exact mechanics of the loop before plugging in live API keys from OpenAI or Anthropic.
Step 1: Define the Tools
Tools are standard Python functions that our agent can call. Let us define two simple tools: a word counter and a basic calculator.
def calculate_expression(expression):
"""Safely evaluates a basic mathematical expression."""
try:
# Using a restricted dict for basic safety
allowed_chars = "0123456789+-*/(). "
if all(char in allowed_chars for char in expression):
return str(eval(expression))
return "Error: Invalid characters in mathematical expression."
except Exception as e:
return f"Error: {str(e)}"
def count_words(text):
"""Counts the number of words in a given text string."""
words = text.strip().split()
return f"The text contains {len(words)} words."
Step 2: Create the Agent Class
Now, we will build the SingleAgent class. This class handles the registration of tools, parses the simulated LLM decisions, and runs the execution loop.
class SingleAgent:
def __init__(self):
self.tools = {
"calculate": calculate_expression,
"word_count": count_words
}
self.memory = []
def simulate_llm_decision(self, user_prompt):
"""
Simulates the LLM's reasoning process.
In a real application, this would call an external API like OpenAI.
"""
prompt_lower = user_prompt.lower()
if "calculate" in prompt_lower or "what is" in prompt_lower:
# Extracting a simulated math expression
expression = "".join(c for c in user_prompt if c.isdigit() or c in "+-*/(). ")
return {
"thought": "The user wants to solve a math problem. I should use the calculate tool.",
"action": "calculate",
"action_input": expression.strip()
}
elif "count" in prompt_lower or "how many words" in prompt_lower:
# Extracting text to count
text_to_count = user_prompt.replace("count words in", "").replace("count", "").strip()
return {
"thought": "The user wants to count words in a sentence. I should use the word_count tool.",
"action": "word_count",
"action_input": text_to_count
}
else:
return {
"thought": "I can answer this directly without using any tools.",
"action": None,
"action_input": None
}
def run(self, user_goal):
print(f"Goal: {user_goal}\n")
self.memory.append(f"User Goal: {user_goal}")
# Start the execution loop
step = 1
max_steps = 3
current_input = user_goal
while step <= max_steps:
print(f"--- Step {step} ---")
# 1. Reason (Call LLM)
decision = self.simulate_llm_decision(current_input)
print(f"Thought: {decision['thought']}")
action = decision["action"]
action_input = decision["action_input"]
# 2. Act (Execute Tool)
if action and action in self.tools:
print(f"Action: Call tool '{action}' with input: '{action_input}'")
tool_function = self.tools[action]
observation = tool_function(action_input)
# 3. Observe
print(f"Observation: {observation}\n")
self.memory.append(f"Action: {action}({action_input}) -> Observation: {observation}")
# Update current input with the observation to let the agent finalize
current_input = f"Based on the observation: '{observation}', formulate the final answer."
else:
# No action needed, agent can formulate the final answer
print("Action: Direct Response")
final_response = user_goal if step == 1 else current_input
print(f"\nFinal Answer: {final_response}")
break
step += 1
else:
print("Execution stopped: Reached maximum steps without resolving.")
Step 3: Running Your First Agent
Let us instantiate our agent and run it with two different goals to see how it dynamically selects tools and processes the results.
# Instantiate the agent
my_agent = SingleAgent()
# Test Case 1: Mathematical Calculation
print("=== Running Test Case 1 ===")
my_agent.run("Calculate 150 * (2 + 3)")
print("\n" + "="*40 + "\n")
# Test Case 2: Word Counting
print("=== Running Test Case 2 ===")
my_agent.run("Count words in: Python autonomous agents are incredibly powerful tools.")
Real-World Use Cases
Single-agent systems are highly effective for focused, structured tasks. Some common real-world applications include:
- Personal Productivity Assistants: Reading emails, extracting key dates, and automatically drafting calendar invites using scheduling tools.
- Automated Customer Support: An agent that can look up a customer's order ID in a database, check shipping status via a carrier API, and write a personalized update email.
- Data Extraction and Analysis: Scraping a specific webpage, extracting numerical data, and passing it to a plotting tool to generate reports.
Common Mistakes to Avoid
- Infinite Loops: If an agent's observation does not satisfy the LLM's criteria, it might call the same tool repeatedly. Always implement a
max_stepslimit in your execution loop to prevent runaway API costs. - Unstructured Tool Outputs: If your tools return messy or unstructured text, the LLM might fail to parse the observation. Keep tool outputs clean, concise, and structured (JSON is highly recommended in production).
- Insecure Execution: Avoid giving agents tools like raw
eval()or unrestricted terminal access without strict input validation and sandboxing.
Interview Notes & Conceptual Questions
- What is the difference between a simple chatbot and an autonomous agent? A chatbot simply generates text based on prompt history. An autonomous agent can dynamically decide to execute external actions (tools) and change its behavior based on the tool's output.
- What is the ReAct pattern? ReAct stands for Reasoning and Acting. It is a paradigm where LLMs generate both reasoning traces (thoughts) and task-specific actions in an alternating fashion, allowing for better error correction and decision-making.
- How do you handle tool execution failures? You should catch exceptions inside the tool functions and return the error message as an observation. This allows the LLM to "see" the error and attempt to fix its input parameters in the next step.
Summary
Building a single-agent system in Python is all about orchestrating a loop of reasoning, action, and observation. By combining an LLM brain with targeted Python functions (tools), you can build systems capable of solving complex, multi-step problems autonomously. In subsequent modules, we will explore how to scale this architecture to multi-agent environments where multiple specialized agents collaborate to solve even larger tasks.