Building Your First AI Agent in Python: A Practical Guide

under

AI agents are no longer a research curiosity — they’re production tools, and if you haven’t started building with them yet, you’re already behind.

Why Build AI Agents with Python

Most developers first encounter agents through demo videos that make them look magical. The reality is more grounded: an AI agent is just a loop. It takes input, decides what to do, calls tools, observes results, and repeats until it has an answer or hits a stopping condition. Python dominates this space because the ecosystem — LangChain, OpenAI’s SDK, LlamaIndex, AutoGen — is built Python-first, full stop.

If you’re a full-stack or PHP developer eyeing this space, don’t let that discourage you. The concepts transfer directly. You’ll spin up a Python service, expose it via an API, and consume it from your Laravel or Node app. The AI logic lives in Python; your existing stack stays intact.

What Actually Makes Something an “Agent”

A plain LLM call is not an agent. You send text, you get text back. An agent has:

  • Memory — some form of state across turns
  • Tools — functions the model can call (search, calculator, database lookup)
  • A reasoning loop — the model decides which tool to use and when to stop

That distinction matters when you’re architecting. A customer support chatbot that just does RAG is not an agent. A system where the model can look up an order, check inventory, and send a refund email — that’s an agent.

Setting Up Your Python Environment to Build AI Agents

Before writing a single line of agent code, get your environment sorted. Skipping this causes 80% of the “it works on my machine” frustrations.

python -m venv agents-env
source agents-env/bin/activate  # Windows: agents-env\Scripts\activate
pip install openai langchain langchain-openai python-dotenv

Create a .env file and never hardcode credentials:

OPENAI_API_KEY=sk-...

Load it at the top of every script:

from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")

This is non-negotiable for anything beyond a throwaway experiment.

Choosing Your Agent Framework

You have real choices here, and they matter:

  • LangChain — batteries included, sometimes too many batteries. Best when you want pre-built chains, memory modules, and tool integrations fast.
  • OpenAI Assistants API — managed state and tool-calling handled server-side. Less control, less boilerplate.
  • AutoGen — multi-agent conversations. Use this when agents need to talk to each other.
  • Raw OpenAI SDK — maximum control, maximum verbosity. Good for learning; painful at scale.

For a first agent, start with LangChain. You can always strip it out later once you understand what it’s actually abstracting away.

Building Your First AI Agent in Python: A Step-by-Step Example

Let’s build a research assistant that can search the web and summarize findings. This is the “hello world” of agents — complex enough to be real, simple enough to understand completely.

Define Your Tools

Tools are regular Python functions decorated to tell the model what they do:

from langchain.tools import tool
import requests

@tool
def search_web(query: str) -> str:
    """Search the web for information about a given query. 
    Returns a summary of search results."""
    # Using a simple API for demonstration
    response = requests.get(
        "https://api.duckduckgo.com/",
        params={"q": query, "format": "json", "no_html": 1}
    )
    data = response.json()
    abstract = data.get("AbstractText", "")
    related = [r["Text"] for r in data.get("RelatedTopics", [])[:3]]

    if abstract:
        return abstract
    elif related:
        return " | ".join(related)
    else:
        return "No results found for this query."

@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression. Input should be a valid Python math expression."""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}"

Note: The docstring is your tool’s description — the model reads it to decide when to use the tool. Write it like you’re explaining it to a junior dev on their first day.

Wire Up the Agent

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

llm = ChatOpenAI(model="gpt-4o", temperature=0)

tools = [search_web, calculate]

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful research assistant. Use tools when you need current information or need to perform calculations. Be concise and accurate."),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

Run It

result = agent_executor.invoke({
    "input": "What is the population of Tokyo, and if 3.2% are tourists, how many tourists is that?"
})

print(result["output"])

With verbose=True you’ll see the model’s reasoning: which tool it called, what arguments it passed, and what it got back. That transparency is invaluable when things break — and they will.

Adding Memory So Your Agent Remembers Context

A stateless agent is only useful for one-shot queries. For anything conversational, you need memory:

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

message_history = ChatMessageHistory()

agent_with_history = RunnableWithMessageHistory(
    agent_executor,
    lambda session_id: message_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)

# First turn
agent_with_history.invoke(
    {"input": "My name is Alex and I'm researching renewable energy."},
    config={"configurable": {"session_id": "session_001"}}
)

# Second turn — agent remembers context
agent_with_history.invoke(
    {"input": "What were the main topics I mentioned?"},
    config={"configurable": {"session_id": "session_001"}}
)

In production, replace ChatMessageHistory with a persistent backend. Redis and PostgreSQL are both well-supported. For Laravel developers exposing this as an API, pass the session_id from your backend and let Python manage the conversation state.

Common Mistakes When You Build AI Agents with Python

I’ve shipped several agent systems and watched others get burned by the same patterns. Here’s what actually bites people:

Trusting the model too much. Agents will hallucinate tool arguments, call the wrong tool, or decide they’re done when they’re not. Wrap every tool in error handling. Log every call. Validate outputs before they touch real systems. Every time.

Not setting token limits. A runaway agent can burn through your API budget in minutes. Set max_iterations on your AgentExecutor:

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=10,
    max_execution_time=30,  # seconds
    handle_parsing_errors=True
)

Overpromising on tool descriptions. If your tool description says “search the entire internet,” the model will try to use it for everything. Be specific: “Search for current news articles from the past 30 days.”

Skipping observability. In production, integrate LangSmith or LangFuse from day one. You can’t debug an agent you can’t observe. These tools show you exactly what the model reasoned, what tools it called, and where latency is coming from. Don’t wait until something breaks in prod to set this up.

Choosing the wrong model. gpt-3.5-turbo struggles with complex multi-step reasoning. For agents, use gpt-4o or Claude 3.5 Sonnet at minimum. The cost difference is real, but so is the reliability difference. Is saving a few cents per call worth your agent randomly failing halfway through a task?

Conclusion

Building AI agents with Python is one of the most valuable skills a developer can pick up right now. You’re not just building chatbots — you’re building systems that can reason, act, and adapt. The barrier is genuinely low: a working agent with real tools takes under an hour to get running, and the concepts scale directly to production systems handling thousands of requests.

Start with the example above. Get it running locally. Then pick one real task from your current project — a database lookup, a form validation, a Slack notification — and make it a tool. That’s how you go from “I understand agents” to “I’ve shipped an agent.” The gap between those two states is always just one concrete implementation.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.