top of page

Adding Human in the Loop to your Chatbot using LangGraph | Beginner's Guide | Part 4

In our previous blogs, we built a LangGraph-powered chatbot that could use tools like web search, maintain memory and hold contextual conversations with users across sessions. But even the smartest bots need help sometimes.



What happens when the AI isn't confident enough to proceed?What if your chatbot needs to escalate a decision to a human before acting?Or maybe, you want to keep a human in control for critical operations?


That’s exactly where Human-in-the-Loop (HITL) systems come into play.

In this post, we’ll take our chatbot to the next level by:


  • Adding human fallback support to the graph using interrupt

  • Understanding how LangGraph lets humans pause, resume, and guide the chatbot

  • Building a conversational agent that knows when to ask for help


But before we dive into code, let’s understand why HITL matters in AI systems.



Why Human-in-the-Loop (HITL) Is a Game-Changer in AI Systems


While automation is powerful, it’s rarely perfect. Most real-world AI systems work best when combined with human oversight—especially in domains like healthcare, legal, finance, customer service, or high-risk decision-making.



Automation Alone Has Limitations


AI models like GPT-4 can answer a wide range of questions, but they’re still probabilistic systems. This means:


  • They can hallucinate facts.

  • They can’t always verify sensitive information.

  • They lack common sense or nuanced judgment in unfamiliar edge cases.


When a bot is unsure or when the stakes are high, we don’t want it guessing.



Human in the Loop = Safer, Smarter, More Reliable


By incorporating humans as part of the loop, we gain:


  • Accuracy: Humans can validate or correct AI-generated outputs.

  • Accountability: Human decisions can be logged, reviewed, and audited.

  • Control: Automation can be paused or rerouted to a human operator when confidence is low or user intent is ambiguous.

  • Trust: End users feel more comfortable knowing there’s a human option behind the curtain.


This model is especially useful in AI agents and chatbots where:


  • Tasks may need manual approval.

  • Users might expect a “real person” for complicated queries.

  • Tool calls might require human intervention (e.g., confirming a payment or booking).



HITL in LangGraph


LangGraph provides a native way to interrupt execution and wait for a human decision, thanks to its interrupt() mechanism. When the model determines that a human should take over, LangGraph will:


  1. Pause the graph execution.

  2. Expose a message or query to the human.

  3. Resume the flow once the human responds.


This fits beautifully into multi-turn conversations, agent workflows, and tool-based bots—while giving you complete control over how and when humans are looped in.



What We’ll Build — A LangGraph Chatbot That Knows When to Ask for Help


In this tutorial, we’ll extend our memory-enabled LangGraph chatbot from the previous blog by giving it the power to pause and request human input when it encounters a query that requires human assistance.


Specifically, we’ll build a chatbot that:


  • Responds to general user messages as before (e.g., questions, greetings).

  • Uses tools (like TavilySearch) when it detects a need for external information.

  • Recognizes when it needs human help and triggers a pause in execution.

  • Waits for a human to provide a response (asynchronously).

  • Resumes the conversation from the exact point where it was paused once a human responds.


This is made possible by combining:


  • MemorySaver — to retain conversation state across steps and threads.

  • interrupt() — to pause the graph and await human input.

  • ToolNode and conditional routing — to manage whether to respond directly, call a tool, or escalate to a human.


Reminder: This code builds directly on top of the LangGraph chatbot we previously created with memory and tool usage. If you haven’t seen that, check out the last blog here to get up and running.

Once complete, you’ll have a chatbot capable of escalating edge cases like this:


User: I need expert guidance for building an AI agent. Could you request assistance for me?

Chatbot: [Requests human input]

Human (later): “Check out LangGraph. It’s reliable and extensible for agent workflows.”

Chatbot: Great! I’ve shared the expert recommendation with you.

This kind of system is invaluable when deploying LLMs in enterprise apps, customer support, internal assistants, or regulated industries where judgment, oversight, and escalation paths are necessary.




Setup & Prerequisites — Let’s Get Ready to Build!


Before we dive into the code, let’s make sure you’ve got everything set up. Don’t worry—it’s quick!



What You’ll Need:


  • Python 3.9+ installed

  • A working OpenAI API key (for GPT-4.1)

  • Access to Jupyter Notebook or any Python IDE

  • Prior chatbot code from our previous blog — since this is built on top of it



Install Required Packages:


Let’s install the required dependencies. We’ll be using LangChain, LangGraph, and the langchain-tavily tool for search:


pip install -U "langchain[openai]" langgraph langchain-tavily

Note: If you haven’t set up your OpenAI API key before, you can do so like this:

export OPENAI_API_KEY="sk-..."  # Mac/Linux # or set OPENAI_API_KEY="sk-..."     # Windows

Now, let’s break down the code we’re using in this tutorial — step by step — to see how it all comes together. Buckle up!



Step-by-Step Code Walkthrough — Let’s Build the HITL Chatbot



Step 1: Initialise the Language Model


import os 
from langchain.chat_models import init_chat_model 

os.environ["OPENAI_API_KEY"] = "sk-..." 

llm = init_chat_model("openai:gpt-4.1")

We start by importing and initializing the chat model. We’re using GPT-4.1 here, but feel free to use GPT-3.5 or another model if needed.


Setting the environment variable ensures your API key is picked up securely.



Step 2: Define the Tools (Including the Human in the Loop)


from langchain_tavily import TavilySearch 
from langchain_core.tools import tool 
from typing_extensions import TypedDict 
from typing import Annotated 
from langgraph.types import Command, interrupt

We bring in:


  • TavilySearch: A search tool to answer factual queries from the web.

  • @tool: A decorator to define tools.

  • interrupt: This is our hero! It allows the graph to pause and wait for human intervention.


