mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
390cc541ea
* Clean-up * Rollback sample --------- Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
482 lines
17 KiB
C#
482 lines
17 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Microsoft.Agents.AI.Workflows.Declarative.CodeGen;
|
|
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
|
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
|
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
|
using Microsoft.Bot.ObjectModel;
|
|
|
|
namespace Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
|
|
|
|
internal sealed class WorkflowTemplateVisitor : DialogActionVisitor
|
|
{
|
|
private readonly string _rootId;
|
|
private readonly WorkflowModel<string> _workflowModel;
|
|
|
|
public WorkflowTemplateVisitor(
|
|
string workflowId,
|
|
WorkflowTypeInfo typeInfo)
|
|
{
|
|
this._rootId = workflowId;
|
|
this._workflowModel = new WorkflowModel<string>(new RootTemplate(workflowId, typeInfo));
|
|
|
|
WorkflowDiagnostics.SetFoundryProduct();
|
|
}
|
|
|
|
public bool HasUnsupportedActions { get; private set; }
|
|
|
|
public string Complete(string? workflowNamespace = null, string? workflowPrefix = null)
|
|
{
|
|
WorkflowCodeBuilder builder = new(this._rootId);
|
|
|
|
this._workflowModel.Build(builder);
|
|
|
|
return builder.GenerateCode(workflowNamespace, workflowPrefix);
|
|
}
|
|
|
|
protected override void Visit(ActionScope item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
string parentId = GetParentId(item);
|
|
|
|
// Handle case where root element is its own parent
|
|
if (item.Id.Equals(parentId))
|
|
{
|
|
parentId = WorkflowActionVisitor.Steps.Root(parentId);
|
|
}
|
|
|
|
this.ContinueWith(new EmptyTemplate(item.Id.Value, this._rootId), parentId, condition: null, CompletionHandler);
|
|
|
|
//// Complete the action scope.
|
|
void CompletionHandler()
|
|
{
|
|
// No completion for root scope
|
|
if (this._workflowModel.GetDepth(item.Id.Value) > 1)
|
|
{
|
|
// Define post action for this scope
|
|
string completionId = this.ContinuationFor(item.Id.Value);
|
|
this._workflowModel.AddLinkFromPeer(item.Id.Value, completionId);
|
|
// Transition to post action of parent scope
|
|
this._workflowModel.AddLink(completionId, WorkflowActionVisitor.Steps.Post(parentId));
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void VisitConditionItem(ConditionItem item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
string parentId = GetParentId(item);
|
|
ConditionGroupTemplate? conditionGroup = this._workflowModel.LocateParent<ConditionGroupTemplate>(parentId);
|
|
if (conditionGroup is not null)
|
|
{
|
|
string stepId = ConditionGroupExecutor.Steps.Item(conditionGroup.Model, item);
|
|
this._workflowModel.AddNode(new EmptyTemplate(stepId, this._rootId), parentId, CompletionHandler);
|
|
|
|
base.VisitConditionItem(item);
|
|
|
|
// Complete the condition item.
|
|
void CompletionHandler()
|
|
{
|
|
string completionId = this.ContinuationFor(stepId);
|
|
this._workflowModel.AddLink(completionId, WorkflowActionVisitor.Steps.Post(conditionGroup.Id));
|
|
|
|
// Merge link when no action group is defined
|
|
if (!item.Actions.Any())
|
|
{
|
|
this._workflowModel.AddLink(stepId, completionId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void Visit(ConditionGroup item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
ConditionGroupTemplate action = new(item);
|
|
this.ContinueWith(action);
|
|
this.ContinuationFor(action.Id, parentId: action.ParentId);
|
|
|
|
string? lastConditionItemId = null;
|
|
foreach (ConditionItem conditionItem in item.Conditions)
|
|
{
|
|
// Create conditional link for conditional action
|
|
lastConditionItemId = ConditionGroupExecutor.Steps.Item(item, conditionItem);
|
|
this._workflowModel.AddLink(action.Id, lastConditionItemId, $@"ActionExecutor.IsMatch(""{lastConditionItemId}"", result)");
|
|
|
|
conditionItem.Accept(this);
|
|
}
|
|
|
|
if (item.ElseActions?.Actions.Length > 0)
|
|
{
|
|
if (lastConditionItemId is not null)
|
|
{
|
|
// Create clean start for else action from prior conditions
|
|
this.RestartAfter(lastConditionItemId, action.Id);
|
|
}
|
|
|
|
// Create conditional link for else action
|
|
string stepId = ConditionGroupExecutor.Steps.Else(item);
|
|
this._workflowModel.AddLink(action.Id, stepId, $@"ActionExecutor.IsMatch(""{stepId}"", result)");
|
|
}
|
|
}
|
|
|
|
protected override void Visit(GotoAction item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Represent action with default executor
|
|
DefaultTemplate action = new(item, this._rootId);
|
|
this.ContinueWith(action);
|
|
// Transition to target action
|
|
this._workflowModel.AddLink(action.Id, item.ActionId.Value);
|
|
// Define a clean-start to ensure "goto" is not a source for any edge
|
|
this.RestartAfter(action.Id, action.ParentId);
|
|
}
|
|
|
|
protected override void Visit(Foreach item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Entry point for loop
|
|
ForeachTemplate action = new(item);
|
|
string loopId = ForeachExecutor.Steps.Next(action.Id);
|
|
this.ContinueWith(action, condition: null, CompletionHandler); // Foreach
|
|
// Transition to select the next item
|
|
this.ContinueWith(new EmptyTemplate(loopId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.TakeNextAsync)}"), action.Id);
|
|
|
|
// Transition to post action if no more items
|
|
string continuationId = this.ContinuationFor(action.Id, parentId: action.ParentId); // Action continuation
|
|
this._workflowModel.AddLink(loopId, continuationId, $"!{action.Id.FormatName()}.{nameof(ForeachExecutor.HasValue)}");
|
|
|
|
// Transition to start of inner actions if there is a current item
|
|
string startId = ForeachExecutor.Steps.Start(action.Id);
|
|
this._workflowModel.AddNode(new EmptyTemplate(startId, this._rootId), action.Id);
|
|
this._workflowModel.AddLink(loopId, startId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.HasValue)}");
|
|
|
|
void CompletionHandler()
|
|
{
|
|
// Transition to end of inner actions
|
|
string endActionsId = ForeachExecutor.Steps.End(action.Id); // Loop continuation
|
|
this.ContinueWith(new EmptyTemplate(endActionsId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.ResetAsync)}"), action.Id);
|
|
// Transition to select the next item
|
|
this._workflowModel.AddLink(endActionsId, loopId);
|
|
}
|
|
}
|
|
|
|
protected override void Visit(BreakLoop item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Locate the nearest "Foreach" loop that contains this action
|
|
ForeachTemplate? loopAction = this._workflowModel.LocateParent<ForeachTemplate>(item.GetParentId());
|
|
// Skip action if its not contained a loop
|
|
if (loopAction is not null)
|
|
{
|
|
// Represent action with default executor
|
|
DefaultTemplate action = new(item, this._rootId);
|
|
this.ContinueWith(action);
|
|
// Transition to post action
|
|
this._workflowModel.AddLink(action.Id, WorkflowActionVisitor.Steps.Post(loopAction.Id));
|
|
// Define a clean-start to ensure "break" is not a source for any edge
|
|
this.RestartAfter(action.Id, action.ParentId);
|
|
}
|
|
}
|
|
|
|
protected override void Visit(ContinueLoop item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Locate the nearest "Foreach" loop that contains this action
|
|
ForeachTemplate? loopAction = this._workflowModel.LocateParent<ForeachTemplate>(item.GetParentId());
|
|
// Skip action if its not contained a loop
|
|
if (loopAction is not null)
|
|
{
|
|
// Represent action with default executor
|
|
DefaultTemplate action = new(item, this._rootId);
|
|
this.ContinueWith(action);
|
|
// Transition to select the next item
|
|
this._workflowModel.AddLink(action.Id, ForeachExecutor.Steps.Start(loopAction.Id));
|
|
// Define a clean-start to ensure "continue" is not a source for any edge
|
|
this.RestartAfter(action.Id, action.ParentId);
|
|
}
|
|
}
|
|
|
|
protected override void Visit(Question item)
|
|
{
|
|
this.NotSupported(item);
|
|
//this.Trace(item);
|
|
|
|
//this.ContinueWith(new QuestionTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(EndDialog item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Represent action with default executor
|
|
DefaultTemplate action = new(item, this._rootId);
|
|
this.ContinueWith(action);
|
|
// Define a clean-start to ensure "end" is not a source for any edge
|
|
this.RestartAfter(action.Id, action.ParentId);
|
|
}
|
|
|
|
protected override void Visit(EndConversation item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
// Represent action with default executor
|
|
DefaultTemplate action = new(item, this._rootId);
|
|
this.ContinueWith(action);
|
|
// Define a clean-start to ensure "end" is not a source for any edge
|
|
this.RestartAfter(action.Id, action.ParentId);
|
|
}
|
|
|
|
protected override void Visit(CreateConversation item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new CreateConversationTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(AddConversationMessage item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new AddConversationMessageTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(CopyConversationMessages item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new CopyConversationMessagesTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(InvokeAzureAgent item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new InvokeAzureAgentTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(RetrieveConversationMessage item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new RetrieveConversationMessageTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(RetrieveConversationMessages item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new RetrieveConversationMessagesTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(SetVariable item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new SetVariableTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(SetMultipleVariables item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new SetMultipleVariablesTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(SetTextVariable item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new SetTextVariableTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(ClearAllVariables item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new ClearAllVariablesTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(ResetVariable item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new ResetVariableTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(EditTable item)
|
|
{
|
|
this.NotSupported(item);
|
|
//this.Trace(item);
|
|
|
|
//this.ContinueWith(new EditTableTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(EditTableV2 item)
|
|
{
|
|
this.NotSupported(item);
|
|
//this.Trace(item);
|
|
|
|
//this.ContinueWith(new EditTableV2Template(item));
|
|
}
|
|
|
|
protected override void Visit(ParseValue item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new ParseValueTemplate(item));
|
|
}
|
|
|
|
protected override void Visit(SendActivity item)
|
|
{
|
|
this.Trace(item);
|
|
|
|
this.ContinueWith(new SendActivityTemplate(item));
|
|
}
|
|
|
|
#region Not supported
|
|
|
|
protected override void Visit(AnswerQuestionWithAI item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(DeleteActivity item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(GetActivityMembers item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(UpdateActivity item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(ActivateExternalTrigger item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(DisableTrigger item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(WaitForConnectorTrigger item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(InvokeConnectorAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(InvokeCustomModelAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(InvokeFlowAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(InvokeAIBuilderModelAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(InvokeSkillAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(AdaptiveCardPrompt item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(CSATQuestion item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(OAuthInput item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(BeginDialog item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(UnknownDialogAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(RepeatDialog item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(ReplaceDialog item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(CancelAllDialogs item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(CancelDialog item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(EmitEvent item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(GetConversationMembers item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(HttpRequestAction item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(RecognizeIntent item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(TransferConversation item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(TransferConversationV2 item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(SignOutUser item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(LogCustomTelemetryEvent item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(DisconnectedNodeContainer item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(CreateSearchQuery item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(SearchKnowledgeSources item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(SearchAndSummarizeWithCustomModel item) => this.NotSupported(item);
|
|
|
|
protected override void Visit(SearchAndSummarizeContent item) => this.NotSupported(item);
|
|
|
|
#endregion
|
|
|
|
private void ContinueWith(
|
|
ActionTemplate action,
|
|
string? condition = null,
|
|
Action? completionHandler = null)
|
|
{
|
|
this.ContinueWith(action, action.ParentId, condition, completionHandler);
|
|
}
|
|
|
|
private void ContinueWith(
|
|
IModeledAction action,
|
|
string parentId,
|
|
string? condition = null,
|
|
Action? completionHandler = null)
|
|
{
|
|
this._workflowModel.AddNode(action, parentId, completionHandler);
|
|
this._workflowModel.AddLinkFromPeer(parentId, action.Id, condition);
|
|
}
|
|
|
|
private string ContinuationFor(string parentId, string? stepAction = null) => this.ContinuationFor(parentId, parentId, stepAction);
|
|
|
|
private string ContinuationFor(string actionId, string parentId, string? stepAction = null)
|
|
{
|
|
actionId = WorkflowActionVisitor.Steps.Post(actionId);
|
|
|
|
this._workflowModel.AddNode(new EmptyTemplate(actionId, this._rootId, stepAction), parentId);
|
|
|
|
return actionId;
|
|
}
|
|
|
|
private void RestartAfter(string actionId, string parentId) =>
|
|
this._workflowModel.AddNode(new EmptyTemplate(WorkflowActionVisitor.Steps.Restart(actionId), this._rootId), parentId);
|
|
|
|
private static string GetParentId(BotElement item) =>
|
|
item.GetParentId() ??
|
|
throw new DeclarativeModelException($"Missing parent ID for action element: {item.GetId()} [{item.GetType().Name}].");
|
|
|
|
private void NotSupported(DialogAction item)
|
|
{
|
|
Debug.WriteLine($"> UNKNOWN: {FormatItem(item)} => {FormatParent(item)}");
|
|
this.HasUnsupportedActions = true;
|
|
}
|
|
|
|
private void Trace(BotElement item) =>
|
|
Debug.WriteLine($"> VISIT: {new string('\t', this._workflowModel.GetDepth(item.GetParentId()))}{FormatItem(item)} => {FormatParent(item)}");
|
|
|
|
private void Trace(DialogAction item)
|
|
{
|
|
string? parentId = item.GetParentId();
|
|
if (item.Id.Equals(parentId ?? string.Empty))
|
|
{
|
|
parentId = WorkflowActionVisitor.Steps.Root(parentId);
|
|
}
|
|
|
|
Debug.WriteLine($"> VISIT: {new string('\t', this._workflowModel.GetDepth(parentId))}{FormatItem(item)} => {FormatParent(item)}");
|
|
}
|
|
|
|
private static string FormatItem(BotElement element) => $"{element.GetType().Name} ({element.GetId()})";
|
|
|
|
private static string FormatParent(BotElement element) =>
|
|
element.Parent is null ?
|
|
throw new DeclarativeModelException($"Undefined parent for {element.GetType().Name} that is member of {element.GetId()}.") :
|
|
$"{element.Parent.GetType().Name} ({element.GetParentId()})";
|
|
}
|