AI agents — autonomous programs that can reason, plan, and execute multi-step tasks — have moved from research papers to production codebases. If you’ve been curious about building your own AI agent in Python, LangGraph is one of the best frameworks to start with in 2026. In this guide, we’ll build a practical AI agent from scratch that can search the web and answer complex questions using tool calling.
What Is LangGraph?
LangGraph is a library from the LangChain ecosystem designed specifically for building stateful, multi-step AI agents as graphs. Unlike simple prompt-response chains, LangGraph lets you define complex workflows where an LLM can:
- Decide which tools to call
- Loop back and retry when results are insufficient
- Maintain state across multiple reasoning steps
- Handle branching logic and conditional paths
Think of it as a state machine where the LLM is the decision engine at each node.
Prerequisites
Before we start, make sure you have:
- Python 3.11 or later
- An OpenAI API key (or any LLM provider supported by LangChain)
- A Tavily API key for web search (free tier available)
Install the required packages:
pip install langgraph langchain-openai tavily-python langchain-communityStep 1: Define Your Tools
AI agents are only as useful as the tools they can access. Let’s give our agent the ability to search the web and perform calculations:
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import tool
# Web search tool
search_tool = TavilySearchResults(
max_results=3,
search_depth="advanced"
)
# Custom calculator tool
@tool
def calculator(expression: str) -> str:
"""Evaluate a mathematical expression. Use this for any math calculations."""
try:
result = eval(expression, {"__builtins__": {}}, {})
return f"Result: {result}"
except Exception as e:
return f"Error: {str(e)}"
tools = [search_tool, calculator]The @tool decorator automatically generates the schema the LLM needs to understand when and how to call each tool.
Step 2: Set Up the LLM with Tool Binding
Next, we bind our tools to the language model so it knows what’s available:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
llm_with_tools = llm.bind_tools(tools)Tool binding tells the LLM the function signatures, descriptions, and parameter types. When the model decides it needs information, it generates a structured tool call instead of hallucinating an answer.
Step 3: Build the Agent Graph
Here’s where LangGraph shines. We define our agent as a graph with nodes and edges:
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import HumanMessage
# Define the function that calls the LLM
def agent_node(state: MessagesState):
"""The agent reasons about the current state and decides what to do."""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# Define the routing logic
def should_continue(state: MessagesState):
"""Check if the agent wants to call a tool or is done."""
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return END
# Build the graph
graph = StateGraph(MessagesState)
# Add nodes
graph.add_node("agent", agent_node)
graph.add_node("tools", ToolNode(tools))
# Add edges
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, ["tools", END])
graph.add_edge("tools", "agent")
# Compile
agent = graph.compile()The flow is simple but powerful:
- The agent node receives the conversation and decides what to do
- If it wants to use a tool, the should_continue function routes to the tools node
- The tools node executes the tool and sends results back to the agent
- The agent reasons about the tool results and either calls more tools or returns a final answer
Step 4: Run Your Agent
Let’s test our agent with a complex question that requires both search and calculation:
# Ask a question that requires multiple steps
result = agent.invoke({
"messages": [
HumanMessage(
content="What is the current population of India and Japan? "
"Calculate the ratio of India's population to Japan's."
)
]
})
# Print the final response
print(result["messages"][-1].content)The agent will: (1) search for India’s population, (2) search for Japan’s population, (3) use the calculator to compute the ratio, and (4) synthesize everything into a coherent answer.
Step 5: Add Memory for Multi-Turn Conversations
Real agents need memory. LangGraph makes this easy with checkpointers:
from langgraph.checkpoint.memory import MemorySaver
# Add persistent memory
memory = MemorySaver()
agent_with_memory = graph.compile(checkpointer=memory)
# Use thread IDs to maintain separate conversations
config = {"configurable": {"thread_id": "user-123"}}
# First message
result1 = agent_with_memory.invoke(
{"messages": [HumanMessage(content="Search for the latest Python 3.14 features")]},
config=config
)
# Follow-up (agent remembers the context)
result2 = agent_with_memory.invoke(
{"messages": [HumanMessage(content="Which of those features is most useful for data science?")]},
config=config
)The MemorySaver stores the full conversation history per thread, so your agent maintains context across interactions. For production, swap it with SqliteSaver or PostgresSaver.
Step 6: Streaming Responses
For a better user experience, stream the agent’s reasoning in real time:
async for event in agent_with_memory.astream_events(
{"messages": [HumanMessage(content="Compare Rust vs Go for backend development in 2026")]},
config=config,
version="v2"
):
if event["event"] == "on_chat_model_stream":
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)Best Practices for Production Agents
Before deploying your agent, consider these battle-tested tips:
- Set max iterations: Use
recursion_limitin the config to prevent infinite tool-calling loops - Add error handling: Wrap tool execution in try/catch blocks and return meaningful error messages
- Use structured outputs: For predictable responses, define Pydantic models for your agent’s final output
- Log everything: Use LangSmith for tracing — it shows every step the agent took and why
- Rate limit tool calls: Especially for external APIs, add throttling to avoid hitting limits
What’s Next?
Now that you have a working agent, here are some directions to explore:
- Multi-agent systems: Use LangGraph’s
CommandAPI to build teams of specialized agents that collaborate - Human-in-the-loop: Add approval nodes where the agent pauses and asks for human confirmation before executing sensitive actions
- Custom state: Extend
MessagesStatewith your own fields to track agent progress, scores, or metadata - Deploy with LangGraph Platform: Ship your agent as a scalable API with built-in persistence and monitoring
AI agents are rapidly becoming a core part of modern software architecture. LangGraph gives you the control and flexibility to build agents that are reliable enough for production — not just impressive demos. Start small, add tools incrementally, and always keep a human in the loop for critical decisions.

Leave a Reply