diff --git a/dotnet/src/SemanticKernel.IntegrationTests/AI/AIServiceType.cs b/dotnet/src/SemanticKernel.IntegrationTests/AI/AIServiceType.cs
new file mode 100644
index 000000000000..901903634e02
--- /dev/null
+++ b/dotnet/src/SemanticKernel.IntegrationTests/AI/AIServiceType.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace SemanticKernel.IntegrationTests.AI;
+
+///
+/// Enumeration to run integration tests for different AI services
+///
+public enum AIServiceType
+{
+ ///
+ /// Open AI service
+ ///
+ OpenAI = 0,
+
+ ///
+ /// Azure Open AI service
+ ///
+ AzureOpenAI = 1
+}
diff --git a/dotnet/src/SemanticKernel.IntegrationTests/AI/OpenAICompletionTests.cs b/dotnet/src/SemanticKernel.IntegrationTests/AI/OpenAICompletionTests.cs
index 3b9c186c472a..63351caec1f9 100644
--- a/dotnet/src/SemanticKernel.IntegrationTests/AI/OpenAICompletionTests.cs
+++ b/dotnet/src/SemanticKernel.IntegrationTests/AI/OpenAICompletionTests.cs
@@ -31,6 +31,9 @@ public OpenAICompletionTests(ITestOutputHelper output)
.AddEnvironmentVariables()
.AddUserSecrets()
.Build();
+
+ this._serviceConfiguration.Add(AIServiceType.OpenAI, this.ConfigureOpenAI);
+ this._serviceConfiguration.Add(AIServiceType.AzureOpenAI, this.ConfigureAzureOpenAI);
}
[Theory(Skip = "OpenAI will often throttle requests. This test is for manual verification.")]
@@ -40,15 +43,7 @@ public async Task OpenAITestAsync(string prompt, string expectedAnswerContains)
// Arrange
IKernel target = Kernel.Builder.WithLogger(this._logger).Build();
- OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get();
- Assert.NotNull(openAIConfiguration);
-
- target.Config.AddOpenAITextCompletion(
- serviceId: openAIConfiguration.Label,
- modelId: openAIConfiguration.ModelId,
- apiKey: openAIConfiguration.ApiKey);
-
- target.Config.SetDefaultTextCompletionService(openAIConfiguration.Label);
+ this.ConfigureOpenAI(target);
IDictionary skill = TestHelpers.GetSkill("ChatSkill", target);
@@ -66,18 +61,7 @@ public async Task AzureOpenAITestAsync(string prompt, string expectedAnswerConta
// Arrange
IKernel target = Kernel.Builder.WithLogger(this._logger).Build();
- // OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get();
- // Assert.NotNull(openAIConfiguration);
- AzureOpenAIConfiguration? azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get();
- Assert.NotNull(azureOpenAIConfiguration);
-
- target.Config.AddAzureOpenAITextCompletion(
- serviceId: azureOpenAIConfiguration.Label,
- deploymentName: azureOpenAIConfiguration.DeploymentName,
- endpoint: azureOpenAIConfiguration.Endpoint,
- apiKey: azureOpenAIConfiguration.ApiKey);
-
- target.Config.SetDefaultTextCompletionService(azureOpenAIConfiguration.Label);
+ this.ConfigureAzureOpenAI(target);
IDictionary skill = TestHelpers.GetSkill("ChatSkill", target);
@@ -118,11 +102,41 @@ public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedO
Assert.Contains(expectedOutput, this._testOutputHelper.GetLogs(), StringComparison.InvariantCultureIgnoreCase);
}
+ [Theory(Skip = "This test is for manual verification.")]
+ [InlineData("\n", AIServiceType.OpenAI)]
+ [InlineData("\r\n", AIServiceType.OpenAI)]
+ [InlineData("\n", AIServiceType.AzureOpenAI)]
+ [InlineData("\r\n", AIServiceType.AzureOpenAI)]
+ public async Task CompletionWithDifferentLineEndingsAsync(string lineEnding, AIServiceType service)
+ {
+ // Arrange
+ var prompt =
+ $"Given a json input and a request. Apply the request on the json input and return the result. " +
+ $"Put the result in between tags{lineEnding}" +
+ $"Input:{lineEnding}{{\"name\": \"John\", \"age\": 30}}{lineEnding}{lineEnding}Request:{lineEnding}name";
+
+ const string expectedAnswerContains = "John";
+
+ IKernel target = Kernel.Builder.WithLogger(this._logger).Build();
+
+ this._serviceConfiguration[service](target);
+
+ IDictionary skill = TestHelpers.GetSkill("ChatSkill", target);
+
+ // Act
+ SKContext actual = await target.RunAsync(prompt, skill["Chat"]);
+
+ // Assert
+ Assert.Contains(expectedAnswerContains, actual.Result, StringComparison.InvariantCultureIgnoreCase);
+ }
+
#region internals
private readonly XunitLogger _logger;
private readonly RedirectOutput _testOutputHelper;
+ private readonly Dictionary> _serviceConfiguration = new();
+
public void Dispose()
{
this.Dispose(true);
@@ -143,5 +157,34 @@ private void Dispose(bool disposing)
}
}
+ private void ConfigureOpenAI(IKernel kernel)
+ {
+ var openAIConfiguration = this._configuration.GetSection("OpenAI").Get();
+
+ Assert.NotNull(openAIConfiguration);
+
+ kernel.Config.AddOpenAITextCompletion(
+ serviceId: openAIConfiguration.Label,
+ modelId: openAIConfiguration.ModelId,
+ apiKey: openAIConfiguration.ApiKey);
+
+ kernel.Config.SetDefaultTextCompletionService(openAIConfiguration.Label);
+ }
+
+ private void ConfigureAzureOpenAI(IKernel kernel)
+ {
+ var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get();
+
+ Assert.NotNull(azureOpenAIConfiguration);
+
+ kernel.Config.AddAzureOpenAITextCompletion(
+ serviceId: azureOpenAIConfiguration.Label,
+ deploymentName: azureOpenAIConfiguration.DeploymentName,
+ endpoint: azureOpenAIConfiguration.Endpoint,
+ apiKey: azureOpenAIConfiguration.ApiKey);
+
+ kernel.Config.SetDefaultTextCompletionService(azureOpenAIConfiguration.Label);
+ }
+
#endregion
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Text/StringExtensionsTests.cs b/dotnet/src/SemanticKernel.UnitTests/Text/StringExtensionsTests.cs
new file mode 100644
index 000000000000..c5b7a1cd5889
--- /dev/null
+++ b/dotnet/src/SemanticKernel.UnitTests/Text/StringExtensionsTests.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.SemanticKernel.Text;
+using Xunit;
+
+namespace SemanticKernel.UnitTests.Text;
+
+///
+/// Unit tests for
+///
+public class StringExtensionsTests
+{
+ [Theory]
+ [InlineData("\r\n", "\n")]
+ [InlineData("Test string\r\n", "Test string\n")]
+ [InlineData("\r\nTest string", "\nTest string")]
+ [InlineData("\r\nTest string\r\n", "\nTest string\n")]
+ public void ItNormalizesLineEndingsCorrectly(string input, string expectedString)
+ {
+ // Act
+ input = input.NormalizeLineEndings();
+
+ // Assert
+ Assert.Equal(expectedString, input);
+ }
+}
diff --git a/dotnet/src/SemanticKernel/AI/OpenAI/Services/AzureTextCompletion.cs b/dotnet/src/SemanticKernel/AI/OpenAI/Services/AzureTextCompletion.cs
index ab6431328e0a..ad3d31614649 100644
--- a/dotnet/src/SemanticKernel/AI/OpenAI/Services/AzureTextCompletion.cs
+++ b/dotnet/src/SemanticKernel/AI/OpenAI/Services/AzureTextCompletion.cs
@@ -74,7 +74,7 @@ public async Task CompleteAsync(string text, CompleteRequestSettings req
var requestBody = Json.Serialize(new AzureTextCompletionRequest
{
- Prompt = text,
+ Prompt = text.NormalizeLineEndings(),
Temperature = requestSettings.Temperature,
TopP = requestSettings.TopP,
PresencePenalty = requestSettings.PresencePenalty,
diff --git a/dotnet/src/SemanticKernel/AI/OpenAI/Services/OpenAITextCompletion.cs b/dotnet/src/SemanticKernel/AI/OpenAI/Services/OpenAITextCompletion.cs
index 5194f1d6227b..96b7ac1f8052 100644
--- a/dotnet/src/SemanticKernel/AI/OpenAI/Services/OpenAITextCompletion.cs
+++ b/dotnet/src/SemanticKernel/AI/OpenAI/Services/OpenAITextCompletion.cs
@@ -74,7 +74,7 @@ public Task CompleteAsync(string text, CompleteRequestSettings requestSe
var requestBody = Json.Serialize(new OpenAITextCompletionRequest
{
Model = this._modelId,
- Prompt = text,
+ Prompt = text.NormalizeLineEndings(),
Temperature = requestSettings.Temperature,
TopP = requestSettings.TopP,
PresencePenalty = requestSettings.PresencePenalty,
diff --git a/dotnet/src/SemanticKernel/Text/StringExtensions.cs b/dotnet/src/SemanticKernel/Text/StringExtensions.cs
index fcf60d771127..f94636ff8d27 100644
--- a/dotnet/src/SemanticKernel/Text/StringExtensions.cs
+++ b/dotnet/src/SemanticKernel/Text/StringExtensions.cs
@@ -12,4 +12,9 @@ internal static bool EqualsIgnoreCase(this string src, string value)
{
return src.Equals(value, StringComparison.InvariantCultureIgnoreCase);
}
+
+ internal static string NormalizeLineEndings(this string src)
+ {
+ return src.Replace("\r\n", "\n", StringComparison.InvariantCultureIgnoreCase);
+ }
}