Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Human-in-the-Loop

Human-in-the-loop (HITL) refers to the process where an Agent actively interrupts to request execution permission or additional information from humans, and continues execution after receiving human feedback.

LangGraph’s HITL functionality can be implemented through the built-in middleware HumanInTheLoopMiddleware (HITL). After triggering HITL, the middleware saves the current state to a checkpointer checkpoint and waits for human response. After receiving the response, it restores the state from the checkpoint and continues task execution.

import os
import uuid
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

# Load model configuration
_ = load_dotenv()

Below we use the HITL middleware to configure manual approval workflows for three tools.

# Configure LLM service
llm = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    model="qwen3-coder-plus",
)

# Tool functions
@tool
def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"

@tool
def add_numbers(a: float, b: float) -> float:
    """Add two numbers and return the sum."""
    return a + b

@tool
def calculate_bmi(weight_kg: float, height_m: float) -> float:
    """Calculate BMI given weight in kg and height in meters."""
    if height_m <= 0 or weight_kg <= 0:
        raise ValueError("height_m and weight_kg must be greater than 0.")
    return weight_kg / (height_m ** 2)

# Create Agent with tool calling
tool_agent = create_agent(
    model=llm,
    tools=[get_weather, add_numbers, calculate_bmi],
    middleware=[
        HumanInTheLoopMiddleware( 
            interrupt_on={
                # No need to trigger human approval
                "get_weather": False,
                # Requires approval, allows approve, edit, reject decision types
                "add_numbers": True,
                # Requires approval, allows approve, reject decision types
                "calculate_bmi": {"allowed_decisions": ["approve", "reject"]},
            },
            description_prefix="Tool execution pending approval",
        ),
    ],
    checkpointer=InMemorySaver(),
    system_prompt="You are a helpful assistant",
)
# Run Agent
config = {'configurable': {'thread_id': str(uuid.uuid4())}}
result = tool_agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "I am 180cm tall and weigh 90kg, what is my BMI?"
        # "content": "what is the weather in sf"
    }]},
    config=config,
)

# result['messages'][-1].content
result.get('__interrupt__')
[Interrupt(value={'action_requests': [{'name': 'calculate_bmi', 'args': {'height_m': 1.8, 'weight_kg': 90}, 'description': "Tool execution pending approval\n\nTool: calculate_bmi\nArgs: {'height_m': 1.8, 'weight_kg': 90}"}], 'review_configs': [{'action_name': 'calculate_bmi', 'allowed_decisions': ['approve', 'reject']}]}, id='9f0da0ba54cd2526d9ca530ba6fc27f2')]

From the interrupt information, we can see that the Agent has triggered the calculate_bmi tool call and entered a waiting-for-approval state. Below we send an “approval” command to the Agent to resume its execution.

# Resume with approval decision
result = tool_agent.invoke(
    Command(
        resume={"decisions": [{"type": "approve"}]}  # or "edit", "reject"
    ), 
    config=config
)

result['messages'][-1].content
'Your BMI is approximately 27.78, which is considered overweight.'

Reference documentation: