Fan-out/in fails with subgraphs #330

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

Originally created by @Karjan1 on GitHub (Nov 29, 2024).

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangGraph/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 LangGraph/LangChain rather than my code.
  • I am sure this is better as an issue rather than a GitHub discussion, since this is a LangGraph bug and not a design question.

Example Code

import operator
from langgraph.graph import START, StateGraph
from typing import TypedDict, Annotated
from langgraph.types import Send

# Define subgraph
class SubgraphState(TypedDict):
    father: str

def get_kids(state: SubgraphState):
    return {"kids": [state["father"] + " Junior"]}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(get_kids)
subgraph_builder.add_edge(START, "get_kids")
subgraph = subgraph_builder.compile()


# Define parent graph
class ParentState(TypedDict):
    fathers: list[str]
    kids: Annotated[list[str], operator.add]

def parent_node_1(state: ParentState):
    pass

def parent_fanout(state: ParentState):
    return [
        Send(
            "child_node",
            {
                "father": father,
            },
        ) for father in state["fathers"]
    ]

def parent_node_2(state: ParentState):
    pass


builder = StateGraph(ParentState)
builder.add_node("parent_node_1", parent_node_1)
builder.add_node("child_node", subgraph)
# Replacing subgraph with functions makes it work as expected
# builder.add_node("child_node", get_kids)
builder.add_node("parent_node_2", parent_node_2)
builder.add_edge(START, "parent_node_1")
builder.add_conditional_edges("parent_node_1", parent_fanout, ["child_node"])
builder.add_edge("child_node", "parent_node_2")
graph = builder.compile()


# Run graph
context = {"fathers": ["Jon", "Matt"]}
print(graph.invoke(context))

Error Message and Stack Trace (if applicable)

