diff --git a/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs deleted file mode 100644 index 7465fe5610c9..000000000000 --- a/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.ClientModel; -using System.Text; -using Azure.AI.Agents.Persistent; -using Azure.Identity; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents.AzureAI; -using Microsoft.SemanticKernel.Agents.OpenAI; -using OpenAI; - -namespace Step06; -public class Step06_FoundryAgentProcess : BaseTest -{ - public Step06_FoundryAgentProcess(ITestOutputHelper output) : base(output, redirectSystemConsoleOutput: true) - { - this.Client = - this.UseOpenAIConfig ? - OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) : - !string.IsNullOrWhiteSpace(this.ApiKey) ? - OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) : - OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!)); - } - - protected OpenAIClient Client { get; init; } - - // Target Open AI Services - protected override bool ForceOpenAI => true; - - /// - /// This example demonstrates how to create a process with two agents that can chat with each other. A student agent and a teacher agent are created. - /// The process will keep track of the interaction count between the two agents. - /// - [Fact] - public async Task ProcessWithTwoAgentMathChat() - { - var endpoint = TestConfiguration.AzureAI.Endpoint; - PersistentAgentsClient client = new(endpoint.TrimEnd('/'), new DefaultAzureCredential(), new PersistentAgentsAdministrationClientOptions().WithPolicy(endpoint, "2025-05-15-preview")); - - Azure.Response? studentAgent = null; - Azure.Response? teacherAgent = null; - - try - { - // Create the single agents - studentAgent = await client.Administration.CreateAgentAsync( - model: "gpt-4o", - name: "Student", - instructions: "You are a student that answer question from teacher, when teacher gives you question you answer them." - ); - - teacherAgent = await client.Administration.CreateAgentAsync( - model: "gpt-4o", - name: "Teacher", - instructions: "You are a teacher that create pre-school math question for student and check answer.\nIf the answer is correct, you stop the conversation by saying [COMPLETE].\nIf the answer is wrong, you ask student to fix it." - ); - - // Define the process with a state type - var processBuilder = new FoundryProcessBuilder("two_agent_math_chat"); - - // Create a thread for the student - processBuilder.AddThread("Student", KernelProcessThreadLifetime.Scoped); - processBuilder.AddThread("Teacher", KernelProcessThreadLifetime.Scoped); - - // Add the student - var student = processBuilder.AddStepFromAgent(studentAgent); - - // Add the teacher - var teacher = processBuilder.AddStepFromAgent(teacherAgent); - - /**************************** Orchestrate ***************************/ - - // When the process starts, activate the student agent - processBuilder.OnProcessEnter().SendEventTo( - student, - thread: "_variables_.Student", - messagesIn: ["_variables_.TeacherMessages"], - inputs: new Dictionary { }); - - // When the student agent exits, update the process state to save the student's messages and update interaction counts - processBuilder.OnStepExit(student) - .UpdateProcessState(path: "StudentMessages", operation: StateUpdateOperations.Set, value: "_agent_.messages_out"); - - // When the student agent is finished, send the messages to the teacher agent - processBuilder.OnEvent(student, "_default_") - .SendEventTo(teacher, messagesIn: ["_variables_.StudentMessages"], thread: "Teacher"); - - // When the teacher agent exits with a message containing '[COMPLETE]', update the process state to save the teacher's messages and update interaction counts and emit the `correct_answer` event - processBuilder.OnStepExit(teacher, condition: "jmespath(contains(to_string(_agent_.messages_out), '[COMPLETE]'))") - .EmitEvent( - eventName: "correct_answer", - payload: new Dictionary - { - { "Question", "_variables_.TeacherMessages" }, - { "Answer", "_variables_.StudentMessages" } - }) - .UpdateProcessState(path: "_variables_.TeacherMessages", operation: StateUpdateOperations.Set, value: "_agent_.messages_out"); - - // When the teacher agent exits with a message not containing '[COMPLETE]', update the process state to save the teacher's messages and update interaction counts - processBuilder.OnStepExit(teacher, condition: "_default_") - .UpdateProcessState(path: "_variables_.TeacherMessages", operation: StateUpdateOperations.Set, value: "_agent_.messages_out"); - - // When the teacher agent is finished, send the messages to the student agent - processBuilder.OnEvent(teacher, "_default_", condition: "_default_") - .SendEventTo(student, messagesIn: ["_variables_.TeacherMessages"], thread: "Student"); - - // When the teacher agent emits the `correct_answer` event, stop the process - processBuilder.OnEvent(teacher, "correct_answer") - .StopProcess(); - - // Verify that the process can be built and serialized to json - var processJson = await processBuilder.ToJsonAsync(); - Assert.NotEmpty(processJson); - - var content = await RunWorkflowAsync(client, processBuilder, [new(MessageRole.User, "Go")]); - Assert.NotEmpty(content); - } - finally - { - // Clean up the agents - await client.Administration.DeleteAgentAsync(studentAgent?.Value.Id); - await client.Administration.DeleteAgentAsync(teacherAgent?.Value.Id); - } - } - - private async Task RunWorkflowAsync(PersistentAgentsClient client, FoundryProcessBuilder processBuilder, List? initialMessages = null) where T : class, new() - { - Workflow? workflow = null; - StringBuilder output = new(); - - try - { - // publish the workflow - workflow = await client.Administration.Pipeline.PublishWorkflowAsync(processBuilder); - - // threadId is used to store the thread ID - PersistentAgentThread thread = await client.Threads.CreateThreadAsync(messages: initialMessages ?? []); - - // create run - await foreach (var run in client.Runs.CreateRunStreamingAsync(thread.Id, workflow.Id)) - { - if (run is Azure.AI.Agents.Persistent.MessageContentUpdate contentUpdate) - { - output.Append(contentUpdate.Text); - Console.Write(contentUpdate.Text); - } - else if (run is Azure.AI.Agents.Persistent.RunUpdate runUpdate) - { - if (runUpdate.UpdateKind == Azure.AI.Agents.Persistent.StreamingUpdateReason.RunInProgress && !runUpdate.Value.Id.StartsWith("wf_run", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine(); - Console.Write($"{runUpdate.Value.Metadata["x-agent-name"]}> "); - } - } - } - - // delete thread, so we can start over - Console.WriteLine($"\nDeleting thread {thread?.Id}..."); - await client.Threads.DeleteThreadAsync(thread?.Id); - return output.ToString(); - } - finally - { - // // delete workflow - Console.WriteLine($"Deleting workflow {workflow?.Id}..."); - await client.Administration.Pipeline.DeleteWorkflowAsync(workflow!); - } - } - - /// - /// Represents the state of the two-agent math chat process. - /// - public class TwoAgentMathState - { - public List StudentMessages { get; set; } - - public List TeacherMessages { get; set; } - - public StudentState StudentState { get; set; } = new(); - - public int InteractionCount { get; set; } - } - - /// - /// Represents the state of the student agent. - /// - public class StudentState - { - public int InteractionCount { get; set; } - - public string Name { get; set; } - } -} diff --git a/dotnet/src/Agents/AzureAI/Extensions/FoundryWorkflowExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/FoundryWorkflowExtensions.cs deleted file mode 100644 index 4c02567c5a32..000000000000 --- a/dotnet/src/Agents/AzureAI/Extensions/FoundryWorkflowExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Azure.AI.Agents.Persistent; -using Azure.Core; - -namespace Microsoft.SemanticKernel.Agents.AzureAI; - -/// -/// Extensions for configuring the PersistentAgentsAdministrationClientOptions with a routing policy for Foundry Workflows. -/// -public static class FoundryWorkflowExtensions -{ - /// - /// Adds a routing policy to the PersistentAgentsAdministrationClientOptions for Foundry Workflows. - /// - /// - /// - /// - /// - /// - public static PersistentAgentsAdministrationClientOptions WithPolicy(this PersistentAgentsAdministrationClientOptions options, string endpoint, string apiVersion) - { - if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var _endpoint)) - { - throw new ArgumentException("The endpoint must be an absolute URI.", nameof(endpoint)); - } - - options.AddPolicy(new HttpPipelineRoutingPolicy(_endpoint, apiVersion), HttpPipelinePosition.PerCall); - - return options; - } -} diff --git a/dotnet/src/Experimental/Process.Core/FoundryListenForBuilder.cs b/dotnet/src/Experimental/Process.Core/FoundryListenForBuilder.cs deleted file mode 100644 index 3fe9bd25ed7c..000000000000 --- a/dotnet/src/Experimental/Process.Core/FoundryListenForBuilder.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Process.Internal; - -namespace Microsoft.SemanticKernel; - -/// -/// Builder class for defining Processes that can be exported to Foundry. -/// -[Experimental("SKEXP0081")] -public class FoundryListenForBuilder -{ - private readonly ProcessBuilder _processBuilder; - private readonly ListenForBuilder _listenForBuilder; - - /// - /// Initializes a new instance of the class. - /// - /// The process builder. - public FoundryListenForBuilder(ProcessBuilder processBuilder) - { - this._processBuilder = processBuilder; - this._listenForBuilder = new ListenForBuilder(processBuilder); - } - - /// - /// Listens for an input event. - /// - /// - /// - /// - public FoundryListenForTargetBuilder InputEvent(string eventName, KernelProcessEdgeCondition? condition = null) - { - return new(this._listenForBuilder.InputEvent(eventName, condition)); - } - - /// - /// Defines a message to listen for from a specific process step. - /// - /// - /// - public FoundryListenForTargetBuilder ProcessStart(KernelProcessEdgeCondition? condition = null) - { - return this.InputEvent(ProcessConstants.Declarative.OnEnterEvent, condition); - } - - /// - /// Defines a message to listen for from a specific process step. - /// - /// The type of the message. - /// The process step from which the message originates. - /// Condition that must be met for the message to be processed - /// A builder for defining the target of the message. - public FoundryListenForTargetBuilder Message(string messageType, ProcessStepBuilder from, string? condition = null) - { - KernelProcessEdgeCondition? edgeCondition = null; - if (!string.IsNullOrWhiteSpace(condition)) - { - edgeCondition = new KernelProcessEdgeCondition( - (e, s) => - { - var wrapper = new DeclarativeConditionContentWrapper - { - State = s, - Event = e.Data - }; - - var result = JMESPathConditionEvaluator.EvaluateCondition(wrapper, condition); - return Task.FromResult(result); - }, condition); - } - - return new(this._listenForBuilder.Message(messageType, from, edgeCondition)); - } - - /// - /// Defines a message to listen for from a specific process step. - /// - /// The process step from which the message originates. - /// Condition that must be met for the message to be processed - /// A builder for defining the target of the message. - public FoundryListenForTargetBuilder ResultFrom(ProcessStepBuilder from, string? condition = null) - { - KernelProcessEdgeCondition? edgeCondition = null; - if (!string.IsNullOrWhiteSpace(condition)) - { - edgeCondition = new KernelProcessEdgeCondition( - (e, s) => - { - var wrapper = new DeclarativeConditionContentWrapper - { - State = s, - Event = e.Data - }; - - var result = JMESPathConditionEvaluator.EvaluateCondition(wrapper, condition); - return Task.FromResult(result); - }, condition); - } - - return new(this._listenForBuilder.OnResult(from, edgeCondition)); - } - - /// - /// Listen for the OnEnter event from a specific process step. - /// - /// The process step from which the message originates. - /// Condition that must be met for the message to be processed - /// A builder for defining the target of the message. - public FoundryListenForTargetBuilder OnEnter(ProcessStepBuilder from, string? condition = null) - { - return this.Message(ProcessConstants.Declarative.OnEnterEvent, from, condition); - } - - /// - /// Listen for the OnEnter event from a specific process step. - /// - /// The process step from which the message originates. - /// Condition that must be met for the message to be processed - /// A builder for defining the target of the message. - public FoundryListenForTargetBuilder OnExit(ProcessStepBuilder from, string? condition = null) - { - return this.Message(ProcessConstants.Declarative.OnExitEvent, from, condition); - } -} diff --git a/dotnet/src/Experimental/Process.Core/FoundryListenForTargetBuilder.cs b/dotnet/src/Experimental/Process.Core/FoundryListenForTargetBuilder.cs deleted file mode 100644 index 3f5642e1df6b..000000000000 --- a/dotnet/src/Experimental/Process.Core/FoundryListenForTargetBuilder.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.SemanticKernel; - -/// -/// Builder class for defining targets to listen for in a process. -/// -[Experimental("SKEXP0081")] -public class FoundryListenForTargetBuilder -{ - private readonly ListenForTargetBuilder _listenForTargetBuilder; - - internal FoundryListenForTargetBuilder(ListenForTargetBuilder listenForTargetBuilder) - { - this._listenForTargetBuilder = listenForTargetBuilder; - } - - /// - /// Initializes a new instance of the class. - /// - /// The list of message sources. - /// The process builder. - /// The group ID for the message sources. - public FoundryListenForTargetBuilder(List messageSources, ProcessBuilder processBuilder, KernelProcessEdgeGroupBuilder? edgeGroup = null) - { - this._listenForTargetBuilder = new ListenForTargetBuilder(messageSources, processBuilder, edgeGroup); - } - - /// - /// Signals that the output of the source step should be sent to the specified target when the associated event fires. - /// - /// The output target. - /// The thread to send the event to. - /// The inputs to the target. - /// The messages to be sent to the target. - /// A fresh builder instance for fluid definition - public ProcessStepEdgeBuilder SendEventTo(ProcessAgentBuilder target, string? thread = null, Dictionary? inputs = null, List? messagesIn = null) where TProcessState : class, new() - { - return this._listenForTargetBuilder.SendEventTo_Internal(new ProcessAgentInvokeTargetBuilder(target, thread, messagesIn ?? [], inputs ?? [])); - } - - /// - /// Signals that the specified event should be emitted. - /// - /// - /// - /// - public FoundryListenForTargetBuilder EmitEvent(string eventName, Dictionary? payload = null) - { - return new(this._listenForTargetBuilder.EmitEvent(eventName, payload)); - } - - /// - /// Signals that the specified state variable should be updated in the process state. - /// - /// - /// - /// - /// - public FoundryListenForTargetBuilder UpdateProcessState(string path, StateUpdateOperations operation, object? value) - { - return new(this._listenForTargetBuilder.UpdateProcessState(path, operation, value)); - } - - /// - /// Signals that the process should be stopped. - /// - public void StopProcess(string? thread = null, Dictionary? inputs = null, List? messagesIn = null) - { - var target = new ProcessAgentInvokeTargetBuilder(EndStep.Instance, thread, messagesIn ?? [], inputs ?? []); - this._listenForTargetBuilder.SendEventTo_Internal(target); - } -} diff --git a/dotnet/src/Experimental/Process.Core/FoundryMessageSourceBuilder.cs b/dotnet/src/Experimental/Process.Core/FoundryMessageSourceBuilder.cs deleted file mode 100644 index 25cf713711ae..000000000000 --- a/dotnet/src/Experimental/Process.Core/FoundryMessageSourceBuilder.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Process.Internal; - -namespace Microsoft.SemanticKernel; - -/// -/// Builder class for defining message sources in a Foundry process. -/// -[Experimental("SKEXP0081")] -public class FoundryMessageSourceBuilder -{ - /// - /// Initializes a new instance of the class. - /// - /// The meassage type - /// The source step builder - /// Condition that must be met for the message to be processed - public FoundryMessageSourceBuilder(string messageType, ProcessStepBuilder source, string? condition) - { - this.MessageType = messageType; - this.Source = source; - this.Condition = condition; - } - - /// - /// The message type - /// - public string MessageType { get; } - - /// - /// The source step builder. - /// - public ProcessStepBuilder Source { get; } - - /// - /// The condition that must be met for the message to be processed. - /// - public string? Condition { get; } - - /// - /// Builds the message source. - /// - /// - internal MessageSourceBuilder Build() - { - KernelProcessEdgeCondition? edgeCondition = null; - if (!string.IsNullOrWhiteSpace(this.Condition)) - { - edgeCondition = new KernelProcessEdgeCondition( - (e, s) => - { - var wrapper = new DeclarativeConditionContentWrapper - { - State = s, - Event = e.Data - }; - - var result = JMESPathConditionEvaluator.EvaluateCondition(wrapper, this.Condition); - return Task.FromResult(result); - }); - } - return new MessageSourceBuilder(this.MessageType, this.Source, edgeCondition); - } -} diff --git a/dotnet/src/Experimental/Process.Core/FoundryProcessBuilder.cs b/dotnet/src/Experimental/Process.Core/FoundryProcessBuilder.cs deleted file mode 100644 index ea80d4d060b6..000000000000 --- a/dotnet/src/Experimental/Process.Core/FoundryProcessBuilder.cs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Azure.AI.Agents.Persistent; -using Azure.Core; -using Azure.Identity; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.AzureAI; -using Microsoft.SemanticKernel.Process.Models; - -namespace Microsoft.SemanticKernel; - -/// -/// A builder for creating a process that can be deployed to Azure Foundry. -/// -[Experimental("SKEXP0081")] -public class FoundryProcessBuilder where TProcessState : class, new() -{ - private readonly ProcessBuilder _processBuilder; - private static readonly string[] s_scopes = ["https://management.azure.com/"]; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the process. This is required. - /// The description of the Process. - public FoundryProcessBuilder(string id, string? description = null) - { - this._processBuilder = new ProcessBuilder(id, description, processBuilder: null, typeof(TProcessState)); - } - - /// - /// Adds an to the process. - /// - /// The name of the thread. - /// The policy that determines the lifetime of the - /// - public ProcessBuilder AddThread(string threadName, KernelProcessThreadLifetime threadPolicy = KernelProcessThreadLifetime.Scoped) - { - return this._processBuilder.AddThread(threadName, threadPolicy); - } - - /// - /// Adds a step to the process from a declarative agent. - /// - /// The - /// The unique Id of the step. If not provided, the name of the step Type will be used. - /// Aliases that have been used by previous versions of the step, used for supporting backward compatibility when reading old version Process States - /// Specifies the thread reference to be used by the agent. If not provided, the agent will create a new thread for each invocation. - /// Specifies the human-in-the-loop mode for the agent. If not provided, the default is . - public ProcessAgentBuilder AddStepFromAgent(AgentDefinition agentDefinition, string? stepId = null, IReadOnlyList? aliases = null, string? defaultThread = null, HITLMode humanInLoopMode = HITLMode.Never) - { - Verify.NotNull(agentDefinition); - if (agentDefinition.Type != AzureAIAgentFactory.AzureAIAgentType) - { - throw new ArgumentException($"The agent type '{agentDefinition.Type}' is not supported. Only '{AzureAIAgentFactory.AzureAIAgentType}' is supported."); - } - - return this._processBuilder.AddStepFromAgent(agentDefinition, stepId, aliases, defaultThread, humanInLoopMode); - } - - /// - /// Adds a step to the process from a . - /// - /// The - /// The unique Id of the step. If not provided, the name of the step Type will be used. - /// Aliases that have been used by previous versions of the step, used for supporting backward compatibility when reading old version Process States - /// Specifies the thread reference to be used by the agent. If not provided, the agent will create a new thread for each invocation. - /// Specifies the human-in-the-loop mode for the agent. If not provided, the default is . - public ProcessAgentBuilder AddStepFromAgent(PersistentAgent persistentAgent, string? stepId = null, IReadOnlyList? aliases = null, string? defaultThread = null, HITLMode humanInLoopMode = HITLMode.Never) - { - Verify.NotNull(persistentAgent); - - var agentDefinition = new AgentDefinition - { - Id = persistentAgent.Id, - Type = AzureAIAgentFactory.AzureAIAgentType, - Name = persistentAgent.Name, - Description = persistentAgent.Description - }; - - return this._processBuilder.AddStepFromAgent(agentDefinition, stepId, aliases, defaultThread, humanInLoopMode); - } - - /// - /// Adds a step to the process from a declarative agent. - /// - /// Id of the step. If not provided, the Id will come from the agent Id. - /// The - /// Specifies the thread reference to be used by the agent. If not provided, the agent will create a new thread for each invocation. - /// Specifies the human-in-the-loop mode for the agent. If not provided, the default is . - /// - /// - /// - public ProcessAgentBuilder AddStepFromAgentProxy(string stepId, AgentDefinition agentDefinition, string? threadName = null, HITLMode humanInLoopMode = HITLMode.Never, IReadOnlyList? aliases = null) // TODO: Is there a better way to model this? - { - Verify.NotNullOrWhiteSpace(stepId); - Verify.NotNull(agentDefinition); - if (agentDefinition.Type != AzureAIAgentFactory.AzureAIAgentType) - { - throw new ArgumentException($"The agent type '{agentDefinition.Type}' is not supported. Only '{AzureAIAgentFactory.AzureAIAgentType}' is supported."); - } - - return this._processBuilder.AddStepFromAgentProxy(agentDefinition, threadName, stepId, humanInLoopMode, aliases); - } - - /// - /// Provides an instance of for defining an input edge to a process. - /// - /// The Id of the external event. - /// An instance of - internal ProcessEdgeBuilder OnInputEvent(string eventId) - { - return this._processBuilder.OnInputEvent(eventId); - } - - /// - /// Creates a instance to define a listener for incoming messages. - /// - /// The process step from which the message originates. - /// The name of the event to listen for. - /// An optional condition using JMESPath syntax. - /// - public FoundryListenForTargetBuilder OnEvent(ProcessStepBuilder step, string eventName, string? condition = null) - { - Verify.NotNull(step); - Verify.NotNullOrWhiteSpace(eventName); - return new FoundryListenForBuilder(this._processBuilder).Message(eventName, step, condition); - } - - /// - /// Creates a instance to define a listener for when the process step is entered. - /// - /// - /// - /// - public FoundryListenForTargetBuilder OnStepEnter(ProcessStepBuilder step, string? condition = null) - { - Verify.NotNull(step); - return new FoundryListenForBuilder(this._processBuilder).OnEnter(step, condition); - } - - /// - /// Creates a instance to define a listener for when the process step is exited. - /// - /// - /// - /// - public FoundryListenForTargetBuilder OnStepExit(ProcessStepBuilder step, string? condition = null) - { - Verify.NotNull(step); - return new FoundryListenForBuilder(this._processBuilder).OnExit(step, condition); - } - - /// - /// Creates a instance to define a listener for when the process starts. - /// - /// - public FoundryListenForTargetBuilder OnProcessEnter() - { - return new FoundryListenForBuilder(this._processBuilder).ProcessStart(); - } - - /// - /// Builds the process. - /// - /// An instance of - /// - public KernelProcess Build(KernelProcessStateMetadata? stateMetadata = null) - { - return this._processBuilder.Build(stateMetadata); - } - - /// - /// Deploys the process to Azure Foundry. - /// - /// Th workflow endpoint to deploy to. - /// The credential to use. - /// - /// - public async Task DeployToFoundryAsync(string endpoint, TokenCredential? credential = null, CancellationToken cancellationToken = default) - { - // Build the process - var process = this.Build(); - - // Serialize and deploy - using var httpClient = new HttpClient(); - if (credential != null) - { - var token = await credential.GetTokenAsync(new TokenRequestContext(s_scopes), cancellationToken).ConfigureAwait(false); - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Token}"); - } - else - { - var token = await new DefaultAzureCredential().GetTokenAsync(new TokenRequestContext(s_scopes), cancellationToken).ConfigureAwait(false); - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Token}"); - } - - var workflow = await WorkflowBuilder.BuildWorkflow(process).ConfigureAwait(false); - string json = WorkflowSerializer.SerializeToJson(workflow); - using var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync(new Uri($"{endpoint}/agents?api-version=2025-05-01-preview"), content, cancellationToken).ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) - { - var errorContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - throw new KernelException($"Failed to deploy process. Response: {errorContent}"); - } - - var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - var foundryWorkflow = JsonSerializer.Deserialize(responseContent); - return foundryWorkflow?.Id ?? throw new KernelException("Failed to parse the response from Foundry."); - } - - /// - /// Serializes the process to JSON. - /// - public async Task ToJsonAsync() - { - var process = this.Build(); - var workflow = await WorkflowBuilder.BuildWorkflow(process).ConfigureAwait(false); - return WorkflowSerializer.SerializeToJson(workflow); - } - - /// - /// Serializes the process to YAML. - /// - public async Task ToYamlAsync() - { - var process = this.Build(); - var workflow = await WorkflowBuilder.BuildWorkflow(process).ConfigureAwait(false); - return WorkflowSerializer.SerializeToYaml(workflow); - } - - private class FoundryWorkflow - { - [JsonPropertyName("id")] - public string? Id { get; set; } - } -} - -/// -/// A builder for creating a process that can be deployed to Azure Foundry. -/// -public class FoundryProcessBuilder : FoundryProcessBuilder -{ - /// - /// Initializes a new instance of the class. - /// - /// - public FoundryProcessBuilder(string id) : base(id) - { - } -} - -/// -/// A default process state for the . -/// -public class FoundryProcessDefaultState -{ -} diff --git a/dotnet/src/Experimental/Process.Core/FoundryWorkflowExtensions.cs b/dotnet/src/Experimental/Process.Core/FoundryWorkflowExtensions.cs deleted file mode 100644 index 15ca43c0c83a..000000000000 --- a/dotnet/src/Experimental/Process.Core/FoundryWorkflowExtensions.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.ClientModel.Primitives; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Azure.Core; -using Azure.Core.Pipeline; - -namespace Microsoft.SemanticKernel.Agents.AzureAI; - -/// -/// Extensions for managing Foundry Workflows -/// -public static class FoundryWorkflowExtensions -{ - /// - /// Publishes a workflow using a and a . - /// - /// The process state type. - /// The client pipeline. - /// The process builder. - /// The published . - public static async Task PublishWorkflowAsync(this ClientPipeline pipeline, FoundryProcessBuilder process) where T : class, new() - { - // Send the request - using var message = pipeline.CreateMessage(); - var payload = await process.ToJsonAsync().ConfigureAwait(false); - message.Request.Method = "POST"; - message.Request.Uri = new Uri("https://localhost/agents"); - message.Request.Content = System.ClientModel.BinaryContent.Create(new MemoryStream(Encoding.UTF8.GetBytes(payload))); - message.Request.Headers.Add("Content-Type", "application/json"); - - await pipeline.SendAsync(message).ConfigureAwait(false); - - if (message.Response?.Status < 200 || message.Response?.Status >= 300) - { - var errorContent = await message.Response.Content.AsJsonAsync().ConfigureAwait(false); - - throw new KernelException($"Error publishing workflow: {errorContent}"); - } - - var responseJson = await message.Response!.Content.AsJsonAsync().ConfigureAwait(false) ?? string.Empty; - - using var doc = JsonDocument.Parse(responseJson); - var workflowId = doc.RootElement.GetProperty("id").GetString() ?? string.Empty; - - return new Workflow() { Id = workflowId }; - } - - /// - /// Publishes a workflow using an and a . - /// - /// The process state type. - /// The HTTP pipeline. - /// The process builder. - /// The published . - public static async Task PublishWorkflowAsync(this HttpPipeline pipeline, FoundryProcessBuilder process) where T : class, new() - { - // Send the request - using var message = pipeline.CreateMessage(); - message.Request.Method = RequestMethod.Post; - message.Request.Uri.Reset(new Uri("https://localhost/agents")); - message.Request.Content = RequestContent.Create(new MemoryStream(Encoding.UTF8.GetBytes(await process.ToJsonAsync().ConfigureAwait(false)))); - message.Request.Headers.Add("Content-Type", "application/json"); - - await pipeline.SendAsync(message, default).ConfigureAwait(false); - - if (message.Response?.Status < 200 || message.Response?.Status >= 300) - { - var errorContent = await message.Response.Content.AsJsonAsync().ConfigureAwait(false); - - throw new KernelException($"Error publishing workflow: {errorContent}"); - } - - var responseJson = await message.Response!.Content.AsJsonAsync().ConfigureAwait(false) ?? string.Empty; - - using var doc = JsonDocument.Parse(responseJson); - var workflowId = doc.RootElement.GetProperty("id").GetString() ?? string.Empty; - - Console.WriteLine($"Creating workflow {workflowId}..."); - - return new Workflow() { Id = workflowId }; - } - - /// - /// Deletes a workflow using a . - /// - /// The client pipeline. - /// The workflow to delete. - public static async Task DeleteWorkflowAsync(this ClientPipeline pipeline, Workflow workflow) - { - // Send the request - using var message = pipeline.CreateMessage(); - message.Request.Method = "DELETE"; - message.Request.Uri = new Uri($"https://localhost/agents/{workflow.Id}"); - - await pipeline.SendAsync(message).ConfigureAwait(false); - - if (message.Response?.Status < 200 || message.Response?.Status >= 300) - { - throw new KernelException($"Failed to delete workflow: {message.Response?.Status} {message.Response?.ReasonPhrase}"); - } - } - - /// - /// Deletes a workflow using an . - /// - /// The HTTP pipeline. - /// The workflow to delete. - public static async Task DeleteWorkflowAsync(this HttpPipeline pipeline, Workflow workflow) - { - // Send the request - using var message = pipeline.CreateMessage(); - message.Request.Method = RequestMethod.Delete; - message.Request.Uri.Reset(new Uri($"https://localhost/agents/{workflow.Id}")); - - await pipeline.SendAsync(message, default).ConfigureAwait(false); - - if (message.Response?.Status < 200 || message.Response?.Status >= 300) - { - throw new KernelException($"Failed to delete workflow: {message.Response?.Status} {message.Response?.ReasonPhrase}"); - } - } - - /// - /// Reads the as a JSON string asynchronously. - /// - /// The binary data. - /// The JSON string. - public static async Task AsJsonAsync(this BinaryData data) - { - if (data == null || data.Length == 0) - { - return string.Empty; - } - - using var reader = new StreamReader(data.ToStream(), Encoding.UTF8); - - return await reader.ReadToEndAsync().ConfigureAwait(false); - } -} diff --git a/dotnet/src/Experimental/Process.Core/ProcessAgentBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessAgentBuilder.cs index 91e295cdff08..3a4b39f82751 100644 --- a/dotnet/src/Experimental/Process.Core/ProcessAgentBuilder.cs +++ b/dotnet/src/Experimental/Process.Core/ProcessAgentBuilder.cs @@ -240,7 +240,7 @@ internal ProcessFunctionTargetBuilder GetInvokeAgentFunctionTargetBuilder() /// /// Builder for a process step that represents an agent. /// -public class ProcessAgentBuilder : ProcessAgentBuilder +public class ProcessAgentBuilder : ProcessAgentBuilder { /// /// Creates a new instance of the class. diff --git a/dotnet/src/Experimental/Process.Core/ProcessExporter.cs b/dotnet/src/Experimental/Process.Core/ProcessExporter.cs deleted file mode 100644 index bd95513a5923..000000000000 --- a/dotnet/src/Experimental/Process.Core/ProcessExporter.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Linq; -using System.Text.Json; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace Microsoft.SemanticKernel.Process; - -/// -/// Export a process to a string representation. -/// -public sealed class ProcessExporter -{ - /// - /// Export a process to a string representation. - /// - /// - /// - public static string ExportProcess(KernelProcess process) - { - Verify.NotNull(process); - - Workflow workflow = new() - { - Name = process.State.Name, - Description = process.Description, - FormatVersion = "1.0", - WorkflowVersion = process.State.Version, - Nodes = [.. process.Steps.Select(step => GetNodeFromStep(step))], - // Orchestration - // Suggested Inputs - // Variables - // Schema - // Error handling - }; - - return ""; - } - - private static Node GetNodeFromStep(KernelProcessStepInfo stepInfo) - { - Verify.NotNull(stepInfo); - - if (stepInfo is KernelProcess) - { - throw new KernelException("Processes that contain a subprocess are not currently exportable."); - } - else if (stepInfo is KernelProcessAgentStep agentStep) - { - var agentNode = new Node() - { - Id = agentStep.State.Id ?? throw new KernelException("All steps must have an Id."), - Description = agentStep.Description, - Type = "agent", - Inputs = agentStep.Inputs.ToDictionary((kvp) => kvp.Key, (kvp) => - { - var value = kvp.Value; - var schema = KernelJsonSchemaBuilder.Build(value); - var schemaJson = JsonSerializer.Serialize(schema.RootElement); - - var deserializer = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .IgnoreUnmatchedProperties() - .Build(); - - var yamlSchema = deserializer.Deserialize(schemaJson); - if (yamlSchema is null) - { - throw new KernelException("Failed to deserialize schema."); - } - - return yamlSchema; - }), - OnComplete = null, // TODO: OnComplete, - OnError = null // TODO: OnError - }; - } - else if (stepInfo is KernelProcessMap mapStep) - { - throw new KernelException("Processes that contain a map step are not currently exportable."); - } - else if (stepInfo is KernelProcessProxy proxyStep) - { - throw new KernelException("Processes that contain a proxy step are not currently exportable."); - } - else - { - throw new KernelException("Processes that contain non Foundry-Agent step are not currently exportable."); - } - - return new Node(); - } -} diff --git a/dotnet/src/Experimental/Process.UnitTests/Process.UnitTests.csproj b/dotnet/src/Experimental/Process.UnitTests/Process.UnitTests.csproj index 2d53676bcbb6..566e3f5559aa 100644 --- a/dotnet/src/Experimental/Process.UnitTests/Process.UnitTests.csproj +++ b/dotnet/src/Experimental/Process.UnitTests/Process.UnitTests.csproj @@ -11,28 +11,6 @@ $(NoWarn);CA2007,CA1812,CA1861,CA1063,VSTHRD111,SKEXP0001,SKEXP0050,SKEXP0080,SKEXP0110;OPENAI001,CA1024 - - - - - - - - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - diff --git a/dotnet/src/Experimental/Process.UnitTests/ProcessSerializationTests.cs b/dotnet/src/Experimental/Process.UnitTests/ProcessSerializationTests.cs deleted file mode 100644 index a073c47b2a13..000000000000 --- a/dotnet/src/Experimental/Process.UnitTests/ProcessSerializationTests.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Process.UnitTests.Steps; -using Xunit; - -namespace Microsoft.SemanticKernel.Process.UnitTests; - -/// -/// Unit testing of . -/// -public class ProcessSerializationTests -{ - /// - /// Verify initialization of . - /// - [Fact(Skip = "More work left to do.")] - public async Task KernelProcessFromYamlWorksAsync() - { - // Arrange - var yaml = this.ReadResource("workflow1.yaml"); - - // Act - var process = await ProcessBuilder.LoadFromYamlAsync(yaml); - - // Assert - Assert.NotNull(process); - } - - /// - /// Verify initialization of from a YAML file that contains only a .NET workflow. - /// - /// - [Fact] - public async Task KernelProcessFromDotnetOnlyWorkflow1YamlAsync() - { - // Arrange - var yaml = this.ReadResource("dotnetOnlyWorkflow1.yaml"); - - // Act - var process = await ProcessBuilder.LoadFromYamlAsync(yaml); - - // Assert - Assert.NotNull(process); - - var stepKickoff = process.Steps.FirstOrDefault(s => s.State.Id == "kickoff"); - var stepA = process.Steps.FirstOrDefault(s => s.State.Id == "a_step"); - var stepB = process.Steps.FirstOrDefault(s => s.State.Id == "b_step"); - var stepC = process.Steps.FirstOrDefault(s => s.State.Id == "c_step"); - - Assert.NotNull(stepKickoff); - Assert.NotNull(stepA); - Assert.NotNull(stepB); - Assert.NotNull(stepC); - - // kickoff step has outgoing edge to aStep and bStep on event startAStep - Assert.Single(stepKickoff.Edges); - var kickoffStartEdges = stepKickoff.Edges["kickoff.StartARequested"]; - Assert.Equal(2, kickoffStartEdges.Count); - Assert.Contains(kickoffStartEdges, e => (e.OutputTarget as KernelProcessFunctionTarget)!.StepId == "a_step"); - Assert.Contains(kickoffStartEdges, e => (e.OutputTarget as KernelProcessFunctionTarget)!.StepId == "b_step"); - - // aStep and bStep have grouped outgoing edges to cStep on event aStepDone and bStepDone - Assert.Single(stepA.Edges); - var aStepDoneEdges = stepA.Edges["a_step.AStepDone"]; - Assert.Single(aStepDoneEdges); - var aStepDoneEdge = aStepDoneEdges.First(); - Assert.Equal("c_step", (aStepDoneEdge.OutputTarget as KernelProcessFunctionTarget)!.StepId); - Assert.NotEmpty(aStepDoneEdge.GroupId ?? ""); - - Assert.Single(stepB.Edges); - var bStepDoneEdges = stepB.Edges["b_step.BStepDone"]; - Assert.Single(bStepDoneEdges); - var bStepDoneEdge = bStepDoneEdges.First(); - Assert.Equal("c_step", (bStepDoneEdge.OutputTarget as KernelProcessFunctionTarget)!.StepId); - Assert.NotEmpty(bStepDoneEdge.GroupId ?? ""); - - // cStep has outgoing edge to kickoff step on event cStepDone and one to end the process on event exitRequested - Assert.Equal(2, stepC.Edges.Count); - var cStepDoneEdges = stepC.Edges["c_step.CStepDone"]; - Assert.Single(cStepDoneEdges); - var cStepDoneEdge = cStepDoneEdges.First(); - Assert.Equal("kickoff", (cStepDoneEdge.OutputTarget as KernelProcessFunctionTarget)!.StepId); - Assert.Null(cStepDoneEdge.GroupId); - - var exitRequestedEdges = stepC.Edges["Microsoft.SemanticKernel.Process.EndStep"]; - Assert.Single(exitRequestedEdges); - var exitRequestedEdge = exitRequestedEdges.First(); - Assert.Equal("Microsoft.SemanticKernel.Process.EndStep", (exitRequestedEdge.OutputTarget as KernelProcessFunctionTarget)!.StepId); - - // edges to cStep are in the same group - Assert.Equal(aStepDoneEdge.GroupId, bStepDoneEdge.GroupId); - } - - /// - /// Verify initialization of from a YAML file that contains foundry_agents - /// - /// - [Fact] - public async Task KernelProcessFromScenario1YamlAsync() - { - // Arrange - var yaml = this.ReadResource("scenario1.yaml"); - // Act - var process = await ProcessBuilder.LoadFromYamlAsync(yaml); - // Assert - Assert.NotNull(process); - } - - /// - /// Verify that the process can be serialized to YAML and deserialized back to a workflow. - /// - /// - [Fact] - public async Task ProcessToWorkflowWorksAsync() - { - var process = this.GetProcess(); - var workflow = await WorkflowBuilder.BuildWorkflow(process); - string yaml = WorkflowSerializer.SerializeToYaml(workflow); - - Assert.NotNull(workflow); - } - - /// - /// Verify initialization of from a YAML file that contains references to C# class and chat completion agent. - /// - [Fact] - public async Task KernelProcessFromCombinedWorkflowYamlAsync() - { - // Arrange - var yaml = this.ReadResource("combined-workflow.yaml"); - - // Act - var process = await ProcessBuilder.LoadFromYamlAsync(yaml); - - // Assert - Assert.NotNull(process); - Assert.Contains(process.Steps, step => step.State.Id == "GetProductInfo"); - Assert.Contains(process.Steps, step => step.State.Id == "Summarize"); - } - - private KernelProcess GetProcess() - { - // Create the process builder. - ProcessBuilder processBuilder = new("ProcessWithDapr"); - - // Add some steps to the process. - var kickoffStep = processBuilder.AddStepFromType(); - var myAStep = processBuilder.AddStepFromType(); - var myBStep = processBuilder.AddStepFromType(); - - // ########## Configuring initial state on steps in a process ########### - // For demonstration purposes, we add the CStep and configure its initial state with a CurrentCycle of 1. - // Initializing state in a step can be useful for when you need a step to start out with a predetermines - // configuration that is not easily accomplished with dependency injection. - var myCStep = processBuilder.AddStepFromType(initialState: new() { CurrentCycle = 1 }); - - // Setup the input event that can trigger the process to run and specify which step and function it should be routed to. - processBuilder - .OnInputEvent(CommonEvents.StartProcess) - .SendEventTo(new ProcessFunctionTargetBuilder(kickoffStep)); - - // When the kickoff step is finished, trigger both AStep and BStep. - kickoffStep - .OnEvent(CommonEvents.StartARequested) - .SendEventTo(new ProcessFunctionTargetBuilder(myAStep)) - .SendEventTo(new ProcessFunctionTargetBuilder(myBStep)); - - processBuilder - .ListenFor() - .AllOf(new() - { - new(messageType: CommonEvents.AStepDone, source: myAStep), - new(messageType: CommonEvents.BStepDone, source: myBStep) - }) - .SendEventTo(new ProcessStepTargetBuilder(myCStep, inputMapping: (inputEvents) => - { - // Map the input events to the CStep's input parameters. - // In this case, we are mapping the output of AStep to the first input parameter of CStep - // and the output of BStep to the second input parameter of CStep. - return new() - { - { "astepdata", inputEvents[$"aStep.{CommonEvents.AStepDone}"] }, - { "bstepdata", inputEvents[$"bStep.{CommonEvents.BStepDone}"] } - }; - })); - - // When CStep has finished without requesting an exit, activate the Kickoff step to start again. - myCStep - .OnEvent(CommonEvents.CStepDone) - .SendEventTo(new ProcessFunctionTargetBuilder(kickoffStep)); - - // When the CStep has finished by requesting an exit, stop the process. - myCStep - .OnEvent(CommonEvents.ExitRequested) - .StopProcess(); - - var process = processBuilder.Build(); - return process; - } - - private string ReadResource(string name) - { - // Get the current assembly - Assembly assembly = Assembly.GetExecutingAssembly(); - - // Specify the resource name - string resourceName = $"SemanticKernel.Process.UnitTests.Resources.{name}"; - - // Get the resource stream - using (Stream? resourceStream = assembly.GetManifestResourceStream(resourceName)) - { - if (resourceStream != null) - { - using (StreamReader reader = new(resourceStream)) - { - string content = reader.ReadToEnd(); - return content; - } - } - else - { - throw new InvalidOperationException($"Resource {resourceName} not found in assembly {assembly.FullName}"); - } - } - } -} diff --git a/dotnet/src/Experimental/Process.UnitTests/Resources/combined-workflow.yaml b/dotnet/src/Experimental/Process.UnitTests/Resources/combined-workflow.yaml deleted file mode 100644 index 5934688bab80..000000000000 --- a/dotnet/src/Experimental/Process.UnitTests/Resources/combined-workflow.yaml +++ /dev/null @@ -1,44 +0,0 @@ -workflow: - id: combined_workflow - name: ProductSummarization - inputs: - events: - cloud_events: - - type: input_message_received - data_schema: - type: string - nodes: - - id: GetProductInfo - type: dotnet - description: Gets product information - agent: - type: SemanticKernel.Process.UnitTests.Steps.ProductInfoProvider, SemanticKernel.Process.UnitTests - on_complete: - - on_condition: - type: default - emits: - - event_type: GetProductInfo.OnResult - - id: Summarize - type: declarative - description: Summarizes the information - agent: - type: chat_completion_agent - name: SummarizationAgent - description: Summarizes the information - instructions: Summarize the provided information in 3 sentences - on_complete: - - on_condition: - type: default - emits: - - event_type: ProcessCompleted - orchestration: - - listen_for: - event: input_message_received - from: _workflow_ - then: - - node: GetProductInfo - - listen_for: - from: GetProductInfo - event: GetProductInfo.OnResult - then: - - node: Summarize diff --git a/dotnet/src/Experimental/Process.UnitTests/Resources/dotnetOnlyWorkflow1.yaml b/dotnet/src/Experimental/Process.UnitTests/Resources/dotnetOnlyWorkflow1.yaml deleted file mode 100644 index 4b7466b80038..000000000000 --- a/dotnet/src/Experimental/Process.UnitTests/Resources/dotnetOnlyWorkflow1.yaml +++ /dev/null @@ -1,205 +0,0 @@ -id: dotnetOnlyWorkflow1 -format_version: "1.0" # The version of the declarative spec being used to define this workflow. -workflow_version: "1.5" # The version of the workflow itself. -name: report_generation_pipeline -description: "A workflow that generates and publishes a report on a given topic." -suggested_inputs: - events: - - type: "research_requested" - payload: - topic: "Create a report on AI agents at Microsoft." - -# Input that the workflow supports. -# The way the events get sent to the workflow may differ depending on the platform. Some platforms may support sending events directly to the workflow, -# while others may require using a chat completion interface similar to how local tool calls work. -inputs: # The structured inputs supported by the workflow. - events: - cloud_events: - - type: "StartRequested" - data_schema: - type: string - - type: "StartARequested" - data_schema: - type: string - -# Schemas for the data types used in the workflow. These can be defined inline or referenced from an external schema. -schemas: - research_data: - type: object - properties: - summary: { type: string } - articles: { type: array, items: { type: string } } - required: [summary, articles] - - draft: - type: object - properties: - content: { type: string } - word_count: { type: integer } - required: [content, word_count] - - report_feedback: - type: object - properties: - passed: { type: boolean } - content: { type: string } - feedback: { type: string } - required: [passed, content, feedback] - - report: - type: object - properties: - content: { type: string } - approval_reason: { type: string } - required: [content, approval_reason] - -# The nodes that make up the workflow. A node is a wrapper around something that can be invoked such as code, an agent, a tool, etc. -nodes: - - id: kickoff - type: dotnet # dotnet | python - version: "1.0" - description: "Kickoff the workflow" - agent: - type: "Microsoft.SemanticKernel.Process.UnitTests.Steps.KickoffStep, SemanticKernel.Process.UnitTests" - id: kickoff_agent - inputs: - input: - type: string - agent_input_mapping: - topic: "inputs.input" - on_complete: - - on_condition: - type: Eval - expression: "results.articles.length > '0'" - emits: - - event_type: data_fetched - schema: - $ref: "#/workflow/schemas/research_data" - payload: "$agent.outputs.results" - - on_condition: - type: default - emits: - - event_type: data_fetch_no_results - - - id: a_step - type: dotnet - version: "1.0" - description: "A step" - inputs: - research_data: - schema: - $ref: "#/workflow/schemas/research_data" - last_feedback: - type: string - agent: - type: "Microsoft.SemanticKernel.Process.UnitTests.Steps.AStep, SemanticKernel.Process.UnitTests" - id: a_step_agent - on_complete: - - on_condition: - type: default - emits: - - event_type: draft_created - schema: - $ref: "#/workflow/schemas/draft" - payload: "$agent.outputs.draft" - - - id: b_step - type: dotnet - version: "1.0" - description: "B Step" - agent: - type: "Microsoft.SemanticKernel.Process.UnitTests.Steps.BStep, SemanticKernel.Process.UnitTests" - id: b_step_agent - on_complete: - - on_condition: - type: eval - expression: "report_feedback.passed == 'true'" - emits: - - event_type: report_approved - schema: - $ref: "#/workflow/schemas/report" - payload: - object: - content: "$agent.outputs.report_feedback.content" - approval_reason: "$agent.outputs.report_feedback.feedback" - - on_condition: - type: default - emits: - - event_type: report_rejected - schema: - $ref: "#/workflow/schemas/report_feedback" - payload: "$agent.outputs.report_feedback" - updates: - - variable: revision_count - operation: increment - value: 1 - - variable: last_feedback - operation: set - value: "$agent.outputs.report_feedback.feedback" - - - id: c_step - type: dotnet - version: "1.0" - description: "C Step" - agent: - type: "Microsoft.SemanticKernel.Process.UnitTests.Steps.CStep, SemanticKernel.Process.UnitTests" - id: c_step_agent - # inputs: - # type: string - agent_input_mapping: - event_payload: "$.inputs.report" - event_type: "human_approval_request" - on_complete: - - on_condition: - type: default - emits: - - event_type: human_approved - schema: - $ref: "#/workflow/schemas/report" - updates: - - variable: approved_report - operation: set - value: "$agent.outputs.report" - - variable: $workflow.thread - operation: set - value: "$agent.outputs.report" - -# The orchestration of the workflow. This defines the sequence of events and actions that make up the workflow. -orchestration: - - - listen_for: - event: "StartRequested" - from: _workflow_ - then: - - node: kickoff - - - listen_for: - event: "StartARequested" - from: kickoff - then: - - node: a_step - - node: b_step - - - listen_for: - all_of: - - event: "AStepDone" - from: a_step - - event: "BStepDone" - from: b_step - then: - - node: c_step - inputs: - aStepData: a_step.AStepDone - bStepData: b_step.BStepDone - - - listen_for: - event: "CStepDone" - from: c_step - then: - - node: kickoff - - - listen_for: - event: "ExitRequested" - from: c_step - then: - - node: End diff --git a/dotnet/src/Experimental/Process.UnitTests/Resources/scenario1.yaml b/dotnet/src/Experimental/Process.UnitTests/Resources/scenario1.yaml deleted file mode 100644 index 509ea58c479f..000000000000 --- a/dotnet/src/Experimental/Process.UnitTests/Resources/scenario1.yaml +++ /dev/null @@ -1,88 +0,0 @@ -id: two_agent_math_chat -format_version: "1.0" -name: student_teacher_chat -description: - A workflow that has student and teacher that does question answering - about math -inputs: - messages: - events: - cloud_events: - - type: "input_message_received" - data_schema: - type: string -variables: {} -schemas: {} -nodes: - - id: Student - type: declarative - version: "1.0" - description: Solves problem - agent: - type: foundry_agent - id: "{{student.id}}" - name: "{{student.name}}" - human_in_loop_mode: onNoMessage - stream_output: true - inputs: - Question: - type: messages - on_invoke: - on_error: - on_complete: - - on_condition: - type: default - emits: - - event_type: Answer - schema: - type: messages - - id: Teacher - type: declarative - version: "1.0" - description: Giving the problem - agent: - type: foundry_agent - id: "{{teacher.id}}" - name: "{{teacher.name}}" - human_in_loop_mode: never - stream_output: true - inputs: - Answer: - type: messages - on_invoke: - on_error: - on_complete: - - on_condition: - type: default - emits: - - event_type: Question - schema: - type: messages - - id: End - type: declarative - version: "1.0" - description: Terminal State - -orchestration: - - listen_for: - event: input_message_received - from: _workflow_ - then: - - node: Student - - listen_for: - event: Answer - from: Student - then: - - node: Teacher - - listen_for: - event: Question - from: Teacher - condition: Question.NotContains('[COMPLETE]') - then: - - node: Student - - listen_for: - event: Question - from: Teacher - condition: Question.Contains('[COMPLETE]') - then: - - node: End diff --git a/dotnet/src/Experimental/Process.UnitTests/Resources/workflow1.yaml b/dotnet/src/Experimental/Process.UnitTests/Resources/workflow1.yaml deleted file mode 100644 index 86591dbb46e3..000000000000 --- a/dotnet/src/Experimental/Process.UnitTests/Resources/workflow1.yaml +++ /dev/null @@ -1,396 +0,0 @@ -workflow: - format_version: "1.0" # The version of the declarative spec being used to define this workflow. - workflow_version: "1.5" # The version of the workflow itself. - name: report_generation_pipeline - description: "A workflow that generates and publishes a report on a given topic." - suggested_inputs: - events: - - type: "research_requested" - payload: - topic: "Create a report on AI agents at Microsoft." - - # Input that the workflow supports. - # The way the events get sent to the workflow may differ depending on the platform. Some platforms may support sending events directly to the workflow, - # while others may require using a chat completion interface similar to how local tool calls work. - inputs: # The structured inputs supported by the workflow. - events: - cloud_events: - - type: "research_requested" - data_schema: - type: string - filters: # optional filters on cloud event attributes - - filter: "$.source == 'my_input_source'" - - # Variables used by the agents in the workflow. Variables can be defined as read-only or mutable. - # Read-only variables are initialized with a default value and cannot be modified during the workflow execution. - variables: - max_retries: - type: integer # defaults to mutable: false - default: 3 - scope: "workflow" - report_length_threshold: - type: integer - default: 500 - research_history: - type: "chat_history" # defaults to scope: "run", should it be thread? - is_mutable: true - acls: - - node: "researcher" - access: "read" - drafting_history: - type: "chat_history" - is_mutable: true - research_memory: - type: "memory" - is_mutable: true - drafting_memory: - type: "memory" - is_mutable: true - drafting_whiteboard: - type: "whiteboard" - is_mutable: true - revision_count: - type: integer - default: 0 - is_mutable: true - last_feedback: - type: string - default: "" - is_mutable: true - approved_report: - type: "string" - is_mutable: true - - # Schemas for the data types used in the workflow. These can be defined inline or referenced from an external schema. - schemas: - research_data: - type: object - properties: - summary: { type: string } - articles: { type: array, items: { type: string } } - required: [summary, articles] - - draft: - type: object - properties: - content: { type: string } - word_count: { type: integer } - required: [content, word_count] - - report_feedback: - type: object - properties: - passed: { type: boolean } - content: { type: string } - feedback: { type: string } - required: [passed, content, feedback] - - report: - type: object - properties: - content: { type: string } - approval_reason: { type: string } - required: [content, approval_reason] - - # The nodes that make up the workflow. A node is a wrapper around something that can be invoked such as code, an agent, a tool, etc. - nodes: - - id: fetch_data - type: declarative - version: "1.0" - description: "Fetches relevant research data on the given topic." - agent: - type: foundry_agent - id: research_agent - # name: research_agent - # description: "Find the most relevant articles and summarize key points ${{topic}}." - # inputs: - # topic: - # type: string - # outputs: - # results: - # $ref: "#/workflow/schemas/research_data" - inputs: - input: - type: string - agent_input_mapping: - topic: "inputs.input" - on_invoke: # mvp? - emits: - updates: - on_error: # mvp? - emits: - updates: - on_complete: - - on_condition: - type: state - expression: "$agent.outputs.results.articles.length > 0" # json path or something standard, look at Azure pipelines, GH, etc. - emits: - - event_type: data_fetched - schema: - $ref: "#/workflow/schemas/research_data" - payload: "$agent.outputs.results" - - on_condition: - type: default - emits: - - event_type: data_fetch_no_results - - - id: draft_report - type: declarative - version: "1.0" - description: "Generates a draft report based on the research data." - inputs: - research_data: - schema: - $ref: "#/workflow/schemas/research_data" - last_feedback: - type: string - agent: - type: foundry_agent - id: report_drafter - # name: generate_draft - # prompt: "Create a well-structured draft based on the given research data." - # inputs: - # research_data: - # type: object - # $ref: "#/workflow/schemas/research_data" - # last_feedback: - # type: string - # outputs: - # draft: - # $ref: "#/workflow/schemas/draft" - on_invoke: # mvp? - emits: - updates: - on_error: # mvp? - emits: - updates: - on_complete: - - on_condition: - type: default - emits: - - event_type: draft_created - schema: - $ref: "#/workflow/schemas/draft" - payload: "$agent.outputs.draft" - - - id: proofread_report - type: declarative - version: "1.0" - description: "Proofreads the draft report for grammar, clarity, and factual accuracy." - agent: - type: foundry_agent - id: proofreader - # The agent is already deployed to Foundry and is only referenced here by Id. - # The definition looks like this: - # name: proofreader - # prompt: "Review the draft for grammar, clarity, and factual accuracy." - # inputs: - # draft: - # $ref: "#/workflow/schemas/draft" - # output: - # report_feedback: - # $ref: "#/workflow/schemas/report_feedback" - # tools: - # ... - inputs: - draft: - schema: - $ref: "#/workflow/schemas/draft" - agent_input_mapping: - draft: "$.inputs.draft" # Should only be needed when mapping is not 1:1. - - on_invoke: # mvp? - emits: - updates: - on_error: # mvp? - emits: - updates: - on_complete: - - on_condition: - type: state # need to support structured and unstructured evaluation - expression: "$agent.outputs.report_feedback.passed == true" # json path or something standard - emits: - - event_type: report_approved - schema: - $ref: "#/workflow/schemas/report" - payload: - object: - content: "$agent.outputs.report_feedback.content" - approval_reason: "$agent.outputs.report_feedback.feedback" - - on_condition: - type: default - emits: - - event_type: report_rejected - schema: - $ref: "#/workflow/schemas/report_feedback" - payload: "$agent.outputs.report_feedback" - updates: # discuss more with AK - - variable: $.variables.revision_count - operation: increment - value: 1 - - variable: last_feedback - operation: set - value: "$agent.outputs.report_feedback.feedback" - - - id: human_review - type: declarative - version: "1.0" - description: "Human reviewer for the final report." - # Could be a pre-built agent template from the Foundry Catalog. The behavior is to - # yield an event to the workflow and wait for a response before resuming. - agent: - type: foundry_agent - id: built_in/yield_event - # name: yield_event - # inputs: - # event_type: - # type: string - # event_payload: - # type: object - # output: - # event_response: - # type: object - inputs: - report: - schema: - $ref: "#/workflow/schemas/report" - agent_input_mapping: - event_payload: "$.inputs.report" - event_type: "human_approval_request" - on_invoke: # mvp? - emits: - updates: - on_error: # mvp? - emits: - updates: - on_complete: - - on_condition: - type: default - emits: - - event_type: human_approved - schema: - $ref: "#/workflow/schemas/report" - updates: - - variable: approved_report - operation: set - value: "$agent.outputs.report" - - variable: $workflow.thread - operation: append - value: "$agent.outputs.report" - - - id: error_handler - type: declarative - version: "1.0" - description: "Handles errors that occur during the workflow." - agent: - type: foundry_agent - id: built_in/yield_event - # name: yield_event - # inputs: - # event_type: - # type: string - # event_payload: - # type: object - # output: - # event_response: - # type: object - # schema: - # $ref: "#/workflow/schemas/human_approval_response" - inputs: - error_details: - type: object - error_type: - type: string - agent_input_mapping: - event_payload: "$.inputs.error" - event_type: "$.inputs.error_type" - on_invoke: # mvp? - emits: - updates: - on_error: # mvp? - emits: - updates: - on_complete: - - on_condition: - type: default - emits: - - event_type: human_approved - schema: - $ref: "#/workflow/schemas/report" - updates: # append to the main thread to "output" the answer - - variable: approved_report - operation: set - value: "$agent.outputs.report" - - # The orchestration of the workflow. This defines the sequence of events and actions that make up the workflow. - orchestration: - - - listen_for: - event: "research_requested" - from: $.workflow - then: - - node: fetch_data - inputs: - input: $.event.payload - last_feedback: "" - - - listen_for: - event: "data_fetched" - from: fetch_data - then: - - node: draft_report - inputs: - research_data: $.event.payload - - - listen_for: - event: "draft_created" - from: draft_report - then: - - node: proofread_report - inputs: - draft: $.event.payload - - - listen_for: - event: "report_approved" - from: proofread_report - then: - - node: human_review - inputs: # input mapping for different entry points - report: $.event.payload - - - listen_for: - all_of: # Want to also support any_of - AK needs to figure out implementation - - event: "report_approved" - from: proofread_report - - event: "human_approved" - from: human_review - then: - - node: publish_report - inputs: - report: $.event.payload - - # The compatibility matrix for the workflow. This defines the compatibility of the workflow with different versions of itself. - upgrade: - - from_versions: - min_version: "0.1" - max_version_exclusive: "1.0" - strategy: "not_compatible" - - from_versions: - min_version: "1.0" - max_version_exclusive: "*" - strategy: "backward_compatible" - - # The error handling for the workflow. This defines how errors are handled at different levels of the workflow. - error_handling: - on_error: - - listen_for: - event: "*_failed" - then: - - node: error_handler - inputs: - error_details: $.event.payload - error_type: "unknown_error" - default: - - node: logging_service - inputs: - error_details: $.event.payload