Now we define a custom tool:


@tool 
def human_assistance(query: str) -> str: 
	"""Request assistance from a human.""" 

	human_response = interrupt({"query": query}) 

	return human_response["data"]

This tool does something amazing: when invoked, it stops the chatbot and raises a signal (interrupt) that waits until a human provides a response. Once you manually resume the graph, it continues from where it left off.



Step 3: Define State and Set Up the Graph


from langgraph.graph import StateGraph, START, END 
from langgraph.graph.message import add_messages 

class State(TypedDict): 
	messages: Annotated[list, add_messages]

We define the structure of our conversation state. messages is a list that will store user inputs, assistant responses, and tool outputs.


Now we build the graph:


graph_builder = StateGraph(State)

This will help us wire up nodes, tools, memory, and flow logic.



Step 4: Add Our Chatbot Logic


def chatbot(state: State): 
	message = llm_with_tools.invoke(state["messages"]) 
	assert len(message.tool_calls) <= 1 
	return {"messages": [message]}

This function is the core of the bot. It:


  • Feeds the message list to the LLM (with tools attached).

  • Returns the assistant response and any tool calls it generates.


We disable parallel tool calls (assert len(...)) because LangGraph doesn't resume multiple tool calls cleanly after a human interruption.



Step 5: Bind Tools and Wire It Up


tools = [TavilySearch(max_results=2), human_assistance] llm_with_tools = llm.bind_tools(tools) 

from langgraph.prebuilt import ToolNode, tools_condition 

graph_builder.add_node("chatbot", chatbot) graph_builder.add_node("tools", ToolNode(tools=tools)) graph_builder.add_conditional_edges("chatbot", tools_condition) graph_builder.add_edge("tools", "chatbot") graph_builder.add_edge(START, "chatbot")

This sets up:


  • The chatbot node to handle LLM responses.

  • The tools node to run either the Tavily tool or human assistance based on the response.

  • Routing logic using tools_condition, which decides if a tool should be called.

  • Edges that define the flow of control from chatbot → tool → chatbot (loop), starting from START.



Step 6: Add Memory (Same as Before)


from langgraph.checkpoint.memory import MemorySaver 

memory = MemorySaver() 
graph = graph_builder.compile(checkpointer=memory)

Just like in our previous tutorial, we use MemorySaver to:


  • Keep track of messages.

  • Isolate memory per thread_id.

  • Resume exactly where we left off, even after an interrupt.


Remember: MemorySaver is in-memory only (great for testing). For real deployments, use Redis or other backends.


Step 7: Test It Out!


Let’s run the chatbot and trigger the human-in-the-loop mechanism:


user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?" 

config = {"configurable": {"thread_id": "1"}} 

events = graph.stream( {"messages": [{"role": "user", "content": user_input}]}, config, stream_mode="values", ) 

for event in events: 
	if "messages" in event: event["messages"][-1].pretty_print()

Output:



At this point, the chatbot recognises it needs help and calls the human_assistance tool. Execution is paused.


Now we (a human!) respond manually:


human_response = ( "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent. " "It's much more reliable and extensible than simple autonomous agents." ) 

from langgraph.types import Command 

human_command = Command(resume={"data": human_response}) 
events = graph.stream(human_command, config, stream_mode="values") 

for event in events: if "messages" in event: 
	event["messages"][-1].pretty_print()

Output:


Boom! The graph resumes, processes your input, and continues the chat. You’ve just experienced a full human-in-the-loop workflow.



Peek into the Bot’s Brain — Inspecting Memory State


LangGraph makes it super easy to inspect the current state of a conversation — even when human input has interrupted the flow. This is especially helpful for debugging or just satisfying your curiosity!


Here's how you can peek under the hood:


snapshot = graph.get_state(config) 
snapshot.next

The get_state() function returns the entire current memory of the graph, including:


  • The message history for the given thread_id

  • Any intermediate outputs from tools

  • What step the bot will execute next


If you check snapshot.next, you'll see where the graph is planning to go after human input. It’s like seeing the next move on a chessboard.


This is incredibly useful if your workflow feels "stuck" and you want to understand what LangGraph is waiting for.


Visualise Your Workflow with Mermaid


LangGraph can generate a diagram of your workflow to show how your nodes connect — great for documentation or just visual learners (like us!).


Try running this:


from IPython.display import Image, display 

try: display(Image(graph.get_graph().draw_mermaid_png())) 
except Exception: # Optional — requires Graphviz and Mermaid setup pass

Output:




This will output a neat visual map of your graph:


  • You’ll see START → chatbot → tools → chatbot → ... → END

  • Tool calls (like human assistance or Tavily) are neatly integrated

  • It’s also helpful to understand loops and conditions at a glance


If this doesn’t render on your machine, no worries — just make sure graphviz and mermaid-cli are installed via your system or notebook kernel.



Recap:


You’ve officially leveled up! Here's a summary of what you’ve achieved in this tutorial:


  • You started with a memory-powered chatbot from the previous blog

  • You introduced a Human-in-the-Loop (HITL) tool using LangGraph's interrupt()

  • You learned how to pause your chatbot and resume with external (human) input

  • You inspected graph state and message history with get_state()

  • You visualized the control flow of your chatbot graph like a pro


The beauty of LangGraph is in how easy it makes building structured, traceable, and flexible LLM workflows — and now you've added human-level decision making on top of it. Bravo! 👏



You may also like these blogs:




Building an intelligent, tool-powered AI system with human feedback in the loop? We’d love to help. Codersarts specializes in real-world AI development and can assist with: LangChain & LangGraph integrations, Toolchains, RAG systems, agents, and more. Building scalable, production-grade workflows


Comments


bottom of page