feat: Add name and description support to workflows (#1183)

Add optional name and description fields to workflows in both Python and .NET implementations, matching the existing agent API pattern.

Python changes:
- Add name/description parameters to WorkflowBuilder.__init__
- Add name/description attributes to Workflow class
- Include name/description in to_dict() serialization
- Add WORKFLOW_NAME and WORKFLOW_DESCRIPTION OTEL attributes
- Add tests in test_serialization.py and test_workflow_observability.py

.NET changes:
- Add Name and Description properties to Workflow and Workflow<T>
- Add WithName() and WithDescription() fluent methods to WorkflowBuilder
- Add WorkflowName and WorkflowDescription OTEL tags
- Add test in WorkflowBuilderSmokeTests.cs

This enables applications like DevUI to display human-readable workflow names (e.g., 'Data Processing Pipeline') instead of auto-generated UUIDs (e.g., 'Workflow 50fdd917').

Fixes: #1181
This commit is contained in:
Victor Dibia
2025-10-04 15:20:17 +00:00
committed by GitHub
parent fd819c6c02
commit 5a7ca13af6
8 changed files with 194 additions and 11 deletions
@@ -5,6 +5,8 @@ namespace Microsoft.Agents.AI.Workflows.Observability;
internal static class Tags
{
public const string WorkflowId = "workflow.id";
public const string WorkflowName = "workflow.name";
public const string WorkflowDescription = "workflow.description";
public const string WorkflowDefinition = "workflow.definition";
public const string BuildErrorMessage = "build.error.message";
public const string BuildErrorType = "build.error.type";
@@ -56,14 +56,28 @@ public class Workflow
/// </summary>
public string StartExecutorId { get; }
/// <summary>
/// Gets the optional human-readable name of the workflow.
/// </summary>
public string? Name { get; internal init; }
/// <summary>
/// Gets the optional description of what the workflow does.
/// </summary>
public string? Description { get; internal init; }
/// <summary>
/// Initializes a new instance of the <see cref="Workflow"/> class with the specified starting executor identifier
/// and input type.
/// </summary>
/// <param name="startExecutorId">The unique identifier of the starting executor for the workflow. Cannot be <c>null</c>.</param>
internal Workflow(string startExecutorId)
/// <param name="name">Optional human-readable name for the workflow.</param>
/// <param name="description">Optional description of what the workflow does.</param>
internal Workflow(string startExecutorId, string? name = null, string? description = null)
{
this.StartExecutorId = Throw.IfNull(startExecutorId);
this.Name = name;
this.Description = description;
}
/// <summary>
@@ -193,7 +207,10 @@ public class Workflow<T> : Workflow
/// Initializes a new instance of the <see cref="Workflow{T}"/> class with the specified starting executor identifier
/// </summary>
/// <param name="startExecutorId">The unique identifier of the starting executor for the workflow. Cannot be <c>null</c>.</param>
public Workflow(string startExecutorId) : base(startExecutorId)
/// <param name="name">Optional human-readable name for the workflow.</param>
/// <param name="description">Optional description of what the workflow does.</param>
public Workflow(string startExecutorId, string? name = null, string? description = null)
: base(startExecutorId, name, description)
{
}
@@ -36,6 +36,8 @@ public class WorkflowBuilder
private readonly HashSet<string> _outputExecutors = [];
private readonly string _startExecutorId;
private string? _name;
private string? _description;
private static readonly string s_namespace = typeof(WorkflowBuilder).Namespace!;
private static readonly ActivitySource s_activitySource = new(s_namespace);
@@ -114,6 +116,28 @@ public class WorkflowBuilder
return this;
}
/// <summary>
/// Sets the human-readable name for the workflow.
/// </summary>
/// <param name="name">The name of the workflow.</param>
/// <returns>The current <see cref="WorkflowBuilder"/> instance, enabling fluent configuration.</returns>
public WorkflowBuilder WithName(string name)
{
this._name = name;
return this;
}
/// <summary>
/// Sets the description for the workflow.
/// </summary>
/// <param name="description">The description of what the workflow does.</param>
/// <returns>The current <see cref="WorkflowBuilder"/> instance, enabling fluent configuration.</returns>
public WorkflowBuilder WithDescription(string description)
{
this._description = description;
return this;
}
/// <summary>
/// Binds the specified executor to the workflow, allowing it to participate in workflow execution.
/// </summary>
@@ -372,7 +396,7 @@ public class WorkflowBuilder
activity?.AddEvent(new ActivityEvent(EventNames.BuildValidationCompleted));
var workflow = new Workflow(this._startExecutorId)
var workflow = new Workflow(this._startExecutorId, this._name, this._description)
{
Registrations = this._executors,
Edges = this._edges,
@@ -382,6 +406,14 @@ public class WorkflowBuilder
// Using the start executor ID as a proxy for the workflow ID
activity?.SetTag(Tags.WorkflowId, workflow.StartExecutorId);
if (workflow.Name is not null)
{
activity?.SetTag(Tags.WorkflowName, workflow.Name);
}
if (workflow.Description is not null)
{
activity?.SetTag(Tags.WorkflowDescription, workflow.Description);
}
if (activity is not null)
{
var workflowJsonDefinitionData = new WorkflowJsonDefinitionData