From Chatbots to Autonomous Agents
An LLM chatbot responds to a single prompt. An agent reasons, plans, uses tools, and takes actions in a loop until the task is done. This is the most transformative pattern in AI engineering.
What Makes an Agent?
Chatbot: User → LLM → Response (one-shot)
Agent: User → LLM → Think → Act → Observe → Think → Act → ... → Final Answer
└─── uses tools: search, code, APIs, databases ───┘
The ReAct Pattern (Reasoning + Acting)
Thought: I need to find the current stock price of Apple.
Action: search("AAPL stock price today")
Observation: AAPL is trading at $198.50, up 2.3% today.
Thought: Now I need to calculate the market cap.
Action: calculator("198.50 * 15400000000")
Observation: 3,056,900,000,000
Thought: I have all the information needed.
Answer: Apple (AAPL) is trading at $198.50 with a market cap of ~$3.06 trillion.
Building a ReAct Agent from Scratch
from openai import OpenAI
import json, re
client = OpenAI()
# Define tools the agent can use
TOOLS = {
"search": lambda q: f"Search results for '{q}': [simulated results]",
"calculator": lambda expr: str(eval(expr)),
"get_weather": lambda city: f"Weather in {city}: 72F, sunny",
}
TOOL_DESCRIPTIONS = """
Available tools:
- search(query): Search the web for information
- calculator(expression): Evaluate a math expression
- get_weather(city): Get current weather for a city
"""
def react_agent(question, max_steps=5):
"""ReAct agent that reasons and acts in a loop."""
messages = [
{"role": "system", "content": f"""You are a helpful agent that solves problems step by step.
{TOOL_DESCRIPTIONS}
Follow this format EXACTLY:
Thought: <your reasoning>
Action: <tool_name>(<argument>)
OR when you have the final answer:
Thought: <your reasoning>
Answer: <your final answer>"""},
{"role": "user", "content": question}
]
for step in range(max_steps):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0
)
output = response.choices[0].message.content
print(f"Step {step + 1}:\n{output}\n")
# Check if we have a final answer
if "Answer:" in output:
answer = output.split("Answer:")[-1].strip()
return answer
# Extract and execute action
action_match = re.search(r"Action:\s*(\w+)\((.+?)\)", output)
if action_match:
tool_name = action_match.group(1)
tool_arg = action_match.group(2).strip("\"' ")
if tool_name in TOOLS:
observation = TOOLS[tool_name](tool_arg)
messages.append({"role": "assistant", "content": output})
messages.append({"role": "user",
"content": f"Observation: {observation}"})
else:
messages.append({"role": "assistant", "content": output})
messages.append({"role": "user",
"content": f"Observation: Unknown tool '{tool_name}'"})
else:
break
return "Agent could not find an answer within the step limit."
# Test the agent
answer = react_agent("What is the weather in Tokyo and what is 72F in Celsius?")
print(f"Final: {answer}")
Function Calling with OpenAI
tools = [
{
"type": "function",
"function": {
"name": "search_database",
"description": "Search the product database",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"category": {"type": "string", "enum": ["electronics", "clothing", "books"]},
"max_price": {"type": "number", "description": "Maximum price filter"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "place_order",
"description": "Place an order for a product",
"parameters": {
"type": "object",
"properties": {
"product_id": {"type": "string"},
"quantity": {"type": "integer", "minimum": 1}
},
"required": ["product_id", "quantity"]
}
}
}
]
def run_agent_with_tools(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for call in message.tool_calls:
fn_name = call.function.name
fn_args = json.loads(call.function.arguments)
result = execute_function(fn_name, fn_args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result)
})
else:
return message.content
def execute_function(name, args):
"""Execute a tool function and return results."""
if name == "search_database":
return {"products": [{"id": "P001", "name": "Laptop", "price": 999}]}
elif name == "place_order":
return {"order_id": "ORD-789", "status": "confirmed"}
return {"error": "Unknown function"}
answer = run_agent_with_tools(
"Find me a laptop under $1000 and order the best one")
print(answer)
Agent Design Patterns
| Pattern | Use Case | Complexity |
|---|---|---|
| ReAct | General problem solving | Medium |
| Plan-and-Execute | Multi-step tasks | High |
| Reflexion | Self-improving agents | High |
| Tool-only | API orchestration | Low |
| Chain-of-Thought | Reasoning tasks | Low |
Key Takeaway
Agents extend LLMs from text generators to autonomous problem solvers. The ReAct pattern (think → act → observe → repeat) is the foundation of all agent architectures. Master tool integration and you can build agents that accomplish real-world tasks.