Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions dotnet/src/IntegrationTest/AI/OpenAICompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using IntegrationTests.TestSettings;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Configuration;
using Microsoft.SemanticKernel.KernelExtensions;
using Microsoft.SemanticKernel.Orchestration;
using Xunit;
Expand Down Expand Up @@ -91,6 +92,35 @@ public async Task AzureOpenAITestAsync(string prompt, string expectedAnswerConta
Assert.Contains(expectedAnswerContains, actual.Result, StringComparison.InvariantCultureIgnoreCase);
}

[Theory]
[InlineData("Where is the most famous fish market in Seattle, Washington, USA?",
"Error executing action [attempt 1 of 1]. Reason: Unauthorized. Will retry after 2000ms")]
public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedOutput)
{
// Arrange
var retryConfig = new KernelConfig.HttpRetryConfig();
retryConfig.RetryableStatusCodes.Add(System.Net.HttpStatusCode.Unauthorized);
IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)).Build();

OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get<OpenAIConfiguration>();
Assert.NotNull(openAIConfiguration);

target.Config.AddOpenAICompletionBackend(
label: openAIConfiguration.Label,
modelId: openAIConfiguration.ModelId,
apiKey: "INVALID_KEY");

target.Config.SetDefaultCompletionBackend(openAIConfiguration.Label);

IDictionary<string, ISKFunction> skill = GetSkill("SummarizeSkill", target);

// Act
await target.RunAsync(prompt, skill["Summarize"]);

// Assert
Assert.Contains(expectedOutput, this._testOutputHelper.GetLogs(), StringComparison.InvariantCultureIgnoreCase);
}

