diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs index 027eea1bca..a190f4b154 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs @@ -191,7 +191,7 @@ public static async Task GetAIAgentAsync( AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, options.Name, cancellationToken).ConfigureAwait(false); var agentVersion = agentRecord.Versions.Latest; - var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true); + var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: !options.UseProvidedChatClientAsIs); return AsChatClientAgent( aiProjectClient, @@ -522,21 +522,23 @@ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion // Check function tools foreach (ResponseTool responseTool in definitionTools) { - if (requireInvocableTools && responseTool is FunctionTool functionTool) + if (responseTool is FunctionTool functionTool) { // Check if a tool with the same type and name exists in the provided tools. - // When invocable tools are required, match only AIFunction. + // Always prefer matching AIFunction when available, regardless of requireInvocableTools. var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name); - if (matchingTool is null) + if (matchingTool is not null) { - (missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}"); + (agentTools ??= []).Add(matchingTool!); + continue; } - else + + if (requireInvocableTools) { - (agentTools ??= []).Add(matchingTool!); + (missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}"); + continue; } - continue; } (agentTools ??= []).Add(responseTool.AsAITool()); diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs index 2f2e276ae9..a7b9c54aac 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs @@ -2375,6 +2375,70 @@ public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesSetting Assert.NotNull(agent); } + /// + /// Verify that GetAIAgentAsync with UseProvidedChatClientAsIs=true skips tool validation + /// and does not throw even when server-side function tools exist without matching invocable tools. + /// + [Fact] + public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_SkipsToolValidationAsync() + { + // Arrange + PromptAgentDefinition definition = new("test-model") { Instructions = "Test" }; + definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false)); + + AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition); + var options = new ChatClientAgentOptions + { + Name = "test-agent", + ChatOptions = new ChatOptions { Instructions = "Test" }, + UseProvidedChatClientAsIs = true + }; + + // Act - should not throw even without tools when UseProvidedChatClientAsIs is true + ChatClientAgent agent = await client.GetAIAgentAsync(options); + + // Assert + Assert.NotNull(agent); + } + + /// + /// Verify that GetAIAgentAsync with UseProvidedChatClientAsIs=true still matches provided AIFunction tools + /// to server-side function definitions, instead of falling back to the ResponseToolAITool wrapper. + /// + [Fact] + public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesProvidedToolsAsync() + { + // Arrange + PromptAgentDefinition definition = new("test-model") { Instructions = "Test" }; + definition.Tools.Add(ResponseTool.CreateFunctionTool("my_function", BinaryData.FromString("{}"), strictModeEnabled: false)); + + AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition); + + var providedTool = AIFunctionFactory.Create(() => "test", "my_function", "A test function"); + var options = new ChatClientAgentOptions + { + Name = "test-agent", + UseProvidedChatClientAsIs = true, + ChatOptions = new ChatOptions + { + Instructions = "Test", + Tools = [providedTool] + }, + }; + + // Act - UseProvidedChatClientAsIs is true, but provided AIFunctions should still be matched and preserved + ChatClientAgent agent = await client.GetAIAgentAsync(options); + + // Assert + Assert.NotNull(agent); + + // Verify the provided AIFunction was matched and preserved in ChatOptions.Tools (not replaced by AsAITool wrapper) + var chatOptions = agent.GetService(); + Assert.NotNull(chatOptions); + Assert.NotNull(chatOptions!.Tools); + Assert.Contains(chatOptions.Tools, t => t is AIFunction af && af.Name == "my_function"); + } + #endregion #region Empty Version and ID Handling Tests