Skip to content

.Net: Use type to represent python code execution result #11931

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

Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ public async Task ItShouldExecutePythonCodeAsync()
var result = await this._sut.ExecuteCodeAsync(code);

// Assert
Assert.Contains("8", result);
Assert.Contains("Succeeded", result);
Assert.Equal("Succeeded", result.Status);
Assert.Contains("8", result.ToString());
}

[Fact(Skip = SkipReason)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text;
using System.Text.Json.Serialization;

namespace Microsoft.SemanticKernel.Plugins.Core.CodeInterpreter;

/// <summary>
/// Represents the result of a Python code execution.
/// </summary>
public sealed class SessionsPythonCodeExecutionResult
{
/// <summary>
/// Gets or sets the status of the execution (e.g., Succeeded, Failed).
/// </summary>
[JsonPropertyName("status")]
public required string Status { get; set; }

/// <summary>
/// Gets or sets the detailed result of the execution.
/// </summary>
[JsonPropertyName("result")]
public ExecutionDetails? Result { get; set; }

/// <summary>
/// Returns a string representation of the execution result.
/// </summary>
public override string ToString()
{
StringBuilder sb = new();

sb.AppendLine($"Status: {this.Status}");
if (this.Result is not null)
{
sb.AppendLine($"Result: {this.Result.ExecutionResult}");
sb.AppendLine($"Stdout: {this.Result.StdOut}");
sb.AppendLine($"Stderr: {this.Result.StdErr}");
}

return sb.ToString();
}

/// <summary>
/// Represents the detailed result of a Python code execution.
/// </summary>
public sealed class ExecutionDetails
{
/// <summary>
/// Gets or sets the standard output (stdout) of the code execution.
/// </summary>
[JsonPropertyName("stdout")]
public string? StdOut { get; set; }

/// <summary>
/// Gets or sets the standard error (stderr) of the code execution.
/// </summary>
[JsonPropertyName("stderr")]
public string? StdErr { get; set; }

/// <summary>
/// Gets or sets the result of the code execution.
/// </summary>
[JsonPropertyName("executionResult")]
public string? ExecutionResult { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Add spaces directly after \n sequences to replicate indentation.
Keep everything in a single line; the \n sequences will represent line breaks
when the string is processed or displayed.
""")]
public async Task<string> ExecuteCodeAsync(
public async Task<SessionsPythonCodeExecutionResult> ExecuteCodeAsync(
[Description("The valid Python code to execute.")] string code,
CancellationToken cancellationToken = default)
{
Expand All @@ -101,20 +101,7 @@ public async Task<string> ExecuteCodeAsync(

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

var responseContent = JsonSerializer.Deserialize<JsonElement>(await response.Content.ReadAsStringWithExceptionMappingAsync(cancellationToken).ConfigureAwait(false));

var result = responseContent.GetProperty("result");

return $"""
Status:
{responseContent.GetProperty("status").GetRawText()}
Result:
{result.GetProperty("executionResult").GetRawText()}
Stdout:
{result.GetProperty("stdout").GetRawText()}
Stderr:
{result.GetProperty("stderr").GetRawText()}
""";
return JsonSerializer.Deserialize<SessionsPythonCodeExecutionResult>(await response.Content.ReadAsStringWithExceptionMappingAsync(cancellationToken).ConfigureAwait(false))!;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel.Plugins.Core.CodeInterpreter;
using Xunit;

namespace SemanticKernel.Plugins.UnitTests.Core;

public class SessionsPythonCodeExecutionResultTests
{
[Fact]
public void ItShouldConvertResultToString()
{
// Arrange
var result = new SessionsPythonCodeExecutionResult
{
Status = "Succeeded",
Result = new SessionsPythonCodeExecutionResult.ExecutionDetails
{
StdOut = "Hello World",
StdErr = "Error",
ExecutionResult = "42"
}
};

// Act
string resultString = result.ToString();

// Assert
Assert.Contains("Status: Succeeded", resultString);
Assert.Contains("Result: 42", resultString);
Assert.Contains("Stdout: Hello World", resultString);
Assert.Contains("Stderr: Error", resultString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,18 @@ public async Task ItShouldExecuteCodeAsync()
{
Content = new StringContent(responseContent),
};
var expectedResult = """
Status:
"Succeeded"
Result:
""
Stdout:
"Hello World!\n"
Stderr:
""
""";

// Arrange
var plugin = new SessionsPythonPlugin(this._defaultSettings, this._httpClientFactory);

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

// Assert
Assert.Equal(expectedResult, result);
Assert.Equal("Succeeded", result.Status);
Assert.Equal("Hello World!\n", result.Result?.StdOut);
Assert.True(string.IsNullOrEmpty(result.Result?.StdErr));
Assert.True(string.IsNullOrEmpty(result.Result?.ExecutionResult));
}

[Theory]
Expand Down
Loading