Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HTTP retry logic and cancellation support for OpenAI services #58

Merged
merged 22 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
451ae9d
Create IDelegatingHandlerFactory for HTTP retry
lemillermicrosoft Mar 10, 2023
fd33568
Update tests for Retry
lemillermicrosoft Mar 10, 2023
0edec68
Refactor kernel retry mechanism, add new handler, and adjust logging …
lemillermicrosoft Mar 10, 2023
dae56cb
Make DefaultHttpRetryHandler and DefaultHttpRetryHandlerFactory public
lemillermicrosoft Mar 11, 2023
3d1ccf0
PR feedback on tests and samples
lemillermicrosoft Mar 11, 2023
3534768
kernel syntax examples
lemillermicrosoft Mar 11, 2023
0a6fd74
Relocate HttpRetryConfig class to Reliability namespace
lemillermicrosoft Mar 11, 2023
38a55d3
r# cleanup first, competes with format
lemillermicrosoft Mar 11, 2023
cbeb264
Improve retry logic and documentation in DefaultHttpRetryHandler
lemillermicrosoft Mar 13, 2023
2f6a82b
Add test case for retry handler with max total delay
lemillermicrosoft Mar 13, 2023
d1aba65
only drain response if we aren't returning the response as-is
lemillermicrosoft Mar 14, 2023
eafebed
Remove request cloning and content draining in DefaultHttpRetryHandler
lemillermicrosoft Mar 14, 2023
7a58ae2
Fix rebase build error
lemillermicrosoft Mar 14, 2023
23c5b39
Refactor OpenAIClientAbstract constructor to accept handler factory
lemillermicrosoft Mar 14, 2023
5c74caa
format method signature
lemillermicrosoft Mar 14, 2023
05a24f9
Fix HTTP handler factory configuration in KernelConfig and KernelBuilder
lemillermicrosoft Mar 14, 2023
cb48216
xmldocs
lemillermicrosoft Mar 14, 2023
989c786
wrap signatures
lemillermicrosoft Mar 14, 2023
ec2e746
Add time spent to error logging in DefaultHttpRetryHandler
lemillermicrosoft Mar 14, 2023
881730b
Rename SemanticKernel.Test project to SemanticKernel.UnitTests
lemillermicrosoft Mar 14, 2023
0547680
formatting
lemillermicrosoft Mar 14, 2023
cfb83b3
fix time formatting in logs
lemillermicrosoft Mar 14, 2023
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
12 changes: 6 additions & 6 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@
"detail": "Runs tasks to validate changes before checking in.",
"group": "test",
"dependsOn": [
"R# cleanup",
"Build - Semantic-Kernel",
"Test - Semantic-Kernel",
"Run - Kernel-Demo",
"R# cleanup"
"Run - Kernel-Demo"
],
"dependsOrder": "sequence"
},
Expand All @@ -103,7 +103,7 @@
"label": "Test - Semantic-Kernel",
"command": "dotnet",
"type": "process",
"args": ["test", "SemanticKernel.Test.csproj"],
"args": ["test", "SemanticKernel.UnitTests.csproj"],
"problemMatcher": "$msCompile",
"group": "test",
"presentation": {
Expand All @@ -112,7 +112,7 @@
"group": "PR-Validate"
},
"options": {
"cwd": "${workspaceFolder}/dotnet/src/SemanticKernel.Test/"
"cwd": "${workspaceFolder}/dotnet/src/SemanticKernel.UnitTests/"
}
},
{
Expand All @@ -123,7 +123,7 @@
"test",
"--collect",
"XPlat Code Coverage;Format=lcov",
"SemanticKernel.Test.csproj"
"SemanticKernel.UnitTests.csproj"
],
"problemMatcher": "$msCompile",
"group": "test",
Expand All @@ -132,7 +132,7 @@
"panel": "shared"
},
"options": {
"cwd": "${workspaceFolder}/dotnet/src/SemanticKernel.Test/"
"cwd": "${workspaceFolder}/dotnet/src/SemanticKernel.UnitTests/"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.KernelExtensions;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Reliability;
using SemanticKernel.IntegrationTests.TestSettings;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -91,6 +92,34 @@ 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 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);

// Use an invalid API key to force a 401 Unauthorized response
target.Config.AddOpenAICompletionBackend(
label: openAIConfiguration.Label,
modelId: openAIConfiguration.ModelId,
apiKey: "INVALID_KEY");

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/SemanticKernel.IntegrationTests/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 SemanticKernel.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,55 @@ namespace SemanticKernel.UnitTests.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]
Expand Down
Loading