Skip to content

Commit fa2d52f

Browse files
.Net: Use type to represent python code execution result (#11931)
### Motivation, Context and Description This PR introduces a new type, `SessionsPythonCodeExecutionResult`, to represent the Python code execution result and refactors the `SessionsPythonPlugin.ExecuteCodeAsync` method to return this type instead of a string. This should allow access to the result in a structured way for scenarios where the result needs to be processed further, for example, in the auto function invocation filters. At the same time, the type overloads the `ToString()` method to be used in scenarios where the result needs to be a string, for example when the function is invoked as part of rendering a prompt template. Contributes to: #10070
1 parent 38caa2c commit fa2d52f

File tree

5 files changed

+109
-28
lines changed

5 files changed

+109
-28
lines changed

dotnet/src/IntegrationTests/Plugins/Core/SessionsPythonPluginTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ public async Task ItShouldExecutePythonCodeAsync()
113113
var result = await this._sut.ExecuteCodeAsync(code);
114114

115115
// Assert
116-
Assert.Contains("8", result);
117-
Assert.Contains("Succeeded", result);
116+
Assert.Equal("Succeeded", result.Status);
117+
Assert.Contains("8", result.ToString());
118118
}
119119

120120
[Fact(Skip = SkipReason)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text;
4+
using System.Text.Json.Serialization;
5+
6+
namespace Microsoft.SemanticKernel.Plugins.Core.CodeInterpreter;
7+
8+
/// <summary>
9+
/// Represents the result of a Python code execution.
10+
/// </summary>
11+
public sealed class SessionsPythonCodeExecutionResult
12+
{
13+
/// <summary>
14+
/// Gets or sets the status of the execution (e.g., Succeeded, Failed).
15+
/// </summary>
16+
[JsonPropertyName("status")]
17+
public required string Status { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the detailed result of the execution.
21+
/// </summary>
22+
[JsonPropertyName("result")]
23+
public ExecutionDetails? Result { get; set; }
24+
25+
/// <summary>
26+
/// Returns a string representation of the execution result.
27+
/// </summary>
28+
public override string ToString()
29+
{
30+
StringBuilder sb = new();
31+
32+
sb.AppendLine($"Status: {this.Status}");
33+
if (this.Result is not null)
34+
{
35+
sb.AppendLine($"Result: {this.Result.ExecutionResult}");
36+
sb.AppendLine($"Stdout: {this.Result.StdOut}");
37+
sb.AppendLine($"Stderr: {this.Result.StdErr}");
38+
}
39+
40+
return sb.ToString();
41+
}
42+
43+
/// <summary>
44+
/// Represents the detailed result of a Python code execution.
45+
/// </summary>
46+
public sealed class ExecutionDetails
47+
{
48+
/// <summary>
49+
/// Gets or sets the standard output (stdout) of the code execution.
50+
/// </summary>
51+
[JsonPropertyName("stdout")]
52+
public string? StdOut { get; set; }
53+
54+
/// <summary>
55+
/// Gets or sets the standard error (stderr) of the code execution.
56+
/// </summary>
57+
[JsonPropertyName("stderr")]
58+
public string? StdErr { get; set; }
59+
60+
/// <summary>
61+
/// Gets or sets the result of the code execution.
62+
/// </summary>
63+
[JsonPropertyName("executionResult")]
64+
public string? ExecutionResult { get; set; }
65+
}
66+
}

dotnet/src/Plugins/Plugins.Core/CodeInterpreter/SessionsPythonPlugin.cs

+2-15
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Add spaces directly after \n sequences to replicate indentation.
8080
Keep everything in a single line; the \n sequences will represent line breaks
8181
when the string is processed or displayed.
8282
""")]
83-
public async Task<string> ExecuteCodeAsync(
83+
public async Task<SessionsPythonCodeExecutionResult> ExecuteCodeAsync(
8484
[Description("The valid Python code to execute.")] string code,
8585
CancellationToken cancellationToken = default)
8686
{
@@ -101,20 +101,7 @@ public async Task<string> ExecuteCodeAsync(
101101

102102
using var response = await this.SendAsync(httpClient, HttpMethod.Post, "executions", cancellationToken, content).ConfigureAwait(false);
103103

104-
var responseContent = JsonSerializer.Deserialize<JsonElement>(await response.Content.ReadAsStringWithExceptionMappingAsync(cancellationToken).ConfigureAwait(false));
105-
106-
var result = responseContent.GetProperty("result");
107-
108-
return $"""
109-
Status:
110-
{responseContent.GetProperty("status").GetRawText()}
111-
Result:
112-
{result.GetProperty("executionResult").GetRawText()}
113-
Stdout:
114-
{result.GetProperty("stdout").GetRawText()}
115-
Stderr:
116-
{result.GetProperty("stderr").GetRawText()}
117-
""";
104+
return JsonSerializer.Deserialize<SessionsPythonCodeExecutionResult>(await response.Content.ReadAsStringWithExceptionMappingAsync(cancellationToken).ConfigureAwait(false))!;
118105
}
119106

120107
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel.Plugins.Core.CodeInterpreter;
4+
using Xunit;
5+
6+
namespace SemanticKernel.Plugins.UnitTests.Core;
7+
8+
public class SessionsPythonCodeExecutionResultTests
9+
{
10+
[Fact]
11+
public void ItShouldConvertResultToString()
12+
{
13+
// Arrange
14+
var result = new SessionsPythonCodeExecutionResult
15+
{
16+
Status = "Succeeded",
17+
Result = new SessionsPythonCodeExecutionResult.ExecutionDetails
18+
{
19+
StdOut = "Hello World",
20+
StdErr = "Error",
21+
ExecutionResult = "42"
22+
}
23+
};
24+
25+
// Act
26+
string resultString = result.ToString();
27+
28+
// Assert
29+
Assert.Contains("Status: Succeeded", resultString);
30+
Assert.Contains("Result: 42", resultString);
31+
Assert.Contains("Stdout: Hello World", resultString);
32+
Assert.Contains("Stderr: Error", resultString);
33+
}
34+
}

dotnet/src/Plugins/Plugins.UnitTests/Core/SessionsPythonPluginTests.cs

+5-11
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,18 @@ public async Task ItShouldExecuteCodeAsync()
7272
{
7373
Content = new StringContent(responseContent),
7474
};
75-
var expectedResult = """
76-
Status:
77-
"Succeeded"
78-
Result:
79-
""
80-
Stdout:
81-
"Hello World!\n"
82-
Stderr:
83-
""
84-
""";
75+
8576
// Arrange
8677
var plugin = new SessionsPythonPlugin(this._defaultSettings, this._httpClientFactory);
8778

8879
// Act
8980
var result = await plugin.ExecuteCodeAsync("print('hello world')");
9081

9182
// Assert
92-
Assert.Equal(expectedResult, result);
83+
Assert.Equal("Succeeded", result.Status);
84+
Assert.Equal("Hello World!\n", result.Result?.StdOut);
85+
Assert.True(string.IsNullOrEmpty(result.Result?.StdErr));
86+
Assert.True(string.IsNullOrEmpty(result.Result?.ExecutionResult));
9387
}
9488

9589
[Theory]

0 commit comments

Comments
 (0)