private static IDictionary<string, ISKFunction> GetSkill(string skillName, IKernel target)
{
string? currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Expand Down
29 changes: 28 additions & 1 deletion dotnet/src/IntegrationTest/RedirectOutput.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.IO;
using System.Text;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace IntegrationTests;

public class RedirectOutput : TextWriter
public class RedirectOutput : TextWriter, ILogger
{
private readonly ITestOutputHelper _output;
private readonly StringBuilder _logs;

public RedirectOutput(ITestOutputHelper output)
{
this._output = output;
this._logs = new StringBuilder();
}

public override Encoding Encoding { get; } = Encoding.UTF8;

public override void WriteLine(string? value)
{
this._output.WriteLine(value);
this._logs.AppendLine(value);
}

public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
return null;
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}

public string GetLogs()
{
return this._logs.ToString();
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
var message = formatter(state, exception);
this._output?.WriteLine(message);
this._logs.AppendLine(message);
}
}
90 changes: 77 additions & 13 deletions dotnet/src/SemanticKernel.Test/Configuration/KernelConfigTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.OpenAI.Services;
using Microsoft.SemanticKernel.Configuration;
Expand All @@ -16,55 +17,118 @@ namespace SemanticKernelTests.Configuration;
public class KernelConfigTests
{
[Fact]
public void RetryMechanismIsSet()
public void HttpRetryHandlerFactoryIsSet()
{
// Arrange
var retry = new PassThroughWithoutRetry();
var retry = new NullHttpRetryHandlerFactory();
var config = new KernelConfig();

// Act
config.SetRetryMechanism(retry);
config.SetHttpRetryHandlerFactory(retry);

// Assert
Assert.Equal(retry, config.RetryMechanism);
Assert.Equal(retry, config.HttpHandlerFactory);
}

[Fact]
public void RetryMechanismIsSetWithCustomImplementation()
public void HttpRetryHandlerFactoryIsSetWithCustomImplementation()
{
// Arrange
var retry = new Mock<IRetryMechanism>();
var retry = new Mock<IDelegatingHandlerFactory>();
var config = new KernelConfig();

// Act
config.SetRetryMechanism(retry.Object);
config.SetHttpRetryHandlerFactory(retry.Object);

// Assert
Assert.Equal(retry.Object, config.RetryMechanism);
Assert.Equal(retry.Object, config.HttpHandlerFactory);
}

[Fact]
public void RetryMechanismIsSetToPassThroughWithoutRetryIfNull()
public void HttpRetryHandlerFactoryIsSetToDefaultHttpRetryHandlerFactoryIfNull()
{
// Arrange
var config = new KernelConfig();

// Act
config.SetRetryMechanism(null);
config.SetHttpRetryHandlerFactory(null);

// Assert
Assert.IsType<PassThroughWithoutRetry>(config.RetryMechanism);
Assert.IsType<DefaultHttpRetryHandlerFactory>(config.HttpHandlerFactory);
}

[Fact]
public void RetryMechanismIsSetToPassThroughWithoutRetryIfNotSet()
public void HttpRetryHandlerFactoryIsSetToDefaultHttpRetryHandlerFactoryIfNotSet()
{
// Arrange
var config = new KernelConfig();

// Act
// Assert
Assert.IsType<PassThroughWithoutRetry>(config.RetryMechanism);
Assert.IsType<DefaultHttpRetryHandlerFactory>(config.HttpHandlerFactory);
}

[Fact]
public async Task NegativeMaxRetryCountThrowsAsync()
{
// Act
await Assert.ThrowsAsync<System.ArgumentOutOfRangeException>(() =>
{
var httpRetryConfig = new KernelConfig.HttpRetryConfig() { MaxRetryCount = -1 };
return Task.CompletedTask;
});
}

[Fact]
public void SetDefaultHttpRetryConfig()
{
// Arrange
var config = new KernelConfig();
var httpRetryConfig = new KernelConfig.HttpRetryConfig() { MaxRetryCount = 1 };

// Act
config.SetDefaultHttpRetryConfig(httpRetryConfig);

// Assert
Assert.Equal(httpRetryConfig, config.DefaultHttpRetryConfig);
}

[Fact]
public void SetDefaultHttpRetryConfigToDefaultIfNotSet()
{
// Arrange
var config = new KernelConfig();

// Act
// Assert
var defaultConfig = new KernelConfig.HttpRetryConfig();
Assert.Equal(defaultConfig.MaxRetryCount, config.DefaultHttpRetryConfig.MaxRetryCount);
Assert.Equal(defaultConfig.MaxRetryDelay, config.DefaultHttpRetryConfig.MaxRetryDelay);
Assert.Equal(defaultConfig.MinRetryDelay, config.DefaultHttpRetryConfig.MinRetryDelay);
Assert.Equal(defaultConfig.MaxTotalRetryTime, config.DefaultHttpRetryConfig.MaxTotalRetryTime);
Assert.Equal(defaultConfig.UseExponentialBackoff, config.DefaultHttpRetryConfig.UseExponentialBackoff);
Assert.Equal(defaultConfig.RetryableStatusCodes, config.DefaultHttpRetryConfig.RetryableStatusCodes);
Assert.Equal(defaultConfig.RetryableExceptionTypes, config.DefaultHttpRetryConfig.RetryableExceptionTypes);
}

[Fact]
public void SetDefaultHttpRetryConfigToDefaultIfNull()
{
// Arrange
var config = new KernelConfig();

// Act
config.SetDefaultHttpRetryConfig(null);

// Assert
var defaultConfig = new KernelConfig.HttpRetryConfig();
Assert.Equal(defaultConfig.MaxRetryCount, config.DefaultHttpRetryConfig.MaxRetryCount);
Assert.Equal(defaultConfig.MaxRetryDelay, config.DefaultHttpRetryConfig.MaxRetryDelay);
Assert.Equal(defaultConfig.MinRetryDelay, config.DefaultHttpRetryConfig.MinRetryDelay);
Assert.Equal(defaultConfig.MaxTotalRetryTime, config.DefaultHttpRetryConfig.MaxTotalRetryTime);
Assert.Equal(defaultConfig.UseExponentialBackoff, config.DefaultHttpRetryConfig.UseExponentialBackoff);
Assert.Equal(defaultConfig.RetryableStatusCodes, config.DefaultHttpRetryConfig.RetryableStatusCodes);
Assert.Equal(defaultConfig.RetryableExceptionTypes, config.DefaultHttpRetryConfig.RetryableExceptionTypes);
}

[Fact]
Expand Down
Loading