/Users/user/Projects/private/llm-playground/.venv/bin/python /Users/user/Projects/private/llm-playground/script.py 
Traceback (most recent call last):
  File "/Users/user/Projects/private/llm-playground/script.py", line 55, in <module>
    print(graph.invoke(context))
          ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1927, in invoke
    for chunk in self.stream(
                 ^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1647, in stream
    for _ in runner.tick(
             ^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 159, in tick
    _panic_or_proceed(
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 367, in _panic_or_proceed
    raise exc
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/executor.py", line 70, in done
    task.result()
  File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/retry.py", line 40, in run_with_retry
    task.proc.invoke(task.input, config)
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 410, in invoke
    input = context.run(step.invoke, input, config, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1927, in invoke
    for chunk in self.stream(
                 ^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1647, in stream
    for _ in runner.tick(
             ^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 104, in tick
    run_with_retry(t, retry_policy, writer=writer)
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/retry.py", line 40, in run_with_retry
    task.proc.invoke(task.input, config)
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 412, in invoke
    input = context.run(step.invoke, input, config)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 176, in invoke
    ret = context.run(self.func, input, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/write.py", line 85, in _write
    self.do_write(
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/write.py", line 130, in do_write
    write.mapper(write.value) if write.mapper is not None else write.value
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/graph/state.py", line 635, in _get_state_key
    raise InvalidUpdateError(
langgraph.errors.InvalidUpdateError: Expected node father to update at least one of ['father'], got {'kids': ['Jon Junior']}

Process finished with exit code 1

Description

When fanning in subgraphs LangGraph fails to merge subgraphs state into the receiving graph state.
I belive the code explains the issue best TLDR of what would be expected:

  • ParentGraph receives list of names
  • ParentGraph sends Send event per each name to SubGraph (when sending to function of ParentGraph it works as expected)
  • SubGraph returns list of kids names for specific father
  • ParentGraph aggregates kids names to return as single list (this step fails)

It works perfectly fine without subgraph but I have pretty complex logic that would be nasty to move to a single node.

System Info

langchain==0.3.7
langchain-anthropic==0.3.0
langchain-community==0.3.7
langchain-core==0.3.18
langchain-groq==0.2.1
langchain-ollama==0.2.0
langchain-openai==0.2.8
langchain-text-splitters==0.3.2
mac
Python 3.12.7

Originally created by @Karjan1 on GitHub (Nov 29, 2024). ### Checked other resources - [X] I added a very descriptive title to this issue. - [X] I searched the [LangGraph](https://langchain-ai.github.io/langgraph/)/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 LangGraph/LangChain rather than my code. - [X] I am sure this is better as an issue [rather than a GitHub discussion](https://github.com/langchain-ai/langgraph/discussions/new/choose), since this is a LangGraph bug and not a design question. ### Example Code ```python import operator from langgraph.graph import START, StateGraph from typing import TypedDict, Annotated from langgraph.types import Send # Define subgraph class SubgraphState(TypedDict): father: str def get_kids(state: SubgraphState): return {"kids": [state["father"] + " Junior"]} subgraph_builder = StateGraph(SubgraphState) subgraph_builder.add_node(get_kids) subgraph_builder.add_edge(START, "get_kids") subgraph = subgraph_builder.compile() # Define parent graph class ParentState(TypedDict): fathers: list[str] kids: Annotated[list[str], operator.add] def parent_node_1(state: ParentState): pass def parent_fanout(state: ParentState): return [ Send( "child_node", { "father": father, }, ) for father in state["fathers"] ] def parent_node_2(state: ParentState): pass builder = StateGraph(ParentState) builder.add_node("parent_node_1", parent_node_1) builder.add_node("child_node", subgraph) # Replacing subgraph with functions makes it work as expected # builder.add_node("child_node", get_kids) builder.add_node("parent_node_2", parent_node_2) builder.add_edge(START, "parent_node_1") builder.add_conditional_edges("parent_node_1", parent_fanout, ["child_node"]) builder.add_edge("child_node", "parent_node_2") graph = builder.compile() # Run graph context = {"fathers": ["Jon", "Matt"]} print(graph.invoke(context)) ``` ### Error Message and Stack Trace (if applicable) ```shell /Users/user/Projects/private/llm-playground/.venv/bin/python /Users/user/Projects/private/llm-playground/script.py Traceback (most recent call last): File "/Users/user/Projects/private/llm-playground/script.py", line 55, in <module> print(graph.invoke(context)) ^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1927, in invoke for chunk in self.stream( ^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1647, in stream for _ in runner.tick( ^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 159, in tick _panic_or_proceed( File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 367, in _panic_or_proceed raise exc File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/executor.py", line 70, in done task.result() File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 449, in result return self.__get_result() ^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result raise self._exception File "/opt/homebrew/Cellar/python@3.12/3.12.7_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/retry.py", line 40, in run_with_retry task.proc.invoke(task.input, config) File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 410, in invoke input = context.run(step.invoke, input, config, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1927, in invoke for chunk in self.stream( ^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1647, in stream for _ in runner.tick( ^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 104, in tick run_with_retry(t, retry_policy, writer=writer) File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/retry.py", line 40, in run_with_retry task.proc.invoke(task.input, config) File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 412, in invoke input = context.run(step.invoke, input, config) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/utils/runnable.py", line 176, in invoke ret = context.run(self.func, input, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/write.py", line 85, in _write self.do_write( File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/pregel/write.py", line 130, in do_write write.mapper(write.value) if write.mapper is not None else write.value ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/user/Projects/private/llm-playground/.venv/lib/python3.12/site-packages/langgraph/graph/state.py", line 635, in _get_state_key raise InvalidUpdateError( langgraph.errors.InvalidUpdateError: Expected node father to update at least one of ['father'], got {'kids': ['Jon Junior']} Process finished with exit code 1 ``` ### Description When fanning in subgraphs LangGraph fails to merge subgraphs state into the receiving graph state. I belive the code explains the issue best TLDR of what would be expected: - ParentGraph receives list of names - ParentGraph sends Send event per each name to SubGraph (when sending to function of ParentGraph it works as expected) - SubGraph returns list of kids names for specific father - ParentGraph aggregates kids names to return as single list (this step fails) It works perfectly fine without subgraph but I have pretty complex logic that would be nasty to move to a single node. ### System Info langchain==0.3.7 langchain-anthropic==0.3.0 langchain-community==0.3.7 langchain-core==0.3.18 langchain-groq==0.2.1 langchain-ollama==0.2.0 langchain-openai==0.2.8 langchain-text-splitters==0.3.2 mac Python 3.12.7
yindo closed this issue 2026-02-20 17:36:44 -05:00
Author
Owner

@gbaian10 commented on GitHub (Nov 29, 2024):

image

@gbaian10 commented on GitHub (Nov 29, 2024): ![image](https://github.com/user-attachments/assets/bf0c0fb4-85e7-4a4c-8bf8-9eb0762e4b03)
Author
Owner

@Karjan1 commented on GitHub (Nov 30, 2024):

Damn, you're totally right. I've spent ~8h validating it from every angle and missed it. Thank you!

@Karjan1 commented on GitHub (Nov 30, 2024): Damn, you're totally right. I've spent ~8h validating it from every angle and missed it. Thank you!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#330