diff --git a/.github/workflows/python-test-coverage-report.yml b/.github/workflows/python-test-coverage-report.yml
index 7f0d323bb710..67c848609f6b 100644
--- a/.github/workflows/python-test-coverage-report.yml
+++ b/.github/workflows/python-test-coverage-report.yml
@@ -25,10 +25,19 @@ jobs:
merge-multiple: true
- name: Display structure of downloaded files
run: ls
+ - name: Read and set PR number
+ # Need to read the PR number from the file saved in the previous workflow
+ # because the workflow_run event does not have access to the PR number
+ # The PR number is needed to post the comment on the PR
+ run: |
+ PR_NUMBER=$(cat pr_number)
+ echo "PR number: $PR_NUMBER"
+ echo "::set-env name=PR_NUMBER::$PR_NUMBER"
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@main
with:
+ issue-number: ${{ env.PR_NUMBER }}
pytest-coverage-path: python/python-coverage.txt
title: "Python Test Coverage Report"
badge-title: "Python Test Coverage"
diff --git a/.github/workflows/python-test-coverage.yml b/.github/workflows/python-test-coverage.yml
index 7ffc9925fb34..5d67b29b6b12 100644
--- a/.github/workflows/python-test-coverage.yml
+++ b/.github/workflows/python-test-coverage.yml
@@ -21,6 +21,11 @@ jobs:
UV_PYTHON: "3.10"
steps:
- uses: actions/checkout@v4
+ # Save the PR number to a file since the workflow_run event
+ # in the coverage report workflow does not have access to it
+ - name: Save PR number
+ run: |
+ echo ${{ github.event.number }} > ./pr_number
- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
@@ -37,6 +42,7 @@ jobs:
path: |
python/python-coverage.txt
python/pytest.xml
+ python/pr_number
overwrite: true
retention-days: 1
if-no-files-found: error
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 2e07233500c9..a032edea6116 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -97,7 +97,7 @@
-
+
diff --git a/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs
new file mode 100644
index 000000000000..93ddda59d614
--- /dev/null
+++ b/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Embeddings;
+using xRetry;
+
+#pragma warning disable format // Format item can be simplified
+#pragma warning disable CA1861 // Avoid constant arrays as arguments
+
+namespace Memory;
+
+// The following example shows how to use Semantic Kernel with OpenAI.
+public class OpenAI_EmbeddingGeneration(ITestOutputHelper output) : BaseTest(output)
+{
+ [RetryFact(typeof(HttpOperationException))]
+ public async Task RunEmbeddingAsync()
+ {
+ Assert.NotNull(TestConfiguration.OpenAI.EmbeddingModelId);
+ Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
+
+ IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
+ kernelBuilder.AddOpenAITextEmbeddingGeneration(
+ modelId: TestConfiguration.OpenAI.EmbeddingModelId!,
+ apiKey: TestConfiguration.OpenAI.ApiKey!);
+ Kernel kernel = kernelBuilder.Build();
+
+ var embeddingGenerator = kernel.GetRequiredService();
+
+ // Generate embeddings for the specified text.
+ var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase."]);
+
+ Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
+ }
+}
diff --git a/dotnet/samples/Concepts/README.md b/dotnet/samples/Concepts/README.md
index d53367f532d0..6b0f28b329ca 100644
--- a/dotnet/samples/Concepts/README.md
+++ b/dotnet/samples/Concepts/README.md
@@ -123,6 +123,7 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
### Memory - Using AI [`Memory`](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/SemanticKernel.Abstractions/Memory) concepts
+- [OpenAI_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs)
- [Ollama_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs)
- [Onnx_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs)
- [HuggingFace_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/HuggingFace_EmbeddingGeneration.cs)
diff --git a/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs b/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs
index 964c55828337..67ba2d34e79a 100644
--- a/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs
+++ b/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs
@@ -430,7 +430,7 @@ private static List CreateRestApiOperationParameters(string op
return null;
}
- var mediaType = s_supportedMediaTypes.FirstOrDefault(requestBody.Content.ContainsKey) ?? throw new KernelException($"Neither of the media types of {operationId} is supported.");
+ var mediaType = GetMediaType(requestBody.Content) ?? throw new KernelException($"Neither of the media types of {operationId} is supported.");
var mediaTypeMetadata = requestBody.Content[mediaType];
var payloadProperties = GetPayloadProperties(operationId, mediaTypeMetadata.Schema);
@@ -438,11 +438,40 @@ private static List CreateRestApiOperationParameters(string op
return new RestApiPayload(mediaType, payloadProperties, requestBody.Description, mediaTypeMetadata?.Schema?.ToJsonSchema());
}
+ ///
+ /// Returns the first supported media type. If none of the media types are supported, an exception is thrown.
+ ///
+ ///
+ /// Handles the case when the media type contains additional parameters e.g. application/json; x-api-version=2.0.
+ ///
+ /// The OpenAPI request body content.
+ /// The first support ed media type.
+ ///
+ private static string? GetMediaType(IDictionary content)
+ {
+ foreach (var mediaType in s_supportedMediaTypes)
+ {
+ foreach (var key in content.Keys)
+ {
+ var keyParts = key.Split(';');
+ if (keyParts[0].Equals(mediaType, StringComparison.OrdinalIgnoreCase))
+ {
+ return key;
+ }
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Create collection of expected responses for the REST API operation for the supported media types.
+ ///
+ /// Responses from the OpenAPI endpoint.
private static IEnumerable<(string, RestApiExpectedResponse)> CreateRestApiOperationExpectedResponses(OpenApiResponses responses)
{
foreach (var response in responses)
{
- var mediaType = s_supportedMediaTypes.FirstOrDefault(response.Value.Content.ContainsKey);
+ var mediaType = GetMediaType(response.Value.Content);
if (mediaType is not null)
{
var matchingSchema = response.Value.Content[mediaType].Schema;
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserExtensionsTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserExtensionsTests.cs
index 0dde27b47306..88cb52d183e6 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserExtensionsTests.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserExtensionsTests.cs
@@ -77,4 +77,23 @@ public async Task ItCanExtractExtensionsOfAllTypesAsync(string documentName)
Assert.True(operation.Extensions.TryGetValue("x-object-extension", out var objectValue));
Assert.Equal("{\"key1\":\"value1\",\"key2\":\"value2\"}", objectValue);
}
+
+ [Theory]
+ [InlineData("documentV3_0.json")]
+ [InlineData("documentV3_1.yaml")]
+ public async Task ItCanParseMediaTypeAsync(string documentName)
+ {
+ // Arrange.
+ using var openApiDocument = ResourcePluginsProvider.LoadFromResource(documentName);
+
+ // Act.
+ var restApi = await this._sut.ParseAsync(openApiDocument);
+
+ // Assert.
+ Assert.NotNull(restApi.Operations);
+ Assert.Equal(7, restApi.Operations.Count);
+ var operation = restApi.Operations.Single(o => o.Id == "Joke");
+ Assert.NotNull(operation);
+ Assert.Equal("application/json; x-api-version=2.0", operation.Payload?.MediaType);
+ }
}
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV30Tests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV30Tests.cs
index 6a00410e24e6..8728771ac54a 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV30Tests.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV30Tests.cs
@@ -236,7 +236,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
var restApi = await this._sut.ParseAsync(this._openApiDocument);
// Assert
- Assert.Equal(6, restApi.Operations.Count);
+ Assert.Equal(7, restApi.Operations.Count);
}
[Fact]
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV31Tests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV31Tests.cs
index 79f3d8330694..6455b95dd34b 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV31Tests.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV31Tests.cs
@@ -236,7 +236,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
var restApi = await this._sut.ParseAsync(this._openApiDocument);
// Assert
- Assert.Equal(6, restApi.Operations.Count);
+ Assert.Equal(7, restApi.Operations.Count);
}
[Fact]
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs
index f05eab224b5e..2242f5032610 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs
@@ -353,7 +353,7 @@ public async Task ItShouldHandleEmptyOperationNameAsync()
var plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("fakePlugin", content, this._executionParameters);
// Assert
- Assert.Equal(6, plugin.Count());
+ Assert.Equal(7, plugin.Count());
Assert.True(plugin.TryGetFunction("GetSecretsSecretname", out var _));
}
@@ -372,7 +372,7 @@ public async Task ItShouldHandleNullOperationNameAsync()
var plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("fakePlugin", content, this._executionParameters);
// Assert
- Assert.Equal(6, plugin.Count());
+ Assert.Equal(7, plugin.Count());
Assert.True(plugin.TryGetFunction("GetSecretsSecretname", out var _));
}
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_0.json b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_0.json
index d92e8b50dde3..a2990fb86f90 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_0.json
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_0.json
@@ -212,6 +212,63 @@
}
}
},
+ "/FunPlugin/Joke": {
+ "post": {
+ "summary": "Generate a funny joke",
+ "operationId": "Joke",
+ "requestBody": {
+ "description": "Joke subject",
+ "content": {
+ "text/plain; x-api-version=2.0": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/json; x-api-version=2.0": {
+ "schema": {
+ "required": [
+ "scenario"
+ ],
+ "type": "object",
+ "properties": {
+ "scenario": {
+ "type": "string",
+ "description": "Joke subject"
+ }
+ }
+ }
+ }
+ },
+ "x-bodyName": "body"
+ },
+ "responses": {
+ "200": {
+ "description": "The OK response",
+ "content": {
+ "text/plain; x-api-version=2.0": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/json; x-api-version=2.0": {
+ "schema": {
+ "required": [
+ "scenario"
+ ],
+ "type": "object",
+ "properties": {
+ "scenario": {
+ "type": "string",
+ "description": "Joke subject"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/test-default-values/{string-parameter}": {
"put": {
"summary": "Operation to test default parameter values.",
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_1.yaml b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_1.yaml
index 54005930d0d0..8c250db741cb 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_1.yaml
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/documentV3_1.yaml
@@ -138,6 +138,38 @@ paths:
text/plain:
schema:
type: string
+ /FunPlugin/Joke:
+ post:
+ summary: Gneerate a funny joke
+ operationId: Joke
+ requestBody:
+ description: Joke subject
+ content:
+ application/json; x-api-version=2.0:
+ schema:
+ type: object
+ properties:
+ scenario:
+ type: string
+ description: Joke subject
+ text/plain; x-api-version=2.0:
+ schema:
+ type: string
+ x-bodyName: body
+ responses:
+ '200':
+ description: The OK response
+ content:
+ text/plain; x-api-version=2.0:
+ schema:
+ type: string
+ application/json; x-api-version=2.0:
+ schema:
+ type: object
+ properties:
+ scenario:
+ type: string
+ description: Joke subject
'/test-default-values/{string-parameter}':
put:
summary: Operation to test default parameter values.