mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Rename workflows projects (#975)
* Renaming Microsoft.Agent.Workflows to Microsoft.Agents.AI.Workflows * Removing local settings. * Removing remining old files from merge.
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class AgentProviderExtensions
|
||||
{
|
||||
public static async IAsyncEnumerable<AgentRunResponseUpdate> InvokeAgentAsync(
|
||||
this WorkflowAgentProvider agentProvider,
|
||||
string executorId,
|
||||
IWorkflowContext context,
|
||||
string agentName,
|
||||
string? conversationId,
|
||||
bool autoSend,
|
||||
string? additionalInstructions = null,
|
||||
IEnumerable<ChatMessage>? inputMessages = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
AIAgent agent = await agentProvider.GetAgentAsync(agentName, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ChatClientAgentRunOptions options =
|
||||
new(
|
||||
new ChatOptions()
|
||||
{
|
||||
Instructions = additionalInstructions,
|
||||
});
|
||||
|
||||
AgentThread agentThread = conversationId is not null && agent is ChatClientAgent chatClientAgent ? chatClientAgent.GetNewThread(conversationId) : agent.GetNewThread();
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentUpdates =
|
||||
inputMessages is not null ?
|
||||
agent.RunStreamingAsync([.. inputMessages], agentThread, options, cancellationToken) :
|
||||
agent.RunStreamingAsync(agentThread, options, cancellationToken);
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agentUpdates.ConfigureAwait(false))
|
||||
{
|
||||
await AssignConversationIdAsync(((ChatResponseUpdate?)update.RawRepresentation)?.ConversationId).ConfigureAwait(false);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunUpdateEvent(executorId, update)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
yield return update;
|
||||
}
|
||||
|
||||
async ValueTask AssignConversationIdAsync(string? assignValue)
|
||||
{
|
||||
if (assignValue is not null && conversationId is null)
|
||||
{
|
||||
conversationId = assignValue;
|
||||
|
||||
await context.QueueConversationUpdateAsync(conversationId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class BotElementExtensions
|
||||
{
|
||||
public static string? GetParentId(this BotElement element) => element.Parent?.GetId();
|
||||
|
||||
public static string GetId(this BotElement element) =>
|
||||
element switch
|
||||
{
|
||||
DialogAction action => action.Id.Value,
|
||||
ConditionItem conditionItem => conditionItem.Id ?? throw new DeclarativeModelException($"Undefined identifier for {nameof(ConditionItem)} that is member of {conditionItem.GetParentId() ?? "(root)"}."),
|
||||
OnActivity activity => activity.Id.Value,
|
||||
SystemTrigger trigger => trigger.Id.Value,
|
||||
_ => throw new DeclarativeModelException($"Unknown identify for element type: {element.GetType().Name}"),
|
||||
};
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class ChatMessageExtensions
|
||||
{
|
||||
public static RecordValue ToRecord(this ChatMessage message) =>
|
||||
FormulaValue.NewRecordFromFields(message.GetMessageFields());
|
||||
|
||||
public static TableValue ToTable(this IEnumerable<ChatMessage> messages) =>
|
||||
FormulaValue.NewTable(TypeSchema.Message.MessageRecordType, messages.Select(message => message.ToRecord()));
|
||||
|
||||
public static IEnumerable<ChatMessage>? ToChatMessages(this DataValue? messages)
|
||||
{
|
||||
if (messages is null || messages is BlankDataValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (messages is TableDataValue table)
|
||||
{
|
||||
return table.ToChatMessages();
|
||||
}
|
||||
|
||||
if (messages is RecordDataValue record)
|
||||
{
|
||||
return [record.ToChatMessage()];
|
||||
}
|
||||
|
||||
if (messages is StringDataValue text)
|
||||
{
|
||||
return [text.ToChatMessage()];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<ChatMessage> ToChatMessages(this TableDataValue messages)
|
||||
{
|
||||
foreach (DataValue message in messages.Values)
|
||||
{
|
||||
if (message is RecordDataValue record)
|
||||
{
|
||||
if (record.Properties.Count == 1 && record.Properties.TryGetValue("Value", out DataValue? singleColumn))
|
||||
{
|
||||
record = singleColumn as RecordDataValue ?? record;
|
||||
}
|
||||
ChatMessage? convertedMessage = record.ToChatMessage();
|
||||
if (convertedMessage is not null)
|
||||
{
|
||||
yield return convertedMessage;
|
||||
}
|
||||
}
|
||||
else if (message is StringDataValue text)
|
||||
{
|
||||
yield return ToChatMessage(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ChatMessage? ToChatMessage(this DataValue message)
|
||||
{
|
||||
if (message is RecordDataValue record)
|
||||
{
|
||||
return record.ToChatMessage();
|
||||
}
|
||||
|
||||
if (message is StringDataValue text)
|
||||
{
|
||||
return text.ToChatMessage();
|
||||
}
|
||||
|
||||
if (message is BlankDataValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new DeclarativeActionException($"Unable to convert {message.GetDataType()} to {nameof(ChatMessage)}.");
|
||||
}
|
||||
|
||||
public static ChatMessage ToChatMessage(this RecordDataValue message) =>
|
||||
new(message.GetRole(), [.. message.GetContent()])
|
||||
{
|
||||
AdditionalProperties = message.GetProperty<RecordDataValue>("metadata").ToMetadata()
|
||||
};
|
||||
|
||||
public static ChatMessage ToChatMessage(this StringDataValue message) => new(ChatRole.User, message.Value);
|
||||
|
||||
public static AdditionalPropertiesDictionary? ToMetadata(this RecordDataValue? metadata)
|
||||
{
|
||||
if (metadata is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AdditionalPropertiesDictionary properties = [];
|
||||
|
||||
foreach (KeyValuePair<string, DataValue> property in metadata.Properties)
|
||||
{
|
||||
properties[property.Key] = property.Value.ToObject();
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static ChatRole ToChatRole(this AgentMessageRole role) =>
|
||||
role switch
|
||||
{
|
||||
AgentMessageRole.Agent => ChatRole.Assistant,
|
||||
AgentMessageRole.User => ChatRole.User,
|
||||
_ => ChatRole.User
|
||||
};
|
||||
|
||||
public static ChatRole ToChatRole(this AgentMessageRole? role) => role?.ToChatRole() ?? ChatRole.User;
|
||||
|
||||
public static AIContent? ToContent(this AgentMessageContentType contentType, string? contentValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return
|
||||
contentType switch
|
||||
{
|
||||
AgentMessageContentType.ImageUrl => new UriContent(contentValue, "image/*"),
|
||||
AgentMessageContentType.ImageFile => new HostedFileContent(contentValue),
|
||||
_ => new TextContent(contentValue)
|
||||
};
|
||||
}
|
||||
|
||||
private static ChatRole GetRole(this RecordDataValue message)
|
||||
{
|
||||
StringDataValue? roleValue = message.GetProperty<StringDataValue>(TypeSchema.Message.Fields.Role);
|
||||
if (roleValue is null || string.IsNullOrWhiteSpace(roleValue.Value))
|
||||
{
|
||||
return ChatRole.User;
|
||||
}
|
||||
|
||||
AgentMessageRole? role = null;
|
||||
if (Enum.TryParse(roleValue.Value, out AgentMessageRole parsedRole))
|
||||
{
|
||||
role = parsedRole;
|
||||
}
|
||||
|
||||
return role.ToChatRole();
|
||||
}
|
||||
|
||||
private static IEnumerable<AIContent> GetContent(this RecordDataValue message)
|
||||
{
|
||||
TableDataValue? content = message.GetProperty<TableDataValue>(TypeSchema.Message.Fields.Content);
|
||||
if (content is not null)
|
||||
{
|
||||
foreach (RecordDataValue contentItem in content.Values)
|
||||
{
|
||||
StringDataValue? contentValue = contentItem?.GetProperty<StringDataValue>(TypeSchema.Message.Fields.ContentValue);
|
||||
if (contentValue is null || string.IsNullOrWhiteSpace(contentValue.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
yield return
|
||||
contentItem?.GetProperty<StringDataValue>(TypeSchema.Message.Fields.ContentType)?.Value switch
|
||||
{
|
||||
TypeSchema.Message.ContentTypes.ImageUrl => new UriContent(contentValue.Value, "image/*"),
|
||||
TypeSchema.Message.ContentTypes.ImageFile => new HostedFileContent(contentValue.Value),
|
||||
_ => new TextContent(contentValue.Value)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TValue? GetProperty<TValue>(this RecordDataValue record, string name)
|
||||
where TValue : DataValue
|
||||
{
|
||||
if (record.Properties.TryGetValue(name, out DataValue? value) && value is TValue dataValue)
|
||||
{
|
||||
return dataValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<NamedValue> GetMessageFields(this ChatMessage message)
|
||||
{
|
||||
yield return new NamedValue(TypeSchema.Discriminator, nameof(ChatMessage).ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Id, message.MessageId.ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Role, message.Role.Value.ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Author, message.AuthorName.ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Content, FormulaValue.NewTable(TypeSchema.Message.ContentRecordType, message.GetContentRecords()));
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Text, message.Text.ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.Metadata, message.AdditionalProperties.ToRecord());
|
||||
}
|
||||
|
||||
private static IEnumerable<RecordValue> GetContentRecords(this ChatMessage message) =>
|
||||
message.Contents.Select(content => FormulaValue.NewRecordFromFields(content.GetContentFields()));
|
||||
|
||||
private static IEnumerable<NamedValue> GetContentFields(this AIContent content)
|
||||
{
|
||||
return
|
||||
content switch
|
||||
{
|
||||
UriContent uriContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageUrl, uriContent.Uri.ToString()),
|
||||
HostedFileContent fileContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageFile, fileContent.FileId),
|
||||
TextContent textContent => CreateContentRecord(TypeSchema.Message.ContentTypes.Text, textContent.Text),
|
||||
_ => []
|
||||
};
|
||||
|
||||
static IEnumerable<NamedValue> CreateContentRecord(string type, string value)
|
||||
{
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.ContentType, type.ToFormula());
|
||||
yield return new NamedValue(TypeSchema.Message.Fields.ContentValue, value.ToFormula());
|
||||
}
|
||||
}
|
||||
private static RecordValue ToRecord(this AdditionalPropertiesDictionary? value)
|
||||
{
|
||||
return FormulaValue.NewRecordFromFields(GetFields());
|
||||
|
||||
IEnumerable<NamedValue> GetFields()
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
foreach (string key in value.Keys)
|
||||
{
|
||||
yield return new NamedValue(key, value[key].ToFormula());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class DataValueExtensions
|
||||
{
|
||||
public static FormulaValue ToFormula(this DataValue? value) =>
|
||||
value switch
|
||||
{
|
||||
null => FormulaValue.NewBlank(),
|
||||
BlankDataValue => FormulaValue.NewBlank(),
|
||||
BooleanDataValue boolValue => FormulaValue.New(boolValue.Value),
|
||||
NumberDataValue numberValue => FormulaValue.New(numberValue.Value),
|
||||
FloatDataValue floatValue => FormulaValue.New(floatValue.Value),
|
||||
StringDataValue stringValue => FormulaValue.New(stringValue.Value),
|
||||
DateTimeDataValue dateTimeValue => FormulaValue.New(dateTimeValue.Value.DateTime),
|
||||
DateDataValue dateValue => FormulaValue.NewDateOnly(dateValue.Value),
|
||||
TimeDataValue timeValue => FormulaValue.New(timeValue.Value),
|
||||
TableDataValue tableValue =>
|
||||
FormulaValue.NewTable(
|
||||
tableValue.Values.FirstOrDefault()?.ParseRecordType() ?? RecordType.Empty(),
|
||||
tableValue.Values.Select(value => value.ToRecordValue())),
|
||||
RecordDataValue recordValue => recordValue.ToRecordValue(),
|
||||
OptionDataValue optionValue => FormulaValue.New(optionValue.Value.Value),
|
||||
_ => FormulaValue.NewError(new Microsoft.PowerFx.ExpressionError { Message = $"Unknown literal type: {value.GetType().Name}" }),
|
||||
};
|
||||
|
||||
public static FormulaType ToFormulaType(this DataValue? value) => value?.GetDataType().ToFormulaType() ?? FormulaType.Blank;
|
||||
|
||||
public static FormulaType ToFormulaType(this DataType? type) =>
|
||||
type switch
|
||||
{
|
||||
null => FormulaType.Blank,
|
||||
BooleanDataType => FormulaType.Boolean,
|
||||
NumberDataType => FormulaType.Decimal,
|
||||
FloatDataType => FormulaType.Number,
|
||||
StringDataType => FormulaType.String,
|
||||
DateTimeDataType => FormulaType.DateTime,
|
||||
DateDataType => FormulaType.Date,
|
||||
TimeDataType => FormulaType.Time,
|
||||
ColorDataType => FormulaType.Color,
|
||||
GuidDataType => FormulaType.Guid,
|
||||
FileDataType => FormulaType.Blob,
|
||||
RecordDataType => RecordType.Empty(),
|
||||
TableDataType => TableType.Empty(),
|
||||
OptionSetDataType => FormulaType.String,
|
||||
AnyType => FormulaType.UntypedObject,
|
||||
_ => FormulaType.Unknown,
|
||||
};
|
||||
|
||||
public static object? ToObject(this DataValue? value) =>
|
||||
value switch
|
||||
{
|
||||
null => null,
|
||||
BlankDataValue => null,
|
||||
BooleanDataValue boolValue => boolValue.Value,
|
||||
NumberDataValue numberValue => numberValue.Value,
|
||||
FloatDataValue floatValue => floatValue.Value,
|
||||
StringDataValue stringValue => stringValue.Value,
|
||||
DateTimeDataValue dateTimeValue => dateTimeValue.Value.DateTime,
|
||||
DateDataValue dateValue => dateValue.Value,
|
||||
TimeDataValue timeValue => timeValue.Value,
|
||||
TableDataValue tableValue => tableValue.Values.Select(value => value.ToDictionary()).ToArray(),
|
||||
RecordDataValue recordValue => recordValue.ToDictionary(),
|
||||
OptionDataValue optionValue => optionValue.Value.Value,
|
||||
_ => throw new DeclarativeModelException($"Unsupported {nameof(DataValue)} type: {value.GetType().Name}"),
|
||||
};
|
||||
|
||||
public static FormulaValue NewBlank(this DataType? type) => FormulaValue.NewBlank(type?.ToFormulaType() ?? FormulaType.Blank);
|
||||
|
||||
public static RecordValue ToRecordValue(this RecordDataValue recordDataValue) =>
|
||||
FormulaValue.NewRecordFromFields(
|
||||
recordDataValue.Properties.Select(
|
||||
property => new NamedValue(property.Key, property.Value.ToFormula())));
|
||||
|
||||
public static RecordType ToRecordType(this RecordDataType record)
|
||||
{
|
||||
RecordType recordType = RecordType.Empty();
|
||||
foreach (KeyValuePair<string, PropertyInfo> property in record.Properties)
|
||||
{
|
||||
recordType = recordType.Add(property.Key, property.Value.Type.ToFormulaType());
|
||||
}
|
||||
return recordType;
|
||||
}
|
||||
|
||||
private static RecordType ParseRecordType(this RecordDataValue record)
|
||||
{
|
||||
RecordType recordType = RecordType.Empty();
|
||||
foreach (KeyValuePair<string, DataValue> property in record.Properties)
|
||||
{
|
||||
recordType = recordType.Add(property.Key, property.Value.ToFormulaType());
|
||||
}
|
||||
return recordType;
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> ToDictionary(this RecordDataValue record)
|
||||
{
|
||||
Dictionary<string, object?> result = [];
|
||||
foreach (KeyValuePair<string, DataValue> property in record.Properties)
|
||||
{
|
||||
result[property.Key] = property.Value.ToObject();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.PowerFx;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class DeclarativeWorkflowOptionsExtensions
|
||||
{
|
||||
private const int DefaultMaximumExpressionLength = 10000;
|
||||
|
||||
public static RecalcEngine CreateRecalcEngine(this DeclarativeWorkflowOptions? context) =>
|
||||
RecalcEngineFactory.Create(context?.MaximumExpressionLength ?? DefaultMaximumExpressionLength, context?.MaximumCallDepth);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class DialogBaseExtensions
|
||||
{
|
||||
public static TDialog WrapWithBot<TDialog>(this TDialog dialog) where TDialog : DialogBase
|
||||
{
|
||||
BotDefinition bot
|
||||
= new BotDefinition.Builder
|
||||
{
|
||||
Components =
|
||||
{
|
||||
new DialogComponent.Builder
|
||||
{
|
||||
SchemaName = dialog.HasSchemaName ? dialog.SchemaName : "default-schema",
|
||||
Dialog = dialog.ToBuilder(),
|
||||
}
|
||||
}
|
||||
}.Build();
|
||||
|
||||
return bot.Descendants().OfType<TDialog>().First();
|
||||
}
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using BlankType = Microsoft.PowerFx.Types.BlankType;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class FormulaValueExtensions
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
|
||||
|
||||
public static FormulaValue NewBlank(this FormulaType? type) => FormulaValue.NewBlank(type ?? FormulaType.Blank);
|
||||
|
||||
public static FormulaValue ToFormula(this object? value) =>
|
||||
value switch
|
||||
{
|
||||
null => FormulaValue.NewBlank(),
|
||||
UnassignedValue => FormulaValue.NewBlank(),
|
||||
FormulaValue formulaValue => formulaValue,
|
||||
bool booleanValue => FormulaValue.New(booleanValue),
|
||||
int decimalValue => FormulaValue.New(decimalValue),
|
||||
long decimalValue => FormulaValue.New(decimalValue),
|
||||
float decimalValue => FormulaValue.New(decimalValue),
|
||||
decimal decimalValue => FormulaValue.New(decimalValue),
|
||||
double numberValue => FormulaValue.New(numberValue),
|
||||
string stringValue => FormulaValue.New(stringValue),
|
||||
DateTime dateonlyValue when dateonlyValue.TimeOfDay == TimeSpan.Zero => FormulaValue.NewDateOnly(dateonlyValue),
|
||||
DateTime datetimeValue => FormulaValue.New(datetimeValue),
|
||||
TimeSpan timeValue => FormulaValue.New(timeValue),
|
||||
ChatMessage chatMessage => chatMessage.ToRecord(),
|
||||
ExpandoObject expandoValue => expandoValue.ToRecord(),
|
||||
object when value is IDictionary dictionaryValue => dictionaryValue.ToRecord(),
|
||||
object when value is IEnumerable tableValue => tableValue.ToTable(),
|
||||
_ => throw new DeclarativeModelException($"Unsupported variable type: {value.GetType().Name}"),
|
||||
};
|
||||
|
||||
public static FormulaType GetFormulaType(this object? value) =>
|
||||
value switch
|
||||
{
|
||||
null => FormulaType.Blank,
|
||||
bool => FormulaType.Boolean,
|
||||
int => FormulaType.Decimal,
|
||||
long => FormulaType.Decimal,
|
||||
float => FormulaType.Decimal,
|
||||
decimal => FormulaType.Decimal,
|
||||
double => FormulaType.Number,
|
||||
string => FormulaType.String,
|
||||
DateTime => FormulaType.DateTime,
|
||||
TimeSpan => FormulaType.Time,
|
||||
object when value is IEnumerable tableValue => tableValue.ToTableType(),
|
||||
ExpandoObject expandoValue => expandoValue.ToRecordType(),
|
||||
_ => FormulaType.Unknown,
|
||||
};
|
||||
|
||||
public static DataValue ToDataValue(this FormulaValue value) =>
|
||||
value switch
|
||||
{
|
||||
BooleanValue booleanValue => BooleanDataValue.Create(booleanValue.Value),
|
||||
DecimalValue decimalValue => NumberDataValue.Create(decimalValue.Value),
|
||||
NumberValue numberValue => FloatDataValue.Create(numberValue.Value),
|
||||
DateValue dateValue => DateDataValue.Create(dateValue.GetConvertedValue(TimeZoneInfo.Utc)),
|
||||
DateTimeValue datetimeValue => DateTimeDataValue.Create(datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)),
|
||||
TimeValue timeValue => TimeDataValue.Create(timeValue.Value),
|
||||
StringValue stringValue => StringDataValue.Create(stringValue.Value),
|
||||
BlankValue => DataValue.Blank(),
|
||||
VoidValue => DataValue.Blank(),
|
||||
RecordValue recordValue => recordValue.ToRecord(),
|
||||
TableValue tableValue => tableValue.ToTable(),
|
||||
_ => throw new DeclarativeModelException($"Unsupported variable type: {value.GetType().Name}"),
|
||||
};
|
||||
|
||||
public static DataType GetDataType(this FormulaValue value) =>
|
||||
value switch
|
||||
{
|
||||
null => DataType.Blank,
|
||||
BooleanValue => DataType.Boolean,
|
||||
DecimalValue => DataType.Number,
|
||||
NumberValue => DataType.Float,
|
||||
DateValue => DataType.Date,
|
||||
DateTimeValue => DataType.DateTime,
|
||||
TimeValue => DataType.Time,
|
||||
StringValue => DataType.String,
|
||||
BlankValue => DataType.Blank,
|
||||
ColorValue => DataType.Color,
|
||||
GuidValue => DataType.Guid,
|
||||
BlobValue => DataType.File,
|
||||
RecordValue recordValue => recordValue.Type.ToDataType(),
|
||||
TableValue tableValue => tableValue.Type.ToDataType(),
|
||||
UntypedObjectValue => DataType.Any,
|
||||
_ => DataType.Unspecified,
|
||||
};
|
||||
|
||||
public static DataType ToDataType(this FormulaType type) =>
|
||||
type switch
|
||||
{
|
||||
null => DataType.Blank,
|
||||
BooleanType => DataType.Boolean,
|
||||
DecimalType => DataType.Number,
|
||||
NumberType => DataType.Float,
|
||||
DateType => DataType.Date,
|
||||
DateTimeType => DataType.DateTime,
|
||||
TimeType => DataType.Time,
|
||||
StringType => DataType.String,
|
||||
BlankType => DataType.Blank,
|
||||
ColorType => DataType.Color,
|
||||
GuidType => DataType.Guid,
|
||||
BlobType => DataType.File,
|
||||
RecordType recordType => recordType.ToDataType(),
|
||||
TableType tableType => tableType.ToDataType(),
|
||||
UntypedObjectType => DataType.Any,
|
||||
_ => DataType.Unspecified,
|
||||
};
|
||||
|
||||
public static string Format(this FormulaValue value) =>
|
||||
value switch
|
||||
{
|
||||
BooleanValue booleanValue => $"{booleanValue.Value}",
|
||||
DecimalValue decimalValue => $"{decimalValue.Value}",
|
||||
NumberValue numberValue => $"{numberValue.Value}",
|
||||
DateValue dateValue => $"{dateValue.GetConvertedValue(TimeZoneInfo.Utc)}",
|
||||
DateTimeValue datetimeValue => $"{datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)}",
|
||||
TimeValue timeValue => $"{timeValue.Value}",
|
||||
StringValue stringValue => stringValue.Value,
|
||||
BlankValue blankValue => string.Empty,
|
||||
VoidValue voidValue => string.Empty,
|
||||
ColorValue colorValue => colorValue.Value.ToString(),
|
||||
GuidValue guidValue => guidValue.Value.ToString("N"),
|
||||
TableValue tableValue => tableValue.ToJson().ToJsonString(s_options),
|
||||
RecordValue recordValue => recordValue.ToJson().ToJsonString(s_options),
|
||||
ErrorValue errorValue => $"Error:{Environment.NewLine}{string.Join(Environment.NewLine, errorValue.Errors.Select(error => $"{error.MessageKey}: {error.Message}"))}",
|
||||
_ => $"[{value.GetType().Name}]",
|
||||
};
|
||||
|
||||
public static TableDataValue ToTable(this TableValue value) =>
|
||||
DataValue.TableFromRecords(value.Rows.Select(row => row.Value.ToRecord()).ToImmutableArray());
|
||||
|
||||
public static RecordDataValue ToRecord(this RecordValue value) =>
|
||||
DataValue.RecordFromFields(value.OriginalFields.Select(field => field.GetKeyValuePair()));
|
||||
|
||||
public static RecordValue ToRecord(this IDictionary value)
|
||||
{
|
||||
return FormulaValue.NewRecordFromFields(GetFields());
|
||||
|
||||
IEnumerable<NamedValue> GetFields()
|
||||
{
|
||||
foreach (string key in value.Keys)
|
||||
{
|
||||
yield return new NamedValue(key, value[key].ToFormula());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RecordDataType ToDataType(this RecordType record)
|
||||
{
|
||||
RecordDataType recordType = new();
|
||||
foreach (string fieldName in record.FieldNames)
|
||||
{
|
||||
recordType.Properties.Add(fieldName, PropertyInfo.Create(record.GetFieldType(fieldName).ToDataType()));
|
||||
}
|
||||
return recordType;
|
||||
}
|
||||
|
||||
private static TableDataType ToDataType(this TableType table)
|
||||
{
|
||||
TableDataType tableType = new();
|
||||
foreach (string fieldName in table.FieldNames)
|
||||
{
|
||||
tableType.Properties.Add(fieldName, PropertyInfo.Create(table.GetFieldType(fieldName).ToDataType()));
|
||||
}
|
||||
return tableType;
|
||||
}
|
||||
|
||||
private static RecordType ToRecordType(this ExpandoObject value)
|
||||
{
|
||||
RecordType recordType = RecordType.Empty();
|
||||
foreach (KeyValuePair<string, object?> property in value)
|
||||
{
|
||||
recordType.Add(property.Key, property.Value.GetFormulaType());
|
||||
}
|
||||
return recordType;
|
||||
}
|
||||
|
||||
private static RecordValue ToRecord(this ExpandoObject value) =>
|
||||
FormulaValue.NewRecordFromFields(
|
||||
value.Select(
|
||||
property => new NamedValue(property.Key, property.Value.ToFormula())));
|
||||
|
||||
private static TableType ToTableType(this IEnumerable value)
|
||||
{
|
||||
foreach (object? element in value)
|
||||
{
|
||||
if (element is not ExpandoObject expandoElement)
|
||||
{
|
||||
throw new DeclarativeModelException($"Invalid table element: {element.GetType().Name}");
|
||||
}
|
||||
|
||||
return expandoElement.ToRecordType().ToTable(); // Return first element
|
||||
}
|
||||
|
||||
return TableType.Empty();
|
||||
}
|
||||
|
||||
private static TableValue ToTable(this IEnumerable value)
|
||||
{
|
||||
Type? elementType = value.GetType().GetElementType();
|
||||
if (elementType is null || elementType == typeof(object))
|
||||
{
|
||||
IEnumerator enumerator = value.GetEnumerator();
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
elementType = enumerator.Current?.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
elementType switch
|
||||
{
|
||||
null => FormulaValue.NewTable(RecordType.EmptySealed(), []),
|
||||
_ when elementType == typeof(ExpandoObject) =>
|
||||
FormulaValue.NewTable(
|
||||
value.ToTableType().ToRecord(),
|
||||
[.. value.OfType<ExpandoObject>().Select(element => element.ToRecord())]),
|
||||
_ when typeof(ChatMessage).IsAssignableFrom(elementType) =>
|
||||
FormulaValue.NewTable(
|
||||
TypeSchema.Message.MessageRecordType,
|
||||
[.. value.OfType<ChatMessage>().Select(message => message.ToRecord())]),
|
||||
_ when typeof(IDictionary).IsAssignableFrom(elementType) => value.ToTableOfRecords(),
|
||||
_ => throw new DeclarativeModelException($"Unsupported element type: {elementType.Name}"),
|
||||
};
|
||||
}
|
||||
|
||||
private static TableValue ToTableOfRecords(this IEnumerable list)
|
||||
{
|
||||
RecordValue[] elements = [.. list.OfType<IDictionary>().Select(table => table.ToRecord())];
|
||||
return FormulaValue.NewTable(elements.First().Type, elements);
|
||||
}
|
||||
|
||||
private static KeyValuePair<string, DataValue> GetKeyValuePair(this NamedValue value) => new(value.Name, value.Value.ToDataValue());
|
||||
|
||||
private static JsonNode ToJson(this FormulaValue value) =>
|
||||
value switch
|
||||
{
|
||||
BooleanValue booleanValue => JsonValue.Create(booleanValue.Value),
|
||||
DecimalValue decimalValue => JsonValue.Create(decimalValue.Value),
|
||||
NumberValue numberValue => JsonValue.Create(numberValue.Value),
|
||||
DateValue dateValue => JsonValue.Create(dateValue.GetConvertedValue(TimeZoneInfo.Utc)),
|
||||
DateTimeValue datetimeValue => JsonValue.Create(datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)),
|
||||
TimeValue timeValue => JsonValue.Create($"{timeValue.Value}"),
|
||||
StringValue stringValue => JsonValue.Create(stringValue.Value),
|
||||
GuidValue guidValue => JsonValue.Create(guidValue.Value),
|
||||
RecordValue recordValue => recordValue.ToJson(),
|
||||
TableValue tableValue => tableValue.ToJson(),
|
||||
BlankValue => JsonValue.Create(string.Empty),
|
||||
_ => $"[{value.GetType().Name}]",
|
||||
};
|
||||
|
||||
private static JsonArray ToJson(this TableValue value)
|
||||
{
|
||||
return new([.. GetJsonElements()]);
|
||||
|
||||
IEnumerable<JsonNode> GetJsonElements()
|
||||
{
|
||||
foreach (DValue<RecordValue> row in value.Rows)
|
||||
{
|
||||
RecordValue recordValue = row.Value;
|
||||
yield return recordValue.ToJson();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonObject ToJson(this RecordValue value)
|
||||
{
|
||||
JsonObject jsonObject = [];
|
||||
foreach (NamedValue field in value.OriginalFields)
|
||||
{
|
||||
jsonObject.Add(field.Name, field.Value.ToJson());
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class IWorkflowContextExtensions
|
||||
{
|
||||
public static ValueTask RaiseInvocationEventAsync(this IWorkflowContext context, DialogAction action, string? priorEventId = null) =>
|
||||
context.AddEventAsync(new DeclarativeActionInvokedEvent(action, priorEventId));
|
||||
|
||||
public static ValueTask RaiseCompletionEventAsync(this IWorkflowContext context, DialogAction action) =>
|
||||
context.AddEventAsync(new DeclarativeActionCompletedEvent(action));
|
||||
|
||||
public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result = null, CancellationToken cancellationToken = default) =>
|
||||
context.SendMessageAsync(new ActionExecutorResult(id, result));
|
||||
|
||||
public static ValueTask QueueStateResetAsync(this IWorkflowContext context, PropertyPath variablePath) =>
|
||||
context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), UnassignedValue.Instance, Throw.IfNull(variablePath.NamespaceAlias));
|
||||
|
||||
public static ValueTask QueueStateUpdateAsync<TValue>(this IWorkflowContext context, PropertyPath variablePath, TValue? value) =>
|
||||
context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.NamespaceAlias));
|
||||
|
||||
public static ValueTask QueueSystemUpdateAsync<TValue>(this IWorkflowContext context, string key, TValue? value) =>
|
||||
DeclarativeContext(context).QueueSystemUpdateAsync(key, value);
|
||||
|
||||
public static FormulaValue ReadState(this IWorkflowContext context, PropertyPath variablePath) =>
|
||||
context.ReadState(Throw.IfNull(variablePath.VariableName), Throw.IfNull(variablePath.NamespaceAlias));
|
||||
|
||||
public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) =>
|
||||
DeclarativeContext(context).State.Get(key, scopeName);
|
||||
|
||||
public static async ValueTask QueueConversationUpdateAsync(this IWorkflowContext context, string conversationId)
|
||||
{
|
||||
RecordValue conversation = (RecordValue)context.ReadState(SystemScope.Names.Conversation, VariableScopeNames.System);
|
||||
conversation.UpdateField("Id", FormulaValue.New(conversationId));
|
||||
await context.QueueSystemUpdateAsync(SystemScope.Names.Conversation, conversation).ConfigureAwait(false);
|
||||
await context.QueueSystemUpdateAsync(SystemScope.Names.ConversationId, FormulaValue.New(conversationId)).ConfigureAwait(false);
|
||||
|
||||
await context.AddEventAsync(new ConversationUpdateEvent(conversationId)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static DeclarativeWorkflowContext DeclarativeContext(IWorkflowContext context)
|
||||
{
|
||||
if (context is not DeclarativeWorkflowContext declarativeContext)
|
||||
{
|
||||
throw new DeclarativeActionException($"Invalid workflow context: {context.GetType().Name}.");
|
||||
}
|
||||
|
||||
return declarativeContext;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class RecordDataTypeExtensions
|
||||
{
|
||||
public static RecordValue ParseRecord(this RecordDataType recordType, JsonElement currentElement)
|
||||
{
|
||||
return FormulaValue.NewRecordFromFields(ParseValues());
|
||||
|
||||
IEnumerable<NamedValue> ParseValues()
|
||||
{
|
||||
foreach (KeyValuePair<string, PropertyInfo> property in recordType.Properties)
|
||||
{
|
||||
JsonElement propertyElement = currentElement.GetProperty(property.Key);
|
||||
FormulaValue? parsedValue =
|
||||
property.Value.Type switch
|
||||
{
|
||||
StringDataType => FormulaValue.New(propertyElement.GetString()),
|
||||
NumberDataType => FormulaValue.New(propertyElement.GetDecimal()),
|
||||
BooleanDataType => FormulaValue.New(propertyElement.GetBoolean()),
|
||||
DateTimeDataType => FormulaValue.New(propertyElement.GetDateTime()),
|
||||
DateDataType => FormulaValue.New(propertyElement.GetDateTime()),
|
||||
TimeDataType => FormulaValue.New(propertyElement.GetDateTimeOffset().TimeOfDay),
|
||||
RecordDataType recordType => recordType.ParseRecord(propertyElement),
|
||||
TableDataType tableType => ParseTable(tableType, propertyElement),
|
||||
_ => throw new InvalidOperationException($"Unsupported data type '{property.Value.Type}' for property '{property.Key}'"),
|
||||
};
|
||||
yield return new NamedValue(property.Key, parsedValue);
|
||||
}
|
||||
|
||||
static TableValue ParseTable(TableDataType tableType, JsonElement propertyElement)
|
||||
{
|
||||
RecordDataType recordType = tableType.ToRecord();
|
||||
return
|
||||
FormulaValue.NewTable(
|
||||
recordType.ToRecordType(),
|
||||
propertyElement.EnumerateArray().Select(tableElement => ParseRecord(recordType, tableElement)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static partial class StringExtensions
|
||||
{
|
||||
#if NET
|
||||
[GeneratedRegex(@"^```(?:\w*)\s*([\s\S]*?)\s*```$", RegexOptions.Multiline)]
|
||||
private static partial Regex TrimJsonDelimiterRegex();
|
||||
#else
|
||||
private static Regex TrimJsonDelimiterRegex() => s_trimJsonDelimiterRegex;
|
||||
private static readonly Regex s_trimJsonDelimiterRegex = new(@"^```(?:\w*)\s*([\s\S]*?)\s*```$", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
#endif
|
||||
|
||||
public static string TrimJsonDelimiter(this string value)
|
||||
{
|
||||
value = value.Trim();
|
||||
|
||||
Match match = TrimJsonDelimiterRegex().Match(value);
|
||||
return match.Success ?
|
||||
match.Groups[1].Value.Trim() :
|
||||
value;
|
||||
}
|
||||
|
||||
public static FormulaValue ToFormula(this string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? FormulaValue.NewBlank() : FormulaValue.New(value);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.PowerFx;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
|
||||
internal static class TemplateExtensions
|
||||
{
|
||||
public static string Format(this RecalcEngine engine, IEnumerable<TemplateLine> template) =>
|
||||
string.Concat(template.Select(engine.Format));
|
||||
|
||||
public static string Format(this RecalcEngine engine, TemplateLine? line) =>
|
||||
line is not null ?
|
||||
string.Concat(line.Segments.Select(engine.Format)) :
|
||||
string.Empty;
|
||||
|
||||
public static string Format(this RecalcEngine engine, TemplateSegment segment)
|
||||
{
|
||||
if (segment is TextSegment textSegment)
|
||||
{
|
||||
return textSegment.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
if (segment is ExpressionSegment { Expression: not null } expressionSegment)
|
||||
{
|
||||
if (expressionSegment.Expression.ExpressionText is not null)
|
||||
{
|
||||
return engine.Eval(expressionSegment.Expression.ExpressionText).Format();
|
||||
}
|
||||
|
||||
if (expressionSegment.Expression.VariableReference is not null)
|
||||
{
|
||||
return engine.Eval(expressionSegment.Expression.VariableReference.ToString()).Format();
|
||||
}
|
||||
}
|
||||
|
||||
throw new DeclarativeModelException($"Unsupported segment type: {segment.GetType().Name}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user