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

In the subgraph, when there is a branch point to __end__(or __start__ point to ...), it will lose part of the edges #1676

Open
5 tasks done
gbaian10 opened this issue Sep 10, 2024 · 4 comments

Comments

@gbaian10
Copy link
Contributor

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 secrets

from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph
from rich import get_console


def foo(_: MessagesState) -> None:
    return


def branch() -> bool:
    return secrets.choice([True, False])


def sub() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    for node in ["A", "B", "Z"]:
        workflow.add_node(node, foo)

    workflow.add_conditional_edges(START, branch, {True: "A", False: END})
    workflow.add_edge("A", "B")
    workflow.add_conditional_edges("B", branch, {True: "Z", False: END})
    workflow.add_edge("Z", END)
    return workflow.compile()


def main() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    workflow.add_node("main_entry", foo)
    workflow.add_node("sub_1", sub())
    workflow.add_node("main_exit", foo)

    workflow.add_edge(START, "main_entry")
    workflow.add_edge("main_entry", "sub_1")
    workflow.add_edge("sub_1", "main_exit")
    workflow.add_edge("main_exit", END)
    return workflow.compile()


if __name__ == "__main__":
    sub_graph = sub()
    get_console().print(sub_graph.get_graph())
    sub_graph.get_graph().draw_mermaid_png(output_file_path="_only_sub.png")

    graph = main()
    get_console().print(graph.get_graph())
    graph.get_graph(xray=1).draw_mermaid_png(output_file_path="_with_sub.png")

Error Message and Stack Trace (if applicable)

Graph(
    nodes={
        '__start__': Node(id='__start__', name='__start__', data=<class 'pydantic.v1.main.LangGraphInput'>, metadata=None),
        'A': Node(id='A', name='A', data=A(func_accepts_config=False, afunc_accepts_config=False, recurse=True), metadata=None),
        'B': Node(id='B', name='B', data=B(func_accepts_config=False, afunc_accepts_config=False, recurse=True), metadata=None),
        'Z': Node(id='Z', name='Z', data=Z(func_accepts_config=False, afunc_accepts_config=False, recurse=True), metadata=None),
        '__end__': Node(id='__end__', name='__end__', data=<class 'pydantic.v1.main.LangGraphOutput'>, metadata=None)
    },
    edges=[
        Edge(source='A', target='B', data=None, conditional=False),
        Edge(source='Z', target='__end__', data=None, conditional=False),
        Edge(source='__start__', target='A', data=True, conditional=True),
        Edge(source='__start__', target='__end__', data=False, conditional=True),
        Edge(source='B', target='Z', data=True, conditional=True),
        Edge(source='B', target='__end__', data=False, conditional=True)
    ]
)
Graph(
    nodes={
        '__start__': Node(id='__start__', name='__start__', data=<class 'pydantic.v1.main.LangGraphInput'>, metadata=None),
        'main_entry': Node(id='main_entry', name='main_entry', data=main_entry(func_accepts_config=False, afunc_accepts_config=False, recurse=True), metadata=None),
        'sub_1': Node(id='sub_1', name='sub_1', data=<langgraph.graph.state.CompiledStateGraph object at 0x000001D7541179B0>, metadata=None),
        'main_exit': Node(id='main_exit', name='main_exit', data=main_exit(func_accepts_config=False, afunc_accepts_config=False, recurse=True), metadata=None),
        '__end__': Node(id='__end__', name='__end__', data=<class 'pydantic.v1.main.LangGraphOutput'>, metadata=None)
    },
    edges=[
        Edge(source='__start__', target='main_entry', data=None, conditional=False),
        Edge(source='main_entry', target='sub_1', data=None, conditional=False),
        Edge(source='main_exit', target='__end__', data=None, conditional=False),
        Edge(source='sub_1', target='main_exit', data=None, conditional=False)
    ]
)

Description

image

When there is an edge pointing from start or towards end, and another edge points to a node, part of the edges will disappear.

System Info

langgraph==0.2.19
langchain-core==0.2.38

@gbaian10
Copy link
Contributor Author

gbaian10 commented Sep 10, 2024

import secrets

from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph


def foo(_: MessagesState) -> None:
    return


