// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.Bot.ObjectModel.Yaml;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Workflows.Declarative;
///
/// Builder for converting a Foundry workflow object-model YAML definition into a process.
///
public static class DeclarativeWorkflowBuilder
{
///
/// Transforms the input message into a based on .
/// Also performs pass-through for input.
///
/// The input message to transform.
/// The transformed message (as
public static ChatMessage DefaultTransform(object message) =>
message switch
{
ChatMessage chatMessage => chatMessage,
string stringMessage => new ChatMessage(ChatRole.User, stringMessage),
_ => new(ChatRole.User, $"{message}")
};
///
/// Builder for converting a Foundry workflow object-model YAML definition into a process.
///
/// The type of the input message
/// The path to the workflow.
/// Configuration options for workflow execution.
/// An optional function to transform the input message into a .
///
public static Workflow Build(
string workflowFile,
DeclarativeWorkflowOptions options,
Func? inputTransform = null)
where TInput : notnull
{
using StreamReader yamlReader = File.OpenText(workflowFile);
return Build(yamlReader, options, inputTransform);
}
///
/// Builds a workflow from the provided YAML definition.
///
/// The type of the input message
/// The reader that provides the workflow object model YAML.
/// Configuration options for workflow execution.
/// An optional function to transform the input message into a .
/// The that corresponds with the YAML object model.
public static Workflow Build(
TextReader yamlReader,
DeclarativeWorkflowOptions options,
Func? inputTransform = null)
where TInput : notnull
{
AdaptiveDialog workflowElement = ReadWorkflow(yamlReader);
string rootId = WorkflowActionVisitor.Steps.Root(workflowElement);
WorkflowFormulaState state = new(options.CreateRecalcEngine());
state.Initialize(workflowElement.WrapWithBot(), options.Configuration);
DeclarativeWorkflowExecutor rootExecutor =
new(rootId,
options,
state,
message => inputTransform?.Invoke(message) ?? DefaultTransform(message));
WorkflowActionVisitor visitor = new(rootExecutor, state, options);
WorkflowElementWalker walker = new(visitor);
walker.Visit(workflowElement);
return visitor.Complete();
}
///
/// Generates source code (provider/executor scaffolding) for the workflow defined in the YAML file.
///
/// The path to the workflow YAML file.
/// The language to use for the generated code.
/// Optional target namespace for the generated code.
/// Optional prefix for generated workflow type.
/// The generated source code representing the workflow.
public static string Eject(
string workflowFile,
DeclarativeWorkflowLanguage workflowLanguage,
string? workflowNamespace = null,
string? workflowPrefix = null)
{
using StreamReader yamlReader = File.OpenText(workflowFile);
return Eject(yamlReader, workflowLanguage, workflowNamespace, workflowPrefix);
}
///
/// Generates source code (provider/executor scaffolding) for the workflow defined in the provided YAML reader.
///
/// The reader supplying the workflow YAML.
/// The language to use for the generated code.
/// Optional target namespace for the generated code.
/// Optional prefix for generated workflow type.
/// The generated source code representing the workflow.
public static string Eject(
TextReader yamlReader,
DeclarativeWorkflowLanguage workflowLanguage,
string? workflowNamespace = null,
string? workflowPrefix = null)
{
if (workflowLanguage != DeclarativeWorkflowLanguage.CSharp)
{
throw new NotSupportedException($"Converting workflow to {workflowLanguage} is not currently supported.");
}
AdaptiveDialog workflowElement = ReadWorkflow(yamlReader);
string rootId = WorkflowActionVisitor.Steps.Root(workflowElement);
WorkflowTypeInfo typeInfo = workflowElement.WrapWithBot().Describe();
WorkflowTemplateVisitor visitor = new(rootId, typeInfo);
WorkflowElementWalker walker = new(visitor);
walker.Visit(workflowElement);
return visitor.Complete(workflowNamespace, workflowPrefix);
}
private static AdaptiveDialog ReadWorkflow(TextReader yamlReader)
{
BotElement rootElement = YamlSerializer.Deserialize(yamlReader) ?? throw new DeclarativeModelException("Workflow undefined.");
// "Workflow" is an alias for "AdaptiveDialog"
if (rootElement is not AdaptiveDialog workflowElement)
{
throw new DeclarativeModelException($"Unsupported root element: {rootElement.GetType().Name}. Expected an {nameof(Workflow)}.");
}
return workflowElement;
}
}