Tutorial

Build AI Agents with LangGraph: From State to Tool-Calling Graph

Build autonomous AI agents with LangGraph and Gemini — state, nodes, edges, tool integration, and a compiled graph you can run locally.

Anuj SainiAug 23, 20265 min read

LLM calls are easy. Reliable agents are a graph. This notebook builds one with LangGraph + Gemini — state, nodes, edges, tool integration — that you can run, trace, and extend without rewriting the loop.

What does the course build?

A runnable agent: State (TypedDict) -> Nodes (LLM + tools) -> Conditional edges -> Compiled app that holds a conversation, calls a search tool when needed, and streams answers. Uses free Gemini (or OpenAI via one swap). Complement with API Masterclass for tool-side HTTP and Python fundamentals for TypedDict mechanics.

Ingredients: langgraph, langchain-google-genai, langchain-openai, duckduckgo-search, and a Gemini API key.

How do you configure the LLM brain?

Setup — keep keys out of code:

python
# %pip install -U langgraph langchain-google-genai langchain-openai langchain-community python-dotenv duckduckgo-search
import os, getpass
 
if "GOOGLE_API_KEY" not in os.environ:
    os.environ["GOOGLE_API_KEY"] = getpass.getpass("Enter your Google API Key: ")
python
from langchain_google_genai import ChatGoogleGenerativeAI
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0)
print(llm.invoke("Say hi in one sentence.").content[:120])

Rendered output: a one-sentence greeting — proves the key and model string before graphite.

What is AgentState and why operator.add?

State is the agent's memory; operator.add appends messages instead of overwriting.

python
from typing import TypedDict, Annotated, List
from langchain_core.messages import BaseMessage
import operator
 
class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], operator.add]
 
print("State structure defined.")

If you omit operator.add, each node would replace the full history with its single output — conversation vanishes after one turn.

How do you build and compile the graph?

One node, linear edge — the smallest useful topology.

python
from langgraph.graph import StateGraph, END
 
def call_model(state: AgentState):
    messages = state['messages']
    response = llm.invoke(messages)
    return {"messages": [response]}
 
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.set_entry_point("agent")
workflow.add_edge("agent", END)
app = workflow.compile()
print("Graph compiled successfully.")

Run it:

python
from langchain_core.messages import HumanMessage
result = app.invoke({"messages": [HumanMessage(content="What is LangGraph?")]})
print(result['messages'][-1].content[:400])

Rendered output: a paragraph defining LangGraph as a stateful orchestration layer over LangChain, citing nodes/edges.

How do you add tools and conditional routing?

Bind a search tool and route when the LLM emits tool_calls.

python
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.tools import tool
 
search = DuckDuckGoSearchRun()
tools = [search]
llm_with_tools = llm.bind_tools(tools)
 
def call_model_with_tools(state: AgentState):
    resp = llm_with_tools.invoke(state['messages'])
    return {"messages": [resp]}
 
from langgraph.prebuilt import ToolNode
tool_node = ToolNode(tools)
 
def should_continue(state: AgentState):
    last = state['messages'][-1]
    if getattr(last, 'tool_calls', None):
        return "tools"
    return END
 
workflow2 = StateGraph(AgentState)
workflow2.add_node("agent", call_model_with_tools)
workflow2.add_node("tools", tool_node)
workflow2.set_entry_point("agent")
workflow2.add_conditional_edges("agent", should_continue, {"tools":"tools", END:END})
workflow2.add_edge("tools","agent")
app2 = workflow2.compile()
print("Tool graph compiled")
Feature / Criteria

Gotcha: Passing Secrets Into the State

Putting GOOGLE_API_KEY inside AgentState leaks it into every trace and checkpoint. Read the key once at import time from os.environ or getpass, never include it as a state field.

What next after the graph runs?

Persist conversations to SQLite/Postgres per database guide, add a second tool (fetch via requests), and deploy behind FastAPI. Practise the tool contract on Topfolio Practice.


Download the Notebook and Practise

This article is a walkthrough of a runnable Jupyter notebook. Download the original .ipynb and run it locally or on Colab — every code block above appears in order.

Download the Ultimate Ai Agents Langgraph Course Notebook

Get the complete .ipynb with outputs — runs on any Python 3.10+ environment with pandas, numpy, and the libraries listed in setup.

Download .ipynb

Continue your track: Data Analyst Roadmap · Python and Pandas Guide · SQL NULL Handbook · SQL JOIN Fan-Out · Topfolio Practice · Data Analyst vs Engineer

Dataset generators where applicable are in courses/workbooks/generators/ — see citations atop for the exact *.py source for this notebook.


Frequently Asked Questions

What is LangGraph and why not just call the LLM directly?

LangGraph models an agent as a StateGraph: state is shared memory, nodes are functions (LLM call, tool), edges are routing. It gives you loops, branching, and tool observability that a single llm.invoke cannot.

What is AgentState in LangGraph?

A TypedDict with messages: Annotated[List[BaseMessage], operator.add] so each node appends rather than overwrites history. Every node receives and returns a state patch.

How do tools connect to the graph?

Bind tools via llm.bind_tools(tools), add a tool node, and a conditional edge that routes to tools when the LLM emits tool_calls, otherwise to END.

Can you swap Gemini for OpenAI?

Yes — replace ChatGoogleGenerativeAI with ChatOpenAI; the graph (StateGraph, nodes, edges, compile) stays identical. Use getpass for either API key.

Frequently Asked Questions

What is LangGraph and why not just call the LLM directly?

LangGraph models an agent as a StateGraph: state is shared memory, nodes are functions (LLM call, tool), edges are routing. It gives you loops, branching, and tool observability that a single llm.invoke cannot.

What is AgentState in LangGraph?

A TypedDict with messages: Annotated[List[BaseMessage], operator.add] so each node appends rather than overwrites history. Every node receives and returns a state patch.

How do tools connect to the graph?

Bind tools via llm.bind_tools(tools), add a tool node, and a conditional edge that routes to tools when the LLM emits tool_calls, otherwise to END.

Can you swap Gemini for OpenAI?

Yes — replace ChatGoogleGenerativeAI with ChatOpenAI; the graph (StateGraph, nodes, edges, compile) stays identical. Use getpass for either API key.

Anuj Saini

Written by

Anuj SainiFounder & Lead Instructor

Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.