From fb4bdce2fd9d024cee6053108f8d6a40396096b4 Mon Sep 17 00:00:00 2001
From: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Date: Fri, 15 May 2026 03:06:59 +0800
Subject: [PATCH 1/2] .NET: fix: preserve Responses logprobs field
---
.../Responses/Models/CreateResponse.cs | 6 ++
.../Responses/Models/Response.cs | 6 ++
.../OpenAIResponsesSerializationTests.cs | 60 +++++++++++++++++++
3 files changed, 72 insertions(+)
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/CreateResponse.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/CreateResponse.cs
index 9a8059bd4a2..7b9a82c5882 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/CreateResponse.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/CreateResponse.cs
@@ -127,6 +127,12 @@ internal sealed class CreateResponse
[JsonPropertyName("max_tool_calls")]
public int? MaxToolCalls { get; init; }
+ ///
+ /// Whether to return log probabilities of the output tokens.
+ ///
+ [JsonPropertyName("logprobs")]
+ public bool? Logprobs { get; init; }
+
///
/// An integer between 0 and 20 specifying the number of most likely tokens to return at each token position.
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/Response.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/Response.cs
index 3f9c50e9333..ee00c3dafc4 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/Response.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/Response.cs
@@ -228,6 +228,12 @@ internal sealed record Response
[JsonPropertyName("max_tool_calls")]
public int? MaxToolCalls { get; init; }
+ ///
+ /// Whether log probabilities were requested for the output tokens.
+ ///
+ [JsonPropertyName("logprobs")]
+ public bool? Logprobs { get; init; }
+
///
/// An integer between 0 and 20 specifying the number of most likely tokens to return at each token position.
///
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
index d3a23a382dc..558f6ca0c5f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
@@ -192,6 +192,30 @@ public void Serialize_CreateRequestWithOptions_IncludesAllFields()
Assert.Equal("value2", metadata.GetProperty("key2").GetString());
}
+ [Fact]
+ public void Deserialize_CreateRequestWithLogprobs_RoundTripsFields()
+ {
+ const string Json = """
+ {
+ "model": "gpt-4o-mini",
+ "input": "Return token probabilities.",
+ "logprobs": true,
+ "top_logprobs": 3
+ }
+ """;
+
+ CreateResponse? request = JsonSerializer.Deserialize(Json, OpenAIHostingJsonContext.Default.CreateResponse);
+ string reserializedJson = JsonSerializer.Serialize(request, OpenAIHostingJsonContext.Default.CreateResponse);
+ using var doc = JsonDocument.Parse(reserializedJson);
+ var root = doc.RootElement;
+
+ Assert.NotNull(request);
+ Assert.True(request.Logprobs);
+ Assert.Equal(3, request.TopLogprobs);
+ Assert.True(root.GetProperty("logprobs").GetBoolean());
+ Assert.Equal(3, root.GetProperty("top_logprobs").GetInt32());
+ }
+
[Fact]
public void Serialize_NullableFields_AreOmittedWhenNull()
{
@@ -535,6 +559,42 @@ public void Deserialize_MetadataResponse_HasModelParameters()
Assert.Equal(150, response.MaxOutputTokens);
}
+ [Fact]
+ public void Deserialize_ResponseWithLogprobs_RoundTripsFields()
+ {
+ const string Json = """
+ {
+ "id": "resp_logprobs",
+ "object": "response",
+ "created_at": 1715000000,
+ "model": "gpt-4o-mini",
+ "status": "completed",
+ "output": [],
+ "usage": {
+ "input_tokens": 0,
+ "input_tokens_details": { "cached_tokens": 0 },
+ "output_tokens": 0,
+ "output_tokens_details": { "reasoning_tokens": 0 },
+ "total_tokens": 0
+ },
+ "tools": [],
+ "logprobs": true,
+ "top_logprobs": 3
+ }
+ """;
+
+ Response? response = JsonSerializer.Deserialize(Json, OpenAIHostingJsonContext.Default.Response);
+ string reserializedJson = JsonSerializer.Serialize(response, OpenAIHostingJsonContext.Default.Response);
+ using var doc = JsonDocument.Parse(reserializedJson);
+ var root = doc.RootElement;
+
+ Assert.NotNull(response);
+ Assert.True(response.Logprobs);
+ Assert.Equal(3, response.TopLogprobs);
+ Assert.True(root.GetProperty("logprobs").GetBoolean());
+ Assert.Equal(3, root.GetProperty("top_logprobs").GetInt32());
+ }
+
[Fact]
public void Deserialize_ToolCallResponse_HasFunctionCall()
{
From 8ed968b480c73b9c8a246532d1e48a6a08243136 Mon Sep 17 00:00:00 2001
From: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Date: Tue, 9 Jun 2026 18:25:04 +0800
Subject: [PATCH 2/2] .NET: propagate Responses logprobs metadata
---
.../Responses/AgentResponseExtensions.cs | 1 +
.../Responses/AgentResponseUpdateExtensions.cs | 1 +
.../Responses/InMemoryResponsesService.cs | 1 +
.../OpenAIResponsesSerializationTests.cs | 16 +++++++++-------
4 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseExtensions.cs
index 2734fad4272..e1863d802cc 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseExtensions.cs
@@ -53,6 +53,7 @@ public static Response ToResponse(
Error = null,
Id = context.ResponseId,
Instructions = request.Instructions,
+ Logprobs = request.Logprobs,
MaxOutputTokens = request.MaxOutputTokens,
MaxToolCalls = request.MaxToolCalls,
Metadata = request.Metadata is IReadOnlyDictionary metadata ? new Dictionary(metadata) : [],
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseUpdateExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseUpdateExtensions.cs
index ac63237d568..7930cba8380 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseUpdateExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentResponseUpdateExtensions.cs
@@ -250,6 +250,7 @@ Response CreateResponse(ResponseStatus status = ResponseStatus.Completed, IEnume
Error = null,
Id = context.ResponseId,
Instructions = request.Instructions,
+ Logprobs = request.Logprobs,
MaxOutputTokens = request.MaxOutputTokens,
MaxToolCalls = request.MaxToolCalls,
Metadata = request.Metadata != null ? new Dictionary(request.Metadata) : [],
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs
index 6224120ac9c..c0f9c0a015d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs
@@ -366,6 +366,7 @@ private ResponseState InitializeResponse(string responseId, CreateResponse reque
Id = responseId,
IncompleteDetails = null,
Instructions = request.Instructions,
+ Logprobs = request.Logprobs,
MaxOutputTokens = request.MaxOutputTokens,
MaxToolCalls = request.MaxToolCalls,
Metadata = metadata,
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
index 558f6ca0c5f..98e3fb43541 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesSerializationTests.cs
@@ -193,7 +193,7 @@ public void Serialize_CreateRequestWithOptions_IncludesAllFields()
}
[Fact]
- public void Deserialize_CreateRequestWithLogprobs_RoundTripsFields()
+ public void Deserialize_CreateResponseWithLogprobs_RoundTripsFields()
{
const string Json = """
{
@@ -204,14 +204,15 @@ public void Deserialize_CreateRequestWithLogprobs_RoundTripsFields()
}
""";
- CreateResponse? request = JsonSerializer.Deserialize(Json, OpenAIHostingJsonContext.Default.CreateResponse);
- string reserializedJson = JsonSerializer.Serialize(request, OpenAIHostingJsonContext.Default.CreateResponse);
+ CreateResponse? createResponse = JsonSerializer.Deserialize(Json, OpenAIHostingJsonContext.Default.CreateResponse);
+ string reserializedJson = JsonSerializer.Serialize(createResponse, OpenAIHostingJsonContext.Default.CreateResponse);
using var doc = JsonDocument.Parse(reserializedJson);
var root = doc.RootElement;
- Assert.NotNull(request);
- Assert.True(request.Logprobs);
- Assert.Equal(3, request.TopLogprobs);
+ Assert.NotNull(createResponse);
+ Assert.True(createResponse.Logprobs.HasValue);
+ Assert.True(createResponse.Logprobs.Value);
+ Assert.Equal(3, createResponse.TopLogprobs);
Assert.True(root.GetProperty("logprobs").GetBoolean());
Assert.Equal(3, root.GetProperty("top_logprobs").GetInt32());
}
@@ -589,7 +590,8 @@ public void Deserialize_ResponseWithLogprobs_RoundTripsFields()
var root = doc.RootElement;
Assert.NotNull(response);
- Assert.True(response.Logprobs);
+ Assert.True(response.Logprobs.HasValue);
+ Assert.True(response.Logprobs.Value);
Assert.Equal(3, response.TopLogprobs);
Assert.True(root.GetProperty("logprobs").GetBoolean());
Assert.Equal(3, root.GetProperty("top_logprobs").GetInt32());