Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

// This sample demonstrates basic usage of the DevUI in an ASP.NET Core application with AI agents.

using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
Expand All @@ -18,10 +19,11 @@ namespace DevUI_Step01_BasicUsage;
/// <remarks>
/// This sample shows how to:
/// 1. Set up Azure OpenAI as the chat client
/// 2. Register agents and workflows using the hosting packages
/// 3. Map the DevUI endpoint which automatically configures the middleware
/// 4. Map the dynamic OpenAI Responses API for Python DevUI compatibility
/// 5. Access the DevUI in a web browser
/// 2. Create function tools for agents to use
/// 3. Register agents and workflows using the hosting packages with tools
/// 4. Map the DevUI endpoint which automatically configures the middleware
/// 5. Map the dynamic OpenAI Responses API for Python DevUI compatibility
/// 6. Access the DevUI in a web browser
///
/// The DevUI provides an interactive web interface for testing and debugging AI agents.
/// DevUI assets are served from embedded resources within the assembly.
Expand Down Expand Up @@ -50,10 +52,30 @@ private static void Main(string[] args)

builder.Services.AddChatClient(chatClient);

// Register sample agents
builder.AddAIAgent("assistant", "You are a helpful assistant. Answer questions concisely and accurately.");
// Define some example tools
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";

[Description("Calculate the sum of two numbers.")]
static double Add([Description("The first number.")] double a, [Description("The second number.")] double b)
=> a + b;

[Description("Get the current time.")]
static string GetCurrentTime()
=> DateTime.Now.ToString("HH:mm:ss");

// Register sample agents with tools
builder.AddAIAgent("assistant", "You are a helpful assistant. Answer questions concisely and accurately.")
.WithAITools(
AIFunctionFactory.Create(GetWeather, name: "get_weather"),
AIFunctionFactory.Create(GetCurrentTime, name: "get_current_time")
);

builder.AddAIAgent("poet", "You are a creative poet. Respond to all requests with beautiful poetry.");
builder.AddAIAgent("coder", "You are an expert programmer. Help users with coding questions and provide code examples.");

builder.AddAIAgent("coder", "You are an expert programmer. Help users with coding questions and provide code examples.")
.WithAITool(AIFunctionFactory.Create(Add, name: "add"));