def branch() -> bool:
    return secrets.choice([True, False])


def sub() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    for node in ["A", "B", "M", "Y", "Z"]:
        workflow.add_node(node, foo)

    workflow.add_conditional_edges(START, branch, {True: "A", False: "B"})
    workflow.add_edge("A", "M")
    workflow.add_edge("B", "M")
    workflow.add_conditional_edges("M", branch, {True: "Y", False: "Z"})
    workflow.add_edge("Y", END)
    workflow.add_edge("Z", END)
    return workflow.compile()


def main() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    workflow.add_node("main_entry", foo)
    workflow.add_node("sub_1", sub())
    workflow.add_node("main_exit", foo)

    workflow.add_edge(START, "main_entry")
    workflow.add_edge("main_entry", "sub_1")
    workflow.add_edge("sub_1", "main_exit")
    workflow.add_edge("main_exit", END)
    return workflow.compile()


if __name__ == "__main__":
    sub_graph = sub()
    sub_graph.get_graph().draw_mermaid_png(output_file_path="_only_sub.png")

    graph = main()
    graph.get_graph(xray=1).draw_mermaid_png(output_file_path="_with_sub.png")

image

But in this example, edges didn't disappear; instead, start and end appeared.
I'm not sure if they should appear.

Because in a simple single-line subgraph, start and end will be eliminated.

import secrets

from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph


def foo(_: MessagesState) -> None:
    return


def branch() -> bool:
    return secrets.choice([True, False])


def sub() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    for node in ["A", "B", "C"]:
        workflow.add_node(node, foo)

    workflow.add_edge(START, "A")
    workflow.add_edge("A", "B")
    workflow.add_edge("B", "C")
    workflow.add_edge("C", END)
    return workflow.compile()


def main() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    workflow.add_node("main_entry", foo)
    workflow.add_node("sub_1", sub())
    workflow.add_node("main_exit", foo)

    workflow.add_edge(START, "main_entry")
    workflow.add_edge("main_entry", "sub_1")
    workflow.add_edge("sub_1", "main_exit")
    workflow.add_edge("main_exit", END)
    return workflow.compile()


if __name__ == "__main__":
    sub_graph = sub()
    sub_graph.get_graph().draw_mermaid_png(output_file_path="_only_sub.png")

    graph = main()
    graph.get_graph(xray=1).draw_mermaid_png(output_file_path="_with_sub.png")

image

@gbaian10
Copy link
Contributor Author

But when we try to direct the three edges towards end, everything seems to become normal again.

import secrets

from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph


def foo(_: MessagesState) -> None:
    return


def branch() -> bool:
    return secrets.choice([True, False])


def sub() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    for node in ["A", "B", "M", "N", "Z"]:
        workflow.add_node(node, foo)

    workflow.add_conditional_edges(START, branch, {True: "A", False: "B"})
    workflow.add_edge("A", "M")
    workflow.add_edge("B", "N")

    workflow.add_conditional_edges("M", branch, {True: "Z", False: END})
    workflow.add_edge("Z", END)
    workflow.add_edge("N", END)
    return workflow.compile()


def main() -> CompiledStateGraph:
    workflow = StateGraph(MessagesState)
    workflow.add_node("main_entry", foo)
    workflow.add_node("sub_1", sub())
    workflow.add_node("main_exit", foo)

    workflow.add_edge(START, "main_entry")
    workflow.add_edge("main_entry", "sub_1")
    workflow.add_edge("sub_1", "main_exit")
    workflow.add_edge("main_exit", END)
    return workflow.compile()


if __name__ == "__main__":
    sub_graph = sub()
    sub_graph.get_graph().draw_mermaid_png(output_file_path="_only_sub.png")

    graph = main()
    graph.get_graph(xray=1).draw_mermaid_png(output_file_path="_with_sub.png")

image

@fazam0616
Copy link

Hi! I'm a student at UofT and our team would love to work on this issue. Have there been any more developments/bugs you've noticed?

@gbaian10
Copy link
Contributor Author

Hi! I'm a student at UofT and our team would love to work on this issue. Have there been any more developments/bugs you've noticed?

I'm not sure if anyone is currently addressing this issue.

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

2 participants