mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Workflows - Support "structured inputs" feature for declarative workflows (#2053)
This commit is contained in:
@@ -116,6 +116,7 @@
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj" />
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Demo.Workflows.Declarative.ConfirmInput;
|
||||
/// and confirm it matches the original input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Demo.Workflows.Declarative.DeepResearch;
|
||||
/// using the Magentic orchestration pattern developed by AutoGen.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Demo.Workflows.Declarative.FunctionTools;
|
||||
/// with function tools assigned. Exits the loop when the user enters "exit".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ProjectsDebugTargetFrameworks>net9.0</ProjectsDebugTargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
|
||||
<InjectSharedFoundryAgents>true</InjectSharedFoundryAgents>
|
||||
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
|
||||
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative\Microsoft.Agents.AI.Workflows.Declarative.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="InputArguments.yaml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
#
|
||||
# This workflow demonstrates providing input arguments to an agent.
|
||||
#
|
||||
# Example input:
|
||||
# I'd like to go on vacation.
|
||||
#
|
||||
kind: Workflow
|
||||
trigger:
|
||||
|
||||
kind: OnConversationStart
|
||||
id: workflow_demo
|
||||
actions:
|
||||
|
||||
# Capture the original user message for input to the location-aware agent
|
||||
- kind: SetVariable
|
||||
id: set_count_increment
|
||||
variable: Local.InputMessage
|
||||
value: =System.LastMessage
|
||||
|
||||
# Invoke the triage agent to determine location requirements
|
||||
- kind: InvokeAzureAgent
|
||||
id: solicit_input
|
||||
conversationId: =System.ConversationId
|
||||
agent:
|
||||
name: LocationTriageAgent
|
||||
input:
|
||||
messages: =Local.ActionMessage
|
||||
output:
|
||||
messages: Local.TriageResponse
|
||||
|
||||
# Request input from the user based on the triage response
|
||||
- kind: RequestExternalInput
|
||||
id: request_requirements
|
||||
variable: Local.NextInput
|
||||
|
||||
# Capture the most recent interaction for evaluation
|
||||
- kind: SetTextVariable
|
||||
id: set_status_message
|
||||
variable: Local.LocationStatusInput
|
||||
value: |-
|
||||
AGENT - {MessageText(Local.TriageResponse)}
|
||||
|
||||
USER - {MessageText(Local.NextInput)}
|
||||
|
||||
# Evaluate the status of the location triage
|
||||
- kind: InvokeAzureAgent
|
||||
id: evaluate_location
|
||||
agent:
|
||||
name: LocationCaptureAgent
|
||||
input:
|
||||
messages: =UserMessage(Local.LocationStatusInput)
|
||||
output:
|
||||
responseObject: Local.LocationResponse
|
||||
|
||||
# Determine if the location information is complete
|
||||
- kind: ConditionGroup
|
||||
id: check_completion
|
||||
conditions:
|
||||
|
||||
- condition: |-
|
||||
=Local.LocationResponse.is_location_defined = false Or
|
||||
Local.LocationResponse.is_location_confirmed = false
|
||||
id: check_done
|
||||
actions:
|
||||
|
||||
# Capture the action message for input to the triage agent
|
||||
- kind: SetVariable
|
||||
id: set_next_message
|
||||
variable: Local.ActionMessage
|
||||
value: =AgentMessage(Local.LocationResponse.action)
|
||||
|
||||
- kind: GotoAction
|
||||
id: goto_solicit_input
|
||||
actionId: solicit_input
|
||||
|
||||
elseActions:
|
||||
|
||||
# Create a new conversation so the prior context does not interfere
|
||||
- kind: CreateConversation
|
||||
id: conversation_location
|
||||
conversationId: Local.LocationConversationId
|
||||
|
||||
# Invoke the location-aware agent with the location argument
|
||||
# and loop until the user types "EXIT"
|
||||
- kind: InvokeAzureAgent
|
||||
id: location_response
|
||||
conversationId: =Local.LocationConversationId
|
||||
agent:
|
||||
name: LocationAwareAgent
|
||||
input:
|
||||
messages: =Local.InputMessage
|
||||
arguments:
|
||||
location: =Local.LocationResponse.place
|
||||
externalLoop:
|
||||
when: =Upper(System.LastMessage.Text) <> "EXIT"
|
||||
output:
|
||||
autoSend: true
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.Agents;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenAI.Responses;
|
||||
using Shared.Foundry;
|
||||
using Shared.Workflows;
|
||||
|
||||
namespace Demo.Workflows.Declarative.InputArguments;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate a workflow that consumes input arguments to dynamically enhance the agent
|
||||
/// instructions. Exits the loop when the user enters "exit".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
// Initialize configuration
|
||||
IConfiguration configuration = Application.InitializeConfig();
|
||||
Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint));
|
||||
|
||||
// Ensure sample agents exist in Foundry.
|
||||
await CreateAgentAsync(foundryEndpoint, configuration);
|
||||
|
||||
// Get input from command line or console
|
||||
string workflowInput = Application.GetInput(args);
|
||||
|
||||
// Create the workflow factory. This class demonstrates how to initialize a
|
||||
// declarative workflow from a YAML file. Once the workflow is created, it
|
||||
// can be executed just like any regular workflow.
|
||||
WorkflowFactory workflowFactory = new("InputArguments.yaml", foundryEndpoint);
|
||||
|
||||
// Execute the workflow: The WorkflowRunner demonstrates how to execute
|
||||
// a workflow, handle the workflow events, and providing external input.
|
||||
// This also includes the ability to checkpoint workflow state and how to
|
||||
// resume execution.
|
||||
WorkflowRunner runner = new();
|
||||
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, workflowInput);
|
||||
}
|
||||
|
||||
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration)
|
||||
{
|
||||
AgentClient agentsClient = new(foundryEndpoint, new AzureCliCredential());
|
||||
|
||||
await agentsClient.CreateAgentAsync(
|
||||
agentName: "LocationTriageAgent",
|
||||
agentDefinition: DefineLocationTriageAgent(configuration),
|
||||
agentDescription: "Chats with the user to solicit a location of interest.");
|
||||
|
||||
await agentsClient.CreateAgentAsync(
|
||||
agentName: "LocationCaptureAgent",
|
||||
agentDefinition: DefineLocationCaptureAgent(configuration),
|
||||
agentDescription: "Evaluate the status of soliciting the location.");
|
||||
|
||||
await agentsClient.CreateAgentAsync(
|
||||
agentName: "LocationAwareAgent",
|
||||
agentDefinition: DefineLocationAwareAgent(configuration),
|
||||
agentDescription: "Chats with the user with location awareness.");
|
||||
}
|
||||
|
||||
private static PromptAgentDefinition DefineLocationTriageAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModelMini))
|
||||
{
|
||||
Instructions =
|
||||
"""
|
||||
Your only job is to solicit a location from the user.
|
||||
|
||||
Always repeat back the location when addressing the user, except when it is not known.
|
||||
"""
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineLocationCaptureAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModelMini))
|
||||
{
|
||||
Instructions =
|
||||
"""
|
||||
Request a location from the user. This location could be their own location
|
||||
or perhaps a location they are interested in.
|
||||
|
||||
City level precision is sufficient.
|
||||
|
||||
If extrapolating region and country, confirm you have it right.
|
||||
""",
|
||||
TextOptions =
|
||||
new ResponseTextOptions
|
||||
{
|
||||
TextFormat =
|
||||
ResponseTextFormat.CreateJsonSchemaFormat(
|
||||
"TaskEvaluation",
|
||||
BinaryData.FromString(
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"place": {
|
||||
"type": "string",
|
||||
"description": "Captures only your understanding of the location specified by the user without explanation, or 'unknown' if not yet defined."
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "The instruction for the next action to take regarding the need for additional detail or confirmation."
|
||||
},
|
||||
"is_location_defined": {
|
||||
"type": "boolean",
|
||||
"description": "True if the user location is understood."
|
||||
},
|
||||
"is_location_confirmed": {
|
||||
"type": "boolean",
|
||||
"description": "True if the user location is confirmed. An unambiguous location may be implicitly confirmed without explicit user confirmation."
|
||||
}
|
||||
},
|
||||
"required": ["place", "action", "is_location_defined", "is_location_confirmed"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
"""),
|
||||
jsonSchemaFormatDescription: null,
|
||||
jsonSchemaIsStrict: true),
|
||||
}
|
||||
};
|
||||
|
||||
private static PromptAgentDefinition DefineLocationAwareAgent(IConfiguration configuration) =>
|
||||
new(configuration.GetValue(Application.Settings.FoundryModelMini))
|
||||
{
|
||||
// Parameterized instructions reference the "location" input argument.
|
||||
Instructions =
|
||||
"""
|
||||
Talk to the user about their request.
|
||||
Their request is related to a specific location: {{location}}.
|
||||
""",
|
||||
StructuredInputs =
|
||||
{
|
||||
["location"] =
|
||||
new StructuredInputDefinition
|
||||
{
|
||||
IsRequired = false,
|
||||
DefaultValue = BinaryData.FromString(@"""unknown"""),
|
||||
Description = "The user's location",
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Demo.Workflows.Declarative.Marketing;
|
||||
/// sequentially engaging in a task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Demo.Workflows.Declarative.StudentTeacher;
|
||||
/// in an iterative conversation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Demo.Workflows.Declarative.ToolApproval;
|
||||
/// has an MCP tool that requires approval. Exits the loop when the user enters "exit".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the README.md file in the parent folder (../Declarative/README.md) for detailed
|
||||
/// See the README.md file in the parent folder (../README.md) for detailed
|
||||
/// information the configuration required to run this sample.
|
||||
/// </remarks>
|
||||
internal sealed class Program
|
||||
|
||||
@@ -7,10 +7,12 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.Agents;
|
||||
using Azure.Core;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Responses;
|
||||
|
||||
@@ -32,6 +34,11 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
private AgentClient? _agentClient;
|
||||
private ConversationClient? _conversationClient;
|
||||
|
||||
/// <summary>
|
||||
/// Optional options used when creating the <see cref="AgentClient"/>.
|
||||
/// </summary>
|
||||
public AgentClientOptions? ClientOptions { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<string> CreateConversationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -78,6 +85,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
string? agentVersion,
|
||||
string? conversationId,
|
||||
IEnumerable<ChatMessage>? messages,
|
||||
IDictionary<string, object?>? inputArguments,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentVersion agentVersionResult = await this.QueryAgentAsync(agentId, agentVersion, cancellationToken).ConfigureAwait(false);
|
||||
@@ -90,6 +98,14 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
AllowMultipleToolCalls = this.AllowMultipleToolCalls,
|
||||
};
|
||||
|
||||
if (inputArguments is not null)
|
||||
{
|
||||
JsonNode jsonNode = inputArguments.ToFormula().ToJson();
|
||||
ResponseCreationOptions responseCreationOptions = new();
|
||||
responseCreationOptions.SetStructuredInputs(BinaryData.FromString(jsonNode.ToJsonString()));
|
||||
chatOptions.RawRepresentationFactory = (_) => responseCreationOptions;
|
||||
}
|
||||
|
||||
ChatClientAgentRunOptions runOptions = new(chatOptions);
|
||||
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentResponse =
|
||||
@@ -206,7 +222,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
{
|
||||
if (this._agentClient is null)
|
||||
{
|
||||
AgentClientOptions clientOptions = new();
|
||||
AgentClientOptions clientOptions = this.ClientOptions ?? new();
|
||||
|
||||
if (httpClient is not null)
|
||||
{
|
||||
|
||||
+2
-1
@@ -17,9 +17,10 @@ internal static class AgentProviderExtensions
|
||||
string? conversationId,
|
||||
bool autoSend,
|
||||
IEnumerable<ChatMessage>? inputMessages = null,
|
||||
IDictionary<string, object?>? inputArguments = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentUpdates = agentProvider.InvokeAgentAsync(agentName, null, conversationId, inputMessages, cancellationToken);
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> agentUpdates = agentProvider.InvokeAgentAsync(agentName, null, conversationId, inputMessages, inputArguments, cancellationToken);
|
||||
|
||||
// Enable "autoSend" behavior if this is the workflow conversation.
|
||||
bool isWorkflowConversation = context.IsWorkflowConversation(conversationId, out string? workflowConversationId);
|
||||
|
||||
+18
-17
@@ -163,6 +163,24 @@ internal static class FormulaValueExtensions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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}]",
|
||||
};
|
||||
|
||||
public static RecordValue ToRecord(this Dictionary<string, PortableValue> value) =>
|
||||
FormulaValue.NewRecordFromFields(
|
||||
value.Select(
|
||||
@@ -256,23 +274,6 @@ internal static class FormulaValueExtensions
|
||||
|
||||
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()]);
|
||||
|
||||
@@ -33,5 +33,5 @@ public abstract class AgentExecutor(string id, FormulaSession session, WorkflowA
|
||||
bool autoSend,
|
||||
IEnumerable<ChatMessage>? inputMessages = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, inputMessages, cancellationToken);
|
||||
=> agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, inputMessages, inputArguments: null, cancellationToken);
|
||||
}
|
||||
|
||||
+19
-2
@@ -60,8 +60,8 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA
|
||||
string? conversationId = this.GetConversationId();
|
||||
string agentName = this.GetAgentName();
|
||||
bool autoSend = this.GetAutoSendValue();
|
||||
|
||||
AgentRunResponse agentResponse = await agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, messages, cancellationToken).ConfigureAwait(false);
|
||||
Dictionary<string, object?>? inputParameters = this.GetStructuredInputs();
|
||||
AgentRunResponse agentResponse = await agentProvider.InvokeAgentAsync(this.Id, context, agentName, conversationId, autoSend, messages, inputParameters, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ChatMessage[] actionableMessages = FilterActionableContent(agentResponse).ToArray();
|
||||
if (actionableMessages.Length > 0)
|
||||
@@ -107,6 +107,23 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, WorkflowA
|
||||
await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Dictionary<string, object?>? GetStructuredInputs()
|
||||
{
|
||||
Dictionary<string, object?>? inputs = null;
|
||||
|
||||
if (this.AgentInput?.Arguments is not null)
|
||||
{
|
||||
inputs = [];
|
||||
|
||||
foreach (KeyValuePair<string, ValueExpression> argument in this.AgentInput.Arguments)
|
||||
{
|
||||
inputs[argument.Key] = this.Evaluator.GetValue(argument.Value).Value.ToObject();
|
||||
}
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private IEnumerable<ChatMessage>? GetInputMessages()
|
||||
{
|
||||
DataValue? userInput = null;
|
||||
|
||||
+2
-5
@@ -8,7 +8,6 @@ using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Microsoft.Bot.ObjectModel.Abstractions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
|
||||
|
||||
@@ -17,17 +16,15 @@ internal sealed class SetVariableExecutor(SetVariable model, WorkflowFormulaStat
|
||||
{
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
PropertyPath variablePath = Throw.IfNull(this.Model.Variable?.Path, $"{nameof(this.Model)}.{nameof(model.Variable)}");
|
||||
|
||||
if (this.Model.Value is null)
|
||||
{
|
||||
await this.AssignAsync(variablePath, FormulaValue.NewBlank(), context).ConfigureAwait(false);
|
||||
await this.AssignAsync(this.Model.Variable?.Path, FormulaValue.NewBlank(), context).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Value);
|
||||
|
||||
await this.AssignAsync(variablePath, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
|
||||
await this.AssignAsync(this.Model.Variable?.Path, expressionResult.Value.ToFormula(), context).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
|
||||
internal sealed class AgentMessage : MessageFunction
|
||||
{
|
||||
public const string FunctionName = nameof(AgentMessage);
|
||||
|
||||
public AgentMessage() : base(FunctionName) { }
|
||||
|
||||
public static FormulaValue Execute(StringValue input) => Create(ChatRole.Assistant, input);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
|
||||
internal abstract class MessageFunction : ReflectionFunction
|
||||
{
|
||||
protected MessageFunction(string functionName)
|
||||
: base(functionName, FormulaType.String, FormulaType.String)
|
||||
{ }
|
||||
|
||||
protected static FormulaValue Create(ChatRole role, StringValue input) =>
|
||||
string.IsNullOrEmpty(input.Value) ?
|
||||
FormulaValue.NewBlank(RecordType.Empty()) :
|
||||
FormulaValue.NewRecordFromFields(
|
||||
new NamedValue(TypeSchema.Discriminator, nameof(ChatMessage).ToFormula()),
|
||||
new NamedValue(TypeSchema.Message.Fields.Role, FormulaValue.New(role.Value)),
|
||||
new NamedValue(
|
||||
TypeSchema.Message.Fields.Content,
|
||||
FormulaValue.NewTable(
|
||||
RecordType.Empty()
|
||||
.Add(TypeSchema.Message.Fields.ContentType, FormulaType.String)
|
||||
.Add(TypeSchema.Message.Fields.ContentValue, FormulaType.String),
|
||||
[
|
||||
FormulaValue.NewRecordFromFields(
|
||||
new NamedValue(TypeSchema.Message.Fields.ContentType, FormulaValue.New(TypeSchema.Message.ContentTypes.Text)),
|
||||
new NamedValue(TypeSchema.Message.Fields.ContentValue, input))
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.PowerFx;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
|
||||
internal static class MessageText
|
||||
{
|
||||
public const string FunctionName = nameof(MessageText);
|
||||
|
||||
public sealed class StringInput()
|
||||
: ReflectionFunction(FunctionName, FormulaType.String, FormulaType.String)
|
||||
{
|
||||
public static FormulaValue Execute(StringValue input) => input;
|
||||
}
|
||||
|
||||
public sealed class RecordInput() : ReflectionFunction(FunctionName, FormulaType.String, RecordType.Empty())
|
||||
{
|
||||
public static FormulaValue Execute(RecordValue input) => FormulaValue.New(GetTextFromRecord(input));
|
||||
}
|
||||
|
||||
public sealed class TableInput() : ReflectionFunction(FunctionName, FormulaType.String, TableType.Empty())
|
||||
{
|
||||
public static FormulaValue Execute(TableValue tableValue)
|
||||
{
|
||||
return FormulaValue.New(string.Join("\n", GetText()));
|
||||
|
||||
IEnumerable<string> GetText()
|
||||
{
|
||||
foreach (DValue<RecordValue> row in tableValue.Rows)
|
||||
{
|
||||
string text = GetTextFromRecord(row.Value);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
yield return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTextFromRecord(RecordValue recordValue)
|
||||
{
|
||||
FormulaValue textValue = recordValue.GetField(TypeSchema.Message.Fields.Text);
|
||||
|
||||
return textValue switch
|
||||
{
|
||||
StringValue stringValue => stringValue.Value.Trim(),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-26
@@ -1,38 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
|
||||
internal sealed class UserMessage : ReflectionFunction
|
||||
internal sealed class UserMessage : MessageFunction
|
||||
{
|
||||
public const string FunctionName = nameof(UserMessage);
|
||||
|
||||
public UserMessage()
|
||||
: base(FunctionName, FormulaType.String, FormulaType.String)
|
||||
{ }
|
||||
public UserMessage() : base(FunctionName) { }
|
||||
|
||||
public static FormulaValue Execute(StringValue input) =>
|
||||
string.IsNullOrEmpty(input.Value) ?
|
||||
FormulaValue.NewBlank(RecordType.Empty()) :
|
||||
FormulaValue.NewRecordFromFields(
|
||||
new NamedValue(TypeSchema.Discriminator, nameof(ChatMessage).ToFormula()),
|
||||
new NamedValue(TypeSchema.Message.Fields.Role, FormulaValue.New(ChatRole.User.Value)),
|
||||
new NamedValue(
|
||||
TypeSchema.Message.Fields.Content,
|
||||
FormulaValue.NewTable(
|
||||
RecordType.Empty()
|
||||
.Add(TypeSchema.Message.Fields.ContentType, FormulaType.String)
|
||||
.Add(TypeSchema.Message.Fields.ContentValue, FormulaType.String),
|
||||
[
|
||||
FormulaValue.NewRecordFromFields(
|
||||
new NamedValue(TypeSchema.Message.Fields.ContentType, FormulaValue.New(TypeSchema.Message.ContentTypes.Text)),
|
||||
new NamedValue(TypeSchema.Message.Fields.ContentValue, input))
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
public static FormulaValue Execute(StringValue input) => Create(ChatRole.User, input);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,11 @@ internal static class RecalcEngineFactory
|
||||
}
|
||||
|
||||
config.EnableSetFunction();
|
||||
config.AddFunction(new AgentMessage());
|
||||
config.AddFunction(new UserMessage());
|
||||
config.AddFunction(new MessageText.StringInput());
|
||||
config.AddFunction(new MessageText.RecordInput());
|
||||
config.AddFunction(new MessageText.TableInput());
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -87,9 +87,16 @@ public abstract class WorkflowAgentProvider
|
||||
/// <param name="agentVersion">An optional agent version.</param>
|
||||
/// <param name="conversationId">Optional identifier of the target conversation.</param>
|
||||
/// <param name="messages">The messages to include in the invocation.</param>
|
||||
/// <param name="inputArguments">Optional input arguments for agents that provide support.</param>
|
||||
/// <param name="cancellationToken">A token that propagates notification when operation should be canceled.</param>
|
||||
/// <returns>Asynchronous set of <see cref="AgentRunResponseUpdate"/>.</returns>
|
||||
public abstract IAsyncEnumerable<AgentRunResponseUpdate> InvokeAgentAsync(string agentId, string? agentVersion, string? conversationId, IEnumerable<ChatMessage>? messages, CancellationToken cancellationToken = default);
|
||||
public abstract IAsyncEnumerable<AgentRunResponseUpdate> InvokeAgentAsync(
|
||||
string agentId,
|
||||
string? agentVersion,
|
||||
string? conversationId,
|
||||
IEnumerable<ChatMessage>? messages,
|
||||
IDictionary<string, object?>? inputArguments,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a set of messages from a conversation.
|
||||
|
||||
+3
-1
@@ -15,6 +15,7 @@ internal abstract class AgentProvider(IConfiguration configuration)
|
||||
public const string FunctionTool = "FUNCTIONTOOL";
|
||||
public const string Marketing = "MARKETING";
|
||||
public const string MathChat = "MATHCHAT";
|
||||
public const string InputArguments = "INPUTARGUMENTS";
|
||||
}
|
||||
|
||||
public static class Settings
|
||||
@@ -31,6 +32,7 @@ internal abstract class AgentProvider(IConfiguration configuration)
|
||||
Names.FunctionTool => new FunctionToolAgentProvider(configuration),
|
||||
Names.Marketing => new MarketingAgentProvider(configuration),
|
||||
Names.MathChat => new MathChatAgentProvider(configuration),
|
||||
Names.InputArguments => new PoemAgentProvider(configuration),
|
||||
_ => new TestAgentProvider(configuration),
|
||||
};
|
||||
|
||||
@@ -40,7 +42,7 @@ internal abstract class AgentProvider(IConfiguration configuration)
|
||||
|
||||
await foreach (AgentVersion agent in this.CreateAgentsAsync(foundryEndpoint))
|
||||
{
|
||||
Console.WriteLine($"Created agent: {agent.Name}:{agent.Version})");
|
||||
Console.WriteLine($"Created agent: {agent.Name}:{agent.Version}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Azure.AI.Agents;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Shared.Foundry;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
|
||||
internal sealed class PoemAgentProvider(IConfiguration configuration) : AgentProvider(configuration)
|
||||
{
|
||||
protected override async IAsyncEnumerable<AgentVersion> CreateAgentsAsync(Uri foundryEndpoint)
|
||||
{
|
||||
AgentClient agentClient = new(foundryEndpoint, new AzureCliCredential());
|
||||
|
||||
yield return
|
||||
await agentClient.CreateAgentAsync(
|
||||
agentName: "PoemAgent",
|
||||
agentDefinition: this.DefinePoemAgent(),
|
||||
agentDescription: "Authors original poems");
|
||||
}
|
||||
|
||||
private PromptAgentDefinition DefinePoemAgent() =>
|
||||
new(this.GetSetting(Settings.FoundryModelMini))
|
||||
{
|
||||
Instructions =
|
||||
"""
|
||||
Write a one verse poem on the requested topic in the style of: {{style}}.
|
||||
""",
|
||||
StructuredInputs =
|
||||
{
|
||||
["style"] =
|
||||
new StructuredInputDefinition
|
||||
{
|
||||
IsRequired = false,
|
||||
DefaultValue = BinaryData.FromString(@"""haiku"""),
|
||||
Description = "The style of poem to write",
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+4
-3
@@ -17,11 +17,12 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("CheckSystem.yaml", "CheckSystem.json")]
|
||||
[InlineData("SendActivity.yaml", "SendActivity.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json", true)]
|
||||
[InlineData("ConversationMessages.yaml", "ConversationMessages.json")]
|
||||
[InlineData("ConversationMessages.yaml", "ConversationMessages.json", true)]
|
||||
[InlineData("InputArguments.yaml", "InputArguments.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json", true)]
|
||||
[InlineData("SendActivity.yaml", "SendActivity.json")]
|
||||
public Task ValidateCaseAsync(string workflowFileName, string testcaseFileName, bool externalConveration = false) =>
|
||||
this.RunWorkflowAsync(GetWorkflowPath(workflowFileName, isSample: false), testcaseFileName, externalConveration);
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"description": "Authors a poem in the style specified by the input argument.",
|
||||
"setup": {
|
||||
"input": {
|
||||
"type": "String",
|
||||
"value": "Why is the sky blue?"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"conversation_count": 1,
|
||||
"min_action_count": 1,
|
||||
"min_response_count": 1,
|
||||
"min_message_count": 3,
|
||||
"actions": {
|
||||
"start": [
|
||||
"invoke_poem"
|
||||
],
|
||||
"final": [
|
||||
"invoke_poem"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"min_response_count": 2,
|
||||
"max_response_count": 8,
|
||||
"min_message_count": 4,
|
||||
"max_message_count": 17,
|
||||
"max_message_count": -1,
|
||||
"actions": {
|
||||
"start": [
|
||||
],
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
kind: Workflow
|
||||
trigger:
|
||||
|
||||
kind: OnConversationStart
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
- kind: InvokeAzureAgent
|
||||
id: invoke_poem
|
||||
conversationId: =System.ConversationId
|
||||
agent:
|
||||
name: PoemAgent
|
||||
input:
|
||||
arguments:
|
||||
style: "ee cummings"
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.PowerFx.Functions;
|
||||
|
||||
public sealed class AgentMessageTests
|
||||
{
|
||||
[Fact]
|
||||
public void Construct_Function()
|
||||
{
|
||||
AgentMessage function = new();
|
||||
Assert.NotNull(function);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsBlank_ForEmptyInput()
|
||||
{
|
||||
// Arrange
|
||||
StringValue sourceValue = FormulaValue.New(string.Empty);
|
||||
|
||||
// Act
|
||||
FormulaValue result = AgentMessage.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<BlankValue>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsExpectedRecord_ForNonEmptyInput()
|
||||
{
|
||||
const string Text = "Hello";
|
||||
FormulaValue sourceValue = FormulaValue.New(Text);
|
||||
StringValue stringValue = Assert.IsType<StringValue>(sourceValue);
|
||||
|
||||
FormulaValue result = AgentMessage.Execute(stringValue);
|
||||
|
||||
RecordValue recordResult = Assert.IsType<RecordValue>(result, exactMatch: false);
|
||||
|
||||
// Discriminator
|
||||
FormulaValue discriminator = recordResult.GetField(TypeSchema.Discriminator);
|
||||
StringValue discriminatorValue = Assert.IsType<StringValue>(discriminator);
|
||||
Assert.Equal(nameof(ChatMessage), discriminatorValue.Value);
|
||||
|
||||
// Role
|
||||
FormulaValue role = recordResult.GetField(TypeSchema.Message.Fields.Role);
|
||||
StringValue roleValue = Assert.IsType<StringValue>(role);
|
||||
Assert.Equal(ChatRole.Assistant.Value, roleValue.Value);
|
||||
|
||||
// Content table
|
||||
FormulaValue content = recordResult.GetField(TypeSchema.Message.Fields.Content);
|
||||
TableValue table = Assert.IsType<TableValue>(content, exactMatch: false);
|
||||
|
||||
List<RecordValue> rows = table.Rows.Select(value => value.Value).ToList();
|
||||
Assert.Single(rows);
|
||||
|
||||
StringValue contentType = Assert.IsType<StringValue>(rows[0].GetField(TypeSchema.Message.Fields.ContentType));
|
||||
Assert.Equal(TypeSchema.Message.ContentTypes.Text, contentType.Value);
|
||||
|
||||
StringValue contentValue = Assert.IsType<StringValue>(rows[0].GetField(TypeSchema.Message.Fields.ContentValue));
|
||||
Assert.Equal(Text, contentValue.Value);
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.PowerFx.Functions;
|
||||
|
||||
public sealed class MessageTextTests
|
||||
{
|
||||
[Fact]
|
||||
public void Construct_Function()
|
||||
{
|
||||
MessageText.StringInput function1 = new();
|
||||
Assert.NotNull(function1);
|
||||
|
||||
MessageText.RecordInput function2 = new();
|
||||
Assert.NotNull(function2);
|
||||
|
||||
MessageText.TableInput function3 = new();
|
||||
Assert.NotNull(function3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsEmpty_ForEmptyInput()
|
||||
{
|
||||
// Arrange
|
||||
StringValue sourceValue = FormulaValue.New(string.Empty);
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.StringInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Empty(stringResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsText_ForStringInput()
|
||||
{
|
||||
// Arrange
|
||||
StringValue sourceValue = FormulaValue.New("wowsie");
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.StringInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal(sourceValue.Value, stringResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsText_ForMessageInput()
|
||||
{
|
||||
// Arrange
|
||||
RecordValue sourceValue = new ChatMessage(ChatRole.User, "test message").ToRecord();
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.RecordInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("test message", stringResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsEmpty_ForUnknownInput()
|
||||
{
|
||||
// Arrange
|
||||
RecordValue sourceValue = FormulaValue.NewRecordFromFields(new NamedValue("Anything", FormulaValue.New(333)));
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.RecordInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Empty(stringResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsText_ForMessagesInput()
|
||||
{
|
||||
// Arrange
|
||||
TableValue sourceValue = new ChatMessage[]
|
||||
{
|
||||
new(ChatRole.User, "test message 1"),
|
||||
new(ChatRole.User, "test message 2"),
|
||||
}.ToTable();
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.TableInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("test message 1\ntest message 2", stringResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_ReturnsEmpty_ForEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
TableValue sourceValue = Array.Empty<ChatMessage>().ToTable();
|
||||
|
||||
// Act
|
||||
FormulaValue result = MessageText.TableInput.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
StringValue stringResult = Assert.IsType<StringValue>(result);
|
||||
Assert.Empty(stringResult.Value);
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -22,11 +22,10 @@ public class UserMessageTests
|
||||
public void Execute_ReturnsBlank_ForEmptyInput()
|
||||
{
|
||||
// Arrange
|
||||
FormulaValue sourceValue = FormulaValue.New(string.Empty);
|
||||
StringValue stringValue = Assert.IsType<StringValue>(sourceValue);
|
||||
StringValue sourceValue = FormulaValue.New(string.Empty);
|
||||
|
||||
// Act
|
||||
FormulaValue result = UserMessage.Execute(stringValue);
|
||||
FormulaValue result = UserMessage.Execute(sourceValue);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<BlankValue>(result);
|
||||
|
||||
@@ -119,13 +119,13 @@ trigger:
|
||||
# FACTS
|
||||
Consider this initial fact sheet:
|
||||
|
||||
{Trim(Last(Local.TaskFacts).Text)}
|
||||
{MessageText(Local.TaskFacts)}
|
||||
|
||||
|
||||
# PLAN
|
||||
Here is the plan to follow as best as possible:
|
||||
|
||||
{Last(Local.Plan).Text}
|
||||
{MessageText(Local.Plan)}
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_bwNZiM
|
||||
@@ -247,7 +247,7 @@ trigger:
|
||||
|
||||
Here is the old fact sheet:
|
||||
|
||||
{Local.TaskFacts}"
|
||||
{MessageText(Local.TaskFacts)}"
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_dsBaJU
|
||||
@@ -291,13 +291,13 @@ trigger:
|
||||
# FACTS
|
||||
Consider this initial fact sheet:
|
||||
|
||||
{Local.TaskFacts.Text}
|
||||
{MessageText(Local.TaskFacts)}
|
||||
|
||||
|
||||
# PLAN
|
||||
Here is the plan to follow as best as possible:
|
||||
|
||||
{Local.Plan.Text}
|
||||
{MessageText(Local.Plan)}
|
||||
|
||||
- kind: SetVariable
|
||||
id: setVariable_6J2snP
|
||||
@@ -356,7 +356,7 @@ trigger:
|
||||
- kind: SetVariable
|
||||
id: setVariable_XzNrdM
|
||||
variable: Local.AgentResponseText
|
||||
value: =Last(Local.AgentResponse).Text
|
||||
value: =MessageText(Local.AgentResponse)
|
||||
|
||||
- kind: ResetVariable
|
||||
id: setVariable_8eIx2A
|
||||
|
||||
@@ -35,7 +35,7 @@ trigger:
|
||||
id: check_completion
|
||||
conditions:
|
||||
|
||||
- condition: =!IsBlank(Find("CONGRATULATIONS", Upper(Last(Local.TeacherResponse).Text)))
|
||||
- condition: =!IsBlank(Find("CONGRATULATIONS", Upper(MessageText(Local.TeacherResponse))))
|
||||
id: check_turn_done
|
||||
actions:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ may be executed locally no different from any regular `Workflow` that is defined
|
||||
The difference is that the workflow definition is loaded from a YAML file instead of being defined in code:
|
||||
|
||||
```c#
|
||||
Workflow<string> workflow = DeclarativeWorkflowBuilder.Build<string>("Marketing.yaml", options);
|
||||
Workflow workflow = DeclarativeWorkflowBuilder.Build("Marketing.yaml", options);
|
||||
```
|
||||
|
||||
These example workflows may be executed by the workflow
|
||||
|
||||
Reference in New Issue
Block a user