mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [Feature Branch] Durable Task extension integration tests (#2017)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.DurableTask.Client.Entities;
|
||||
using Microsoft.DurableTask.Entities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenAI;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for scenarios where an external client interacts with Durable Task Agents.
|
||||
/// </summary>
|
||||
[Collection("Sequential")]
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class AgentEntityTests(ITestOutputHelper outputHelper) : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan s_defaultTimeout = Debugger.IsAttached
|
||||
? TimeSpan.FromMinutes(5)
|
||||
: TimeSpan.FromSeconds(30);
|
||||
|
||||
private static readonly IConfiguration s_configuration =
|
||||
new ConfigurationBuilder()
|
||||
.AddUserSecrets(Assembly.GetExecutingAssembly())
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
private readonly ITestOutputHelper _outputHelper = outputHelper;
|
||||
private readonly CancellationTokenSource _cts = new(delay: s_defaultTimeout);
|
||||
|
||||
private CancellationToken TestTimeoutToken => this._cts.Token;
|
||||
|
||||
public void Dispose() => this._cts.Dispose();
|
||||
|
||||
[Fact]
|
||||
public async Task EntityNamePrefixAsync()
|
||||
{
|
||||
// Setup
|
||||
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "TestAgent",
|
||||
instructions: "You are a helpful assistant that always responds with a friendly greeting."
|
||||
);
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start([simpleAgent], this._outputHelper);
|
||||
|
||||
// A proxy agent is needed to call the hosted test agent
|
||||
AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
|
||||
AgentThread thread = simpleAgentProxy.GetNewThread();
|
||||
|
||||
DurableTaskClient client = testHelper.GetClient();
|
||||
|
||||
AgentSessionId sessionId = thread.GetService<AgentSessionId>();
|
||||
EntityInstanceId expectedEntityId = new($"dafx-{simpleAgent.Name}", sessionId.Key);
|
||||
|
||||
EntityMetadata? entity = await client.Entities.GetEntityAsync(expectedEntityId, false, this.TestTimeoutToken);
|
||||
|
||||
Assert.Null(entity);
|
||||
|
||||
// Act: send a prompt to the agent
|
||||
await simpleAgentProxy.RunAsync(
|
||||
message: "Hello!",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Assert: verify the agent state was stored with the correct entity name prefix
|
||||
entity = await client.Entities.GetEntityAsync(expectedEntityId, false, this.TestTimeoutToken);
|
||||
|
||||
Assert.NotNull(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OpenAI;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for scenarios where an external client interacts with Durable Task Agents.
|
||||
/// </summary>
|
||||
[Collection("Sequential")]
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class ExternalClientTests(ITestOutputHelper outputHelper) : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan s_defaultTimeout = Debugger.IsAttached
|
||||
? TimeSpan.FromMinutes(5)
|
||||
: TimeSpan.FromSeconds(30);
|
||||
|
||||
private static readonly IConfiguration s_configuration =
|
||||
new ConfigurationBuilder()
|
||||
.AddUserSecrets(Assembly.GetExecutingAssembly())
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
private readonly ITestOutputHelper _outputHelper = outputHelper;
|
||||
private readonly CancellationTokenSource _cts = new(delay: s_defaultTimeout);
|
||||
|
||||
private CancellationToken TestTimeoutToken => this._cts.Token;
|
||||
|
||||
public void Dispose() => this._cts.Dispose();
|
||||
|
||||
[Fact]
|
||||
public async Task SimplePromptAsync()
|
||||
{
|
||||
// Setup
|
||||
AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
instructions: "You are a helpful assistant that always responds with a friendly greeting.",
|
||||
name: "TestAgent");
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start([simpleAgent], this._outputHelper);
|
||||
|
||||
// A proxy agent is needed to call the hosted test agent
|
||||
AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
|
||||
// Act: send a prompt to the agent and wait for a response
|
||||
AgentThread thread = simpleAgentProxy.GetNewThread();
|
||||
await simpleAgentProxy.RunAsync(
|
||||
message: "Hello!",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
AgentRunResponse response = await simpleAgentProxy.RunAsync(
|
||||
message: "Repeat what you just said but say it like a pirate",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Assert: verify the agent responded appropriately
|
||||
// We can't predict the exact response, but we can check that there is one response
|
||||
Assert.NotNull(response);
|
||||
Assert.NotEmpty(response.Text);
|
||||
|
||||
// Assert: verify the expected log entries were created in the expected category
|
||||
IReadOnlyCollection<LogEntry> logs = testHelper.GetLogs();
|
||||
Assert.NotEmpty(logs);
|
||||
List<LogEntry> agentLogs = [.. logs.Where(log => log.Category.Contains(simpleAgent.Name!)).ToList()];
|
||||
Assert.NotEmpty(agentLogs);
|
||||
Assert.Contains(agentLogs, log => log.EventId.Name == "LogAgentRequest" && log.Message.Contains("Hello!"));
|
||||
Assert.Contains(agentLogs, log => log.EventId.Name == "LogAgentResponse");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallFunctionToolsAsync()
|
||||
{
|
||||
int weatherToolInvocationCount = 0;
|
||||
int packingListToolInvocationCount = 0;
|
||||
|
||||
string GetWeather(string location)
|
||||
{
|
||||
weatherToolInvocationCount++;
|
||||
return $"The weather in {location} is sunny with a high of 75°F and a low of 55°F.";
|
||||
}
|
||||
|
||||
string SuggestPackingList(string weather, bool isSunny)
|
||||
{
|
||||
packingListToolInvocationCount++;
|
||||
return isSunny ? "Pack sunglasses and sunscreen." : "Pack a raincoat and umbrella.";
|
||||
}
|
||||
|
||||
AIAgent tripPlanningAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
instructions: "You are a trip planning assistant. Use the weather tool and packing list tool as needed.",
|
||||
name: "TripPlanningAgent",
|
||||
description: "An agent to help plan your day trips",
|
||||
tools: [AIFunctionFactory.Create(GetWeather), AIFunctionFactory.Create(SuggestPackingList)]
|
||||
);
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start([tripPlanningAgent], this._outputHelper);
|
||||
AIAgent tripPlanningAgentProxy = tripPlanningAgent.AsDurableAgentProxy(testHelper.Services);
|
||||
|
||||
// Act: send a prompt to the agent
|
||||
AgentRunResponse response = await tripPlanningAgentProxy.RunAsync(
|
||||
message: "Help me figure out what to pack for my Seattle trip next Sunday",
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Assert: verify the agent responded appropriately
|
||||
// We can't predict the exact response, but we can check that there is one response
|
||||
Assert.NotNull(response);
|
||||
Assert.NotEmpty(response.Text);
|
||||
|
||||
// Assert: verify the expected log entries were created in the expected category
|
||||
IReadOnlyCollection<LogEntry> logs = testHelper.GetLogs();
|
||||
Assert.NotEmpty(logs);
|
||||
|
||||
List<LogEntry> agentLogs = [.. logs.Where(log => log.Category.Contains(tripPlanningAgent.Name!)).ToList()];
|
||||
Assert.NotEmpty(agentLogs);
|
||||
Assert.Contains(agentLogs, log => log.EventId.Name == "LogAgentRequest" && log.Message.Contains("Seattle trip"));
|
||||
Assert.Contains(agentLogs, log => log.EventId.Name == "LogAgentResponse");
|
||||
|
||||
// Assert: verify the tools were called
|
||||
Assert.Equal(1, weatherToolInvocationCount);
|
||||
Assert.Equal(1, packingListToolInvocationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallLongRunningFunctionToolsAsync()
|
||||
{
|
||||
[Description("Starts a greeting workflow and returns the workflow instance ID")]
|
||||
string StartWorkflowTool(string name)
|
||||
{
|
||||
return DurableAgentContext.Current.ScheduleNewOrchestration(nameof(RunWorkflowAsync), input: name);
|
||||
}
|
||||
|
||||
[Description("Gets the current status of a previously started workflow. A null response means the workflow has not started yet.")]
|
||||
static async Task<OrchestrationMetadata?> GetWorkflowStatusToolAsync(string instanceId)
|
||||
{
|
||||
OrchestrationMetadata? status = await DurableAgentContext.Current.GetOrchestrationStatusAsync(
|
||||
instanceId,
|
||||
includeDetails: true);
|
||||
if (status == null)
|
||||
{
|
||||
// If the status is not found, wait a bit before returning null to give the workflow time to start
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
async Task<string> RunWorkflowAsync(TaskOrchestrationContext context, string name)
|
||||
{
|
||||
// 1. Get agent and create a session
|
||||
DurableAIAgent agent = context.GetAgent("SimpleAgent");
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
// 2. Call an agent and tell it my name
|
||||
await agent.RunAsync($"My name is {name}.", thread);
|
||||
|
||||
// 3. Call the agent again with the same thread (ask it to tell me my name)
|
||||
AgentRunResponse response = await agent.RunAsync("What is my name?", thread);
|
||||
|
||||
return response.Text;
|
||||
}
|
||||
|
||||
using TestHelper testHelper = TestHelper.Start(
|
||||
this._outputHelper,
|
||||
configureAgents: agents =>
|
||||
{
|
||||
// This is the agent that will be used to start the workflow
|
||||
agents.AddAIAgentFactory(
|
||||
"WorkflowAgent",
|
||||
sp => TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "WorkflowAgent",
|
||||
instructions: "You can start greeting workflows and check their status.",
|
||||
services: sp,
|
||||
tools: [
|
||||
AIFunctionFactory.Create(StartWorkflowTool),
|
||||
AIFunctionFactory.Create(GetWorkflowStatusToolAsync)
|
||||
]));
|
||||
|
||||
// This is the agent that will be called by the workflow
|
||||
agents.AddAIAgent(TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
|
||||
name: "SimpleAgent",
|
||||
instructions: "You are a simple assistant."
|
||||
));
|
||||
},
|
||||
durableTaskRegistry: registry => registry.AddOrchestratorFunc<string, string>(nameof(RunWorkflowAsync), RunWorkflowAsync));
|
||||
|
||||
AIAgent workflowManagerAgentProxy = testHelper.Services.GetDurableAgentProxy("WorkflowAgent");
|
||||
|
||||
// Act: send a prompt to the agent
|
||||
AgentThread thread = workflowManagerAgentProxy.GetNewThread();
|
||||
await workflowManagerAgentProxy.RunAsync(
|
||||
message: "Start a greeting workflow for \"John Doe\".",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Act: prompt it again to wait for the workflow to complete
|
||||
AgentRunResponse response = await workflowManagerAgentProxy.RunAsync(
|
||||
message: "Wait for the workflow to complete and tell me the result.",
|
||||
thread,
|
||||
cancellationToken: this.TestTimeoutToken);
|
||||
|
||||
// Assert: verify the agent responded appropriately
|
||||
// We can't predict the exact response, but we can check that there is one response
|
||||
Assert.NotNull(response);
|
||||
Assert.NotEmpty(response.Text);
|
||||
Assert.Contains("John Doe", response.Text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
|
||||
|
||||
internal sealed class LogEntry(
|
||||
string category,
|
||||
LogLevel level,
|
||||
EventId eventId,
|
||||
Exception? exception,
|
||||
string message,
|
||||
object? state,
|
||||
IReadOnlyList<KeyValuePair<string, object?>> contextProperties)
|
||||
{
|
||||
public string Category { get; } = category;
|
||||
|
||||
public DateTime Timestamp { get; } = DateTime.Now;
|
||||
|
||||
public EventId EventId { get; } = eventId;
|
||||
|
||||
public LogLevel LogLevel { get; } = level;
|
||||
|
||||
public Exception? Exception { get; } = exception;
|
||||
|
||||
public string Message { get; } = message;
|
||||
|
||||
public object? State { get; } = state;
|
||||
|
||||
public IReadOnlyList<KeyValuePair<string, object?>> ContextProperties { get; } = contextProperties;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string properties = this.ContextProperties.Count > 0
|
||||
? $"[{string.Join(", ", this.ContextProperties.Select(kvp => $"{kvp.Key}={kvp.Value}"))}] "
|
||||
: string.Empty;
|
||||
|
||||
string eventName = this.EventId.Name ?? string.Empty;
|
||||
string output = $"{this.Timestamp:o} [{this.Category}] {eventName} {properties}{this.Message}";
|
||||
|
||||
if (this.Exception is not null)
|
||||
{
|
||||
output += Environment.NewLine + this.Exception;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
|
||||
|
||||
internal sealed class TestLogger(string category, ITestOutputHelper output) : ILogger
|
||||
{
|
||||
private readonly string _category = category;
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
private readonly ConcurrentQueue<LogEntry> _entries = new();
|
||||
|
||||
public IReadOnlyCollection<LogEntry> GetLogs() => this._entries;
|
||||
|
||||
public void ClearLogs() => this._entries.Clear();
|
||||
|
||||
IDisposable? ILogger.BeginScope<TState>(TState state) => null;
|
||||
|
||||
bool ILogger.IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
void ILogger.Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
LogEntry entry = new(
|
||||
category: this._category,
|
||||
level: logLevel,
|
||||
eventId: eventId,
|
||||
exception: exception,
|
||||
message: formatter(state, exception),
|
||||
state: state,
|
||||
contextProperties: []);
|
||||
|
||||
this._entries.Enqueue(entry);
|
||||
|
||||
try
|
||||
{
|
||||
this._output.WriteLine(entry.ToString());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Expected when tests are shutting down
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
|
||||
|
||||
internal sealed class TestLoggerProvider(ITestOutputHelper output) : ILoggerProvider
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
private readonly ConcurrentDictionary<string, TestLogger> _loggers = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool TryGetLogs(string category, out IReadOnlyCollection<LogEntry> logs)
|
||||
{
|
||||
if (this._loggers.TryGetValue(category, out TestLogger? logger))
|
||||
{
|
||||
logs = logger.GetLogs();
|
||||
return true;
|
||||
}
|
||||
|
||||
logs = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<LogEntry> GetAllLogs()
|
||||
{
|
||||
return this._loggers.Values
|
||||
.OfType<TestLogger>()
|
||||
.SelectMany(logger => logger.GetLogs())
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (TestLogger logger in this._loggers.Values.OfType<TestLogger>())
|
||||
{
|
||||
logger.ClearLogs();
|
||||
}
|
||||
}
|
||||
|
||||
ILogger ILoggerProvider.CreateLogger(string categoryName)
|
||||
{
|
||||
return this._loggers.GetOrAdd(categoryName, _ => new TestLogger(categoryName, this._output));
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsCoreTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugCoreTargetFrameworks)</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Public packages required by integration tests -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
|
||||
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
|
||||
using Microsoft.DurableTask;
|
||||
using Microsoft.DurableTask.Client;
|
||||
using Microsoft.DurableTask.Client.AzureManaged;
|
||||
using Microsoft.DurableTask.Worker;
|
||||
using Microsoft.DurableTask.Worker.AzureManaged;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI.Chat;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests;
|
||||
|
||||
internal sealed class TestHelper : IDisposable
|
||||
{
|
||||
private readonly TestLoggerProvider _loggerProvider;
|
||||
private readonly IHost _host;
|
||||
private readonly DurableTaskClient _client;
|
||||
|
||||
// The static Start method should be used to create instances of this class.
|
||||
private TestHelper(
|
||||
TestLoggerProvider loggerProvider,
|
||||
IHost host,
|
||||
DurableTaskClient client)
|
||||
{
|
||||
this._loggerProvider = loggerProvider;
|
||||
this._host = host;
|
||||
this._client = client;
|
||||
}
|
||||
|
||||
public IServiceProvider Services => this._host.Services;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._host.Dispose();
|
||||
}
|
||||
|
||||
public bool TryGetLogs(string category, out IReadOnlyCollection<LogEntry> logs)
|
||||
=> this._loggerProvider.TryGetLogs(category, out logs);
|
||||
|
||||
public static TestHelper Start(
|
||||
AIAgent[] agents,
|
||||
ITestOutputHelper outputHelper,
|
||||
Action<DurableTaskRegistry>? durableTaskRegistry = null)
|
||||
{
|
||||
return BuildAndStartTestHelper(
|
||||
outputHelper,
|
||||
options => options.AddAIAgents(agents),
|
||||
durableTaskRegistry);
|
||||
}
|
||||
|
||||
public static TestHelper Start(
|
||||
ITestOutputHelper outputHelper,
|
||||
Action<DurableAgentsOptions> configureAgents,
|
||||
Action<DurableTaskRegistry>? durableTaskRegistry = null)
|
||||
{
|
||||
return BuildAndStartTestHelper(
|
||||
outputHelper,
|
||||
configureAgents,
|
||||
durableTaskRegistry);
|
||||
}
|
||||
|
||||
public DurableTaskClient GetClient() => this._client;
|
||||
|
||||
private static TestHelper BuildAndStartTestHelper(
|
||||
ITestOutputHelper outputHelper,
|
||||
Action<DurableAgentsOptions> configureAgents,
|
||||
Action<DurableTaskRegistry>? durableTaskRegistry)
|
||||
{
|
||||
TestLoggerProvider loggerProvider = new(outputHelper);
|
||||
|
||||
IHost host = Host.CreateDefaultBuilder()
|
||||
.ConfigureServices((ctx, services) =>
|
||||
{
|
||||
string dtsConnectionString = GetDurableTaskSchedulerConnectionString(ctx.Configuration);
|
||||
|
||||
// Register durable agents using the caller-supplied registration action and
|
||||
// apply the default chat client for agents that don't supply one themselves.
|
||||
services.ConfigureDurableAgents(
|
||||
options => configureAgents(options),
|
||||
workerBuilder: builder =>
|
||||
{
|
||||
builder.UseDurableTaskScheduler(dtsConnectionString);
|
||||
if (durableTaskRegistry != null)
|
||||
{
|
||||
builder.AddTasks(durableTaskRegistry);
|
||||
}
|
||||
},
|
||||
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
|
||||
})
|
||||
.ConfigureLogging((_, logging) =>
|
||||
{
|
||||
logging.AddProvider(loggerProvider);
|
||||
logging.SetMinimumLevel(LogLevel.Debug);
|
||||
})
|
||||
.Build();
|
||||
host.Start();
|
||||
|
||||
DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>();
|
||||
return new TestHelper(loggerProvider, host, client);
|
||||
}
|
||||
|
||||
private static string GetDurableTaskSchedulerConnectionString(IConfiguration configuration)
|
||||
{
|
||||
// The default value is for local development using the Durable Task Scheduler emulator.
|
||||
return configuration["DURABLE_TASK_SCHEDULER_CONNECTION_STRING"]
|
||||
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
|
||||
}
|
||||
|
||||
internal static ChatClient GetAzureOpenAIChatClient(IConfiguration configuration)
|
||||
{
|
||||
string azureOpenAiEndpoint = configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
?? throw new InvalidOperationException("The required AZURE_OPENAI_ENDPOINT env variable is not set.");
|
||||
string azureOpenAiDeploymentName = configuration["AZURE_OPENAI_DEPLOYMENT"]
|
||||
?? throw new InvalidOperationException("The required AZURE_OPENAI_DEPLOYMENT env variable is not set.");
|
||||
|
||||
// Check if AZURE_OPENAI_KEY is provided for token-based authentication
|
||||
string? azureOpenAiKey = configuration["AZURE_OPENAI_KEY"];
|
||||
|
||||
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
? new AzureOpenAIClient(new Uri(azureOpenAiEndpoint), new AzureKeyCredential(azureOpenAiKey))
|
||||
: new AzureOpenAIClient(new Uri(azureOpenAiEndpoint), new AzureCliCredential());
|
||||
|
||||
return client.GetChatClient(azureOpenAiDeploymentName);
|
||||
}
|
||||
|
||||
internal IReadOnlyCollection<LogEntry> GetLogs()
|
||||
{
|
||||
return this._loggerProvider.GetAllLogs();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user