โฌ
๏ธ Previous - Intro to LangGraph
โก๏ธ Next - LangGraph Writer-Critic Loop
In this lesson, youโll build your first LangGraph project โ a simple joke bot โ to learn how to define state, create nodes, add conditional edges, and run a stateful workflow. No LLMs yet โ just clean, inspectable graph logic to help you master the fundamentals.
Before we dive into tools, agents, and LLMs, letโs master the fundamentals of LangGraph โ how to build and run a graph-based workflow.
To keep things simple (and fun), weโll build a joke bot that tells you jokes from different categories. Thereโs no LLM here โ and thatโs intentional. This example is not about building a useful chatbot. Itโs about helping you:
LangGraph is typically used with agents and language models. But here, weโre skipping all that to focus on the core mechanics โ so when we add complexity later, youโll know exactly whatโs happening under the hood.
Youโll learn how to:
By the end, youโll have a running LangGraph app โ and a clear mental model for how LangGraph systems work.
Before you dive into the project, take a quick look at the joke bot youโre about to build. This video demo walks through the full user experience โ from choosing jokes to switching categories โ so you can see how LangGraph drives the logic behind the scenes.
Itโs short, fun, and gives you a clear picture of the project outcome.
Your project is a simple and playful joke-telling bot, built with LangGraph. We will be using the pyjokes
library to fetch jokes - there are no LLM calls involved in this example.
Hereโs how it works:
The bot keeps track of two key things:
Youโll build reusable functions as graph nodes, route dynamically based on your choices, and see how LangGraph evolves state across steps. By the end, youโll understand how a graph-based workflow actually runs โ not just in theory, but in code.
And yes, it might make you laugh too ๐
Letโs get started.
Before we write any code, letโs take a look at the structure of the graph youโll be building.
Hereโs the Mermaid diagram that shows how the nodes connect:
This flow might look simple โ and thatโs the point. Itโs meant to teach you the LangGraph mechanics with just enough logic to be interesting.
Letโs walk through the key parts:
๐ช show_menu
This is the main control node. Every time it runs, it shows the user three options:
Based on the userโs input, the graph routes to one of the next nodes.
๐ฉ fetch_joke
This node fetches a joke from the selected category and displays it. After showing the joke, it routes back to show_menu
so the user can make another choice.
๐ฆ update_category
This lets the user select a new category of jokes (like โchuckโ or โneutralโ). After the update, it also routes back to the menu.
โ exit_bot
When the user chooses to quit, this node says goodbye and ends the graph by routing to the special __end__
node.
The entire system loops until the user explicitly chooses to quit.
This setup gives us a great little sandbox to practice:
Now that youโve seen the structure, letโs start building it step by step.
In the last lesson, you learned the five steps to building any LangGraph system:
Weโll follow the same structure here โ but this time, weโre doing it hands-on.
We start by declaring what the bot should remember โ the current joke category, what the user just selected, and a list of jokes already told. We also add a quit
flag for graceful exit.
class Joke(BaseModel): text: str category: str class JokeState(BaseModel): jokes: Annotated[List[Joke], add] = [] jokes_choice: Literal["n", "c", "q"] = "n" # next joke, change category, or quit category: str = "neutral" language: str = "en" quit: bool = False
We use Annotated
with add
to automatically append new jokes to the list.
Note that the reducer for jokes
will append new jokes to the existing list, allowing us to keep track of whatโs been told so far. For the other fields, we are using default reducer which simply overwrites the current value. This will be handled by LangGraph automatically.
Each node is just a function that takes in the state and returns updates. We have four simple nodes:
Here's the show_menu
function that displays the menu and gets user input:
def show_menu(state: JokeState) -> dict: user_input = input("[n] Next [c] Category [q] Quit\n> ").strip().lower() return {"jokes_choice": user_input}
Now, let's implement fetch_joke
, which pulls a joke from the pyjokes
library and returns it:
def fetch_joke(state: JokeState) -> dict: joke_text = get_joke(language=state.language, category=state.category) new_joke = Joke(text=joke_text, category=state.category) return {"jokes": [new_joke]}
Next, we implement the update_category
function, which allows the user to change the joke category:
def update_category(state: JokeState) -> dict: categories = ["neutral", "chuck", "all"] selection = int(input("Select category [0=neutral, 1=chuck, 2=all]: ").strip()) return {"category": categories[selection]}
For our last node, we implement the exit_bot
function, which will be called when the user chooses to quit:
def exit_bot(state: JokeState) -> dict: return {"quit": True}
We also define a router function to decide which node to go to next. This will serve as a conditional edge based on the user's choice:
def route_choice(state: JokeState) -> str: if state.jokes_choice == "n": return "fetch_joke" elif state.jokes_choice == "c": return "update_category" elif state.jokes_choice == "q": return "exit_bot" return "exit_bot"
You might have noticed that none of our node functions mutate the state directly. Instead, each node simply returns a dictionary of field updates.
This is by design.
LangGraph handles state management for you โ it takes the updates you return and produces a new state object behind the scenes. This means:
{field_name: new_value}
.This declarative approach keeps your logic clean, predictable, and easy to debug โ especially as your graphs grow in complexity.
So: just return what changed, and let LangGraph handle the rest.
We will use a builder function to create our graph. This is where we define the structure of our workflow and register all the nodes we created earlier.
def build_joke_graph() -> CompiledStateGraph: workflow = StateGraph(JokeState) workflow.add_node("show_menu", show_menu) workflow.add_node("fetch_joke", fetch_joke) workflow.add_node("update_category", update_category) workflow.add_node("exit_bot", exit_bot) workflow.set_entry_point("show_menu") workflow.add_conditional_edges( "show_menu", route_choice, { "fetch_joke": "fetch_joke", "update_category": "update_category", "exit_bot": "exit_bot", } ) workflow.add_edge("fetch_joke", "show_menu") workflow.add_edge("update_category", "show_menu") workflow.add_edge("exit_bot", END) return workflow.compile()
This function instantiates the StateGraph
, registers our nodes, sets the entry point, and defines the routing logic. We are also compiling the graph at this point, which prepares it for execution.
Once everything is wired up, we can have the main function to run the graph. This is where we invoke the graph with an initial state and start the interaction loop.
def main(): graph = build_joke_graph() final_state = graph.invoke(JokeState(), config={"recursion_limit": 100})
Thatโs it โ your first full LangGraph in action.
In the next section, weโll run it and show what it looks like in action.
Now that everythingโs wired up, letโs see your LangGraph joke bot in action.
Weโve kept the lesson code clean and minimal, but the actual project includes a few extra print statements for a better user experience โ youโll find those in the repo if you want to add some flair.
When you run the script, youโll get a simple but functional interactive menu. You can:
โ ๏ธ Yes, the UI is extremely... minimalist. Weโre here to learn LangGraph, not win design awards ๐
Hereโs what a sample session might look like:
๐==========================================================๐ WELCOME TO THE LANGGRAPH JOKE BOT! This example demonstrates agentic state flow without LLMs ============================================================ ๐==========================================================๐ STARTING JOKE BOT SESSION... ============================================================ ๐ญ Menu | Category: NEUTRAL | Jokes: 0 -------------------------------------------------- Pick an option: [n] ๐ญ Next Joke [c] ๐ Change Category [q] ๐ช Quit User Input: n ๐ 3 Database Admins walked into a NoSQL bar. A little while later they walked out because they couldn't find a table. ============================================================ ๐ญ Menu | Category: NEUTRAL | Jokes: 1 -------------------------------------------------- Pick an option: [n] ๐ญ Next Joke [c] ๐ Change Category [q] ๐ช Quit User Input: n ๐ Hardware: The part of a computer that you can kick. ============================================================ ๐ญ Menu | Category: NEUTRAL | Jokes: 2 -------------------------------------------------- Pick an option: [n] ๐ญ Next Joke [c] ๐ Change Category [q] ๐ช Quit User Input: q ๐ช==========================================================๐ช GOODBYE! ============================================================ ๐==========================================================๐ SESSION COMPLETE! ============================================================ ๐ You enjoyed 2 jokes during this session! ๐ Final category: NEUTRAL ๐ Thanks for using the LangGraph Joke Bot! ============================================================
You can see the full loop in action: input โ state update โ reroute โ repeat.
Notice how:
Itโs simple, predictable, and easy to extend โ which is exactly what you want in the early stages of building agentic workflows.
Letโs take it further.
Now that you've built your first LangGraph system, it's time to flex those new muscles. Try extending the joke bot with a few extra features. These will push you to think about state design, routing, and node logic โ just like youโll need to do in real agentic systems.
Here are a couple of fun upgrades โ and a bonus challenge for those ready to bring in an LLM:
pyjokes
supports multiple languages (like en
, de
, es
, etc.). Letโs make our bot multilingual!
What to do:
JokeState
called language
update_language
) that lets the user pick a language[l] ๐ Change Language
) and update the routing logicLetโs give the user a way to reset the joke history.
What to do:
[r] ๐ Reset Joke History
reset_jokes
) that clears the list of past jokespyjokes
In the next lesson, weโll replace pyjokes
with an actual LLM call โ but if you want to try it yourself now, hereโs the challenge:
What to try:
Swap the fetch_joke
node logic with an LLM call (OpenAI, Anthropic, Mistral, etc.)
Design a prompt that returns a single joke for the selected category
Use category
to control the theme:
"programmer"
โ general dev jokes"chuck"
โ Chuck Norris jokes"dad"
โ classic dad jokes (or whatever your LLM supports)Weโll walk through one possible implementation in the next lesson โ but if you're ready to explore early, go for it.
You just built and ran your first LangGraph project โ no LLMs, no agents, just clean graph logic.
Along the way, you:
And thanks to LangGraph, you didnโt have to worry about:
Itโs not the prettiest UI โ but that wasnโt the point.
You now know how LangGraph workflows really run โ and how to build them from the ground up.
Next up: LangSmith โ a tool that helps you debug, trace, and improve your graph-based systems.
Onward.
โฌ
๏ธ Previous - Intro to LangGraph
โก๏ธ Next - LangGraph Writer-Critic Loop