From c92363db0d0653d9eb651141216c0c1c5d8b2cd8 Mon Sep 17 00:00:00 2001 From: Taehan Hank Kim Date: Sun, 1 Mar 2026 17:34:09 +0900 Subject: [PATCH 1/2] fix(core): fix nested subgraph rendering in draw_mermaid for 3+ level nesting --- .../langchain_core/runnables/graph_mermaid.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/core/langchain_core/runnables/graph_mermaid.py b/libs/core/langchain_core/runnables/graph_mermaid.py index 2b8bf2526e388..e002fe99333fa 100644 --- a/libs/core/langchain_core/runnables/graph_mermaid.py +++ b/libs/core/langchain_core/runnables/graph_mermaid.py @@ -236,15 +236,24 @@ def add_subgraph(edges: list[Edge], prefix: str) -> None: # Add empty subgraphs (subgraphs with no internal edges) if with_styles: for prefix, subgraph_node in subgraph_nodes.items(): - if ":" not in prefix and prefix not in seen_subgraphs: - mermaid_graph += f"\tsubgraph {prefix}\n" + parts = prefix.split(":") + if all(part in seen_subgraphs for part in parts): + continue - # Add nodes that belong to this subgraph - for key, node in subgraph_node.items(): - mermaid_graph += render_node(key, node) + # Open subgraph blocks for each nesting level + opened = [] + for part in parts: + if part not in seen_subgraphs: + mermaid_graph += f"\tsubgraph {part}\n" + seen_subgraphs.add(part) + opened.append(part) + + # Add nodes that belong to this subgraph + for key, node in subgraph_node.items(): + mermaid_graph += render_node(key, node) + for _ in opened: mermaid_graph += "\tend\n" - seen_subgraphs.add(prefix) # Add custom styles for nodes if with_styles: From 435cec1f5eac5461e31463b80ef53980138d5405 Mon Sep 17 00:00:00 2001 From: Taehan Hank Kim Date: Tue, 3 Mar 2026 13:06:39 +0900 Subject: [PATCH 2/2] test(core): add regression test for nested empty subgraph rendering --- .../runnables/__snapshots__/test_graph.ambr | 23 +++++++++++++ .../tests/unit_tests/runnables/test_graph.py | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr b/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr index a455975d20870..c7542f8506d2f 100644 --- a/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr +++ b/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr @@ -2002,6 +2002,29 @@ ''' # --- +# name: test_nested_empty_subgraph_mermaid[mermaid] + ''' + --- + config: + flowchart: + curve: linear + --- + graph TD; + __start__([

__start__

]):::first + __end__([

__end__

]):::last + __start__ --> parent\3achild\3agrandchild; + parent\3achild\3agrandchild --> __end__; + subgraph parent + subgraph child + parent\3achild\3agrandchild(grandchild) + end + end + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_parallel_subgraph_mermaid[mermaid] ''' --- diff --git a/libs/core/tests/unit_tests/runnables/test_graph.py b/libs/core/tests/unit_tests/runnables/test_graph.py index c7d705cfd6e5a..dc3358c87a240 100644 --- a/libs/core/tests/unit_tests/runnables/test_graph.py +++ b/libs/core/tests/unit_tests/runnables/test_graph.py @@ -472,6 +472,39 @@ def test_single_node_subgraph_mermaid(snapshot: SnapshotAssertion) -> None: assert graph.draw_mermaid() == snapshot(name="mermaid") +def test_nested_empty_subgraph_mermaid(snapshot: SnapshotAssertion) -> None: + """Test 3+ level nested subgraphs with no internal edges render correctly.""" + empty_data = BaseModel + nodes = { + "__start__": Node( + id="__start__", name="__start__", data=empty_data, metadata=None + ), + "parent:child:grandchild": Node( + id="parent:child:grandchild", + name="grandchild", + data=empty_data, + metadata=None, + ), + "__end__": Node(id="__end__", name="__end__", data=empty_data, metadata=None), + } + edges = [ + Edge( + source="__start__", + target="parent:child:grandchild", + data=None, + conditional=False, + ), + Edge( + source="parent:child:grandchild", + target="__end__", + data=None, + conditional=False, + ), + ] + graph = Graph(nodes, edges) + assert graph.draw_mermaid() == snapshot(name="mermaid") + + def test_runnable_get_graph_with_invalid_input_type() -> None: """Test that error isn't raised when getting graph with invalid input type."""