Human in the loop multi-turn conversation example reimplement failed #610

Closed
opened 2026-02-20 17:40:57 -05:00 by yindo · 1 comment
Owner

Originally created by @VinhPhamBG on GitHub (May 6, 2025).

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

from typing import Annotated, Literal
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from markitdown import MarkItDown
import random
from typing import Annotated, Literal

from langchain_core.tools import tool
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState

@tool
def get_travel_recommendations():
    """Get recommendation for travel destinations"""
    return random.choice(["aruba", "turks and caicos"])


@tool
def get_hotel_recommendations(location: Literal["aruba", "turks and caicos"]):
    """Get hotel recommendations for a given destination."""
    return {
        "aruba": [
            "The Ritz-Carlton, Aruba (Palm Beach)"
            "Bucuti & Tara Beach Resort (Eagle Beach)"
        ],
        "turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"],
    }[location]


def make_handoff_tool(*, agent_name: str):
    """Create a tool that can return handoff via a Command"""
    tool_name = f"transfer_to_{agent_name}"

    @tool(tool_name)
    def handoff_to_agent(
        state: Annotated[dict, InjectedState],
        tool_call_id: Annotated[str, InjectedToolCallId],
    ):
        """Ask another agent for help."""
        tool_message = {
            "role": "tool",
            "content": f"Successfully transferred to {agent_name}",
            "name": tool_name,
            "tool_call_id": tool_call_id,
        }
        return Command(
            # navigate to another agent node in the PARENT graph
            goto=agent_name,
            graph=Command.PARENT,
            # This is the state update that the agent `agent_name` will see when it is invoked.
            # We're passing agent's FULL internal message history AND adding a tool message to make sure
            # the resulting chat history is valid.
            update={"messages": state["messages"] + [tool_message]},
        )

    return handoff_to_agent

model = ChatOpenAI(
    model_name="gpt-4.1-mini",
)
# Define travel advisor tools and ReAct agent
travel_advisor_tools = [
    get_travel_recommendations,
    make_handoff_tool(agent_name="hotel_advisor"),
]
travel_advisor = create_react_agent(
    model,
    travel_advisor_tools,
    prompt=(
        "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). "
        "If you need hotel recommendations, ask 'hotel_advisor' for help. "
        "You MUST include human-readable response before transferring to another agent."
    ),
)


def call_travel_advisor(
    state: MessagesState,
) -> Command[Literal["hotel_advisor", "human"]]:
    # You can also add additional logic like changing the input to the agent / output from the agent, etc.
    # NOTE: we're invoking the ReAct agent with the full history of messages in the state
    response = travel_advisor.invoke(state)
    return Command(update=response, goto="human")


# Define hotel advisor tools and ReAct agent
hotel_advisor_tools = [
    get_hotel_recommendations,
    make_handoff_tool(agent_name="travel_advisor"),
]
hotel_advisor = create_react_agent(
    model,
    hotel_advisor_tools,
    prompt=(
        "You are a hotel expert that can provide hotel recommendations for a given destination. "
        "If you need help picking travel destinations, ask 'travel_advisor' for help."
        "You MUST include human-readable response before transferring to another agent."
    ),
)


def call_hotel_advisor(
    state: MessagesState,
) -> Command[Literal["travel_advisor", "human"]]:
    response = hotel_advisor.invoke(state)
    return Command(update=response, goto="human")


def human_node(
    state: MessagesState, config
) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]:
    """A node for collecting user input."""

    user_input = interrupt("Ready for user input.")

    # identify the last active agent
    # (the last active node before returning to human)
    langgraph_triggers = config["metadata"]["langgraph_triggers"]
    if len(langgraph_triggers) != 1:
        raise AssertionError("Expected exactly 1 trigger in human node")

    active_agent = langgraph_triggers[0].split(":")[1]

    return Command(
        update={
            "messages": [
                {
                    "role": "human",
                    "content": user_input,
                }
            ]
        },
        goto=active_agent,
    )


builder = StateGraph(MessagesState)
builder.add_node("travel_advisor", call_travel_advisor)
builder.add_node("hotel_advisor", call_hotel_advisor)

