Graph Persistence when input is not None, graph will reset to entry point #36

Closed
opened 2026-02-20 17:23:36 -05:00 by yindo · 2 comments
Owner

Originally created by @faze059 on GitHub (Feb 29, 2024).

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.

Example Code

@tool("web_search")
def web_search(query: str) -> str:
    """Search with Google SERP API by a query"""
    search = SerpAPIWrapper()
    return search.run(query)

tools = [web_search]
prompt = hub.pull("hwchase17/openai-functions-agent")
llm = ChatOpenAI(model="gpt-4-turbo-preview", streaming=True)
agent_runnable = create_openai_functions_agent(llm, tools, prompt)


class AgentState(TypedDict):
    input: Annotated[Sequence[BaseMessage], operator.add]
    chat_history: list[BaseMessage]
    agent_outcome: Union[AgentAction, AgentFinish, None]
    intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]

from langchain_core.agents import AgentFinish
from langgraph.prebuilt.tool_executor import ToolExecutor

tool_executor1 = ToolExecutor(tools)
tool_executor2 = ToolExecutor([])

# Define the agent
def run_agent(data):
    agent_outcome = agent_runnable.invoke(data)
    return {"agent_outcome": agent_outcome}


# Define the function to execute tools
def execute_tools1(data):
    agent_action = data["agent_outcome"]
    output = tool_executor1.invoke(agent_action)
    print('execute_tools1')
    return {"intermediate_steps": [(agent_action, str(output))]}

# Define the function to execute tools
def execute_tools2(data):
    agent_action = data["agent_outcome"]
    output = tool_executor2.invoke(agent_action)

    print('execute_tools2')
    return {"intermediate_steps": [(agent_action, str(output))]}

def should_continue(data):
    if isinstance(data["agent_outcome"], AgentFinish):
        return "end"
    else:
        return "continue"


workflow = StateGraph(AgentState)

workflow.add_node("agent", run_agent)
workflow.add_node("action1", execute_tools1)
workflow.add_node("action2", execute_tools2)

workflow.set_entry_point("agent")
workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "action1",
        "end": END,
    },
)

workflow.add_edge("action1", "action2")
workflow.add_edge("action2", "agent")


memory = SqliteSaver.from_conn_string(":memory:")

app = workflow.compile(checkpointer=memory, interrupt_before=["action2"])


inputs = {"input": [HumanMessage(content="what is the weather in sf")], "chat_history": []}


for s in app.stream(inputs, {"configurable": {"thread_id": "3"}}):
    print(list(s.values())[0])
    print("----")

inputs = {"input": [HumanMessage(content="what is the weather in NY")]}

for s in app.stream(inputs, {"configurable": {"thread_id": "3"}}):
    print(list(s.values())[0])
    print("----")

Error Message and Stack Trace (if applicable)

No response

Description

Bug Report: StateGraph Workflow Persistence Issue with Configurable Thread ID

Summary

When using the StateGraph workflow with a configurable thread ID to maintain state across multiple iterations of a stream, the graph does not resume from the expected node following an interruption and the presence of new input. Instead of continuing from the interrupted point with the new input, the workflow restarts from the entry point. This behavior diverges from the documented functionality, where supplying None as input for a subsequent iteration should prompt the graph to continue from the interrupted node.

Steps to Reproduce

  1. Initialize a StateGraph with an entry point and multiple nodes, including an interruption point (interrupt_before=["action2"]).
  2. Compile the graph with a SqliteSaver configured for in-memory persistence and a configurable thread ID.
  3. Stream inputs through the graph using a specific thread ID, and interrupt the workflow at the predetermined point.
  4. Attempt to resume the workflow by streaming additional inputs with the same thread ID, expecting the workflow to pick up from the point of interruption.

Expected Behavior

Upon streaming new inputs with the same thread ID after an interruption, the workflow should resume from the node immediately following the last executed node before the interruption.

Actual Behavior

The workflow restarts from the entry point node, disregarding the previously executed path and the interruption point, leading to an unexpected re-initialization of the workflow.

Thank you for looking into this issue. Please let me know if there's any more information I can provide to help diagnose and resolve this problem.

System Info

langchain==0.1.9
langchain-community==0.0.24
langchain-core==0.1.27
langchain-openai==0.0.8
langchainhub==0.1.14
python==3.12
langgraph==0.0.26