// Register sample workflows
var assistantBuilder = builder.AddAIAgent("workflow-assistant", "You are a helpful assistant in a workflow.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ namespace Microsoft.Agents.AI.DevUI.Entities;
[JsonSerializable(typeof(MetaResponse))]
[JsonSerializable(typeof(EnvVarRequirement))]
[JsonSerializable(typeof(List<EntityInfo>))]
[JsonSerializable(typeof(List<JsonElement>))]
[JsonSerializable(typeof(List<Dictionary<string, JsonElement>>))]
[JsonSerializable(typeof(List<Dictionary<string, string>>))]
[JsonSerializable(typeof(Dictionary<string, JsonElement>))]
[JsonSerializable(typeof(Dictionary<string, bool>))]
[JsonSerializable(typeof(Dictionary<string, Dictionary<string, string>>))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(int))]
[ExcludeFromCodeCoverage]
internal sealed partial class EntitiesJsonContext : JsonSerializerContext;
34 changes: 30 additions & 4 deletions dotnet/src/Microsoft.Agents.AI.DevUI/Entities/EntityInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ internal sealed record EntityInfo(
string Name,

[property: JsonPropertyName("description")]
string? Description = null,
string? Description,

[property: JsonPropertyName("framework")]
string Framework = "dotnet",
string Framework,

[property: JsonPropertyName("tools")]
List<string>? Tools = null,
List<string> Tools,

[property: JsonPropertyName("metadata")]
Dictionary<string, JsonElement>? Metadata = null
Dictionary<string, JsonElement> Metadata
)
{
[JsonPropertyName("source")]
Expand All @@ -54,6 +54,32 @@ internal sealed record EntityInfo(
[JsonPropertyName("original_url")]
public string? OriginalUrl { get; init; }

// Deployment support
[JsonPropertyName("deployment_supported")]
public bool DeploymentSupported { get; init; }

[JsonPropertyName("deployment_reason")]
public string? DeploymentReason { get; init; }

// Agent-specific fields
[JsonPropertyName("instructions")]
public string? Instructions { get; init; }

[JsonPropertyName("model_id")]
public string? ModelId { get; init; }

[JsonPropertyName("chat_client_type")]
public string? ChatClientType { get; init; }

[JsonPropertyName("context_providers")]
public List<string>? ContextProviders { get; init; }

[JsonPropertyName("middleware")]
public List<string>? Middleware { get; init; }

[JsonPropertyName("module_path")]
public string? ModulePath { get; init; }

// Workflow-specific fields
[JsonPropertyName("required_env_vars")]
public List<EnvVarRequirement>? RequiredEnvVars { get; init; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Checkpointing;

Expand All @@ -17,41 +19,47 @@ internal static class WorkflowSerializationExtensions
/// Converts a workflow to a dictionary representation compatible with DevUI frontend.
/// This matches the Python workflow.to_dict() format expected by the UI.
/// </summary>
public static Dictionary<string, object> ToDevUIDict(this Workflow workflow)
/// <param name="workflow">The workflow to convert.</param>
/// <returns>A dictionary with string keys and JsonElement values containing the workflow data.</returns>
public static Dictionary<string, JsonElement> ToDevUIDict(this Workflow workflow)
{
var result = new Dictionary<string, object>
var result = new Dictionary<string, JsonElement>
{
["id"] = workflow.Name ?? Guid.NewGuid().ToString(),
["start_executor_id"] = workflow.StartExecutorId,
["max_iterations"] = MaxIterationsDefault
["id"] = Serialize(workflow.Name ?? Guid.NewGuid().ToString(), EntitiesJsonContext.Default.String),
["start_executor_id"] = Serialize(workflow.StartExecutorId, EntitiesJsonContext.Default.String),
["max_iterations"] = Serialize(MaxIterationsDefault, EntitiesJsonContext.Default.Int32)
};

// Add optional fields
if (!string.IsNullOrEmpty(workflow.Name))
{
result["name"] = workflow.Name;
result["name"] = Serialize(workflow.Name, EntitiesJsonContext.Default.String);
}

if (!string.IsNullOrEmpty(workflow.Description))
{
result["description"] = workflow.Description;
result["description"] = Serialize(workflow.Description, EntitiesJsonContext.Default.String);
}

// Convert executors to Python-compatible format
result["executors"] = ConvertExecutorsToDict(workflow);
result["executors"] = Serialize(
ConvertExecutorsToDict(workflow),
EntitiesJsonContext.Default.DictionaryStringDictionaryStringString);

// Convert edges to edge_groups format
result["edge_groups"] = ConvertEdgesToEdgeGroups(workflow);
result["edge_groups"] = Serialize(
ConvertEdgesToEdgeGroups(workflow),
EntitiesJsonContext.Default.ListDictionaryStringJsonElement);

return result;
}

/// <summary>
/// Converts workflow executors to a dictionary format compatible with Python
/// </summary>
private static Dictionary<string, object> ConvertExecutorsToDict(Workflow workflow)
private static Dictionary<string, Dictionary<string, string>> ConvertExecutorsToDict(Workflow workflow)
{
var executors = new Dictionary<string, object>();
var executors = new Dictionary<string, Dictionary<string, string>>();

// Extract executor IDs from edges and start executor
// (Registrations is internal, so we infer executors from the graph structure)
Expand All @@ -73,7 +81,7 @@ private static Dictionary<string, object> ConvertExecutorsToDict(Workflow workfl
// Create executor entries (we can't access internal Registrations for type info)
foreach (var executorId in executorIds)
{
executors[executorId] = new Dictionary<string, object>
executors[executorId] = new Dictionary<string, string>
{
["id"] = executorId,
["type"] = "Executor"
Expand All @@ -86,9 +94,9 @@ private static Dictionary<string, object> ConvertExecutorsToDict(Workflow workfl
/// <summary>
/// Converts workflow edges to edge_groups format expected by the UI
/// </summary>
private static List<object> ConvertEdgesToEdgeGroups(Workflow workflow)
private static List<Dictionary<string, JsonElement>> ConvertEdgesToEdgeGroups(Workflow workflow)
{
var edgeGroups = new List<object>();
var edgeGroups = new List<Dictionary<string, JsonElement>>();
var edgeGroupId = 0;

// Get edges using the public ReflectEdges method
Expand All @@ -101,13 +109,13 @@ private static List<object> ConvertEdgesToEdgeGroups(Workflow workflow)
if (edgeInfo is DirectEdgeInfo directEdge)
{
// Single edge group for direct edges
var edges = new List<object>();
var edges = new List<Dictionary<string, string>>();

foreach (var source in directEdge.Connection.SourceIds)
{
foreach (var sink in directEdge.Connection.SinkIds)
{
var edge = new Dictionary<string, object>
var edge = new Dictionary<string, string>
{
["source_id"] = source,
["target_id"] = sink
Expand All @@ -123,71 +131,77 @@ private static List<object> ConvertEdgesToEdgeGroups(Workflow workflow)
}
}

edgeGroups.Add(new Dictionary<string, object>
var edgeGroup = new Dictionary<string, JsonElement>
{
["id"] = $"edge_group_{edgeGroupId++}",
["type"] = "SingleEdgeGroup",
["edges"] = edges
});
["id"] = Serialize($"edge_group_{edgeGroupId++}", EntitiesJsonContext.Default.String),
["type"] = Serialize("SingleEdgeGroup", EntitiesJsonContext.Default.String),
["edges"] = Serialize(edges, EntitiesJsonContext.Default.ListDictionaryStringString)
};

edgeGroups.Add(edgeGroup);
}
else if (edgeInfo is FanOutEdgeInfo fanOutEdge)
{
// FanOut edge group
var edges = new List<object>();
var edges = new List<Dictionary<string, string>>();

foreach (var source in fanOutEdge.Connection.SourceIds)
{
foreach (var sink in fanOutEdge.Connection.SinkIds)
{
edges.Add(new Dictionary<string, object>
edges.Add(new Dictionary<string, string>
{
["source_id"] = source,
["target_id"] = sink
});
}
}

var fanOutGroup = new Dictionary<string, object>
var fanOutGroup = new Dictionary<string, JsonElement>
{
["id"] = $"edge_group_{edgeGroupId++}",
["type"] = "FanOutEdgeGroup",
["edges"] = edges
["id"] = Serialize($"edge_group_{edgeGroupId++}", EntitiesJsonContext.Default.String),
["type"] = Serialize("FanOutEdgeGroup", EntitiesJsonContext.Default.String),
["edges"] = Serialize(edges, EntitiesJsonContext.Default.ListDictionaryStringString)
};

if (fanOutEdge.HasAssigner)
{
fanOutGroup["selection_func_name"] = "selector";
fanOutGroup["selection_func_name"] = Serialize("selector", EntitiesJsonContext.Default.String);
}

edgeGroups.Add(fanOutGroup);
}
else if (edgeInfo is FanInEdgeInfo fanInEdge)
{
// FanIn edge group
var edges = new List<object>();
var edges = new List<Dictionary<string, string>>();

foreach (var source in fanInEdge.Connection.SourceIds)
{
foreach (var sink in fanInEdge.Connection.SinkIds)
{
edges.Add(new Dictionary<string, object>
edges.Add(new Dictionary<string, string>
{
["source_id"] = source,
["target_id"] = sink
});
}
}

edgeGroups.Add(new Dictionary<string, object>
var edgeGroup = new Dictionary<string, JsonElement>
{
["id"] = $"edge_group_{edgeGroupId++}",
["type"] = "FanInEdgeGroup",
["edges"] = edges
});
["id"] = Serialize($"edge_group_{edgeGroupId++}", EntitiesJsonContext.Default.String),
["type"] = Serialize("FanInEdgeGroup", EntitiesJsonContext.Default.String),
["edges"] = Serialize(edges, EntitiesJsonContext.Default.ListDictionaryStringString)
};

edgeGroups.Add(edgeGroup);
}
}
}

return edgeGroups;
}

private static JsonElement Serialize<T>(T value, JsonTypeInfo<T> typeInfo) => JsonSerializer.SerializeToElement(value, typeInfo);
}
Loading
Loading