DEV Community

Midas126
Midas126

Posted on

Building Your Own AI Agent: A Practical Guide with LangGraph

From Chatbots to Autonomous Agents: The Next AI Frontier

If you've experimented with ChatGPT or Claude, you've experienced conversational AI. But what happens when we give AI systems the ability to not just talk, but to act? This is the world of AI agents—autonomous systems that can perceive their environment, make decisions, and execute tasks to achieve specific goals.

While enterprise MCP gateways represent one sophisticated implementation, you don't need a massive infrastructure to start building agents. In this guide, I'll show you how to create your own research agent using LangGraph, a framework for building stateful, multi-actor applications with LLMs.

What Makes an AI Agent Different?

Traditional LLM applications follow a simple pattern: prompt in, response out. Agents break this mold by adding three key components:

  1. Tools: Functions the agent can call (web search, calculator, API calls)
  2. Memory: The ability to maintain context across interactions
  3. Planning: Breaking down complex tasks into executable steps

Think of it as upgrading from a knowledgeable assistant who can only answer questions to one who can actually do things for you.

Setting Up Your Development Environment

Let's start by installing the necessary packages:

pip install langgraph langchain-openai langchain-community
Enter fullscreen mode Exit fullscreen mode

You'll also need an OpenAI API key (or you can substitute with other providers):

import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
Enter fullscreen mode Exit fullscreen mode

Building a Research Agent Step by Step

Step 1: Define Your Agent's Tools

Tools are what give your agent its capabilities. Let's create a simple web search tool and a calculator:

from langchain_community.tools import DuckDuckGoSearchRun
from langchain.tools import Tool
from langchain.chains import LLMMathChain
from langchain_openai import ChatOpenAI

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

# Web search tool
search = DuckDuckGoSearchRun()
search_tool = Tool(
    name="Web Search",
    func=search.run,
    description="Useful for searching the web for current information"
)

# Calculator tool
math_chain = LLMMathChain.from_llm(llm=llm)
math_tool = Tool(
    name="Calculator",
    func=math_chain.run,
    description="Useful for mathematical calculations"
)

tools = [search_tool, math_tool]
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Agent State

LangGraph uses a stateful approach. Let's define what our agent needs to remember:

from typing import TypedDict, List, Annotated
import operator

class AgentState(TypedDict):
    question: str
    steps: Annotated[List[str], operator.add]
    context: Annotated[List[str], operator.add]
    answer: str
Enter fullscreen mode Exit fullscreen mode

This state tracks:

  • The original question
  • Steps taken (for transparency)
  • Context gathered
  • Final answer

Step 3: Build the Agent Graph

Here's where LangGraph shines—it lets us define our agent's workflow as a graph:

from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolExecutor, ToolInvocation

# Create the graph
graph_builder = StateGraph(AgentState)

# Tool executor
tool_executor = ToolExecutor(tools)

# Define nodes
def plan_step(state: AgentState):
    """Decide what to do next"""
    question = state["question"]
    context = state.get("context", [])

    prompt = f"""Based on the question: {question}
    And current context: {context if context else 'No context yet'}

    Decide what to do next. Options:
    1. Search for more information if needed
    2. Calculate if it's a math problem
    3. Answer if you have enough information

    Return only the action and reasoning."""

    response = llm.invoke(prompt)
    return {"steps": [f"Planned: {response.content}"]}

def execute_tool(state: AgentState):
    """Execute the chosen tool"""
    last_step = state["steps"][-1] if state["steps"] else ""

    if "search" in last_step.lower():
        # Extract search query
        query = state["question"]
        result = search_tool.run(query)
        return {
            "context": [f"Search result: {result}"],
            "steps": [f"Searched for: {query}"]
        }
    elif "calculate" in last_step.lower():
        # Extract math problem
        problem = state["question"]
        result = math_tool.run(problem)
        return {
            "context": [f"Calculation: {result}"],
            "steps": [f"Calculated: {problem}"]
        }
    return {"steps": ["No tool executed"]}

def generate_answer(state: AgentState):
    """Generate final answer"""
    question = state["question"]
    context = "\n".join(state.get("context", []))

    prompt = f"""Question: {question}

    Context gathered:
    {context}

    Provide a comprehensive answer based on the context."""

    response = llm.invoke(prompt)
    return {"answer": response.content}

