Skip to content

Commit 94df8ff

Browse files
authored
Add subgraph rendering (#21)
* Add subgraph rendering * Fixes * More fixes
1 parent 4b61a7c commit 94df8ff

File tree

11 files changed

+122
-17
lines changed

11 files changed

+122
-17
lines changed

.rubocop.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ Metrics/ParameterLists:
1919
Exclude:
2020
- "lib/mars/agent.rb"
2121

22+
Metrics/AbcSize:
23+
Enabled: true
24+
Exclude:
25+
- "lib/mars/rendering/graph/parallel_workflow.rb"
26+
2227
Style/Documentation:
2328
Enabled: false
2429

examples/complex_llm_workflow/diagram.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ parallel_workflow_aggregator[Parallel workflow Aggregator]
88
agent2[Agent2]
99
agent3[Agent3]
1010
agent4[Agent4]
11+
subgraph parallel_workflow["Parallel workflow"]
12+
agent2
13+
agent3
14+
agent4
15+
end
16+
subgraph sequential_workflow["Sequential workflow"]
17+
agent1
18+
gate
19+
parallel_workflow
20+
parallel_workflow_aggregator
21+
end
1122
in --> agent1
1223
agent1 --> gate
1324
gate -->|failure| out

examples/complex_workflow/diagram.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ agent2[Agent2]
1010
agent3[Agent3]
1111
parallel_workflow_2_aggregator[Parallel workflow 2 Aggregator]
1212
agent5[Agent5]
13+
subgraph parallel_workflow_2["Parallel workflow 2"]
14+
sequential_workflow
15+
agent5
16+
end
17+
subgraph parallel_workflow["Parallel workflow"]
18+
agent2
19+
agent3
20+
end
21+
subgraph sequential_workflow["Sequential workflow"]
22+
agent4
23+
parallel_workflow
24+
parallel_workflow_aggregator
25+
end
26+
subgraph main_pipeline["Main Pipeline"]
27+
agent1
28+
gate
29+
parallel_workflow_aggregator
30+
parallel_workflow_2
31+
parallel_workflow_2_aggregator
32+
end
1333
in --> agent1
1434
agent1 --> gate
1535
gate -->|warning| agent4

examples/parallel_workflow/diagram.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ aggregator[Aggregator]
66
agent1[Agent1]
77
agent2[Agent2]
88
agent3[Agent3]
9+
subgraph parallel_workflow["Parallel workflow"]
10+
agent1
11+
agent2
12+
agent3
13+
end
914
in --> agent1
1015
in --> agent2
1116
in --> agent3

lib/mars/rendering/graph/base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def build_graph(builder = Mars::Rendering::Graph::Builder.new)
1414
builder.add_edge(sink_node, "out")
1515
end
1616

17-
[builder.adjacency, builder.nodes]
17+
[builder.adjacency, builder.nodes, builder.subgraphs]
1818
end
1919

2020
def node_id

lib/mars/rendering/graph/builder.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ module Mars
44
module Rendering
55
module Graph
66
class Builder
7-
attr_reader :adjacency, :nodes
7+
attr_reader :adjacency, :nodes, :subgraphs
88

99
def initialize
1010
@adjacency = Hash.new { |h, k| h[k] = [] }
1111
@nodes = {}
12+
@subgraphs = {}
1213
end
1314

1415
def add_edge(from, to, value = nil)
@@ -24,6 +25,18 @@ def add_node(id, value, type)
2425

2526
nodes[id] = Node.new(id, value, type)
2627
end
28+
29+
def add_subgraph(id, name)
30+
return if subgraphs.key?(id)
31+
32+
subgraphs[id] = Subgraph.new(id, name, [])
33+
end
34+
35+
def add_node_to_subgraph(id, node_id)
36+
return if subgraphs[id]&.nodes&.include?(node_id)
37+
38+
subgraphs[id].nodes << node_id
39+
end
2740
end
2841
end
2942
end

lib/mars/rendering/graph/parallel_workflow.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,26 @@ module ParallelWorkflow
77
include Base
88

99
def to_graph(builder, parent_id: nil, value: nil)
10+
builder.add_subgraph(node_id, name) if steps.any?
1011
builder.add_node(aggregator.node_id, aggregator.name, Node::STEP)
1112

13+
build_steps_graph(builder, parent_id, value)
14+
15+
[aggregator.node_id]
16+
end
17+
18+
private
19+
20+
def build_steps_graph(builder, parent_id, value)
1221
steps.each do |step|
1322
sink_nodes = step.to_graph(builder, parent_id: parent_id, value: value)
23+
24+
builder.add_node_to_subgraph(node_id, step.node_id)
25+
1426
sink_nodes.each do |sink_node|
1527
aggregator.to_graph(builder, parent_id: sink_node)
1628
end
1729
end
18-
19-
[aggregator.node_id]
2030
end
2131
end
2232
end

lib/mars/rendering/graph/sequential_workflow.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,31 @@ module SequentialWorkflow
77
include Base
88

99
def to_graph(builder, parent_id: nil, value: nil)
10+
builder.add_subgraph(node_id, name) if steps.any?
11+
12+
parent_id, value, sink_nodes = build_steps_graph(builder, parent_id, value)
13+
14+
builder.add_edge(parent_id, "out", value) if sink_nodes.empty?
15+
16+
sink_nodes.flatten
17+
end
18+
19+
private
20+
21+
def build_steps_graph(builder, parent_id, value)
1022
sink_nodes = []
23+
1124
steps.each do |step|
1225
sink_nodes = step.to_graph(builder, parent_id: parent_id, value: value)
1326
value = nil # We don't want to pass the value to subsequent steps
1427
parent_id = step.node_id
15-
end
1628

17-
builder.add_edge(parent_id, "out", value) if sink_nodes.empty?
29+
builder.add_node_to_subgraph(node_id, step.node_id)
1830

19-
sink_nodes.flatten
31+
sink_nodes.each { |sink_node| builder.add_node_to_subgraph(node_id, sink_node) }
32+
end
33+
34+
[parent_id, value, sink_nodes]
2035
end
2136
end
2237
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
module Mars
4+
module Rendering
5+
module Graph
6+
class Subgraph
7+
attr_reader :id, :name, :nodes
8+
9+
def initialize(id, name, nodes)
10+
@id = id
11+
@name = name
12+
@nodes = nodes
13+
end
14+
end
15+
end
16+
end
17+
end

lib/mars/rendering/mermaid.rb

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
module Mars
44
module Rendering
55
class Mermaid
6-
attr_reader :obj, :graph, :nodes
6+
attr_reader :obj, :graph, :nodes, :subgraphs
77

88
def initialize(obj)
99
@obj = obj
10-
@graph, @nodes = obj.build_graph
10+
@graph, @nodes, @subgraphs = obj.build_graph
1111
end
1212

1313
def render(options = {})
@@ -23,17 +23,29 @@ def render(options = {})
2323
end
2424

2525
def graph_mermaid
26-
nodes_mermaid = nodes.keys.map { |node_id| "#{node_id}#{shape(node_id)}" }
27-
edges_mermaid = []
26+
nodes_mermaid + subgraphs_mermaid + edges_mermaid
27+
end
28+
29+
def nodes_mermaid
30+
nodes.keys.map { |node_id| "#{node_id}#{shape(node_id)}" }
31+
end
2832

33+
def subgraphs_mermaid
34+
subgraphs.values.reverse.map do |subgraph|
35+
node_names = subgraph.nodes
36+
"subgraph #{subgraph.id}[\"#{subgraph.name}\"]\n #{node_names.join("\n ")}\nend"
37+
end
38+
end
39+
40+
def edges_mermaid
41+
edges = []
2942
graph.each do |from, tos|
3043
tos.each do |to|
3144
node_id, value = to
32-
edges_mermaid << "#{from} -->#{edge_value(value)} #{node_id}"
45+
edges << "#{from} -->#{edge_value(value)} #{node_id}"
3346
end
3447
end
35-
36-
nodes_mermaid + edges_mermaid
48+
edges
3749
end
3850

3951
def shape(node_id)

0 commit comments

Comments
 (0)