Originally created by @faze059 on GitHub (Feb 29, 2024). ### Checked other resources - [X] I added a very descriptive title to this issue. - [X] I searched the LangChain documentation with the integrated search. - [X] I used the GitHub search to find a similar question and didn't find it. - [X] I am sure that this is a bug in LangChain rather than my code. ### Example Code ```python @tool("web_search") def web_search(query: str) -> str: """Search with Google SERP API by a query""" search = SerpAPIWrapper() return search.run(query) tools = [web_search] prompt = hub.pull("hwchase17/openai-functions-agent") llm = ChatOpenAI(model="gpt-4-turbo-preview", streaming=True) agent_runnable = create_openai_functions_agent(llm, tools, prompt) class AgentState(TypedDict): input: Annotated[Sequence[BaseMessage], operator.add] chat_history: list[BaseMessage] agent_outcome: Union[AgentAction, AgentFinish, None] intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add] from langchain_core.agents import AgentFinish from langgraph.prebuilt.tool_executor import ToolExecutor tool_executor1 = ToolExecutor(tools) tool_executor2 = ToolExecutor([]) # Define the agent def run_agent(data): agent_outcome = agent_runnable.invoke(data) return {"agent_outcome": agent_outcome} # Define the function to execute tools def execute_tools1(data): agent_action = data["agent_outcome"] output = tool_executor1.invoke(agent_action) print('execute_tools1') return {"intermediate_steps": [(agent_action, str(output))]} # Define the function to execute tools def execute_tools2(data): agent_action = data["agent_outcome"] output = tool_executor2.invoke(agent_action) print('execute_tools2') return {"intermediate_steps": [(agent_action, str(output))]} def should_continue(data): if isinstance(data["agent_outcome"], AgentFinish): return "end" else: return "continue" workflow = StateGraph(AgentState) workflow.add_node("agent", run_agent) workflow.add_node("action1", execute_tools1) workflow.add_node("action2", execute_tools2) workflow.set_entry_point("agent") workflow.add_conditional_edges( "agent", should_continue, { "continue": "action1", "end": END, }, ) workflow.add_edge("action1", "action2") workflow.add_edge("action2", "agent") memory = SqliteSaver.from_conn_string(":memory:") app = workflow.compile(checkpointer=memory, interrupt_before=["action2"]) inputs = {"input": [HumanMessage(content="what is the weather in sf")], "chat_history": []} for s in app.stream(inputs, {"configurable": {"thread_id": "3"}}): print(list(s.values())[0]) print("----") inputs = {"input": [HumanMessage(content="what is the weather in NY")]} for s in app.stream(inputs, {"configurable": {"thread_id": "3"}}): print(list(s.values())[0]) print("----") ``` ### Error Message and Stack Trace (if applicable) _No response_ ### Description # Bug Report: StateGraph Workflow Persistence Issue with Configurable Thread ID ## Summary When using the `StateGraph` workflow with a configurable thread ID to maintain state across multiple iterations of a stream, the graph does not resume from the expected node following an interruption and the presence of new input. Instead of continuing from the interrupted point with the new input, the workflow restarts from the entry point. This behavior diverges from the documented functionality, where supplying `None` as input for a subsequent iteration should prompt the graph to continue from the interrupted node. ## Steps to Reproduce 1. Initialize a `StateGraph` with an entry point and multiple nodes, including an interruption point (`interrupt_before=["action2"]`). 2. Compile the graph with a `SqliteSaver` configured for in-memory persistence and a configurable thread ID. 3. Stream inputs through the graph using a specific thread ID, and interrupt the workflow at the predetermined point. 4. Attempt to resume the workflow by streaming additional inputs with the same thread ID, expecting the workflow to pick up from the point of interruption. ## Expected Behavior Upon streaming new inputs with the same thread ID after an interruption, the workflow should resume from the node immediately following the last executed node before the interruption. ## Actual Behavior The workflow restarts from the entry point node, disregarding the previously executed path and the interruption point, leading to an unexpected re-initialization of the workflow. Thank you for looking into this issue. Please let me know if there's any more information I can provide to help diagnose and resolve this problem. ### System Info langchain==0.1.9 langchain-community==0.0.24 langchain-core==0.1.27 langchain-openai==0.0.8 langchainhub==0.1.14 python==3.12 langgraph==0.0.26
yindo closed this issue 2026-02-20 17:23:37 -05:00
Author
Owner

@hinthornw commented on GitHub (Mar 18, 2024):

Could you re-write the example code so i can run it (by including imports, for example)? Will save me time in fixing this for you.

@hinthornw commented on GitHub (Mar 18, 2024): Could you re-write the example code so i can run it (by including imports, for example)? Will save me time in fixing this for you.
Author
Owner

@nfcampos commented on GitHub (Mar 29, 2024):

This behavior diverges from the documented functionality, where supplying None as input for a subsequent iteration should prompt the graph to continue from the interrupted node.

The issue is precisely that you are diverging from the documented functionality. If you call the graph again with None as the input it will resume from the last interruption. If instead you call (which is what I see in your example code) the graph again with new input then we need to discard the previous interruption and start fresh from the start.

If you want to edit some of the past messages before resuming after an interruption look at this example https://github.com/langchain-ai/langgraph/blob/main/examples/time-travel.ipynb

@nfcampos commented on GitHub (Mar 29, 2024): >This behavior diverges from the documented functionality, where supplying None as input for a subsequent iteration should prompt the graph to continue from the interrupted node. The issue is precisely that you are diverging from the documented functionality. If you call the graph again with `None` as the input it will resume from the last interruption. If instead you call (which is what I see in your example code) the graph again with new input then we need to discard the previous interruption and start fresh from the start. If you want to edit some of the past messages before resuming after an interruption look at this example https://github.com/langchain-ai/langgraph/blob/main/examples/time-travel.ipynb
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#36