# This adds a node to collect human input, which will route
# back to the active agent.
builder.add_node("human", human_node)

# We'll always start with a general travel advisor.
builder.add_edge(START, "travel_advisor")


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

import uuid

thread_config = {"configurable": {"thread_id": uuid.uuid4()}}

inputs = [
    # 1st round of conversation,
    {
        "messages": [
            {"role": "user", "content": "i wanna go somewhere warm in the caribbean"}
        ]
    },
    # Since we're using `interrupt`, we'll need to resume using the Command primitive.
    # 2nd round of conversation,
    Command(
        resume="could you recommend a nice hotel in one of the areas and tell me which area it is."
    ),
    # 3rd round of conversation,
    Command(
        resume="i like the first one. could you recommend something to do near the hotel?"
    ),
]

for idx, user_input in enumerate(inputs):
    print()
    print(f"--- Conversation Turn {idx + 1} ---")
    print()
    print(f"User: {user_input}")
    print()
    for update in graph.stream(
        user_input,
        config=thread_config,
        stream_mode="updates",
    ):
        for node_id, value in update.items():
            if isinstance(value, dict) and value.get("messages", []):
                last_message = value["messages"][-1]
                if isinstance(last_message, dict) or last_message.type != "ai":
                    continue
                print(f"{node_id}: {last_message.content}")

Error Message and Stack Trace (if applicable)

--- Conversation Turn 1 ---

User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}

travel_advisor: A great warm destination in the Caribbean that I recommend is Aruba. It's known for its beautiful beaches, sunny weather, and vibrant culture. Would you like recommendations for hotels or activities there?

--- Conversation Turn 2 ---

User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')


--- Conversation Turn 3 ---

User: Command(resume='i like the first one. could you recommend something to do near the hotel?')

Description

I'm trying to reimplement the example of Human in the loop multi-turn conversation (tutorial url:https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo) but the interrupt can not resume

System Info

System Information

OS: Linux
OS Version: #55-Ubuntu SMP Mon Nov 20 19:51:53 UTC 2023
Python Version: 3.12.9 | packaged by Anaconda, Inc. | (main, Feb 6 2025, 18:56:27) [GCC 11.2.0]

Package Information

langchain_core: 0.3.56
langchain: 0.3.18
langchain_community: 0.3.17
langsmith: 0.1.147
langchain_anthropic: 0.3.12
langchain_cohere: 0.3.5
langchain_experimental: 0.3.4
langchain_fireworks: 0.3.0
langchain_openai: 0.2.14
langchain_tavily: 0.1.5
langchain_text_splitters: 0.3.8
langgraph_api: 0.2.9
langgraph_cli: 0.2.8
langgraph_license: Installed. No version info available.
langgraph_runtime: Installed. No version info available.
langgraph_runtime_inmem: 0.0.9
langgraph_sdk: 0.1.66

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.11.18
aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
aiohttp<4.0.0,>=3.9.1: Installed. No version info available.
anthropic<1,>=0.49.0: Installed. No version info available.
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
blockbuster: 1.5.24
click: 8.1.8
cloudpickle: 3.1.1
cohere: 5.15.0
cryptography: 44.0.2
dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
fireworks-ai>=0.13.0: Installed. No version info available.
httpx: 0.27.2
httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
jsonpatch<2.0,>=1.33: Installed. No version info available.
jsonschema-rs: 0.29.1
langchain-anthropic;: Installed. No version info available.
langchain-aws;: Installed. No version info available.
langchain-cohere;: Installed. No version info available.
langchain-community;: Installed. No version info available.
langchain-core<1.0.0,>=0.3.34: Installed. No version info available.
langchain-core<1.0.0,>=0.3.51: Installed. No version info available.
langchain-core<1.0.0,>=0.3.53: Installed. No version info available.
langchain-core<1.0.0,>=0.3.55: Installed. No version info available.
langchain-deepseek;: Installed. No version info available.
langchain-fireworks;: Installed. No version info available.
langchain-google-genai;: Installed. No version info available.
langchain-google-vertexai;: Installed. No version info available.
langchain-groq;: Installed. No version info available.
langchain-huggingface;: Installed. No version info available.
langchain-mistralai;: Installed. No version info available.
langchain-ollama;: Installed. No version info available.
langchain-openai;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.6: Installed. No version info available.
langchain-together;: Installed. No version info available.
langchain<1.0.0,>=0.3.18: Installed. No version info available.
langgraph: 0.3.34
langgraph-checkpoint: 2.0.25
langsmith-pyo3: Installed. No version info available.
langsmith<0.4,>=0.1.125: Installed. No version info available.
langsmith<0.4,>=0.1.17: Installed. No version info available.
mypy: 1.15.0
numpy<2,>=1.26.4;: Installed. No version info available.
numpy<3,>=1.26.2;: Installed. No version info available.
openai: 1.62.0
openai<2.0.0,>=1.10.0: Installed. No version info available.
orjson: 3.10.16
packaging<25,>=23.2: Installed. No version info available.
pandas: 2.2.3
pydantic: 2.11.3
pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
pydantic<3.0.0,>=2.5.2;: Installed. No version info available.
pydantic<3.0.0,>=2.7.4: Installed. No version info available.
pydantic<3.0.0,>=2.7.4;: Installed. No version info available.
pyjwt: 2.10.1
python-dotenv: 1.0.1
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.3
requests-toolbelt: 1.0.0
requests<3,>=2: Installed. No version info available.
SQLAlchemy<3,>=1.4: Installed. No version info available.
sse-starlette: 2.1.3
starlette: 0.41.3
structlog: 25.3.0
tabulate: 0.9.0
tenacity: 9.1.2
tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken: 0.7.0
typing-extensions>=4.7: Installed. No version info available.
uvicorn: 0.34.2
watchfiles: 0.20.0

