diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/AgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/AgentsClientExtensions.cs
index b1b92ee3be..48a2485bbf 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/AgentsClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/AgentsClientExtensions.cs
@@ -119,6 +119,7 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
+ requireInvocableTools: true,
cancellationToken);
}
@@ -130,6 +131,10 @@ public static class AgentsClientExtensions
/// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.
/// Provides a way to customize the creation of the underlying used by the agent.
/// An optional for configuring the underlying OpenAI client.
+ ///
+ /// This defaults to and indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.
+ /// Setting this to will require manual handling of in-proc tool invocations by the caller.
+ ///
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations based on the provided version of the Azure AI Agent.
/// Thrown when or is .
@@ -140,6 +145,7 @@ public static class AgentsClientExtensions
IList? tools = null,
Func? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
+ bool requireInvocableTools = true,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -147,16 +153,13 @@ public static class AgentsClientExtensions
ValidateUsingToolsParameter(agentVersion, tools);
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- var agentOptions = CreateChatClientAgentOptions(agentVersion, tools);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ tools,
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools);
}
///
@@ -189,18 +192,15 @@ public static class AgentsClientExtensions
var agentVersion = agentRecord.Versions.Latest;
- ValidateUsingToolsParameter(agentVersion, options.ChatOptions?.Tools);
+ var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, options);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ agentOptions,
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools: true);
}
///
@@ -233,18 +233,15 @@ public static class AgentsClientExtensions
var agentVersion = agentRecord.Versions.Latest;
- ValidateUsingToolsParameter(agentVersion, options.ChatOptions?.Tools);
+ var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
-
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, options);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ agentOptions,
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools: true);
}
///
@@ -287,6 +284,7 @@ public static class AgentsClientExtensions
creationOptions,
clientFactory,
openAIClientOptions,
+ requireInvocableTools: true,
cancellationToken);
}
@@ -330,6 +328,7 @@ public static class AgentsClientExtensions
creationOptions,
clientFactory,
openAIClientOptions,
+ requireInvocableTools: true,
cancellationToken);
}
@@ -356,6 +355,7 @@ public static class AgentsClientExtensions
Throw.IfNull(agentsClient);
Throw.IfNull(options);
Throw.IfNullOrWhitespace(model);
+ const bool RequireInvocableTools = true;
if (string.IsNullOrWhiteSpace(options.Name))
{
@@ -367,7 +367,7 @@ public static class AgentsClientExtensions
Instructions = options.Instructions,
};
- ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
+ ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
AgentVersionCreationOptions? versionCreationOptions = null;
if (!string.IsNullOrWhiteSpace(options.Description))
@@ -377,16 +377,15 @@ public static class AgentsClientExtensions
AgentVersion agentVersion = agentsClient.CreateAgentVersion(options.Name, agentDefinition, versionCreationOptions, cancellationToken).Value;
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
+ var agentOptions = CreateChatClientAgentOptions(agentVersion, options, RequireInvocableTools);
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, options);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ agentOptions,
+ clientFactory,
+ openAIClientOptions,
+ RequireInvocableTools);
}
///
@@ -412,6 +411,7 @@ public static class AgentsClientExtensions
Throw.IfNull(agentsClient);
Throw.IfNull(options);
Throw.IfNullOrWhitespace(model);
+ const bool RequireInvocableTools = true;
if (string.IsNullOrWhiteSpace(options.Name))
{
@@ -423,7 +423,7 @@ public static class AgentsClientExtensions
Instructions = options.Instructions,
};
- ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
+ ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
AgentVersionCreationOptions? versionCreationOptions = null;
if (!string.IsNullOrWhiteSpace(options.Description))
@@ -431,18 +431,17 @@ public static class AgentsClientExtensions
(versionCreationOptions ??= new()).Description = options.Description;
}
- AgentVersion agentVersion = (await agentsClient.CreateAgentVersionAsync(options.Name, agentDefinition, versionCreationOptions, cancellationToken).ConfigureAwait(false)).Value;
+ AgentVersion agentVersion = await agentsClient.CreateAgentVersionAsync(options.Name, agentDefinition, versionCreationOptions, cancellationToken).ConfigureAwait(false);
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
+ var agentOptions = CreateChatClientAgentOptions(agentVersion, options, RequireInvocableTools);
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, options);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ agentOptions,
+ clientFactory,
+ openAIClientOptions,
+ RequireInvocableTools);
}
///
@@ -455,6 +454,7 @@ public static class AgentsClientExtensions
/// Settings that control the creation of the agent.
/// A factory function to customize the creation of the chat client used by the agent.
/// An optional for configuring the underlying OpenAI client.
+ /// This defaults to true and indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them. Setting this to false will require manual handling of in-proc tool invocations by the caller.
/// A token to monitor for cancellation requests.
/// A instance that can be used to perform operations on the newly created agent.
/// Thrown when or is .
@@ -467,6 +467,7 @@ public static class AgentsClientExtensions
AgentVersionCreationOptions? creationOptions = null,
Func? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
+ bool requireInvocableTools = true,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -474,19 +475,17 @@ public static class AgentsClientExtensions
Throw.IfNull(agentDefinition);
ValidateUsingToolsParameter(agentDefinition, tools);
- ApplyToolsToAgentDefinition(agentDefinition, tools);
+ ApplyToolsToAgentDefinition(agentDefinition, tools, requireInvocableTools);
AgentVersion agentVersion = agentsClient.CreateAgentVersion(name, agentDefinition, creationOptions, cancellationToken).Value;
- IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
- if (clientFactory is not null)
- {
- chatClient = clientFactory(chatClient);
- }
-
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, tools);
-
- return new ChatClientAgent(chatClient, agentOptions);
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ tools,
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools);
}
///
@@ -500,6 +499,7 @@ public static class AgentsClientExtensions
/// Settings that control the creation of the agent.
/// A factory function to customize the creation of the chat client used by the agent.
/// An optional for configuring the underlying OpenAI client.
+ /// This defaults to true and indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them. Setting this to false will require manual handling of in-proc tool invocations by the caller.
/// A token to monitor for cancellation requests.
/// A instance that can be used to perform operations on the newly created agent.
/// Thrown when or is .
@@ -512,6 +512,7 @@ public static class AgentsClientExtensions
AgentVersionCreationOptions? agentVersionCreationOptions = null,
Func? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
+ bool requireInvocableTools = true,
CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhitespace(name);
@@ -519,9 +520,30 @@ public static class AgentsClientExtensions
Throw.IfNull(agentDefinition);
ValidateUsingToolsParameter(agentDefinition, tools);
- ApplyToolsToAgentDefinition(agentDefinition, tools);
+ ApplyToolsToAgentDefinition(agentDefinition, tools, requireInvocableTools);
AgentVersion agentVersion = await agentsClient.CreateAgentVersionAsync(name, agentDefinition, agentVersionCreationOptions, cancellationToken).ConfigureAwait(false);
+
+ return CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ tools,
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools);
+ }
+
+ #region Private
+
+ /// This method creates an with the specified ChatClientAgentOptions.
+ private static ChatClientAgent CreateChatClientAgent(
+ AgentsClient agentsClient,
+ AgentVersion agentVersion,
+ ChatClientAgentOptions agentOptions,
+ Func? clientFactory,
+ OpenAIClientOptions? openAIClientOptions,
+ bool requireInvocableTools)
+ {
IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, openAIClientOptions);
if (clientFactory is not null)
@@ -529,18 +551,31 @@ public static class AgentsClientExtensions
chatClient = clientFactory(chatClient);
}
- ChatClientAgentOptions agentOptions = CreateChatClientAgentOptions(agentVersion, tools);
-
return new ChatClientAgent(chatClient, agentOptions);
}
- #region Private
+ /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters.
+ private static ChatClientAgent CreateChatClientAgent(
+ AgentsClient agentsClient,
+ AgentVersion agentVersion,
+ IList? tools,
+ Func? clientFactory,
+ OpenAIClientOptions? openAIClientOptions,
+ bool requireInvocableTools)
+ => CreateChatClientAgent(
+ agentsClient,
+ agentVersion,
+ CreateChatClientAgentOptions(agentVersion, tools, requireInvocableTools),
+ clientFactory,
+ openAIClientOptions,
+ requireInvocableTools);
///
/// This method creates for the specified and the provided tools.
///
/// The agent version.
/// The tools to use when interacting with the agent.
+ /// Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.
/// The created .
/// Thrown when the agent definition requires in-process tools but none were provided.
/// Thrown when the agent definition required tools were not provided.
@@ -548,7 +583,7 @@ public static class AgentsClientExtensions
/// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided
/// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server.
///
- private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, IList? tools)
+ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, IList? tools, bool requireInvocableTools)
{
var agentDefinition = agentVersion.Definition;
@@ -556,12 +591,12 @@ public static class AgentsClientExtensions
if (agentDefinition is PromptAgentDefinition { Tools: { Count: > 0 } definitionTools })
{
// Check if no tools were provided while the agent definition requires in-proc tools.
- if (tools is null or { Count: 0 } && definitionTools.Any(t => t is FunctionTool))
+ if (requireInvocableTools && tools is null or { Count: 0 } && definitionTools.Any(t => t is FunctionTool))
{
throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
}
- // Agregate all missing in-proc tools for a single error message.
+ // Agregate all missing tools for a single error message.
List? missingTools = null;
// Check function tools
@@ -570,7 +605,11 @@ public static class AgentsClientExtensions
if (responseTool is FunctionTool functionTool)
{
// Check if a tool with the same type and name exists in the provided tools.
- var matchingTool = tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
+ var matchingTool = tools?.FirstOrDefault(t =>
+ requireInvocableTools
+ ? t is AIFunction tf && functionTool.FunctionName == tf.Name // When invocable tools are required, match only AIFunction.
+ : t is AIFunctionDeclaration tfd && functionTool.FunctionName == tfd.Name); // Otherwise, matching only AIFunctionDeclaration is sufficient.
+
if (matchingTool is null)
{
(missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
@@ -618,9 +657,19 @@ public static class AgentsClientExtensions
return agentOptions;
}
- private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options)
+ ///
+ /// Creates a new instance of configured for the specified agent version and
+ /// optional base options.
+ ///
+ /// The agent version to use when configuring the chat client agent options.
+ /// An optional instance whose relevant properties will be copied to the
+ /// returned options. If , only default values are used.
+ /// Specifies whether the returned options must include invocable tools. Set to to require
+ /// invocable tools; otherwise, .
+ /// A instance configured according to the specified parameters.
+ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options, bool requireInvocableTools)
{
- var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions?.Tools);
+ var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions?.Tools, requireInvocableTools);
if (options is not null)
{
agentOptions.AIContextProviderFactory = options.AIContextProviderFactory;
@@ -659,7 +708,21 @@ public static class AgentsClientExtensions
}
}
- private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList? tools)
+ ///
+ /// Adds the specified AI tools to a prompt agent definition, ensuring that all tools are compatible and, if required, invocable.
+ ///
+ /// This method ensures that only compatible and properly constructed tools are added to the agent definition.
+ /// When is , all tools must be
+ /// invocable AIFunctions, which can be created using AIFunctionFactory.Create. Tools are converted to ResponseTool
+ /// instances before being added.
+ /// The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools.
+ /// A list of AI tools to add to the agent definition. If null or empty, no tools are added.
+ /// Indicates whether all provided tools must be invocable AI functions. If set to , only
+ /// invocable AIFunctions are accepted.
+ /// Thrown if is not a .
+ /// Thrown if is and a tool is an
+ /// that is not invocable, or if a tool cannot be converted to a .
+ private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList? tools, bool requireInvocableTools)
{
if (tools is { Count: > 0 })
{
@@ -671,7 +734,7 @@ public static class AgentsClientExtensions
foreach (var tool in tools)
{
// Ensure that any AIFunctions provided are In-Proc, not just the declarations.
- if (tool is AIFunctionDeclaration and not AIFunction)
+ if (requireInvocableTools && tool is AIFunctionDeclaration and not AIFunction)
{
throw new InvalidOperationException("When providing functions, they need to be invokable AIFunctions and not non-invokable AIFunctionDeclarations. AIFunctions can be created correctly using AIFunctionFactory.Create");
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/AgentsClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/AgentsClientExtensionsTests.cs
index fcf83e4b18..f7ca437d69 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/AgentsClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/AgentsClientExtensionsTests.cs
@@ -5,6 +5,7 @@ using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.IO;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Agents;
@@ -174,6 +175,196 @@ public sealed class AgentsClientExtensionsTests
Assert.Same(testChatClient, retrievedTestClient);
}
+ ///
+ /// Verify that GetAIAgent with requireInvocableTools=true enforces invocable tools.
+ ///
+ [Fact]
+ public void GetAIAgent_WithAgentVersion_WithRequireInvocableToolsTrue_EnforcesInvocableTools()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ AgentVersion agentVersion = this.CreateTestAgentVersion();
+ var tools = new List
+ {
+ AIFunctionFactory.Create(() => "test", "test_function", "A test function")
+ };
+
+ // Act
+ var agent = client.GetAIAgent(agentVersion, tools: tools, requireInvocableTools: true);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ ///
+ /// Verify that GetAIAgent with requireInvocableTools=false allows declarative functions.
+ ///
+ [Fact]
+ public void GetAIAgent_WithAgentVersion_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ AgentVersion agentVersion = this.CreateTestAgentVersion();
+
+ // Act - should not throw even without tools when requireInvocableTools is false
+ var agent = client.GetAIAgent(agentVersion, requireInvocableTools: false);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ #endregion
+
+ #region GetAIAgent(AgentsClient, ChatClientAgentOptions) Tests
+
+ ///
+ /// Verify that GetAIAgent with ChatClientAgentOptions throws ArgumentNullException when client is null.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_WithNullClient_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AgentsClient? client = null;
+ var options = new ChatClientAgentOptions { Name = "test-agent" };
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ client!.GetAIAgent(options));
+
+ Assert.Equal("agentsClient", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgent with ChatClientAgentOptions throws ArgumentNullException when options is null.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_WithNullOptions_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var mockClient = new Mock();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ mockClient.Object.GetAIAgent((ChatClientAgentOptions)null!));
+
+ Assert.Equal("options", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgent with ChatClientAgentOptions throws ArgumentException when options.Name is null.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_WithoutName_ThrowsArgumentException()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ var options = new ChatClientAgentOptions();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ client.GetAIAgent(options));
+
+ Assert.Contains("Agent name must be provided", exception.Message);
+ }
+
+ ///
+ /// Verify that GetAIAgent with ChatClientAgentOptions creates a valid agent.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_CreatesValidAgent()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent");
+ var options = new ChatClientAgentOptions { Name = "test-agent" };
+
+ // Act
+ var agent = client.GetAIAgent(options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("test-agent", agent.Name);
+ }
+
+ ///
+ /// Verify that GetAIAgent with ChatClientAgentOptions and clientFactory applies the factory.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_WithClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent");
+ var options = new ChatClientAgentOptions { Name = "test-agent" };
+ TestChatClient? testChatClient = null;
+
+ // Act
+ var agent = client.GetAIAgent(
+ options,
+ clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
+
+ // Assert
+ Assert.NotNull(agent);
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ #endregion
+
+ #region GetAIAgentAsync(AgentsClient, ChatClientAgentOptions) Tests
+
+ ///
+ /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when client is null.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithOptions_WithNullClient_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ AgentsClient? client = null;
+ var options = new ChatClientAgentOptions { Name = "test-agent" };
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ client!.GetAIAgentAsync(options));
+
+ Assert.Equal("agentsClient", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when options is null.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ var mockClient = new Mock();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ mockClient.Object.GetAIAgentAsync((ChatClientAgentOptions)null!));
+
+ Assert.Equal("options", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with ChatClientAgentOptions creates a valid agent.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithOptions_CreatesValidAgentAsync()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent");
+ var options = new ChatClientAgentOptions { Name = "test-agent" };
+
+ // Act
+ var agent = await client.GetAIAgentAsync(options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("test-agent", agent.Name);
+ }
+
#endregion
#region GetAIAgent(AgentsClient, string) Tests
@@ -532,9 +723,111 @@ public sealed class AgentsClientExtensionsTests
Assert.Contains("Agent name must be provided", exception.Message);
}
+ ///
+ /// Verify that CreateAIAgent with model and options creates a valid agent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithModelAndOptions_CreatesValidAgent()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", instructions: "Test instructions");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Test instructions"
+ };
+
+ // Act
+ var agent = client.CreateAIAgent("test-model", options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("test-agent", agent.Name);
+ Assert.Equal("Test instructions", agent.Instructions);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with model and options and clientFactory applies the factory.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", instructions: "Test instructions");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Test instructions"
+ };
+ TestChatClient? testChatClient = null;
+
+ // Act
+ var agent = client.CreateAIAgent(
+ "test-model",
+ options,
+ clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
+
+ // Assert
+ Assert.NotNull(agent);
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with model and options creates a valid agent.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", instructions: "Test instructions");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Test instructions"
+ };
+
+ // Act
+ var agent = await client.CreateAIAgentAsync("test-model", options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("test-agent", agent.Name);
+ Assert.Equal("Test instructions", agent.Instructions);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with model and options and clientFactory applies the factory.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectlyAsync()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", instructions: "Test instructions");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Test instructions"
+ };
+ TestChatClient? testChatClient = null;
+
+ // Act
+ var agent = await client.CreateAIAgentAsync(
+ "test-model",
+ options,
+ clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
+
+ // Assert
+ Assert.NotNull(agent);
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
#endregion
- #region CreateAIAgentAsync Tests
+ #region CreateAIAgentAsync(AgentsClient, string, AgentDefinition) Tests
///
/// Verify that CreateAIAgentAsync throws ArgumentNullException when agentsClient is null.
@@ -591,6 +884,68 @@ public sealed class AgentsClientExtensionsTests
Assert.Contains("dedicated tools parameter", exception.Message);
}
+ ///
+ /// Verify that CreateAIAgent with requireInvocableTools=true enforces invocable tools.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithRequireInvocableToolsTrue_EnforcesInvocableTools()
+ {
+ // Arrange
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+ var tools = new List
+ {
+ AIFunctionFactory.Create(() => "test", "test_function", "A test function")
+ };
+
+ var definitionResponse = GeneratePromptDefinitionResponse(definition, tools);
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
+
+ // Act
+ var agent = client.CreateAIAgent("test-agent", definition, tools: tools, requireInvocableTools: true);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with requireInvocableTools=false allows declarative functions.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
+ {
+ // Arrange
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definition);
+
+ // Act - should not throw even without tools when requireInvocableTools is false
+ var agent = client.CreateAIAgent("test-agent", definition, requireInvocableTools: false);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ ///
+ /// Verify that CreateAIAgent throws ArgumentException when agent definition has tools but none provided as parameter.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithDefinitionToolsButNoToolsParameter_ThrowsArgumentException()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+
+ // Add a function tool to the definition to simulate a tool requirement
+ definition.Tools.Add(ResponseTool.CreateFunctionTool("required_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ client.CreateAIAgent("test-agent", definition, tools: null, requireInvocableTools: true));
+
+ Assert.Contains("dedicated tools parameter", exception.Message);
+ }
+
///
/// Verify that CreateAIAgent with tools parameter applies tools to the agent definition.
///
@@ -888,6 +1243,209 @@ public sealed class AgentsClientExtensionsTests
#endregion
+ #region Declarative Function Handling Tests
+
+ ///
+ /// Verify that CreateAIAgent throws InvalidOperationException when provided with non-invocable AIFunctionDeclaration when requireInvocableTools=true.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithDeclarativeFunctionAndRequireInvocableTrue_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+
+ // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
+ using var doc = JsonDocument.Parse("{}");
+ var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
+
+ var tools = new List { declarativeFunction };
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ client.CreateAIAgent("test-agent", definition, tools: tools, requireInvocableTools: true));
+
+ Assert.Contains("invokable AIFunctions", exception.Message);
+ }
+
+ ///
+ /// Verify that CreateAIAgent accepts declarative functions when requireInvocableTools=false.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithDeclarativeFunctionAndRequireInvocableFalse_AcceptsDeclarativeFunction()
+ {
+ // Arrange
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+
+ // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
+ using var doc = JsonDocument.Parse("{}");
+ var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
+
+ var tools = new List { declarativeFunction };
+
+ // Generate response with the declarative function
+ var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+ definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
+
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
+
+ // Act
+ var agent = client.CreateAIAgent("test-agent", definition, tools: tools, requireInvocableTools: false);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync throws InvalidOperationException when provided with non-invocable AIFunctionDeclaration when requireInvocableTools=true.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithDeclarativeFunctionAndRequireInvocableTrue_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient();
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+
+ // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
+ using var doc = JsonDocument.Parse("{}");
+ var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
+
+ var tools = new List { declarativeFunction };
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ client.CreateAIAgentAsync("test-agent", definition, tools: tools, requireInvocableTools: true));
+
+ Assert.Contains("invokable AIFunctions", exception.Message);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync accepts declarative functions when requireInvocableTools=false.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithDeclarativeFunctionAndRequireInvocableFalse_AcceptsDeclarativeFunctionAsync()
+ {
+ // Arrange
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+
+ // Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
+ using var doc = JsonDocument.Parse("{}");
+ var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
+
+ var tools = new List { declarativeFunction };
+
+ // Generate response with the declarative function
+ var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
+ definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
+
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
+
+ // Act
+ var agent = await client.CreateAIAgentAsync("test-agent", definition, tools: tools, requireInvocableTools: false);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ #endregion
+
+ #region Options Generation Validation Tests
+
+ ///
+ /// Verify that ChatClientAgentOptions are generated correctly with proper tool matching.
+ ///
+ [Fact]
+ public void CreateAIAgent_GeneratesCorrectChatClientAgentOptions()
+ {
+ // Arrange
+ var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
+ var tools = new List
+ {
+ AIFunctionFactory.Create(() => "result", "test_tool", "A test tool")
+ };
+
+ var definitionResponse = GeneratePromptDefinitionResponse(definition, tools);
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
+
+ // Act
+ var agent = client.CreateAIAgent("test-agent", definition, tools: tools);
+
+ // Assert
+ Assert.NotNull(agent);
+ var agentVersion = agent.GetService();
+ Assert.NotNull(agentVersion);
+ Assert.Equal("test-agent", agentVersion.Name);
+ Assert.Equal("Test instructions", (agentVersion.Definition as PromptAgentDefinition)?.Instructions);
+ }
+
+ ///
+ /// Verify that ChatClientAgentOptions preserve custom properties from input options.
+ ///
+ [Fact]
+ public void GetAIAgent_WithOptions_PreservesCustomProperties()
+ {
+ // Arrange
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", instructions: "Custom instructions", description: "Custom description");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Custom instructions",
+ Description = "Custom description"
+ };
+
+ // Act
+ var agent = client.GetAIAgent(options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("test-agent", agent.Name);
+ Assert.Equal("Custom instructions", agent.Instructions);
+ Assert.Equal("Custom description", agent.Description);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options generates correct ChatClientAgentOptions with tools.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithOptionsAndTools_GeneratesCorrectOptions()
+ {
+ // Arrange
+ var tools = new List
+ {
+ AIFunctionFactory.Create(() => "result", "option_tool", "A tool from options")
+ };
+
+ var definitionResponse = GeneratePromptDefinitionResponse(
+ new PromptAgentDefinition("test-model") { Instructions = "Test" },
+ tools);
+
+ AgentsClient client = this.CreateTestAgentsClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
+
+ var options = new ChatClientAgentOptions
+ {
+ Name = "test-agent",
+ Instructions = "Test",
+ ChatOptions = new ChatOptions { Tools = tools }
+ };
+
+ // Act
+ var agent = client.CreateAIAgent("test-model", options);
+
+ // Assert
+ Assert.NotNull(agent);
+ var agentVersion = agent.GetService();
+ Assert.NotNull(agentVersion);
+ if (agentVersion.Definition is PromptAgentDefinition promptDef)
+ {
+ Assert.NotEmpty(promptDef.Tools);
+ Assert.Single(promptDef.Tools);
+ }
+ }
+
+ #endregion
+
#region AzureAIChatClient Behavior Tests
///