diff --git a/dotnet/samples/DeclarativeWorkflow/DeclarativeWorkflow.csproj b/dotnet/samples/DeclarativeWorkflow/DeclarativeWorkflow.csproj deleted file mode 100644 index ee1a58754c..0000000000 --- a/dotnet/samples/DeclarativeWorkflow/DeclarativeWorkflow.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - Exe - net9.0 - net9.0 - $(ProjectsDebugTargetFrameworks) - enable - disable - 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 - $(NoWarn);CA1812 - - - - true - - - - - - - - - - - - - - - - - - diff --git a/dotnet/samples/DeclarativeWorkflow/Program.cs b/dotnet/samples/DeclarativeWorkflow/Program.cs deleted file mode 100644 index c480e96c23..0000000000 --- a/dotnet/samples/DeclarativeWorkflow/Program.cs +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Azure.AI.Agents.Persistent; -using Azure.Identity; -using Microsoft.Agents.Workflows; -using Microsoft.Agents.Workflows.Declarative; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Configuration; - -namespace Demo.DeclarativeWorkflow; - -/// -/// HOW TO: Create a workflow from a declartive (yaml based) definition. -/// -/// -/// Configuration -/// Define FOUNDRY_PROJECT_ENDPOINT as a user-secret or environment variable that -/// points to your Foundry project endpoint. -/// Usage -/// Provide the path to the workflow definition file as the first argument. -/// All other arguments are intepreted as a queue of inputs. -/// When no input is queued, interactive input is requested from the console. -/// -internal sealed class Program -{ - public static async Task Main(string[] args) - { - Program program = new(args); - await program.ExecuteAsync(); - } - - private async Task ExecuteAsync() - { - // Read and parse the declarative workflow. - Notify($"WORKFLOW: Parsing {Path.GetFullPath(this.WorkflowFile)}"); - - Stopwatch timer = Stopwatch.StartNew(); - - // Use DeclarativeWorkflowBuilder to build a workflow based on a YAML file. - DeclarativeWorkflowOptions options = - new(new FoundryAgentProvider(this.FoundryEndpoint, new AzureCliCredential())) - { - Configuration = this.Configuration - }; - Workflow workflow = DeclarativeWorkflowBuilder.Build(this.WorkflowFile, options); - - Notify($"\nWORKFLOW: Defined {timer.Elapsed}"); - - Notify("\nWORKFLOW: Starting..."); - - // Run the workflow, just like any other workflow - string input = this.GetWorkflowInput(); - StreamingRun run = await InProcessExecution.StreamAsync(workflow, input); - await this.MonitorWorkflowRunAsync(run); - - Notify("\nWORKFLOW: Done!"); - } - - private const string DefaultWorkflow = "HelloWorld.yaml"; - private const string ConfigKeyFoundryEndpoint = "FOUNDRY_PROJECT_ENDPOINT"; - - private static readonly Dictionary s_nameCache = []; - private static readonly HashSet s_fileCache = []; - - private string WorkflowFile { get; } - private string? WorkflowInput { get; } - private string FoundryEndpoint { get; } - private PersistentAgentsClient FoundryClient { get; } - private IConfiguration Configuration { get; } - - private Program(string[] args) - { - this.WorkflowFile = ParseWorkflowFile(args); - this.WorkflowInput = ParseWorkflowInput(args); - - this.Configuration = InitializeConfig(); - - this.FoundryEndpoint = this.Configuration[ConfigKeyFoundryEndpoint] ?? throw new InvalidOperationException($"Undefined configuration setting: {ConfigKeyFoundryEndpoint}"); - this.FoundryClient = new PersistentAgentsClient(this.FoundryEndpoint, new AzureCliCredential()); - } - - private async Task MonitorWorkflowRunAsync(StreamingRun run) - { - string? messageId = null; - - await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false)) - { - if (evt is ExecutorInvokeEvent executorInvoked) - { - Debug.WriteLine($"STEP ENTER #{executorInvoked.ExecutorId}"); - } - else if (evt is ExecutorCompleteEvent executorComplete) - { - Debug.WriteLine($"STEP EXIT #{executorComplete.ExecutorId}"); - } - else if (evt is ExecutorFailureEvent executorFailure) - { - Debug.WriteLine($"STEP ERROR #{executorFailure.ExecutorId}: {executorFailure.Data?.Message ?? "Unknown"}"); - } - else if (evt is ConversationUpdateEvent invokeEvent) - { - Debug.WriteLine($"CONVERSATION: {invokeEvent.Data}"); - } - else if (evt is AgentRunUpdateEvent streamEvent) - { - if (!string.Equals(messageId, streamEvent.Update.MessageId, StringComparison.Ordinal)) - { - messageId = streamEvent.Update.MessageId; - - if (messageId is not null) - { - string? agentId = streamEvent.Update.AuthorName; - if (agentId is not null) - { - if (!s_nameCache.TryGetValue(agentId, out string? realName)) - { - PersistentAgent agent = await this.FoundryClient.Administration.GetAgentAsync(agentId); - s_nameCache[agentId] = agent.Name; - realName = agent.Name; - } - agentId = realName; - } - agentId ??= nameof(ChatRole.Assistant); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($"\n{agentId.ToUpperInvariant()}:"); - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.WriteLine($" [{messageId}]"); - } - } - - ChatResponseUpdate? chatUpdate = streamEvent.Update.RawRepresentation as ChatResponseUpdate; - switch (chatUpdate?.RawRepresentation) - { - case MessageContentUpdate messageUpdate: - string? fileId = messageUpdate.ImageFileId ?? messageUpdate.TextAnnotation?.OutputFileId; - if (fileId is not null && s_fileCache.Add(fileId)) - { - BinaryData content = await this.FoundryClient.Files.GetFileContentAsync(fileId); - await DownloadFileContentAsync(Path.GetFileName(messageUpdate.TextAnnotation?.TextToReplace ?? "response.png"), content); - } - break; - } - try - { - Console.ResetColor(); - Console.Write(streamEvent.Data); - } - finally - { - Console.ResetColor(); - } - } - else if (evt is AgentRunResponseEvent messageEvent) - { - try - { - Console.WriteLine(); - if (messageEvent.Response.AgentId is null) - { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine("ACTIVITY:"); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(messageEvent.Response?.Text.Trim()); - } - else - { - if (messageEvent.Response.Usage is not null) - { - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.WriteLine($"[Tokens Total: {messageEvent.Response.Usage.TotalTokenCount}, Input: {messageEvent.Response.Usage.InputTokenCount}, Output: {messageEvent.Response.Usage.OutputTokenCount}]"); - } - } - } - finally - { - Console.ResetColor(); - } - } - } - } - - private static string ParseWorkflowFile(string[] args) - { - string workflowFile = args.FirstOrDefault() ?? DefaultWorkflow; - - if (!File.Exists(workflowFile) && !Path.IsPathFullyQualified(workflowFile)) - { - string? repoFolder = GetRepoFolder(); - if (repoFolder is not null) - { - workflowFile = Path.Combine(repoFolder, "Workflows", workflowFile); - workflowFile = Path.ChangeExtension(workflowFile, ".yaml"); - } - } - - if (!File.Exists(workflowFile)) - { - throw new InvalidOperationException($"Unable to locate workflow: {Path.GetFullPath(workflowFile)}."); - } - - return workflowFile; - - static string? GetRepoFolder() - { - DirectoryInfo? current = new(Directory.GetCurrentDirectory()); - - while (current is not null) - { - if (Directory.Exists(Path.Combine(current.FullName, ".git"))) - { - return current.FullName; - } - - current = current.Parent; - } - - return null; - } - } - - private string GetWorkflowInput() - { - string? input = this.WorkflowInput; - - try - { - Console.ForegroundColor = ConsoleColor.DarkGreen; - - Console.Write("\nINPUT: "); - - Console.ForegroundColor = ConsoleColor.White; - - if (!string.IsNullOrWhiteSpace(input)) - { - Console.WriteLine(input); - return input; - } - while (string.IsNullOrWhiteSpace(input)) - { - input = Console.ReadLine(); - } - - return input.Trim(); - } - finally - { - Console.ResetColor(); - } - } - - private static string? ParseWorkflowInput(string[] args) - { - if (args.Length == 0) - { - return null; - } - - string[] workflowInput = [.. args.Skip(1)]; - - return workflowInput.FirstOrDefault(); - } - - // Load configuration from user-secrets - private static IConfigurationRoot InitializeConfig() => - new ConfigurationBuilder() - .AddUserSecrets(Assembly.GetExecutingAssembly()) - .AddEnvironmentVariables() - .Build(); - - private static void Notify(string message) - { - Console.ForegroundColor = ConsoleColor.Cyan; - try - { - Console.WriteLine(message); - } - finally - { - Console.ResetColor(); - } - } - - private static async ValueTask DownloadFileContentAsync(string filename, BinaryData content) - { - string filePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(filename)); - filePath = Path.ChangeExtension(filePath, ".png"); - - await File.WriteAllBytesAsync(filePath, content.ToArray()); - - Process.Start( - new ProcessStartInfo - { - FileName = "cmd.exe", - Arguments = $"/C start {filePath}" - }); - } -} diff --git a/dotnet/samples/DeclarativeWorkflow/README.md b/dotnet/samples/DeclarativeWorkflow/README.md deleted file mode 100644 index 5229bd50c2..0000000000 --- a/dotnet/samples/DeclarativeWorkflow/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# Summary - -This demo showcases the ability to parse a declarative Foundry Workflow file (YAML) to build a `Workflow<>` -be executed using the same pattern as any code-based workflow. - -## Configuration - -This demo requires configuration to access agents an [Azure Foundry Project](https://learn.microsoft.com/azure/ai-foundry). - -#### Settings - -We suggest using .NET [Secret Manager](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) -to avoid the risk of leaking secrets into the repository, branches and pull requests. -You can also use environment variables if you prefer. - -To set your secrets as an environment variable (PowerShell): - -```pwsh -$env:FOUNDRY_PROJECT_ENDPOINT="https://..." -``` - -To set your secrets with .NET Secret Manager: - -1. From the root of the respository, navigate the console to the project folder: - - ``` - cd dotnet/demos/DeclarativeWorkflow - ``` - -2. Examine existing secret definitions: - - ``` - dotnet user-secrets list - ``` - -3. If needed, perform first time initialization: - - ``` - dotnet user-secrets init - ``` - -4. Define setting that identifies your Azure Foundry Project (endpoint): - - ``` - dotnet user-secrets set "FOUNDRY_PROJECT_ENDPOINT" "https://..." - ``` - -#### Authorization - -Use [_Azure CLI_](https://learn.microsoft.com/cli/azure/authenticate-azure-cli) to authorize access to your Azure Foundry Project: - - ``` - az login - az account get-access-token - ``` - -#### Agents - -The sample workflows rely on agents defined in your Azure Foundry Project. - -To create agents, run the [`Create.ps1`](../../../workflows/) script. -This will create the agents used in the sample workflows in your Azure Foundry Project and format a script you can copy and use to configure your environment. - -> Note: `Create.ps1` relies upon the `FOUNDRY_PROJECT_ENDPOINT` setting. - -## Execution - -Run the demo from the console by specifying a path to a declarative (YAML) workflow file. -The repository has example workflows available in the root [`/workflows`](../../../workflows) folder. - -1. From the root of the respository, navigate the console to the project folder: - - ```sh - cd dotnet/demos/DeclarativeWorkflow - - ``` - -2. Run the demo referencing a sample workflow by name: - - ```sh - dotnet run HelloWorld - ``` - -3. Run the demo with a path to any workflow file: - - ```sh - dotnet run c:/myworkflows/HelloWorld.yaml - ``` diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/README.md b/dotnet/samples/GettingStarted/Workflows/Declarative/README.md index 57101c9b21..0ddda6992d 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/README.md +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/README.md @@ -24,7 +24,7 @@ To set your secrets with .NET Secret Manager: 1. From the root of the repository, navigate the console to the project folder: ``` - cd dotnet/samples/GettingStarted/Workflows/Declarative + cd dotnet/samples/GettingStarted/Workflows/Declarative/DeclarativeWorkflow ``` 2. Examine existing secret definitions: @@ -71,7 +71,7 @@ The repository has example workflows available in the root [`/workflows`](../../ 1. From the root of the repository, navigate the console to the project folder: ```sh - cd dotnet/samples/GettingStarted/Workflows/Declarative + cd dotnet/samples/GettingStarted/Workflows/Declarative/DeclarativeWorkflow ``` 2. Run the demo referencing a sample workflow by name: