From 7af9e098d02c6ffbcdfe0c967b1789a1fd8c47d5 Mon Sep 17 00:00:00 2001 From: Md Khairul Islam Date: Tue, 13 Oct 2026 20:06:07 -0400 Subject: [PATCH] fix(cli): read Workflow.graph when drawing the dev UI graph build_graph() read agent._graph, but Workflow stores its compiled graph in the public `graph` field. Any Workflow root agent therefore raised AttributeError: 'Workflow' object has no attribute '_graph', so /dev/apps/{app}/graph returned a 500 and the dev UI could not draw the graph. Fixes #7528 --- src/google/adk/cli/agent_graph.py | 6 ++--- tests/unittests/cli/test_agent_graph.py | 33 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/google/adk/cli/agent_graph.py b/src/google/adk/cli/agent_graph.py index cf6bcaf9fca..c905b94e77f 100644 --- a/src/google/adk/cli/agent_graph.py +++ b/src/google/adk/cli/agent_graph.py @@ -203,12 +203,12 @@ async def build_cluster( await build_graph(child, sub_agent, highlight_pairs) if parent_agent: draw_edge(parent_agent.name, sub_agent.name) - elif isinstance(agent, Workflow) and agent._graph is not None: - for wf_node in agent._graph.nodes: + elif isinstance(agent, Workflow) and agent.graph is not None: + for wf_node in agent.graph.nodes: if wf_node.name == START.name: continue await build_graph(child, wf_node, highlight_pairs) - for edge in agent._graph.edges: + for edge in agent.graph.edges: if edge.from_node.name == START.name: continue label = str(edge.route) if edge.route is not None else '' diff --git a/tests/unittests/cli/test_agent_graph.py b/tests/unittests/cli/test_agent_graph.py index 249cb77519e..bb774484373 100644 --- a/tests/unittests/cli/test_agent_graph.py +++ b/tests/unittests/cli/test_agent_graph.py @@ -23,7 +23,10 @@ from google.adk.agents.parallel_agent import ParallelAgent from google.adk.agents.sequential_agent import SequentialAgent from google.adk.cli.agent_graph import get_agent_graph +from google.adk.events.event import Event from google.adk.tools.agent_tool import AgentTool +from google.adk.workflow import DEFAULT_ROUTE +from google.adk.workflow import Workflow import pytest _DARK_GREEN = '#0F5223' @@ -190,6 +193,36 @@ async def test_build_graph_parallel_agent_fans_out_from_the_parent(): assert 'subgraph "cluster_fanout (Parallel Agent)"' in graph.source +@pytest.mark.asyncio +async def test_build_graph_workflow_draws_nodes_and_edges_from_its_graph(): + def check(node_input: str): + return Event(route='retry') + + def done(node_input: str): + return node_input + + writer = LlmAgent(name='writer', model='gemini-2.0-flash') + workflow = Workflow( + name='flow', + edges=[ + ('START', writer, check), + (check, {'retry': writer, DEFAULT_ROUTE: done}), + ], + ) + + graph = await get_agent_graph(workflow, []) + + nodes, edges = _parse(graph.source) + # START is never drawn, and back-edges (the retry loop) are kept. + assert set(nodes) == {'writer', 'check', 'done'} + assert set(edges) == { + ('writer', 'check'), + ('check', 'writer'), + ('check', 'done'), + } + assert 'subgraph cluster_flow' in graph.source + + @pytest.mark.asyncio async def test_build_graph_highlight_pair_fills_both_nodes_and_colors_edge(): graph = await get_agent_graph(