All posts

Building Your First AI Agent (That Actually Does Something Useful)

Build a working AI agent with LangGraph: two tools, a clear prompt, a step limit and a loop that knows when to stop.

4 min read AI engineering

A simple loop behind most working AI agents

A simple loop behind most working AI agents

Most first AI agents don’t fail because of the model.

They fail because the behavior isn’t clear.

So instead of building something abstract, build something real:

an AI agent that reads questions, uses tools when needed, and returns answers.

This is the same setup I used to complete the Hugging Face Agents course and earn the completion certificate.

Step 1: Define What the Agent Should Do

Keep it simple:

  • read a question
  • decide if it needs a tool
  • use the tool
  • return the answer

That’s the entire system:

understand → decide → act → repeat → stop

Everything you build supports this loop.

Step 2: Add Tools (This Shapes Behavior)

Start with just a couple of tools.

Calculator

from langchain_core.tools import tool
from simpleeval import simple_eval

@tool
def calculator(expr: str) -> str:
    """Use this tool for ANY arithmetic or numeric computation."""
    return str(simple_eval(expr))
from ddgs import DDGS

@tool
def web_search(query: str) -> str:
    """Search the web for recent or unknown information."""
    with DDGS() as ddgs:
        results = list(ddgs.text(query, max_results=3))
    return "\n".join([r["body"] for r in results])

Why This Matters

The LLM doesn’t read your function logic.

It reads your docstrings. That’s how it understands what each tool is for and when to use it.

Clear docstrings = better decisions.

What Else You Can Add

Once this works, you can extend with:

  • Wikipedia search (for structured knowledge)
  • page fetch (for extracting full content from a URL)

You don’t need everything at the start. Two or three well-defined tools are enough.

Step 3: Define the Agent Behavior (Prompt)

This is where you guide decisions.

SYSTEM_PROMPT = """
You are a multi-step research agent.
Process:
1. Decide if a tool is needed
2. If yes, call the most relevant tool
3. Use the result to move closer to the answer
4. Stop as soon as you have enough information
"""

That last line matters more than it looks. Without it, agents keep calling tools looking for a more complete answer. This gives them permission to stop.

Step 4: Set Up the Model and State

Before the brain runs, two things need to exist: the model and a way to track the conversation.

from pydantic import BaseModel
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
from langchain_openai import ChatOpenAI
from typing import Annotated

class AgentState(BaseModel):
    messages: Annotated[list[BaseMessage], add_messages]
    steps: int = 0
    model_config = {"arbitrary_types_allowed": True}

model = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0.3,
    max_tokens=1024,
)

AgentState holds everything the agent knows at any point — the message history and how many steps it’s taken. Every node in the graph reads from and writes back to this.

temperature=0.3 keeps the model focused. Higher values make it more creative — not ideal when deciding which tool to call.

Step 5: Build the Agent “Brain”

This is where the model runs.

from langchain_core.messages import SystemMessage

def agent_node(state):
    messages = state["messages"]
    if not any(isinstance(m, SystemMessage) for m in messages):
        messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages
    response = model_with_tools.invoke(messages)
    return {
        "messages": [response],
        "steps": state.get("steps", 0) + 1
    }

This is the decision-making step.

Step 6: Limit the Agent

Keep it controlled.

MAX_STEPS = 7

if steps >= MAX_STEPS:
    return {
        "messages": [
            AIMessage(content="Provide your best possible final answer based on current information.")
        ],
        "steps": steps
    }

Without a hard limit, a confused agent keeps calling tools going nowhere. Seven steps is enough for basic tasks. If it hasn’t found an answer by then, the problem is in the prompt or the tools — not the step count.

Step 7: Build the Loop

Using LangGraph:

from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition

builder = StateGraph(AgentState)
builder.add_node("assistant", agent_node)
builder.add_node("tools", ToolNode([calculator, web_search]))
builder.add_edge(START, "assistant")
builder.add_conditional_edges("assistant", tools_condition)
builder.add_edge("tools", "assistant")
agent = builder.compile()

The flow: think → tool → think → answer

Step 8: Run It

from langchain_core.messages import HumanMessage

response = agent.invoke({
    "messages": [HumanMessage(content="What is 25 * 4?")]
})
print(response["messages"][-1].content)

That’s your working agent.

What Makes This Work

  • tools have clear roles
  • docstrings guide usage
  • the loop allows iteration
  • limits keep it stable

That’s all you need for a solid first agent.

Full Code

This is the minimal version.

The full implementation — with more tools, improved state handling, and the complete pipeline I used to earn the Hugging Face Agents course certificate — is here:

https://github.com/hexa01/simple-ai-agent

Final Thought

Your first AI agent doesn’t need to be complex.

It needs to be clear.

Clear tools. Clear decisions. Clear stopping point.

That’s what makes it work.

First published on Medium ↗.

Have a system to build, or a role to talk about?

Send a message about work, a project, or something I've written.

Go to