Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected Behavior of State Reducer Function in Subgraph #3587

Open
4 tasks done
iamolvr opened this issue Feb 25, 2025 · 2 comments
Open
4 tasks done

Unexpected Behavior of State Reducer Function in Subgraph #3587

iamolvr opened this issue Feb 25, 2025 · 2 comments

Comments

@iamolvr
Copy link

iamolvr commented Feb 25, 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 langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
from langgraph.graph import END, START
from typing import Annotated, TypedDict


def update_dialog_stack(left: list[str], right: str | None) -> list[str]:
    """Push or pop the state."""
    if right is None:
        return left
    if right == "pop":
        return left[:-1]
    return left + [right]


class State(TypedDict):
    dialog_state: Annotated[list[str], update_dialog_stack]


def node_a_child(state):
    return {"dialog_state": "a_child_state"}

def node_b_child(state):
    return {"dialog_state": "b_child_state"}



sub_builder = StateGraph(State)

sub_builder.add_node("node_a_child", node_a_child)
sub_builder.add_edge(START, "node_a_child")

sub_builder.add_node("node_b_child", node_b_child) 
sub_builder.add_edge("node_a_child", "node_b_child")

sub_builder.add_edge("node_b_child", END)


sub_graph = sub_builder.compile()


def node_a_parent(state):
    return {"dialog_state": "a_parent_state"}

def node_b_parent(state):
    return {"dialog_state": "pop"}


main_builder = StateGraph(State)

main_builder.add_node("node_a_parent", node_a_parent)
main_builder.add_edge(START, "node_a_parent")

main_builder.add_node("subgraph", sub_graph)
main_builder.add_edge("node_a_parent", "subgraph")

main_builder.add_node("node_b_parent", node_b_parent)
main_builder.add_edge("subgraph", "node_b_parent")


main_builder.add_edge("node_b_parent", END)


checkpointer_temp = MemorySaver()
main_graph = main_builder.compile(checkpointer_temp, name="parent")


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

result = main_graph.invoke(input={"dialog_state": "init_state"}, config=config, subgraphs=True, debug=True)

Error Message and Stack Trace (if applicable)

Description

I am trying to update a state from a subgraph node. The dialog_state state key is annotated with the update_dialog_stack reducer function, which is taken verbatim from the LangGraph Customer Support Bot tutorial.

The expected behavior for the dialog_state key should be the following:
[init_state][init_state, a_parent_state][init_state, a_parent_state, a_child_state][init_state, a_parent_state, a_child_state, b_child_state][init_state, a_parent_state, a_child_state].

Below is the history of the actual state updates provided.

{'dialog_state': []}

[0:tasks] Starting 1 task for step 0:
- __start__ -> {'dialog_state': 'init_state'}

[0:writes] Finished step 0 with writes to 1 channel:
- dialog_state -> 'init_state'

[0:checkpoint] State at the end of step 0:
{'dialog_state': ['init_state']}

[1:tasks] Starting 1 task for step 1:
- node_a_parent -> {'dialog_state': ['init_state']}

[1:writes] Finished step 1 with writes to 1 channel:
- dialog_state -> 'a_parent_state'

[1:checkpoint] State at the end of step 1:
{'dialog_state': ['init_state', 'a_parent_state']}

[2:tasks] Starting 1 task for step 2:
- subgraph -> {'dialog_state': ['init_state', 'a_parent_state']}

[2:writes] Finished step 2 with writes to 1 channel:
- dialog_state -> [['init_state', 'a_parent_state'], 'a_child_state', 'b_child_state']

[2:checkpoint] State at the end of step 2:
{'dialog_state': ['init_state',
                  'a_parent_state',
                  [[...], 'a_child_state', 'b_child_state']]}

[3:tasks] Starting 1 task for step 3:
- node_b_parent -> {'dialog_state': ['init_state',
                  'a_parent_state',
                  [['init_state', 'a_parent_state'],
                   'a_child_state',
                   'b_child_state']]}

[3:writes] Finished step 3 with writes to 1 channel:
- dialog_state -> 'pop'

[3:checkpoint] State at the end of step 3:
{'dialog_state': ['init_state', 'a_parent_state']}```

### System Info

System Information
------------------
> OS:  Darwin
> OS Version:  Darwin Kernel Version 24.3.0: Thu Jan  2 20:24:16 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6000
> Python Version:  3.12.7 (main, Oct  1 2024, 02:05:46) [Clang 15.0.0 (clang-1500.3.9.4)]

Package Information
-------------------
> langchain_core: 0.3.36
> langchain: 0.3.19
> langsmith: 0.2.11
> langchain_openai: 0.3.6
> langchain_text_splitters: 0.3.6
> langgraph_sdk: 0.1.51

Optional packages not installed
-------------------------------
> langserve

Other Dependencies
------------------
> aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
> async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
> httpx: 0.28.1
> jsonpatch<2.0,>=1.33: Installed. No version info available.
> 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.35: 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-xai;: Installed. No version info available.
> 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.
> numpy<2,>=1.26.4;: Installed. No version info available.
> numpy<3,>=1.26.2;: Installed. No version info available.
> openai<2.0.0,>=1.58.1: Installed. No version info available.
> orjson: 3.10.15
> packaging<25,>=23.2: Installed. No version info available.
> pydantic: 2.10.5
> 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.
> 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.
> 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<1,>=0.7: Installed. No version info available.
> typing-extensions>=4.7: Installed. No version info available.
> zstandard: Installed. No version info available.
@YassinNouh21
Copy link
Contributor

@iamolvr I tried to play around to fix it

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
from langgraph.graph import END, START
from typing import Annotated, TypedDict


def update_dialog_stack(left: list[str], right: str | None) -> list[str]:
    """Push or pop the state."""
    if right is None:
        return left
    if right == "pop":
        return left[:-1]
    # Handle case where right is a list (returned from subgraph)
    if isinstance(right, list) and len(right) >= 2 and isinstance(right[0], list):
        # Extract just the new values from the subgraph
        return left + right[1:]
    return left + [right]


class State(TypedDict):
    dialog_state: Annotated[list[str], update_dialog_stack]


def node_a_child(state):
    return {"dialog_state": "a_child_state"}

def node_b_child(state):
    return {"dialog_state": "b_child_state"}


sub_builder = StateGraph(State)

sub_builder.add_node("node_a_child", node_a_child)
sub_builder.add_edge(START, "node_a_child")

sub_builder.add_node("node_b_child", node_b_child)
sub_builder.add_edge("node_a_child", "node_b_child")

sub_builder.add_edge("node_b_child", END)


sub_graph = sub_builder.compile()


def node_a_parent(state):
    return {"dialog_state": "a_parent_state"}

def node_b_parent(state):
    return {"dialog_state": "pop"}


main_builder = StateGraph(State)

main_builder.add_node("node_a_parent", node_a_parent)
main_builder.add_edge(START, "node_a_parent")

main_builder.add_node("subgraph", sub_graph)
main_builder.add_edge("node_a_parent", "subgraph")

main_builder.add_node("node_b_parent", node_b_parent)
main_builder.add_edge("subgraph", "node_b_parent")


main_builder.add_edge("node_b_parent", END)


checkpointer_temp = MemorySaver()
main_graph = main_builder.compile(checkpointer_temp, name="parent")


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

result = main_graph.invoke(input={"dialog_state": "init_state"}, config=config, subgraphs=True, debug=True)
print("\nFinal state:", result)

@vbarda
Copy link
Collaborator

vbarda commented Mar 3, 2025

@iamolvr i believe this is a similar question to this -- i added an explanation in that issue #3661. hope that helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants