mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET Workflow - Integrated updated CPS Object Model (#681)
* Checkpoint * Update workflows/DeepResearch.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Comment * Fix comment * Update package version * Fix nuget haxx * Checkpoint * Code complete * Testing * Message content workaround * Add sequential flow * Checkpoint * Integration test project * Checkpoint * Checkpoint cleanup * Complete * Update package --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
type: foundry_agent
|
||||
name: BasicAgent
|
||||
description: Basic agent for integration tests
|
||||
model:
|
||||
id: ${AzureAI:ModelDeployment}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Shared.IntegrationTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests execution of workflow created by <see cref="DeclarativeWorkflowBuilder"/>.
|
||||
/// </summary>
|
||||
[Collection("Global")]
|
||||
public sealed class DeclarativeWorkflowTest(ITestOutputHelper output, AgentFixture agentFixture) : WorkflowTest(output), IClassFixture<AgentFixture>
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("SendActivity.yaml", "SendActivity.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
|
||||
public Task Validate(string workflowFileName, string testcaseFileName) =>
|
||||
this.RunWorkflow(workflowFileName, testcaseFileName);
|
||||
|
||||
private Task RunWorkflow(string workflowFileName, string testcaseFileName)
|
||||
{
|
||||
this.Output.WriteLine($"WORKFLOW: {workflowFileName}");
|
||||
this.Output.WriteLine($"TESTCASE: {testcaseFileName}");
|
||||
|
||||
Testcase testcase = ReadTestcase(testcaseFileName);
|
||||
IConfiguration configuration = InitializeConfig();
|
||||
string workflowPath = Path.Combine("Workflows", workflowFileName);
|
||||
|
||||
this.Output.WriteLine($" {testcase.Description}");
|
||||
|
||||
return
|
||||
testcase.Setup.Input.Type switch
|
||||
{
|
||||
nameof(ChatMessage) => this.RunWorkflow<ChatMessage>(testcase, workflowPath, configuration),
|
||||
nameof(String) => this.RunWorkflow<string>(testcase, workflowPath, configuration),
|
||||
_ => throw new NotSupportedException($"Input type '{testcase.Setup.Input.Type}' is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task RunWorkflow<TInput>(
|
||||
Testcase testcase,
|
||||
string workflowPath,
|
||||
IConfiguration configuration) where TInput : notnull
|
||||
{
|
||||
this.Output.WriteLine($"INPUT: {testcase.Setup.Input.Value}");
|
||||
|
||||
AzureAIConfiguration? foundryConfig = configuration.GetSection("AzureAI").Get<AzureAIConfiguration>();
|
||||
Assert.NotNull(foundryConfig);
|
||||
|
||||
IDictionary<string, string?> agentMap = await agentFixture.GetAgentsAsync(foundryConfig);
|
||||
|
||||
IConfiguration workflowConfig =
|
||||
new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(agentMap)
|
||||
.Build();
|
||||
|
||||
DeclarativeWorkflowOptions workflowOptions =
|
||||
new(new AzureAgentProvider(foundryConfig.Endpoint, new AzureCliCredential()))
|
||||
{
|
||||
Configuration = workflowConfig,
|
||||
LoggerFactory = this.Output
|
||||
};
|
||||
Workflow<TInput> workflow = DeclarativeWorkflowBuilder.Build<TInput>(workflowPath, workflowOptions);
|
||||
|
||||
WorkflowEvents workflowEvents = await WorkflowHarness.RunAsync(workflow, (TInput)GetInput<TInput>(testcase));
|
||||
foreach (DeclarativeActionInvokeEvent actionInvokeEvent in workflowEvents.ActionInvokeEvents)
|
||||
{
|
||||
this.Output.WriteLine($"ACTION: {actionInvokeEvent.ActionId} [{actionInvokeEvent.ActionType}]");
|
||||
}
|
||||
|
||||
Assert.Equal(testcase.Validation.ActionCount, workflowEvents.ActionInvokeEvents.Count);
|
||||
Assert.Equal(testcase.Validation.ActionCount, workflowEvents.ActionCompleteEvents.Count);
|
||||
}
|
||||
|
||||
private static object GetInput<TInput>(Testcase testcase) where TInput : notnull =>
|
||||
testcase.Setup.Input.Type switch
|
||||
{
|
||||
nameof(ChatMessage) => new ChatMessage(ChatRole.User, testcase.Setup.Input.Value),
|
||||
nameof(String) => testcase.Setup.Input.Value,
|
||||
_ => throw new NotSupportedException($"Input type '{testcase.Setup.Input.Type}' is not supported."),
|
||||
};
|
||||
|
||||
private static Testcase ReadTestcase(string testcaseFileName)
|
||||
{
|
||||
using Stream testcaseStream = File.Open(Path.Combine("Testcases", testcaseFileName), FileMode.Open);
|
||||
Testcase? testcase = JsonSerializer.Deserialize<Testcase>(testcaseStream, s_jsonSerializerOptions);
|
||||
Assert.NotNull(testcase);
|
||||
return testcase;
|
||||
}
|
||||
|
||||
private static IConfigurationRoot InitializeConfig() =>
|
||||
new ConfigurationBuilder()
|
||||
.AddUserSecrets(Assembly.GetExecutingAssembly())
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = true,
|
||||
};
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.AzureAI;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
|
||||
internal static class AgentFactory
|
||||
{
|
||||
public static async Task<ImmutableDictionary<string, string?>> CreateAsync(string agentsDirectory, AzureAIConfiguration config, CancellationToken cancellationToken)
|
||||
{
|
||||
PersistentAgentsClient clientAgents = new(config.Endpoint, new AzureCliCredential());
|
||||
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.Services.AddSingleton(clientAgents);
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
AzureAIAgentFactory factory = new();
|
||||
|
||||
Dictionary<string, string?> agentMap = [];
|
||||
|
||||
foreach (string file in Directory.GetFiles(agentsDirectory, "*.yaml"))
|
||||
{
|
||||
Debug.WriteLine($"TEST AGENT: Creating - {file}");
|
||||
string agentText = File.ReadAllText(file);
|
||||
|
||||
Agent? agent = await factory.CreateAgentFromYamlAsync(agentText, new AgentCreationOptions() { Kernel = kernel }, configuration: null, cancellationToken);
|
||||
|
||||
Assert.NotNull(agent?.Name);
|
||||
|
||||
Debug.WriteLine($"TEST AGENT: {agent.Name} => {agent.Id}");
|
||||
agentMap[agent.Name] = agent.Id;
|
||||
}
|
||||
|
||||
return agentMap.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
public sealed class AgentFixture : IDisposable
|
||||
{
|
||||
private static ImmutableDictionary<string, string?>? s_agentMap;
|
||||
|
||||
internal async Task<ImmutableDictionary<string, string?>> GetAgentsAsync(AzureAIConfiguration config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
s_agentMap ??= await AgentFactory.CreateAsync("Agents", config, cancellationToken);
|
||||
|
||||
return s_agentMap;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
public sealed class TestOutputAdapter(ITestOutputHelper output) : TextWriter, ILogger, ILoggerFactory
|
||||
{
|
||||
private readonly Stack<string> _scopes = [];
|
||||
|
||||
public override Encoding Encoding { get; } = Encoding.UTF8;
|
||||
|
||||
public void AddProvider(ILoggerProvider provider) => throw new NotSupportedException();
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => this;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public override void WriteLine(object? value = null) => this.SafeWrite($"{value}");
|
||||
|
||||
public override void WriteLine(string? format, params object?[] arg) => this.SafeWrite(string.Format(format ?? string.Empty, arg));
|
||||
|
||||
public override void WriteLine(string? value) => this.SafeWrite(value ?? string.Empty);
|
||||
|
||||
public override void Write(object? value = null) => this.SafeWrite($"{value}");
|
||||
|
||||
public override void Write(char[]? buffer) => this.SafeWrite(new string(buffer));
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
this._scopes.Push($"{state}");
|
||||
return new LoggerScope(() => this._scopes.Pop());
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
string message = formatter(state, exception);
|
||||
string scope = this._scopes.Count > 0 ? $"[{this._scopes.Peek()}] " : string.Empty;
|
||||
output.WriteLine($"{scope}{message}");
|
||||
}
|
||||
|
||||
private void SafeWrite(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
output.WriteLine(value ?? string.Empty);
|
||||
}
|
||||
catch (InvalidOperationException exception) when (exception.Message == "There is no currently active test.")
|
||||
{
|
||||
// This exception is thrown when the test output is accessed outside of a test context.
|
||||
// We can ignore it since we are not in a test context.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LoggerScope(Action action) : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!this._disposed)
|
||||
{
|
||||
action.Invoke();
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
public sealed class Testcase
|
||||
{
|
||||
[JsonConstructor]
|
||||
public Testcase(
|
||||
string description,
|
||||
TestcaseSetup setup,
|
||||
TestcaseValidation validation)
|
||||
{
|
||||
this.Description = description;
|
||||
this.Setup = setup;
|
||||
this.Validation = validation;
|
||||
}
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public TestcaseSetup Setup { get; }
|
||||
|
||||
public TestcaseValidation Validation { get; }
|
||||
}
|
||||
|
||||
public sealed class TestcaseSetup
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TestcaseSetup(TestcaseInput input)
|
||||
{
|
||||
this.Input = input;
|
||||
}
|
||||
public TestcaseInput Input { get; }
|
||||
}
|
||||
|
||||
public sealed class TestcaseInput
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TestcaseInput(string type, string value)
|
||||
{
|
||||
this.Type = type;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public string Type { get; }
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
public sealed class TestcaseValidation
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TestcaseValidation(int actionCount)
|
||||
{
|
||||
this.ActionCount = actionCount;
|
||||
}
|
||||
|
||||
public int ActionCount { get; }
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
internal sealed class WorkflowEvents
|
||||
{
|
||||
public WorkflowEvents(ImmutableList<WorkflowEvent> workflowEvents)
|
||||
{
|
||||
this.Events = workflowEvents;
|
||||
this.EventCounts = workflowEvents.GroupBy(e => e.GetType()).ToImmutableDictionary(e => e.Key, e => e.Count());
|
||||
this.ActionInvokeEvents = workflowEvents.OfType<DeclarativeActionInvokeEvent>().ToImmutableList();
|
||||
this.ActionCompleteEvents = workflowEvents.OfType<DeclarativeActionCompleteEvent>().ToImmutableList();
|
||||
}
|
||||
|
||||
public ImmutableList<WorkflowEvent> Events { get; }
|
||||
public IImmutableDictionary<Type, int> EventCounts { get; }
|
||||
public ImmutableList<DeclarativeActionInvokeEvent> ActionInvokeEvents { get; }
|
||||
public ImmutableList<DeclarativeActionCompleteEvent> ActionCompleteEvents { get; private set; }
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
internal static class WorkflowHarness
|
||||
{
|
||||
public static async Task<WorkflowEvents> RunAsync<TInput>(Workflow<TInput> workflow, TInput input) where TInput : notnull
|
||||
{
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
|
||||
ImmutableList<WorkflowEvent> workflowEvents = run.WatchStreamAsync().ToEnumerable().ToImmutableList();
|
||||
return new WorkflowEvents(workflowEvents);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.ObjectModel;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for workflow tests.
|
||||
/// </summary>
|
||||
public abstract class WorkflowTest : IDisposable
|
||||
{
|
||||
public TestOutputAdapter Output { get; }
|
||||
|
||||
protected WorkflowTest(ITestOutputHelper output)
|
||||
{
|
||||
this.Output = new TestOutputAdapter(output);
|
||||
Console.SetOut(this.Output);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(isDisposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
this.Output.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FormatVariablePath(string variableName, string? scope = null) => $"{scope ?? VariableScopeNames.Topic}.{variableName}";
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn);IDE1006</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectSharedIntegrationTestCode>true</InjectSharedIntegrationTestCode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.Workflows.Declarative\Microsoft.Agents.Workflows.Declarative.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Abstractions" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Yaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Agents\*.yaml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Testcases\*.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Workflows\*.yaml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Produce a single response from an agent.",
|
||||
"setup": {
|
||||
"input": {
|
||||
"type": "String",
|
||||
"value": "Why is the sky blue?"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"action_count": 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Send an activity message .",
|
||||
"setup": {
|
||||
"input": {
|
||||
"type": "String",
|
||||
"value": "Why is the sky blue?"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"action_count": 3
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# This workflow demonstrates a conversation and message manipulation.
|
||||
#
|
||||
# Any Foundry Agent may be used to provide the response.
|
||||
# See: ./setup/QuestionAgent.yaml
|
||||
#
|
||||
kind: AdaptiveDialog
|
||||
beginDialog:
|
||||
|
||||
kind: OnActivity
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
- kind: CreateConversation
|
||||
id: conversation_create1
|
||||
conversationId: Topic.FirstConversationId
|
||||
|
||||
- kind: CreateConversation
|
||||
id: conversation_create2
|
||||
conversationId: Topic.SecondConversationId
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_conversation
|
||||
activity: |-
|
||||
Conversation 1: {Topic.FirstConversationId}
|
||||
Conversation 2: {Topic.SecondConversationId}
|
||||
|
||||
- kind: AddConversationMessage
|
||||
id: add_message
|
||||
message: Topic.MyMessage1
|
||||
role: User
|
||||
conversationId: =Topic.FirstConversationId
|
||||
content:
|
||||
- type: Text
|
||||
value: {System.LastMessage.Text}
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_message
|
||||
activity: |-
|
||||
Messsage 1: {Topic.MyMessage1}
|
||||
|
||||
- kind: CopyConversationMessages
|
||||
id: copy_messages
|
||||
conversationId: =Topic.SecondConversationId
|
||||
messages: =[Topic.MyMessage1]
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_copy
|
||||
activity: Done!
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# This workflow demonstrates a conversation and message manipulation.
|
||||
#
|
||||
# Any Foundry Agent may be used to provide the response.
|
||||
# See: ./setup/QuestionAgent.yaml
|
||||
#
|
||||
kind: AdaptiveDialog
|
||||
beginDialog:
|
||||
|
||||
kind: OnActivity
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
- kind: RetrieveConversationMessage
|
||||
id: get_message
|
||||
message: Topic.MyMessage
|
||||
conversationId: thread_T8xIzNrNcPkUkoCEGzxg80Vt
|
||||
messageId: msg_J4x6YZTDUUWNs60FOUAucldy
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_message
|
||||
activity: |-
|
||||
{Topic.MyMessage}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# This workflow demonstrates a conversation and message manipulation.
|
||||
#
|
||||
# Any Foundry Agent may be used to provide the response.
|
||||
# See: ./setup/QuestionAgent.yaml
|
||||
#
|
||||
kind: AdaptiveDialog
|
||||
beginDialog:
|
||||
|
||||
kind: OnActivity
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
- kind: RetrieveConversationMessages
|
||||
id: get_message
|
||||
messages: Topic.MyMessages
|
||||
conversationId: thread_T8xIzNrNcPkUkoCEGzxg80Vt
|
||||
|
||||
- kind: SendActivity
|
||||
id: sendActivity_message
|
||||
activity: |-
|
||||
{Topic.MyMessages}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
kind: AdaptiveDialog
|
||||
beginDialog:
|
||||
|
||||
kind: OnActivity
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
- kind: InvokeAzureAgent
|
||||
id: invoke_agent
|
||||
agent:
|
||||
name: =Env.BasicAgent
|
||||
input:
|
||||
messages: =[UserMessage(System.LastMessageText)]
|
||||
output:
|
||||
messages: Topic.Answer
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# This workflow provides the most basic example of providing a response that includes the user and environment input.
|
||||
#
|
||||
# No agent setup is required to run this workflow.
|
||||
#
|
||||
kind: AdaptiveDialog
|
||||
beginDialog:
|
||||
|
||||
kind: OnActivity
|
||||
id: workflow_test
|
||||
actions:
|
||||
|
||||
# Capture input
|
||||
- kind: SetVariable
|
||||
id: setvar_userinput
|
||||
variable: Topic.UserInput
|
||||
value: =System.LastMessage.Text
|
||||
|
||||
# Capture environment variable
|
||||
- kind: SetVariable
|
||||
id: setvar_username
|
||||
variable: Global.UserName
|
||||
value: =Env.USERNAME
|
||||
|
||||
# Respond with input
|
||||
- kind: SendActivity
|
||||
id: sendActivity_demo
|
||||
activity: |-
|
||||
Hello {Global.UserName},
|
||||
You said, "{Topic.UserInput}"
|
||||
+4
-5
@@ -168,7 +168,6 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
[InlineData(typeof(InvokeSkillAction.Builder))]
|
||||
[InlineData(typeof(LogCustomTelemetryEvent.Builder))]
|
||||
[InlineData(typeof(OAuthInput.Builder))]
|
||||
[InlineData(typeof(Question.Builder))]
|
||||
[InlineData(typeof(RecognizeIntent.Builder))]
|
||||
[InlineData(typeof(RepeatDialog.Builder))]
|
||||
[InlineData(typeof(ReplaceDialog.Builder))]
|
||||
@@ -192,7 +191,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
BeginDialog =
|
||||
new OnActivity.Builder()
|
||||
{
|
||||
Id = "workflow",
|
||||
Id = "anything",
|
||||
Actions = [unsupportedAction]
|
||||
}
|
||||
};
|
||||
@@ -226,7 +225,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
|
||||
private void AssertMessage(string message)
|
||||
{
|
||||
Assert.Contains(this.WorkflowEvents.OfType<AgentRunResponseEvent>(), e => string.Equals(e.Response.Messages[0].Text.Trim(), message, StringComparison.Ordinal));
|
||||
Assert.Contains(this.WorkflowEvents.OfType<MessageActivityEvent>(), e => string.Equals(e.Message.Trim(), message, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private Task RunWorkflow(string workflowPath) => this.RunWorkflow<string>(workflowPath, string.Empty);
|
||||
@@ -246,7 +245,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
{
|
||||
if (workflowEvent is ExecutorInvokedEvent invokeEvent)
|
||||
{
|
||||
DeclarativeExecutorResult? message = invokeEvent.Data as DeclarativeExecutorResult;
|
||||
ExecutorResultMessage? message = invokeEvent.Data as ExecutorResultMessage;
|
||||
this.Output.WriteLine($"EXEC: {invokeEvent.ExecutorId} << {message?.ExecutorId ?? "?"} [{message?.Result ?? "-"}]");
|
||||
}
|
||||
else if (workflowEvent is AgentRunResponseEvent messageEvent)
|
||||
@@ -258,7 +257,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
|
||||
private sealed class RootExecutor() :
|
||||
ReflectingExecutor<RootExecutor>(WorkflowActionVisitor.RootId("workflow")),
|
||||
ReflectingExecutor<RootExecutor>(WorkflowActionVisitor.Steps.Root("anything")),
|
||||
IMessageHandler<string>
|
||||
{
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context)
|
||||
|
||||
+19
-9
@@ -19,7 +19,7 @@ public class FormulaValueExtensionsTests
|
||||
BooleanDataValue typedValue = Assert.IsType<BooleanDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.Value, typedValue.Value);
|
||||
|
||||
BooleanValue formulaCopy = Assert.IsType<BooleanValue>(dataValue.ToFormulaValue());
|
||||
BooleanValue formulaCopy = Assert.IsType<BooleanValue>(dataValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.Value);
|
||||
|
||||
Assert.Equal(bool.TrueString, formulaValue.Format());
|
||||
@@ -35,7 +35,7 @@ public class FormulaValueExtensionsTests
|
||||
StringDataValue typedValue = Assert.IsType<StringDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.Value, typedValue.Value);
|
||||
|
||||
StringValue formulaCopy = Assert.IsType<StringValue>(typedValue.ToFormulaValue());
|
||||
StringValue formulaCopy = Assert.IsType<StringValue>(typedValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.Value);
|
||||
|
||||
Assert.Equal(formulaValue.Value, formulaValue.Format());
|
||||
@@ -51,7 +51,7 @@ public class FormulaValueExtensionsTests
|
||||
NumberDataValue typedValue = Assert.IsType<NumberDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.Value, typedValue.Value);
|
||||
|
||||
DecimalValue formulaCopy = Assert.IsType<DecimalValue>(typedValue.ToFormulaValue());
|
||||
DecimalValue formulaCopy = Assert.IsType<DecimalValue>(typedValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.Value);
|
||||
|
||||
Assert.Equal("45.3", formulaValue.Format());
|
||||
@@ -67,7 +67,7 @@ public class FormulaValueExtensionsTests
|
||||
FloatDataValue typedValue = Assert.IsType<FloatDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.Value, typedValue.Value);
|
||||
|
||||
NumberValue formulaCopy = Assert.IsType<NumberValue>(typedValue.ToFormulaValue());
|
||||
NumberValue formulaCopy = Assert.IsType<NumberValue>(typedValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.Value);
|
||||
|
||||
Assert.Equal("3.1415926535897", formulaValue.Format());
|
||||
@@ -103,7 +103,7 @@ public class FormulaValueExtensionsTests
|
||||
DateDataValue typedValue = Assert.IsType<DateDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
|
||||
|
||||
DateValue formulaCopy = Assert.IsType<DateValue>(dataValue.ToFormulaValue());
|
||||
DateValue formulaCopy = Assert.IsType<DateValue>(dataValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
|
||||
|
||||
Assert.Equal($"{timestamp}", formulaValue.Format());
|
||||
@@ -120,7 +120,7 @@ public class FormulaValueExtensionsTests
|
||||
DateTimeDataValue typedValue = Assert.IsType<DateTimeDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
|
||||
|
||||
DateTimeValue formulaCopy = Assert.IsType<DateTimeValue>(typedValue.ToFormulaValue());
|
||||
DateTimeValue formulaCopy = Assert.IsType<DateTimeValue>(typedValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
|
||||
|
||||
Assert.Equal($"{timestamp}", formulaValue.Format());
|
||||
@@ -136,7 +136,7 @@ public class FormulaValueExtensionsTests
|
||||
TimeDataValue typedValue = Assert.IsType<TimeDataValue>(dataValue);
|
||||
Assert.Equal(formulaValue.Value, typedValue.Value);
|
||||
|
||||
TimeValue formulaCopy = Assert.IsType<TimeValue>(typedValue.ToFormulaValue());
|
||||
TimeValue formulaCopy = Assert.IsType<TimeValue>(typedValue.ToFormula());
|
||||
Assert.Equal(typedValue.Value, formulaCopy.Value);
|
||||
|
||||
Assert.Equal("10:35:00", formulaValue.Format());
|
||||
@@ -158,7 +158,7 @@ public class FormulaValueExtensionsTests
|
||||
Assert.Contains(property.Key, formulaValue.Fields.Select(field => field.Name));
|
||||
}
|
||||
|
||||
RecordValue formulaCopy = Assert.IsType<RecordValue>(dataValue.ToFormulaValue(), exactMatch: false);
|
||||
RecordValue formulaCopy = Assert.IsType<RecordValue>(dataValue.ToFormula(), exactMatch: false);
|
||||
Assert.Equal(formulaCopy.Fields.Count(), dataValue.Properties.Count);
|
||||
foreach (NamedValue field in formulaCopy.Fields)
|
||||
{
|
||||
@@ -174,6 +174,16 @@ public class FormulaValueExtensionsTests
|
||||
}
|
||||
""",
|
||||
formulaValue.Format().Replace(Environment.NewLine, "\n"));
|
||||
|
||||
Dictionary<string, int> source =
|
||||
new()
|
||||
{
|
||||
["FieldA"] = 1,
|
||||
["FieldB"] = 2,
|
||||
["FieldC"] = 3
|
||||
};
|
||||
FormulaValue formula = source.ToFormula();
|
||||
Assert.IsType<RecordValue>(formula, exactMatch: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -188,7 +198,7 @@ public class FormulaValueExtensionsTests
|
||||
TableDataValue dataValue = formulaValue.ToTable();
|
||||
Assert.Equal(formulaValue.Rows.Count(), dataValue.Values.Length);
|
||||
|
||||
TableValue formulaCopy = Assert.IsType<TableValue>(dataValue.ToFormulaValue(), exactMatch: false);
|
||||
TableValue formulaCopy = Assert.IsType<TableValue>(dataValue.ToFormula(), exactMatch: false);
|
||||
Assert.Equal(formulaCopy.Rows.Count(), dataValue.Values.Length);
|
||||
|
||||
Assert.Equal(
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ public sealed class SendActivityExecutorTest(ITestOutputHelper output) : Workflo
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
Assert.Contains(events, e => e is AgentRunResponseEvent);
|
||||
Assert.Contains(events, e => e is MessageActivityEvent);
|
||||
}
|
||||
|
||||
private SendActivity CreateModel(string displayName, string activityMessage, string? summary = null)
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ using Xunit.Abstractions;
|
||||
namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
|
||||
/// <summary>
|
||||
/// Base test class for <see cref="WorkflowActionExecutor"/> implementations.
|
||||
/// Base test class for <see cref="DeclarativeActionExecutor"/> implementations.
|
||||
/// </summary>
|
||||
public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : WorkflowTest(output)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
|
||||
protected string FormatDisplayName(string name) => $"{this.GetType().Name}_{name}";
|
||||
|
||||
internal async Task<WorkflowEvent[]> Execute(WorkflowActionExecutor executor)
|
||||
internal async Task<WorkflowEvent[]> Execute(DeclarativeActionExecutor executor)
|
||||
{
|
||||
TestWorkflowExecutor workflowExecutor = new();
|
||||
WorkflowBuilder workflowBuilder = new(workflowExecutor);
|
||||
@@ -38,7 +38,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
return events;
|
||||
}
|
||||
|
||||
internal void VerifyModel(DialogAction model, WorkflowActionExecutor action)
|
||||
internal void VerifyModel(DialogAction model, DeclarativeActionExecutor action)
|
||||
{
|
||||
Assert.Equal(model.Id, action.Id);
|
||||
Assert.Equal(model, action.Model);
|
||||
@@ -80,7 +80,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
{
|
||||
public async ValueTask HandleAsync(WorkflowScopes message, IWorkflowContext context)
|
||||
{
|
||||
await context.SendMessageAsync(new DeclarativeExecutorResult(this.Id)).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(new ExecutorResultMessage(this.Id)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user