# Add nodes to graph
graph_builder.add_node("plan", plan_step)
graph_builder.add_node("execute", execute_tool)
graph_builder.add_node("answer", generate_answer)

# Define edges (workflow)
graph_builder.set_entry_point("plan")
graph_builder.add_edge("plan", "execute")
graph_builder.add_edge("execute", "answer")
graph_builder.add_edge("answer", END)

# Compile the graph
graph = graph_builder.compile()
Enter fullscreen mode Exit fullscreen mode

Step 4: Run Your Agent

Now let's test our research agent:

# Initialize state
initial_state = AgentState(
    question="What's the current price of Bitcoin and what would $1000 invested 5 years ago be worth today?",
    steps=[],
    context=[],
    answer=""
)

# Run the agent
result = graph.invoke(initial_state)

print(f"Question: {result['question']}")
print(f"\nSteps taken:")
for step in result['steps']:
    print(f"- {step}")
print(f"\nAnswer: {result['answer']}")
Enter fullscreen mode Exit fullscreen mode

Advanced: Adding Memory and Reflection

A simple agent is good, but a smart agent learns from its actions. Let's add reflection:

def reflect_on_action(state: AgentState):
    """Evaluate if the last action was helpful"""
    last_step = state["steps"][-1] if state["steps"] else ""
    context = state.get("context", [])

    prompt = f"""Evaluate if the last action was sufficient:
    Last action: {last_step}
    Current context: {context}

    Should we:
    1. Continue gathering information?
    2. Proceed to answer?

    Explain your reasoning."""

    response = llm.invoke(prompt)

    if "continue" in response.content.lower():
        return {"steps": [f"Reflection: Need more info - {response.content}"]}, "plan"
    else:
        return {"steps": [f"Reflection: Ready to answer - {response.content}"]}, "answer"

# Update graph with reflection
graph_builder.add_node("reflect", reflect_on_action)
graph_builder.add_edge("execute", "reflect")
graph_builder.add_conditional_edges(
    "reflect",
    lambda state: state["steps"][-1] if "plan" in state["steps"][-1] else "answer"
)
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

Now that you have a basic agent, consider these practical extensions:

  1. Customer Support Agent: Add tools for checking order status, processing returns, and searching knowledge bases
  2. Code Review Agent: Integrate with GitHub API to analyze pull requests and suggest improvements
  3. Personal Research Assistant: Connect to academic databases, news APIs, and your personal notes
  4. Trading Agent: Link to market data APIs with risk management rules (be careful with this one!)

Best Practices and Pitfalls to Avoid

  1. Start Simple: Begin with 2-3 tools before expanding
  2. Add Guardrails: Implement timeout limits and token budgets
  3. Log Everything: Keep detailed logs for debugging and improvement
  4. Human-in-the-Loop: For critical actions, add approval steps
  5. Test Thoroughly: Create test cases for edge cases and failures
# Example of adding a safety check
def safety_check(state: AgentState):
    """Prevent certain types of queries"""
    question = state["question"].lower()
    blocked_terms = ["hack", "exploit", "illegal"]

    if any(term in question for term in blocked_terms):
        return {
            "answer": "I cannot assist with this type of query.",
            "steps": ["Safety check triggered - query blocked"]
        }, END
    return state, "plan"
Enter fullscreen mode Exit fullscreen mode

Your Next Steps

You've just built a functional AI agent from scratch! While this is a basic implementation, it demonstrates the core concepts that power sophisticated systems like enterprise MCP gateways.

Challenge yourself: Try adding one of these features:

  1. Persistent memory using a vector database
  2. Parallel tool execution for faster research
  3. A web interface using Streamlit or Gradio
  4. Integration with a specific API relevant to your work

The agent architecture pattern is becoming fundamental to AI application development. By understanding these building blocks, you're not just using AI tools—you're creating intelligent systems that can autonomously solve real problems.

Share what you build: I'd love to see what agents you create! Drop your GitHub repo or implementation ideas in the comments below.


Want to dive deeper? Check out the LangGraph documentation for more advanced patterns like hierarchical agents, multi-agent collaboration, and human-in-the-loop workflows.

Top comments (0)