Skip to content

Commit b5d5beb

Browse files
SergeyMenshykhglorious-beard
authored andcommitted
.Net: Integration tests for the python code interpreter plugin (microsoft#11817)
### Motivation and Context This PR adds integration tests for the Python code interpreter plugin. They are for manual execution only at the moment, which might be revisited in the future. Additionally, it renames the secret to better reflect its purpose and describes the way to create and configure the Azure Container App Session Pool. Contributes to: microsoft#10070
1 parent deacb76 commit b5d5beb

File tree

7 files changed

+183
-4
lines changed

7 files changed

+183
-4
lines changed

dotnet/samples/Demos/CodeInterpreterPlugin/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
var apiKey = configuration["OpenAI:ApiKey"];
2222
var modelId = configuration["OpenAI:ChatModelId"];
23-
var endpoint = configuration["AzureContainerApps:Endpoint"];
23+
var endpoint = configuration["AzureContainerAppSessionPool:Endpoint"];
2424

2525
// Cached token for the Azure Container Apps service
2626
string? cachedToken = null;

dotnet/samples/Demos/CodeInterpreterPlugin/README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
This example demonstrates how to do AI Code Interpretetion using a Plugin with Azure Container Apps to execute python code in a container.
44

5+
## Create and Configure Azure Container App Session Pool
6+
7+
1. Create a new Container App Session Pool using the Azure CLI or Azure Portal.
8+
2. Specify "Python code interpreter" as the pool type.
9+
3. Add the following roles to the user that will be used to access the session pool:
10+
- The `Azure ContainerApps Session Executor` role to be able to create and manage sessions.
11+
- The `Container Apps SessionPools Contributor` role to be able to work with files.
12+
513
## Configuring Secrets
614

715
The example require credentials to access OpenAI and Azure Container Apps (ACA)
@@ -16,7 +24,7 @@ dotnet user-secrets init
1624
dotnet user-secrets set "OpenAI:ApiKey" "..."
1725
dotnet user-secrets set "OpenAI:ChatModelId" "gpt-3.5-turbo" # or any other function callable model.
1826
19-
dotnet user-secrets set "AzureContainerApps:Endpoint" " .. endpoint .. "
27+
dotnet user-secrets set "AzureContainerAppSessionPool:Endpoint" " .. endpoint .. "
2028
```
2129

2230
### To set your secrets with environment variables
@@ -29,7 +37,7 @@ OpenAI__ApiKey
2937
OpenAI__ChatModelId
3038
3139
# Azure Container Apps
32-
AzureContainerApps__Endpoint
40+
AzureContainerAppSessionPool__Endpoint
3341
```
3442

3543
### Usage Example

dotnet/src/IntegrationTests/IntegrationTests.csproj

+7-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
<PrivateAssets>all</PrivateAssets>
6666
</PackageReference>
6767
<PackageReference Include="Testcontainers.Milvus" />
68-
<PackageReference Include="Testcontainers.MongoDB"/>
68+
<PackageReference Include="Testcontainers.MongoDB" />
6969
</ItemGroup>
7070
<ItemGroup>
7171
<ProjectReference Include="..\Agents\AzureAI\Agents.AzureAI.csproj" />
@@ -109,6 +109,12 @@
109109
</ItemGroup>
110110

111111
<ItemGroup>
112+
<None Update="TestData\SessionsPythonPlugin\file_to_upload_2.txt">
113+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
114+
</None>
115+
<None Update="TestData\SessionsPythonPlugin\file_to_upload_1.txt">
116+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
117+
</None>
112118
<None Update="testsettings.development.json">
113119
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
114120
</None>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
using Microsoft.SemanticKernel.Plugins.Core.CodeInterpreter;
7+
using Microsoft.Extensions.Configuration;
8+
using SemanticKernel.IntegrationTests.TestSettings;
9+
using System.Net.Http;
10+
using Azure.Identity;
11+
using Azure.Core;
12+
using System.Collections.Generic;
13+
14+
namespace SemanticKernel.IntegrationTests.Plugins.Core;
15+
16+
public sealed class SessionsPythonPluginTests : IDisposable
17+
{
18+
private const string SkipReason = "For manual verification only";
19+
20+
private readonly SessionsPythonSettings _settings;
21+
private readonly HttpClientFactory _httpClientFactory;
22+
private readonly SessionsPythonPlugin _sut;
23+
24+
public SessionsPythonPluginTests()
25+
{
26+
var configurationRoot = new ConfigurationBuilder()
27+
.AddJsonFile(path: "testsettings.json", optional: true, reloadOnChange: true)
28+
.AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true)
29+
.AddEnvironmentVariables()
30+
.AddUserSecrets<SessionsPythonPluginTests>()
31+
.Build();
32+
33+
var _configuration = configurationRoot
34+
.GetSection("AzureContainerAppSessionPool")
35+
.Get<AzureContainerAppSessionPoolConfiguration>()!;
36+
37+
this._settings = new(sessionId: Guid.NewGuid().ToString(), endpoint: new Uri(_configuration.Endpoint))
38+
{
39+
CodeExecutionType = SessionsPythonSettings.CodeExecutionTypeSetting.Synchronous,
40+
CodeInputType = SessionsPythonSettings.CodeInputTypeSetting.Inline
41+
};
42+
43+
this._httpClientFactory = new HttpClientFactory();
44+
45+
this._sut = new SessionsPythonPlugin(this._settings, this._httpClientFactory, GetAuthTokenAsync);
46+
}
47+
48+
[Fact(Skip = SkipReason)]
49+
public async Task ItShouldUploadFileAsync()
50+
{
51+
// Act
52+
var result = await this._sut.UploadFileAsync("test_file.txt", @"TestData\SessionsPythonPlugin\file_to_upload_1.txt");
53+
54+
// Assert
55+
Assert.Equal("test_file.txt", result.Filename);
56+
Assert.Equal(322, result.Size);
57+
Assert.NotNull(result.LastModifiedTime);
58+
Assert.Equal("/mnt/data/test_file.txt", result.FullPath);
59+
}
60+
61+
[Fact(Skip = SkipReason)]
62+
public async Task ItShouldDownloadFileAsync()
63+
{
64+
// Arrange
65+
await this._sut.UploadFileAsync("test_file.txt", @"TestData\SessionsPythonPlugin\file_to_upload_1.txt");
66+
67+
// Act
68+
var fileContent = await this._sut.DownloadFileAsync("test_file.txt");
69+
70+
// Assert
71+
Assert.Equal(322, fileContent.Length);
72+
}
73+
74+
[Fact(Skip = SkipReason)]
75+
public async Task ItShouldListFilesAsync()
76+
{
77+
// Arrange
78+
await this._sut.UploadFileAsync("test_file_1.txt", @"TestData\SessionsPythonPlugin\file_to_upload_1.txt");
79+
await this._sut.UploadFileAsync("test_file_2.txt", @"TestData\SessionsPythonPlugin\file_to_upload_2.txt");
80+
81+
// Act
82+
var files = await this._sut.ListFilesAsync();
83+
84+
// Assert
85+
Assert.Equal(2, files.Count);
86+
87+
var firstFile = files[0];
88+
Assert.Equal("test_file_1.txt", firstFile.Filename);
89+
Assert.Equal(322, firstFile.Size);
90+
Assert.NotNull(firstFile.LastModifiedTime);
91+
Assert.Equal("/mnt/data/test_file_1.txt", firstFile.FullPath);
92+
93+
var secondFile = files[1];
94+
Assert.Equal("test_file_2.txt", secondFile.Filename);
95+
Assert.Equal(336, secondFile.Size);
96+
Assert.NotNull(secondFile.LastModifiedTime);
97+
Assert.Equal("/mnt/data/test_file_2.txt", secondFile.FullPath);
98+
}
99+
100+
[Fact(Skip = SkipReason)]
101+
public async Task ItShouldExecutePythonCodeAsync()
102+
{
103+
// Arrange
104+
string code = "result = 5 + 3\nprint(result)";
105+
106+
// Act
107+
var result = await this._sut.ExecuteCodeAsync(code);
108+
109+
// Assert
110+
Assert.Contains("8", result);
111+
Assert.Contains("Success", result);
112+
}
113+
114+
/// <summary>
115+
/// Acquires authentication token for the Azure Container App Session pool.
116+
/// </summary>
117+
private static async Task<string> GetAuthTokenAsync()
118+
{
119+
string resource = "https://acasessions.io/.default";
120+
121+
var credential = new AzureCliCredential();
122+
123+
AccessToken token = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext([resource])).ConfigureAwait(false);
124+
125+
return token.Token;
126+
}
127+
128+
public void Dispose()
129+
{
130+
this._httpClientFactory.Dispose();
131+
}
132+
133+
private sealed class HttpClientFactory : IHttpClientFactory, IDisposable
134+
{
135+
private readonly List<HttpClient> _httpClients = [];
136+
137+
public HttpClient CreateClient(string name)
138+
{
139+
var client = new HttpClient();
140+
this._httpClients.Add(client);
141+
return client;
142+
}
143+
144+
public void Dispose()
145+
{
146+
this._httpClients.ForEach(client => client.Dispose());
147+
}
148+
}
149+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Semantic Kernel
2+
3+
Semantic Kernel is an SDK that integrates Large Language Models (LLMs) like OpenAI, Azure OpenAI, and Hugging Face with conventional programming languages like C#, Python, and Java. Semantic Kernel achieves this by allowing you to define plugins that can be chained together in just a few lines of code.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Semantic Kernel is a software development kit (SDK) that seamlessly combines Large Language Models (LLMs) such as OpenAI, Azure OpenAI, and Hugging Face with traditional programming languages like C#, Python, and Java. It enables the creation of plugins that can be linked together with minimal code, facilitating efficient integration.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
namespace SemanticKernel.IntegrationTests.TestSettings;
6+
7+
[SuppressMessage("Performance", "CA1812:Internal class that is apparently never instantiated",
8+
Justification = "Configuration classes are instantiated through IConfiguration.")]
9+
internal sealed class AzureContainerAppSessionPoolConfiguration(string endpoint)
10+
{
11+
public string Endpoint { get; set; } = endpoint;
12+
}

0 commit comments

Comments
 (0)