7 min read#llm

Building a Simple Chatbot with LangGraph: A Beginner-Friendly Guide

Building a Simple Chatbot with LangGraph: A Beginner-Friendly Guide

Introduction

Large Language Models (LLMs) are great at answering individual questions, but real-world AI applications rarely consist of a single prompt and a single response. A useful chatbot needs to remember previous messages, decide what to do next, call external tools when necessary, and maintain state throughout a conversation.

Without a framework, these behaviors quickly turn into nested if statements, loops, and custom state management. As the application grows, the code becomes increasingly difficult to maintain.

LangGraph solves this problem by allowing us to model an AI application as a graph. Instead of manually controlling every step of the conversation, we define a set of nodes that perform work, edges that determine how execution flows between those nodes, and a shared state that carries information throughout the graph.

In this article, we'll build a simple chatbot from scratch while learning the core concepts that make LangGraph work.

By the end, you'll understand not only how to build a chatbot, but also why LangGraph is designed the way it is.


Prerequisites

Before starting, make sure you have:

  • Python 3.10 or newer
  • Basic knowledge of Python functions and classes
  • An LLM provider (OpenAI, Ollama, Anthropic, etc.)
  • LangChain and LangGraph installed

We'll use Ollama throughout this tutorial, but the overall architecture is identical for other inference engines.


Understanding the Three Core Concepts

Before writing any code, it's important to understand the three building blocks that every LangGraph application consists of.

State

The state is the shared memory of the graph.

Every node receives the current state, performs some work, and returns an update to that state.

Unlike traditional Python programs where functions often modify objects directly, LangGraph nodes return only the changes they want to make. LangGraph then merges those changes into the shared state.

You can think of the state as a notebook that every node is allowed to read from. Instead of erasing the notebook, each node simply writes new information into it.

For a chatbot, the state usually contains the conversation history.


Nodes

A node is simply a Python function.

Its job is to perform one specific task.

Some examples include:

  • Asking an LLM to generate a response.
  • Calling an external tool.
  • Searching a database.
  • Retrieving documents from a vector store.
  • Summarizing previous messages.

Every node follows the same basic idea:

  1. Read the current state.
  2. Perform some work.
  3. Return an update to the state.

This simple interface makes nodes easy to understand and reuse.


Edges

Edges define how execution moves through the graph.

There are two kinds of edges.

A normal edge always moves execution to the next node.

For example:

START → Chatbot → END

A conditional edge allows the graph to make a decision.

For example, after the chatbot generates a response, the graph can decide:

  • Finish the conversation.
  • Call a tool.
  • Ask another model.
  • Continue processing.

This ability to make decisions is what allows LangGraph to build complex AI workflows while keeping the code organized.


Implementing Our First Chatbot

Now it's time to turn the concepts from the previous section into working code.

Although the program is fewer than 40 lines long, it contains every fundamental component of a LangGraph application:

  • A state that stores information shared across the graph.
  • A node that performs work.
  • A graph that connects the nodes together.
  • A checkpointer that remembers conversations.
  • An execution loop that sends user messages into the graph.

Let's examine each piece one at a time.


Step 1: Defining the Graph State

The first thing every LangGraph application needs is a state.

python
from typing import Annotated 
from typing_extensions import TypedDict 
from langchain_core.messages import BaseMessage 
from langgraph.graph.message import add_messages 
class State(TypedDict): 
  messages: Annotated[list[BaseMessage], add_messages] 

The state is the shared memory of the graph.

Every node receives the current state as input and returns changes that should be merged back into it.

In our chatbot, the state contains only one field:

messages

which stores the entire conversation.

Notice that the field is wrapped in Annotated.

python
Annotated[list[BaseMessage], add_messages] 

This is one of the most important lines in the entire program.

Normally, when a node returns a value for a field, LangGraph replaces the previous value. For example, if a node returned:

python
{"messages": [response]} 

the old conversation would disappear.

The add_messages function changes this behavior. Instead of replacing the list, LangGraph appends the new messages to the existing conversation.

Without add_messages, the chatbot would forget everything after every turn.


Step 2: Creating the Language Model

Next, we create the language model.

python
from langchain.chat_models import init_chat_model 
llm = init_chat_model( "llama3.2:1b", model_provider="ollama", )

init_chat_model() creates a chat model that LangChain knows how to communicate with.

