mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: adds support for labels in edges, fixes rendering of labels in dot a… (#1507)
* adds support for labels in edges, fixes rendering of labels in dot and mermaid, adds rendering of labels in edges * Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility. * Unify label in EdgeData * Edge API adjustments, removed useless "sanitizer" * fixed test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jacob Alber <jaalber@microsoft.com> Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
Jacob Alber
Chris
parent
16230d3b20
commit
eb1117fff4
@@ -99,10 +99,30 @@ public static class WorkflowVisualizer
|
||||
}
|
||||
|
||||
// Emit normal edges
|
||||
foreach (var (src, target, isConditional) in ComputeNormalEdges(workflow))
|
||||
foreach (var (src, target, isConditional, label) in ComputeNormalEdges(workflow))
|
||||
{
|
||||
var edgeAttr = isConditional ? " [style=dashed, label=\"conditional\"]" : "";
|
||||
lines.Add($"{indent}\"{MapId(src)}\" -> \"{MapId(target)}\"{edgeAttr};");
|
||||
// Build edge attributes
|
||||
var attributes = new List<string>();
|
||||
|
||||
// Add style for conditional edges
|
||||
if (isConditional)
|
||||
{
|
||||
attributes.Add("style=dashed");
|
||||
}
|
||||
|
||||
// Add label (custom label or default "conditional" for conditional edges)
|
||||
if (label != null)
|
||||
{
|
||||
attributes.Add($"label=\"{EscapeDotLabel(label)}\"");
|
||||
}
|
||||
else if (isConditional)
|
||||
{
|
||||
attributes.Add("label=\"conditional\"");
|
||||
}
|
||||
|
||||
// Combine attributes
|
||||
var attrString = attributes.Count > 0 ? $" [{string.Join(", ", attributes)}]" : "";
|
||||
lines.Add($"{indent}\"{MapId(src)}\" -> \"{MapId(target)}\"{attrString};");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +153,7 @@ public static class WorkflowVisualizer
|
||||
|
||||
private static void EmitWorkflowMermaid(Workflow workflow, List<string> lines, string indent, string? ns = null)
|
||||
{
|
||||
string sanitize(string input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
string MapId(string id) => ns != null ? $"{sanitize(ns)}/{sanitize(id)}" : id;
|
||||
string MapId(string id) => ns != null ? $"{ns}/{id}" : id;
|
||||
|
||||
// Add start node
|
||||
var startExecutorId = workflow.StartExecutorId;
|
||||
@@ -175,14 +190,23 @@ public static class WorkflowVisualizer
|
||||
}
|
||||
|
||||
// Emit normal edges
|
||||
foreach (var (src, target, isConditional) in ComputeNormalEdges(workflow))
|
||||
foreach (var (src, target, isConditional, label) in ComputeNormalEdges(workflow))
|
||||
{
|
||||
if (isConditional)
|
||||
{
|
||||
lines.Add($"{indent}{MapId(src)} -. conditional .--> {MapId(target)};");
|
||||
string effectiveLabel = label != null ? EscapeMermaidLabel(label) : "conditional";
|
||||
|
||||
// Conditional edge, with user label or default
|
||||
lines.Add($"{indent}{MapId(src)} -. {effectiveLabel} .--> {MapId(target)};");
|
||||
}
|
||||
else if (label != null)
|
||||
{
|
||||
// Regular edge with label
|
||||
lines.Add($"{indent}{MapId(src)} -->|{EscapeMermaidLabel(label)}| {MapId(target)};");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular edge without label
|
||||
lines.Add($"{indent}{MapId(src)} --> {MapId(target)};");
|
||||
}
|
||||
}
|
||||
@@ -214,9 +238,9 @@ public static class WorkflowVisualizer
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<(string Source, string Target, bool IsConditional)> ComputeNormalEdges(Workflow workflow)
|
||||
private static List<(string Source, string Target, bool IsConditional, string? Label)> ComputeNormalEdges(Workflow workflow)
|
||||
{
|
||||
var edges = new List<(string, string, bool)>();
|
||||
var edges = new List<(string, string, bool, string?)>();
|
||||
foreach (var edgeGroup in workflow.Edges.Values.SelectMany(x => x))
|
||||
{
|
||||
if (edgeGroup.Kind == EdgeKind.FanIn)
|
||||
@@ -229,14 +253,15 @@ public static class WorkflowVisualizer
|
||||
case EdgeKind.Direct when edgeGroup.DirectEdgeData != null:
|
||||
var directData = edgeGroup.DirectEdgeData;
|
||||
var isConditional = directData.Condition != null;
|
||||
edges.Add((directData.SourceId, directData.SinkId, isConditional));
|
||||
var label = directData.Label;
|
||||
edges.Add((directData.SourceId, directData.SinkId, isConditional, label));
|
||||
break;
|
||||
|
||||
case EdgeKind.FanOut when edgeGroup.FanOutEdgeData != null:
|
||||
var fanOutData = edgeGroup.FanOutEdgeData;
|
||||
foreach (var sinkId in fanOutData.SinkIds)
|
||||
{
|
||||
edges.Add((fanOutData.SourceId, sinkId, false));
|
||||
edges.Add((fanOutData.SourceId, sinkId, false, fanOutData.Label));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -276,5 +301,24 @@ public static class WorkflowVisualizer
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper method to escape special characters in DOT labels
|
||||
private static string EscapeDotLabel(string label)
|
||||
{
|
||||
return label.Replace("\"", "\\\"").Replace("\n", "\\n");
|
||||
}
|
||||
|
||||
// Helper method to escape special characters in Mermaid labels
|
||||
private static string EscapeMermaidLabel(string label)
|
||||
{
|
||||
return label
|
||||
.Replace("&", "&") // Must be first to avoid double-escaping
|
||||
.Replace("|", "|") // Pipe breaks Mermaid delimiter syntax
|
||||
.Replace("\"", """) // Quote character
|
||||
.Replace("<", "<") // Less than
|
||||
.Replace(">", ">") // Greater than
|
||||
.Replace("\n", "<br/>") // Newline to HTML break
|
||||
.Replace("\r", ""); // Remove carriage return
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user