From 0662eb25bc228a5c0ac650cfdb5a960554cd1ae9 Mon Sep 17 00:00:00 2001 From: artl Date: Wed, 9 Apr 2025 20:07:00 -0700 Subject: [PATCH 1/2] The OpenAI chat API does not support built-in tool calling - Remove the unsupported tool before calling - Add test --- .../OpenAIChatClient.cs | 33 +++---- .../OpenAIChatClientTests.cs | 85 +++++++++++++++++++ 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs index d9f43069490..24387aceb62 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs @@ -582,22 +582,25 @@ private static ChatCompletionOptions ToOpenAIOptions(ChatOptions? options) } } - switch (options.ToolMode) + if (result.Tools.Count > 0) { - case NoneChatToolMode: - result.ToolChoice = ChatToolChoice.CreateNoneChoice(); - break; - - case AutoChatToolMode: - case null: - result.ToolChoice = ChatToolChoice.CreateAutoChoice(); - break; - - case RequiredChatToolMode required: - result.ToolChoice = required.RequiredFunctionName is null ? - ChatToolChoice.CreateRequiredChoice() : - ChatToolChoice.CreateFunctionChoice(required.RequiredFunctionName); - break; + switch (options.ToolMode) + { + case NoneChatToolMode: + result.ToolChoice = ChatToolChoice.CreateNoneChoice(); + break; + + case AutoChatToolMode: + case null: + result.ToolChoice = ChatToolChoice.CreateAutoChoice(); + break; + + case RequiredChatToolMode required: + result.ToolChoice = required.RequiredFunctionName is null ? + ChatToolChoice.CreateRequiredChoice() : + ChatToolChoice.CreateFunctionChoice(required.RequiredFunctionName); + break; + } } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs index 78dc920f8cb..ce3e1e95946 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs @@ -773,6 +773,91 @@ public async Task FunctionCallContent_NonStreaming() Assert.Equal("fp_f85bea6784", response.AdditionalProperties[nameof(ChatCompletion.SystemFingerprint)]); } + [Fact] + public async Task UnavailableFuctionCall_NonStreaming() + { + const string Input = """ + { + "messages": [ + { + "role": "user", + "content": "What day is it?" + } + ], + "model": "gpt-4o-mini" + } + """; + + const string Output = """ + { + "id": "chatcmpl-ADydKhrSKEBWJ8gy0KCIU74rN3Hmk", + "object": "chat.completion", + "created": 1727894702, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "December 31, 2023", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 61, + "completion_tokens": 16, + "total_tokens": 77, + "prompt_tokens_details": { + "cached_tokens": 13 + }, + "completion_tokens_details": { + "reasoning_tokens": 90 + } + }, + "system_fingerprint": "fp_f85bea6784" + } + """; + + using VerbatimHttpHandler handler = new(Input, Output); + using HttpClient httpClient = new(handler); + using IChatClient client = CreateChatClient(httpClient, "gpt-4o-mini"); + + var response = await client.GetResponseAsync("What day is it?", new() + { + Tools = [new HostedWebSearchTool()], + }); + Assert.NotNull(response); + + Assert.Equal("December 31, 2023", response.Text); + Assert.Equal("gpt-4o-mini-2024-07-18", response.ModelId); + Assert.Equal(ChatRole.Assistant, response.Messages.Single().Role); + Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1_727_894_702), response.CreatedAt); + Assert.Equal(ChatFinishReason.Stop, response.FinishReason); + Assert.NotNull(response.Usage); + Assert.Equal(61, response.Usage.InputTokenCount); + Assert.Equal(16, response.Usage.OutputTokenCount); + Assert.Equal(77, response.Usage.TotalTokenCount); + + Assert.Equal(new Dictionary + { + { "InputTokenDetails.AudioTokenCount", 0 }, + { "InputTokenDetails.CachedTokenCount", 13 }, + { "OutputTokenDetails.ReasoningTokenCount", 90 }, + { "OutputTokenDetails.AudioTokenCount", 0 }, + { "OutputTokenDetails.AcceptedPredictionTokenCount", 0 }, + { "OutputTokenDetails.RejectedPredictionTokenCount", 0 }, + }, response.Usage.AdditionalCounts); + + Assert.Single(response.Messages.Single().Contents); + TextContent fcc = Assert.IsType(response.Messages.Single().Contents[0]); + + Assert.NotNull(response.AdditionalProperties); + Assert.Equal("fp_f85bea6784", response.AdditionalProperties[nameof(ChatCompletion.SystemFingerprint)]); + } + [Fact] public async Task FunctionCallContent_Streaming() { From f0a360eecc81c899fdf07a3876020b15cfd41af0 Mon Sep 17 00:00:00 2001 From: artl Date: Wed, 9 Apr 2025 20:12:18 -0700 Subject: [PATCH 2/2] Fix naming --- .../OpenAIChatClientTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs index ce3e1e95946..2d7ec89b149 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs @@ -774,7 +774,7 @@ public async Task FunctionCallContent_NonStreaming() } [Fact] - public async Task UnavailableFuctionCall_NonStreaming() + public async Task UnavailableBuiltInFunctionCall_NonStreaming() { const string Input = """ {