Originally created by @VinhPhamBG on GitHub (May 6, 2025). ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use GitHub Discussions. - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python from typing import Annotated, Literal from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain_core.tools.base import InjectedToolCallId from langgraph.prebuilt import create_react_agent, InjectedState from langgraph.graph import MessagesState, StateGraph, START from langgraph.types import Command, interrupt from langgraph.checkpoint.memory import MemorySaver from markitdown import MarkItDown import random from typing import Annotated, Literal from langchain_core.tools import tool from langchain_core.tools.base import InjectedToolCallId from langgraph.prebuilt import InjectedState @tool def get_travel_recommendations(): """Get recommendation for travel destinations""" return random.choice(["aruba", "turks and caicos"]) @tool def get_hotel_recommendations(location: Literal["aruba", "turks and caicos"]): """Get hotel recommendations for a given destination.""" return { "aruba": [ "The Ritz-Carlton, Aruba (Palm Beach)" "Bucuti & Tara Beach Resort (Eagle Beach)" ], "turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"], }[location] def make_handoff_tool(*, agent_name: str): """Create a tool that can return handoff via a Command""" tool_name = f"transfer_to_{agent_name}" @tool(tool_name) def handoff_to_agent( state: Annotated[dict, InjectedState], tool_call_id: Annotated[str, InjectedToolCallId], ): """Ask another agent for help.""" tool_message = { "role": "tool", "content": f"Successfully transferred to {agent_name}", "name": tool_name, "tool_call_id": tool_call_id, } return Command( # navigate to another agent node in the PARENT graph goto=agent_name, graph=Command.PARENT, # This is the state update that the agent `agent_name` will see when it is invoked. # We're passing agent's FULL internal message history AND adding a tool message to make sure # the resulting chat history is valid. update={"messages": state["messages"] + [tool_message]}, ) return handoff_to_agent model = ChatOpenAI( model_name="gpt-4.1-mini", ) # Define travel advisor tools and ReAct agent travel_advisor_tools = [ get_travel_recommendations, make_handoff_tool(agent_name="hotel_advisor"), ] travel_advisor = create_react_agent( model, travel_advisor_tools, prompt=( "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). " "If you need hotel recommendations, ask 'hotel_advisor' for help. " "You MUST include human-readable response before transferring to another agent." ), ) def call_travel_advisor( state: MessagesState, ) -> Command[Literal["hotel_advisor", "human"]]: # You can also add additional logic like changing the input to the agent / output from the agent, etc. # NOTE: we're invoking the ReAct agent with the full history of messages in the state response = travel_advisor.invoke(state) return Command(update=response, goto="human") # Define hotel advisor tools and ReAct agent hotel_advisor_tools = [ get_hotel_recommendations, make_handoff_tool(agent_name="travel_advisor"), ] hotel_advisor = create_react_agent( model, hotel_advisor_tools, prompt=( "You are a hotel expert that can provide hotel recommendations for a given destination. " "If you need help picking travel destinations, ask 'travel_advisor' for help." "You MUST include human-readable response before transferring to another agent." ), ) def call_hotel_advisor( state: MessagesState, ) -> Command[Literal["travel_advisor", "human"]]: response = hotel_advisor.invoke(state) return Command(update=response, goto="human") def human_node( state: MessagesState, config ) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]: """A node for collecting user input.""" user_input = interrupt("Ready for user input.") # identify the last active agent # (the last active node before returning to human) langgraph_triggers = config["metadata"]["langgraph_triggers"] if len(langgraph_triggers) != 1: raise AssertionError("Expected exactly 1 trigger in human node") active_agent = langgraph_triggers[0].split(":")[1] return Command( update={ "messages": [ { "role": "human", "content": user_input, } ] }, goto=active_agent, ) builder = StateGraph(MessagesState) builder.add_node("travel_advisor", call_travel_advisor) builder.add_node("hotel_advisor", call_hotel_advisor) # This adds a node to collect human input, which will route # back to the active agent. builder.add_node("human", human_node) # We'll always start with a general travel advisor. builder.add_edge(START, "travel_advisor") memory = MemorySaver() graph = builder.compile(checkpointer=memory) import uuid thread_config = {"configurable": {"thread_id": uuid.uuid4()}} inputs = [ # 1st round of conversation, { "messages": [ {"role": "user", "content": "i wanna go somewhere warm in the caribbean"} ] }, # Since we're using `interrupt`, we'll need to resume using the Command primitive. # 2nd round of conversation, Command( resume="could you recommend a nice hotel in one of the areas and tell me which area it is." ), # 3rd round of conversation, Command( resume="i like the first one. could you recommend something to do near the hotel?" ), ] for idx, user_input in enumerate(inputs): print() print(f"--- Conversation Turn {idx + 1} ---") print() print(f"User: {user_input}") print() for update in graph.stream( user_input, config=thread_config, stream_mode="updates", ): for node_id, value in update.items(): if isinstance(value, dict) and value.get("messages", []): last_message = value["messages"][-1] if isinstance(last_message, dict) or last_message.type != "ai": continue print(f"{node_id}: {last_message.content}") ``` ### Error Message and Stack Trace (if applicable) ```shell --- Conversation Turn 1 --- User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]} travel_advisor: A great warm destination in the Caribbean that I recommend is Aruba. It's known for its beautiful beaches, sunny weather, and vibrant culture. Would you like recommendations for hotels or activities there? --- Conversation Turn 2 --- User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.') --- Conversation Turn 3 --- User: Command(resume='i like the first one. could you recommend something to do near the hotel?') ``` ### Description I'm trying to reimplement the example of Human in the loop multi-turn conversation (tutorial url:https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo) but the interrupt can not resume ### System Info System Information ------------------ > OS: Linux > OS Version: #55-Ubuntu SMP Mon Nov 20 19:51:53 UTC 2023 > Python Version: 3.12.9 | packaged by Anaconda, Inc. | (main, Feb 6 2025, 18:56:27) [GCC 11.2.0] Package Information ------------------- > langchain_core: 0.3.56 > langchain: 0.3.18 > langchain_community: 0.3.17 > langsmith: 0.1.147 > langchain_anthropic: 0.3.12 > langchain_cohere: 0.3.5 > langchain_experimental: 0.3.4 > langchain_fireworks: 0.3.0 > langchain_openai: 0.2.14 > langchain_tavily: 0.1.5 > langchain_text_splitters: 0.3.8 > langgraph_api: 0.2.9 > langgraph_cli: 0.2.8 > langgraph_license: Installed. No version info available. > langgraph_runtime: Installed. No version info available. > langgraph_runtime_inmem: 0.0.9 > langgraph_sdk: 0.1.66 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp: 3.11.18 > aiohttp<4.0.0,>=3.8.3: Installed. No version info available. > aiohttp<4.0.0,>=3.9.1: Installed. No version info available. > anthropic<1,>=0.49.0: Installed. No version info available. > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > blockbuster: 1.5.24 > click: 8.1.8 > cloudpickle: 3.1.1 > cohere: 5.15.0 > cryptography: 44.0.2 > dataclasses-json<0.7,>=0.5.7: Installed. No version info available. > fireworks-ai>=0.13.0: Installed. No version info available. > httpx: 0.27.2 > httpx-sse<1.0.0,>=0.4.0: Installed. No version info available. > jsonpatch<2.0,>=1.33: Installed. No version info available. > jsonschema-rs: 0.29.1 > langchain-anthropic;: Installed. No version info available. > langchain-aws;: Installed. No version info available. > langchain-cohere;: Installed. No version info available. > langchain-community;: Installed. No version info available. > langchain-core<1.0.0,>=0.3.34: Installed. No version info available. > langchain-core<1.0.0,>=0.3.51: Installed. No version info available. > langchain-core<1.0.0,>=0.3.53: Installed. No version info available. > langchain-core<1.0.0,>=0.3.55: Installed. No version info available. > langchain-deepseek;: Installed. No version info available. > langchain-fireworks;: Installed. No version info available. > langchain-google-genai;: Installed. No version info available. > langchain-google-vertexai;: Installed. No version info available. > langchain-groq;: Installed. No version info available. > langchain-huggingface;: Installed. No version info available. > langchain-mistralai;: Installed. No version info available. > langchain-ollama;: Installed. No version info available. > langchain-openai;: Installed. No version info available. > langchain-text-splitters<1.0.0,>=0.3.6: Installed. No version info available. > langchain-together;: Installed. No version info available. > langchain<1.0.0,>=0.3.18: Installed. No version info available. > langgraph: 0.3.34 > langgraph-checkpoint: 2.0.25 > langsmith-pyo3: Installed. No version info available. > langsmith<0.4,>=0.1.125: Installed. No version info available. > langsmith<0.4,>=0.1.17: Installed. No version info available. > mypy: 1.15.0 > numpy<2,>=1.26.4;: Installed. No version info available. > numpy<3,>=1.26.2;: Installed. No version info available. > openai: 1.62.0 > openai<2.0.0,>=1.10.0: Installed. No version info available. > orjson: 3.10.16 > packaging<25,>=23.2: Installed. No version info available. > pandas: 2.2.3 > pydantic: 2.11.3 > pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available. > pydantic<3.0.0,>=2.5.2;: Installed. No version info available. > pydantic<3.0.0,>=2.7.4: Installed. No version info available. > pydantic<3.0.0,>=2.7.4;: Installed. No version info available. > pyjwt: 2.10.1 > python-dotenv: 1.0.1 > PyYAML>=5.3: Installed. No version info available. > requests: 2.32.3 > requests-toolbelt: 1.0.0 > requests<3,>=2: Installed. No version info available. > SQLAlchemy<3,>=1.4: Installed. No version info available. > sse-starlette: 2.1.3 > starlette: 0.41.3 > structlog: 25.3.0 > tabulate: 0.9.0 > tenacity: 9.1.2 > tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > tiktoken: 0.7.0 > typing-extensions>=4.7: Installed. No version info available. > uvicorn: 0.34.2 > watchfiles: 0.20.0
yindo closed this issue 2026-02-20 17:40:57 -05:00
Author
Owner

@vbarda commented on GitHub (May 7, 2025):

Thanks for reporting - can reproduce. Will investigate

@vbarda commented on GitHub (May 7, 2025): Thanks for reporting - can reproduce. Will investigate
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#610