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 19d602a52a5..bd3811996d5 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs @@ -383,6 +383,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/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..98e3fb43541 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,31 @@ public void Serialize_CreateRequestWithOptions_IncludesAllFields() Assert.Equal("value2", metadata.GetProperty("key2").GetString()); } + [Fact] + public void Deserialize_CreateResponseWithLogprobs_RoundTripsFields() + { + const string Json = """ + { + "model": "gpt-4o-mini", + "input": "Return token probabilities.", + "logprobs": true, + "top_logprobs": 3 + } + """; + + 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(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()); + } + [Fact] public void Serialize_NullableFields_AreOmittedWhenNull() { @@ -535,6 +560,43 @@ 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.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()); + } + [Fact] public void Deserialize_ToolCallResponse_HasFunctionCall() {