Although we're using Ollama in this tutorial, the rest of the code would remain almost identical if you switched to OpenAI, Anthropic, or another supported provider. One of the advantages of LangChain is that it provides a common interface for many different language models.


Step 3: Writing the Chatbot Node

Now we can create our first node.

python
def chatbot(state: State): 
  response = llm.invoke(state["messages"]) 
return { "messages": [response] }

Remember that a node is just a Python function.

It receives the current state as input.

python
state["messages"] 

contains the complete conversation up to this point.

The node sends those messages to the language model.

python
response = llm.invoke(state["messages"])

The model generates a reply, which is returned as an AIMessage.

Finally, the node returns:

python
{ "messages": [response] } 

Notice that the node does not modify the existing state directly.

Instead, it returns only the new information it wants to add.

LangGraph is responsible for merging that update into the shared state.

Because we configured the messages field with add_messages, the new AI response is appended to the conversation instead of replacing it.

This separation between reading state and returning updates is one of the core design principles of LangGraph.


Step 4: Building the Graph

Now that we have a node, we can build the graph itself.

python
builder = StateGraph(State) 
builder.add_node("chatbot", chatbot) 
builder.add_edge(START, "chatbot") 
builder.add_edge("chatbot", END) 

The first line creates a graph whose shared state is defined by our State class.

python
builder = StateGraph(State)

Next, we register the chatbot function as a node.

python
builder.add_node("chatbot", chatbot) 

The first argument is the node's name inside the graph, while the second argument is the Python function that should be executed.

Finally, we connect the nodes together:

START → chatbot → END

Graphically, the workflow looks like this:

Article Media

Although this graph contains only one node, larger applications follow exactly the same pattern. The only difference is that they contain more nodes and more complex routing logic.


Step 5: Adding Memory

Next, we add memory to our chatbot.

python
from langgraph.checkpoint.memory import InMemorySaver 
checkpointer = InMemorySaver() 
graph = builder.compile( checkpointer=checkpointer) 

Without a checkpointer, every call to the graph would be treated as a completely new conversation.

The checkpointer stores the graph's state after each execution and restores it the next time the same conversation continues.

In this example, we use InMemorySaver, which keeps everything in the program's memory.

This is perfect for learning and testing, but because the data is stored only in RAM, all conversations are lost when the program exits.

For production applications, LangGraph also supports persistent checkpointers backed by databases such as PostgreSQL or SQLite.


Step 6: Identifying a Conversation

Since the checkpointer can store multiple conversations, LangGraph needs a way to know which one to load.

That's the purpose of the configuration object.

python
config = { "configurable": { "thread_id": "thread-1" } } 

The thread_id acts as the conversation's unique identifier.

Whenever the graph is invoked with the same thread_id, LangGraph restores the previously saved state and continues the conversation from where it left off.

If a different thread_id is used, LangGraph starts a completely new conversation.

You can think of the thread_id as a chat session ID.


Step 7: Running the Graph

Finally, we create a simple chat loop.

python
while True:
    user_input = input("You: ")

    if user_input.lower() in {"quit", "exit"}:
        break

    result = graph.invoke(
        {
            "messages": [{"role": "user","content": user_input,}],
        },
        config=config
    )

    print("AI:", result["messages"][-1].content)

Each iteration performs the following steps:

  1. Read the user's input.
  2. Wrap it in a message object.
  3. Invoke the graph.
  4. Restore the previous conversation using the thread_id.
  5. Execute the graph (START → chatbot → END).
  6. Save the updated state.
  7. Return the final state.
  8. Print the model's response.

Notice that we pass only the new user message to graph.invoke(). We do not manually resend the entire conversation history. The checkpointer automatically restores the previous messages before the graph runs, and after execution it saves the updated state again.

This is one of the key differences between using LangGraph directly and managing conversation history yourself: the graph handles state restoration and persistence for you, leaving your application code focused on the workflow itself.

Hiwa AI Logo
HIWA AI

Specialized development of autonomous AI systems and resilient distributed nodes for international organizations.

Advanced AI Platform & Intelligent Agents
Digital Contact:

Platform

  • About Hiwa
  • Careers
  • Blog

Contact Information

Tehran, Sattari Expressway, Mokhberi Blvd, before Shahin St., No. 147, 2nd Floor, Unit 3
© 2026 HIWA AI. All rights reserved.
Privacy PolicyCookie PolicyCookie SettingsTerms of UseLegal Notice
Have a question? Ask Hiwa!