gMAS / examples /visualization_example.py
Артём Боярских
chore: initial commit
3193174
"""
Graph visualisation β€” no LLM required.
Demonstrates several ways to render a RoleGraph:
- Mermaid (Markdown / GitHub-friendly)
- ASCII art (terminal-friendly)
- Graphviz DOT (for external tools)
- Rich coloured output (optional)
- Adjacency matrix
- Saving Mermaid / DOT to files
- Rendering PNG / SVG / PDF images (requires graphviz system package)
Run:
python -m examples.visualization_example
"""
import contextlib
import shutil
from pathlib import Path
from builder import build_property_graph
from core.agent import AgentProfile
from core.visualization import (
GraphVisualizer,
MermaidDirection,
VisualizationStyle,
print_graph,
render_to_image,
to_ascii,
to_dot,
to_mermaid,
)
# ── Constants ───────────────────────────────────────────────────────────────────
MERMAID_PREVIEW_LENGTH = 400
DOT_PREVIEW_LENGTH = 500
BYTES_PER_KB = 1024
OUTPUT_DIR = Path(__file__).parent / "visualization_output"
def _ensure_output_dir() -> Path:
OUTPUT_DIR.mkdir(exist_ok=True)
return OUTPUT_DIR
def _header(title: str) -> None:
print(f"\n── {title} ──")
# ── Sample graphs ─────────────────────────────────────────────────────────────
# ── Sample graphs ─────────────────────────────────────────────────────────────
def _sample_graph():
"""Four-agent graph with a parallel branch."""
agents = [
AgentProfile(
agent_id="researcher",
display_name="Researcher",
description="Gathers and synthesises information.",
persona="A thorough researcher.",
tools=["web_search", "document_reader"],
),
AgentProfile(
agent_id="analyzer",
display_name="Data Analyzer",
description="Analyses data and provides insights.",
persona="An analytical expert.",
tools=["statistics", "visualization"],
),
AgentProfile(
agent_id="writer",
display_name="Technical Writer",
description="Writes clear documentation.",
persona="A skilled technical writer.",
tools=["formatter", "spell_checker"],
),
AgentProfile(
agent_id="reviewer",
display_name="Quality Reviewer",
description="Reviews and ensures quality.",
persona="Ensures high quality.",
tools=["grammar_check"],
),
]
edges = [
("researcher", "analyzer"),
("researcher", "writer"),
("analyzer", "writer"),
("writer", "reviewer"),
]
return build_property_graph(
agents,
workflow_edges=edges,
query="Analyse the impact of AI on software development",
include_task_node=True,
)
# ── Demo functions ────────────────────────────────────────────────────────────
def _simple_graph():
"""Minimal 2-agent graph."""
agents = [
AgentProfile(
agent_id="solver", display_name="Problem Solver", description="Solves problems", tools=["calculator"]
),
AgentProfile(agent_id="checker", display_name="Solution Checker", description="Verifies solutions"),
]
graph = build_property_graph(
agents,
workflow_edges=[("solver", "checker")],
query="Calculate 2 + 2",
include_task_node=True,
)
print(to_ascii(graph, show_edges=True))
return graph
def _complex_graph():
"""Graph with parallel branches."""
agents = [
AgentProfile(agent_id="coordinator", display_name="Coordinator"),
AgentProfile(agent_id="researcher_a", display_name="Researcher A"),
AgentProfile(agent_id="researcher_b", display_name="Researcher B"),
AgentProfile(agent_id="analyst", display_name="Analyst"),
AgentProfile(agent_id="synthesizer", display_name="Synthesizer"),
]
edges = [
("coordinator", "researcher_a"),
("coordinator", "researcher_b"),
("researcher_a", "analyst"),
("researcher_b", "analyst"),
("analyst", "synthesizer"),
]
graph = build_property_graph(
agents,
workflow_edges=edges,
query="Research and synthesise findings",
include_task_node=True,
)
print(to_ascii(graph, show_edges=True))
return graph
# ── Demos ─────────────────────────────────────────────────────────────────────
def demo_simple_graph():
_header("Simple 2-agent graph")
print(to_ascii(_simple_graph(), show_edges=True))
def demo_mermaid():
_header("Mermaid (top-bottom)")
print(to_mermaid(_sample_graph(), direction=MermaidDirection.TOP_BOTTOM))
_header("Mermaid (left-right, titled)")
text = to_mermaid(_sample_graph(), direction=MermaidDirection.LEFT_RIGHT, title="Agent Workflow")
print(text[:MERMAID_PREVIEW_LENGTH] + ("…" if len(text) > MERMAID_PREVIEW_LENGTH else ""))
def demo_ascii():
_header("ASCII (with edges)")
print(to_ascii(_sample_graph(), show_edges=True))
_header("ASCII (nodes only)")
print(to_ascii(_sample_graph(), show_edges=False))
def demo_dot():
_header("Graphviz DOT")
dot = to_dot(_sample_graph(), graph_name="AgentWorkflow")
print(dot[:DOT_PREVIEW_LENGTH] + ("…" if len(dot) > DOT_PREVIEW_LENGTH else ""))
def demo_colored():
_header("Coloured output")
try:
import rich # noqa: F401
print_graph(_sample_graph(), format="colored")
except ImportError:
print(" (rich not installed β€” ASCII fallback)")
print_graph(_sample_graph(), format="ascii")
def demo_adjacency_matrix():
_header("Adjacency matrix")
print(GraphVisualizer(_sample_graph()).to_adjacency_matrix())
def demo_complex_graph():
_header("Complex graph with parallel branches")
print(to_ascii(_complex_graph(), show_edges=True))
def demo_save_files():
_header("Saving files")
out = _ensure_output_dir()
viz = GraphVisualizer(_sample_graph())
mermaid_path = out / "agent_graph.md"
viz.save_mermaid(str(mermaid_path), title="Agent Workflow Example")
print(f" Mermaid β†’ {mermaid_path}")
dot_path = out / "agent_graph.dot"
viz.save_dot(str(dot_path), graph_name="AgentWorkflow")
print(f" DOT β†’ {dot_path}")
def demo_render_images():
_header("Rendering images")
try:
import graphviz # noqa: F401
except ImportError:
print(" graphviz Python package not installed β€” skipping.")
return
if not shutil.which("dot"):
print(" Graphviz system binary not found β€” skipping.")
return
out = _ensure_output_dir()
graph = _sample_graph()
for fmt, dpi in [("png", 150), ("svg", None), ("pdf", None)]:
path = out / f"agent_graph.{fmt}"
kwargs = {"dpi": dpi} if dpi else {}
with contextlib.suppress(Exception):
render_to_image(graph, str(path), format=fmt, **kwargs)
print(f" {fmt.upper()} β†’ {path} ({path.stat().st_size} bytes)")
def demo_custom_styled_image():
_header("Custom styled image")
try:
import graphviz # noqa: F401
except ImportError:
print(" graphviz not installed β€” skipping.")
return
if not shutil.which("dot"):
print(" Graphviz system binary not found β€” skipping.")
return
from core.visualization import NodeShape, NodeStyle
style = VisualizationStyle(
direction=MermaidDirection.LEFT_RIGHT,
show_weights=True,
show_tools=True,
max_label_length=30,
agent_style=NodeStyle(
shape=NodeShape.ROUND,
fill_color="#bbdefb",
stroke_color="#0d47a1",
icon="robot",
),
task_style=NodeStyle(
shape=NodeShape.DIAMOND,
fill_color="#ffe0b2",
stroke_color="#e65100",
icon="task",
),
)
out = _ensure_output_dir()
path = out / "agent_graph_styled.png"
with contextlib.suppress(Exception):
viz = GraphVisualizer(_sample_graph(), style)
viz.render_image(str(path), format="png", dpi=150)
if path.exists():
print(f" Styled PNG β†’ {path}")
# ── Entry point ───────────────────────────────────────────────────────────────
def main():
demo_simple_graph()
demo_mermaid()
demo_ascii()
demo_dot()
demo_adjacency_matrix()
demo_colored()
demo_complex_graph()
demo_save_files()
demo_render_images()
demo_custom_styled_image()
if OUTPUT_DIR.exists():
files = sorted(OUTPUT_DIR.glob("agent_graph*"))
if files:
print(f"\nGenerated files in {OUTPUT_DIR}:")
for f in files:
size = f.stat().st_size
label = f"{size / BYTES_PER_KB:.1f} KB" if size > BYTES_PER_KB else f"{size} B"
print(f" {f.name:<35} {label}")
print("\nAll visualisation examples completed βœ…")
if __name__ == "__main__":
main()