What Is an AI Agent?
An AI agent is a software system that can perceive its environment, reason about goals, and take actions autonomously to achieve those goals. Unlike a simple chatbot that just generates text, an agent can call external tools, browse the web, write and execute code, and chain multiple steps together.
The core loop of any agent looks like this:
- Perceive — receive input (user message, sensor data, API response)
- Reason — decide what to do next (often via an LLM)
- Act — call a tool, write to memory, or produce output
- Observe — process the result and loop again
The ReAct Pattern
The most widely-used agent architecture today is ReAct (Reason + Act). The LLM alternates between reasoning (chain-of-thought) and acting (tool calls) until it reaches a final answer.
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "search_web",
"description": "Search the web for current information",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"]
}
}
]
def run_agent(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=4096,
tools=tools,
messages=messages
)
# If the model wants to use a tool
if response.stop_reason == "tool_use":
tool_use = next(b for b in response.content if b.type == "tool_use")
tool_result = execute_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{"type": "tool_result", "tool_use_id": tool_use.id, "content": tool_result}]
})
else:
# Final answer
return response.content[0].text
Tool Design Principles
Good tools make good agents. When designing tools for your agent:
- Keep tools focused — one tool should do one thing well
- Write clear descriptions — the LLM reads your
descriptionto decide when to use the tool - Return structured data — JSON is easier for the model to parse than raw text
- Handle errors gracefully — return error messages in the tool result rather than raising exceptions
A poorly described tool is worse than no tool at all. The LLM will misuse it or ignore it entirely.
Memory Patterns
Agents need memory to handle long tasks and maintain context. There are four main memory types:
- In-context memory — the conversation history in the prompt window
- External storage — databases, vector stores (Pinecone, Chroma), files
- Episodic memory — summaries of past sessions retrieved at startup
- Semantic memory — indexed knowledge bases searched via embeddings
For most projects, start with in-context memory and add a vector store only when you hit the context limit.
Building Your First Agent
Here is a minimal agent with two tools — a calculator and a current-date lookup:
from datetime import datetime
def execute_tool(name: str, inputs: dict) -> str:
if name == "calculate":
try:
result = eval(inputs["expression"]) # Use numexpr in production
return str(result)
except Exception as e:
return f"Error: {e}"
if name == "get_date":
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return "Unknown tool"
Run it with a question like "What is 1234 5678 and what is today's date?"* — you will see the agent call both tools and compose a final answer.
Next Steps
Once you have a basic agent running:
- Add streaming so users see partial output in real time
- Implement human-in-the-loop confirmation for destructive actions
- Add observability — log every tool call and model response for debugging
- Explore multi-agent patterns where specialized agents collaborate
AI agents are rapidly becoming the primary way complex software tasks get automated. Building fluency now puts you ahead of the curve.
Share