From d9ac9616b50b5427ae73bc5b0f9a65bc80a818eb Mon Sep 17 00:00:00 2001 From: kazuki takashima Date: Sun, 3 May 2026 08:55:33 +0900 Subject: [PATCH 1/4] Add clear error and test for missing tool call id in tool results --- .../OpenAIChatCompletionServiceTests.cs | 28 +++++++++++++++++++ .../Core/ClientCore.ChatCompletion.cs | 5 ++++ 2 files changed, 33 insertions(+) diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs index 397c9bb0e39d..a8bbdcf02175 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs @@ -916,6 +916,34 @@ public async Task FunctionResultsCanBeProvidedToLLMAsOneResultPerChatMessageAsyn Assert.Equal("2", assistantMessage2.GetProperty("tool_call_id").GetString()); } + [Fact] + public async Task FunctionResultsWithoutCallIdThrowClearExceptionAsync() + { + // Arrange + this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = new StringContent(ChatCompletionResponse) + }; + + var sut = new OpenAIChatCompletionService(modelId: "gpt-3.5-turbo", apiKey: "NOKEY", httpClient: this._httpClient); + + var chatHistory = new ChatHistory + { + new ChatMessageContent(AuthorRole.Tool, + [ + new FunctionResultContent(functionName: "GetCurrentWeather", pluginName: "MyPlugin", callId: null, result: "rainy") + ]) + }; + + var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions }; + + // Act + var exception = await Assert.ThrowsAsync(() => sut.GetChatMessageContentAsync(chatHistory, settings)); + + // Assert + Assert.Contains("missing a tool call id", exception.Message, StringComparison.OrdinalIgnoreCase); + } + [Fact] public async Task FunctionResultsCanBeProvidedToLLMAsManyResultsInOneChatMessageAsync() { diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs index 7f8dfa41a2d9..7ab40b14a366 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs @@ -759,6 +759,11 @@ private static List CreateRequestMessages(ChatMessageContent messag continue; } + if (string.IsNullOrWhiteSpace(resultContent.CallId)) + { + throw new ArgumentException("Function result message is missing a tool call id. Provide FunctionResultContent.CallId when sending tool results to OpenAI."); + } + toolMessages ??= []; if (resultContent.Result is Exception ex) From f8abc479235b954ed6926f920a8cff44800c80aa Mon Sep 17 00:00:00 2001 From: kazuki takashima Date: Sun, 3 May 2026 09:31:56 +0900 Subject: [PATCH 2/4] Re-trigger CI for merge gatekeeper From 758e41f04e847bef26e853434aa88cafa214c5a8 Mon Sep 17 00:00:00 2001 From: kazuki takashima Date: Wed, 6 May 2026 17:54:01 +0900 Subject: [PATCH 3/4] Address Copilot review: use nameof and rename test method - Use nameof(FunctionResultContent.CallId) and pass paramName to ArgumentException - Rename test to FunctionResultsWithoutCallIdThrowsClearExceptionAsync --- .../Services/OpenAIChatCompletionServiceTests.cs | 2 +- .../Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs index a8bbdcf02175..385880b21322 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Services/OpenAIChatCompletionServiceTests.cs @@ -917,7 +917,7 @@ public async Task FunctionResultsCanBeProvidedToLLMAsOneResultPerChatMessageAsyn } [Fact] - public async Task FunctionResultsWithoutCallIdThrowClearExceptionAsync() + public async Task FunctionResultsWithoutCallIdThrowsClearExceptionAsync() { // Arrange this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK) diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs index 7ab40b14a366..ff4b2a666d16 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs @@ -761,7 +761,9 @@ private static List CreateRequestMessages(ChatMessageContent messag if (string.IsNullOrWhiteSpace(resultContent.CallId)) { - throw new ArgumentException("Function result message is missing a tool call id. Provide FunctionResultContent.CallId when sending tool results to OpenAI."); + throw new ArgumentException( + $"Function result message is missing a tool call id. Provide {nameof(FunctionResultContent.CallId)} when sending tool results to OpenAI.", + nameof(FunctionResultContent.CallId)); } toolMessages ??= []; From 8dbb2db5f4d4ab525709b12b6f7bd2c67fd378ff Mon Sep 17 00:00:00 2001 From: kazuki takashima Date: Wed, 6 May 2026 18:57:07 +0900 Subject: [PATCH 4/4] Address Copilot review: use nameof(message) as paramName and capitalize ID - ArgumentException paramName now refers to the actual method parameter (message) - Capitalize 'ID' in the error message for consistency --- .../Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs index ff4b2a666d16..87fa8932e1c6 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs @@ -762,8 +762,8 @@ private static List CreateRequestMessages(ChatMessageContent messag if (string.IsNullOrWhiteSpace(resultContent.CallId)) { throw new ArgumentException( - $"Function result message is missing a tool call id. Provide {nameof(FunctionResultContent.CallId)} when sending tool results to OpenAI.", - nameof(FunctionResultContent.CallId)); + $"Function result message is missing a tool call ID. Provide {nameof(FunctionResultContent.CallId)} when sending tool results to OpenAI.", + nameof(message)); } toolMessages ??= [];