diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs index ddb1ee7840..a05fca18ba 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs @@ -17,15 +17,21 @@ public static class PersistentAgentsClientExtensions /// The response containing the persistent agent to be converted. Cannot be . /// The default to use when interacting with the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the persistent agent. - public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, Response persistentAgentResponse, ChatOptions? chatOptions = null, Func? clientFactory = null) + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + Response persistentAgentResponse, + ChatOptions? chatOptions = null, + Func? clientFactory = null, + IServiceProvider? services = null) { if (persistentAgentResponse is null) { throw new ArgumentNullException(nameof(persistentAgentResponse)); } - return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, chatOptions, clientFactory); + return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, chatOptions, clientFactory, services); } /// @@ -35,8 +41,14 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA /// The persistent agent metadata to be converted. Cannot be . /// The default to use when interacting with the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the persistent agent. - public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatOptions? chatOptions = null, Func? clientFactory = null) + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + PersistentAgent persistentAgentMetadata, + ChatOptions? chatOptions = null, + Func? clientFactory = null, + IServiceProvider? services = null) { if (persistentAgentMetadata is null) { @@ -62,7 +74,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA Description = persistentAgentMetadata.Description, Instructions = persistentAgentMetadata.Instructions, ChatOptions = chatOptions - }); + }, services: services); } /// @@ -73,6 +85,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA /// The ID of the server side agent to create a for. /// Options that should apply to all runs of the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the persistent agent. public static ChatClientAgent GetAIAgent( @@ -80,6 +93,7 @@ public static ChatClientAgent GetAIAgent( string agentId, ChatOptions? chatOptions = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -93,7 +107,7 @@ public static ChatClientAgent GetAIAgent( } var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken); - return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory, services); } /// @@ -104,6 +118,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Options that should apply to all runs of the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the persistent agent. public static async Task GetAIAgentAsync( @@ -111,6 +126,7 @@ public static async Task GetAIAgentAsync( string agentId, ChatOptions? chatOptions = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -124,7 +140,7 @@ public static async Task GetAIAgentAsync( } var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); - return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory, services); } /// @@ -134,16 +150,22 @@ public static async Task GetAIAgentAsync( /// The response containing the persistent agent to be converted. Cannot be . /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the persistent agent. /// Thrown when or is . - public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, Response persistentAgentResponse, ChatClientAgentOptions options, Func? clientFactory = null) + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + Response persistentAgentResponse, + ChatClientAgentOptions options, + Func? clientFactory = null, + IServiceProvider? services = null) { if (persistentAgentResponse is null) { throw new ArgumentNullException(nameof(persistentAgentResponse)); } - return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, options, clientFactory); + return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, options, clientFactory, services); } /// @@ -153,9 +175,15 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA /// The persistent agent metadata to be converted. Cannot be . /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the persistent agent. /// Thrown when or is . - public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatClientAgentOptions options, Func? clientFactory = null) + public static ChatClientAgent GetAIAgent( + this PersistentAgentsClient persistentAgentsClient, + PersistentAgent persistentAgentMetadata, + ChatClientAgentOptions options, + Func? clientFactory = null, + IServiceProvider? services = null) { if (persistentAgentMetadata is null) { @@ -191,7 +219,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs }; - return new ChatClientAgent(chatClient, agentOptions); + return new ChatClientAgent(chatClient, agentOptions, services: services); } /// @@ -201,6 +229,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the persistent agent. /// Thrown when or is . @@ -210,6 +239,7 @@ public static ChatClientAgent GetAIAgent( string agentId, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -228,7 +258,7 @@ public static ChatClientAgent GetAIAgent( } var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken); - return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory, services); } /// @@ -238,6 +268,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the persistent agent. /// Thrown when or is . @@ -247,6 +278,7 @@ public static async Task GetAIAgentAsync( string agentId, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -265,7 +297,7 @@ public static async Task GetAIAgentAsync( } var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false); - return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory); + return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory, services); } /// @@ -283,6 +315,7 @@ public static async Task GetAIAgentAsync( /// The response format for the agent. /// The metadata for the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the newly created agent. public static async Task CreateAIAgentAsync( @@ -298,6 +331,7 @@ public static async Task CreateAIAgentAsync( BinaryData? responseFormat = null, IReadOnlyDictionary? metadata = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -319,7 +353,7 @@ public static async Task CreateAIAgentAsync( cancellationToken: cancellationToken).ConfigureAwait(false); // Get a local proxy for the agent to work with. - return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken).ConfigureAwait(false); + return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -337,6 +371,7 @@ public static async Task CreateAIAgentAsync( /// The response format for the agent. /// The metadata for the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the newly created agent. public static ChatClientAgent CreateAIAgent( @@ -352,6 +387,7 @@ public static ChatClientAgent CreateAIAgent( BinaryData? responseFormat = null, IReadOnlyDictionary? metadata = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -373,7 +409,7 @@ public static ChatClientAgent CreateAIAgent( cancellationToken: cancellationToken); // Get a local proxy for the agent to work with. - return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken); + return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken); } /// @@ -383,6 +419,7 @@ public static ChatClientAgent CreateAIAgent( /// The model to be used by the agent. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the newly created agent. /// Thrown when or or is . @@ -392,6 +429,7 @@ public static ChatClientAgent CreateAIAgent( string model, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -431,7 +469,7 @@ public static ChatClientAgent CreateAIAgent( } // Get a local proxy for the agent to work with. - return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken); + return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken); } /// @@ -441,6 +479,7 @@ public static ChatClientAgent CreateAIAgent( /// The model to be used by the agent. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the newly created agent. /// Thrown when or or is . @@ -450,6 +489,7 @@ public static async Task CreateAIAgentAsync( string model, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (persistentAgentsClient is null) @@ -489,7 +529,7 @@ public static async Task CreateAIAgentAsync( } // Get a local proxy for the agent to work with. - return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken).ConfigureAwait(false); + return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken).ConfigureAwait(false); } private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools) diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs index 07cb47da81..a36f6957b8 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs @@ -28,19 +28,21 @@ public static class OpenAIAssistantClientExtensions /// The client result containing the assistant. /// Optional chat options. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the assistant. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, ClientResult assistantClientResult, ChatOptions? chatOptions = null, - Func? clientFactory = null) + Func? clientFactory = null, + IServiceProvider? services = null) { if (assistantClientResult is null) { throw new ArgumentNullException(nameof(assistantClientResult)); } - return assistantClient.GetAIAgent(assistantClientResult.Value, chatOptions, clientFactory); + return assistantClient.GetAIAgent(assistantClientResult.Value, chatOptions, clientFactory, services); } /// @@ -50,12 +52,14 @@ public static ChatClientAgent GetAIAgent( /// The assistant metadata. /// Optional chat options. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the assistant. public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, Assistant assistantMetadata, ChatOptions? chatOptions = null, - Func? clientFactory = null) + Func? clientFactory = null, + IServiceProvider? services = null) { if (assistantMetadata is null) { @@ -80,7 +84,7 @@ public static ChatClientAgent GetAIAgent( Description = assistantMetadata.Description, Instructions = assistantMetadata.Instructions, ChatOptions = chatOptions - }); + }, services: services); } /// @@ -90,6 +94,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Options that should apply to all runs of the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. public static ChatClientAgent GetAIAgent( @@ -97,6 +102,7 @@ public static ChatClientAgent GetAIAgent( string agentId, ChatOptions? chatOptions = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (assistantClient is null) @@ -110,7 +116,7 @@ public static ChatClientAgent GetAIAgent( } var assistant = assistantClient.GetAssistant(agentId, cancellationToken); - return assistantClient.GetAIAgent(assistant, chatOptions, clientFactory); + return assistantClient.GetAIAgent(assistant, chatOptions, clientFactory, services); } /// @@ -120,6 +126,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Options that should apply to all runs of the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. public static async Task GetAIAgentAsync( @@ -127,6 +134,7 @@ public static async Task GetAIAgentAsync( string agentId, ChatOptions? chatOptions = null, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (assistantClient is null) @@ -140,7 +148,7 @@ public static async Task GetAIAgentAsync( } var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false); - return assistantClient.GetAIAgent(assistantResponse, chatOptions, clientFactory); + return assistantClient.GetAIAgent(assistantResponse, chatOptions, clientFactory, services); } /// @@ -150,20 +158,22 @@ public static async Task GetAIAgentAsync( /// The client result containing the assistant. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the assistant. /// or is . public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, ClientResult assistantClientResult, ChatClientAgentOptions options, - Func? clientFactory = null) + Func? clientFactory = null, + IServiceProvider? services = null) { if (assistantClientResult is null) { throw new ArgumentNullException(nameof(assistantClientResult)); } - return assistantClient.GetAIAgent(assistantClientResult.Value, options, clientFactory); + return assistantClient.GetAIAgent(assistantClientResult.Value, options, clientFactory, services); } /// @@ -173,13 +183,15 @@ public static ChatClientAgent GetAIAgent( /// The assistant metadata. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations on the assistant. /// or is . public static ChatClientAgent GetAIAgent( this AssistantClient assistantClient, Assistant assistantMetadata, ChatClientAgentOptions options, - Func? clientFactory = null) + Func? clientFactory = null, + IServiceProvider? services = null) { if (assistantMetadata is null) { @@ -215,7 +227,7 @@ public static ChatClientAgent GetAIAgent( UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs }; - return new ChatClientAgent(chatClient, mergedOptions); + return new ChatClientAgent(chatClient, mergedOptions, services: services); } /// @@ -225,6 +237,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. /// or is . @@ -234,6 +247,7 @@ public static ChatClientAgent GetAIAgent( string agentId, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (assistantClient is null) @@ -252,7 +266,7 @@ public static ChatClientAgent GetAIAgent( } var assistant = assistantClient.GetAssistant(agentId, cancellationToken); - return assistantClient.GetAIAgent(assistant, options, clientFactory); + return assistantClient.GetAIAgent(assistant, options, clientFactory, services); } /// @@ -262,6 +276,7 @@ public static ChatClientAgent GetAIAgent( /// The ID of the server side agent to create a for. /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. + /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations on the assistant agent. /// or is . @@ -271,6 +286,7 @@ public static async Task GetAIAgentAsync( string agentId, ChatClientAgentOptions options, Func? clientFactory = null, + IServiceProvider? services = null, CancellationToken cancellationToken = default) { if (assistantClient is null) @@ -289,7 +305,7 @@ public static async Task GetAIAgentAsync( } var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false); - return assistantClient.GetAIAgent(assistantResponse, options, clientFactory); + return assistantClient.GetAIAgent(assistantResponse, options, clientFactory, services); } /// @@ -303,6 +319,7 @@ public static async Task GetAIAgentAsync( /// Optional collection of AI tools that the agent can use during conversations. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. /// An instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. @@ -314,7 +331,8 @@ public static ChatClientAgent CreateAIAgent( string? description = null, IList? tools = null, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) => + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) => client.CreateAIAgent( model, new ChatClientAgentOptions() @@ -328,7 +346,8 @@ public static ChatClientAgent CreateAIAgent( } }, clientFactory, - loggerFactory); + loggerFactory, + services); /// /// Creates an AI agent from an using the OpenAI Assistant API. @@ -338,6 +357,7 @@ public static ChatClientAgent CreateAIAgent( /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. /// An instance backed by the OpenAI Assistant service. /// Thrown when or or is . /// Thrown when is empty or whitespace. @@ -346,7 +366,8 @@ public static ChatClientAgent CreateAIAgent( string model, ChatClientAgentOptions options, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) { Throw.IfNull(client); Throw.IfNullOrEmpty(model); @@ -387,7 +408,7 @@ public static ChatClientAgent CreateAIAgent( options.ChatOptions ??= new ChatOptions(); options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; - return new ChatClientAgent(chatClient, agentOptions, loggerFactory); + return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services); } /// @@ -401,6 +422,8 @@ public static ChatClientAgent CreateAIAgent( /// Optional collection of AI tools that the agent can use during conversations. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. + /// The to monitor for cancellation requests. The default is . /// An instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. @@ -412,7 +435,9 @@ public static async Task CreateAIAgentAsync( string? description = null, IList? tools = null, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) => + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null, + CancellationToken cancellationToken = default) => await client.CreateAIAgentAsync(model, new ChatClientAgentOptions() { @@ -425,7 +450,9 @@ await client.CreateAIAgentAsync(model, } }, clientFactory, - loggerFactory).ConfigureAwait(false); + loggerFactory, + services, + cancellationToken).ConfigureAwait(false); /// /// Creates an AI agent from an using the OpenAI Assistant API. @@ -435,6 +462,8 @@ await client.CreateAIAgentAsync(model, /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. + /// The to monitor for cancellation requests. The default is . /// An instance backed by the OpenAI Assistant service. /// Thrown when or is . /// Thrown when is empty or whitespace. @@ -443,7 +472,9 @@ public static async Task CreateAIAgentAsync( string model, ChatClientAgentOptions options, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null, + CancellationToken cancellationToken = default) { Throw.IfNull(client); Throw.IfNull(model); @@ -468,7 +499,7 @@ public static async Task CreateAIAgentAsync( } // Create the assistant in the assistant service. - var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions).ConfigureAwait(false); + var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions, cancellationToken).ConfigureAwait(false); var assistantId = assistantCreateResult.Value.Id; // Build the local agent object. @@ -483,7 +514,7 @@ public static async Task CreateAIAgentAsync( options.ChatOptions ??= new ChatOptions(); options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; - return new ChatClientAgent(chatClient, agentOptions, loggerFactory); + return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services); } private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools) diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs index c9f2743229..a4375052ed 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs @@ -30,6 +30,7 @@ public static class OpenAIResponseClientExtensions /// Optional collection of AI tools that the agent can use during conversations. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. /// An instance backed by the OpenAI Response service. /// Thrown when is . public static ChatClientAgent CreateAIAgent( @@ -39,7 +40,8 @@ public static ChatClientAgent CreateAIAgent( string? description = null, IList? tools = null, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) { Throw.IfNull(client); @@ -55,7 +57,8 @@ public static ChatClientAgent CreateAIAgent( } }, clientFactory, - loggerFactory); + loggerFactory, + services); } /// @@ -65,13 +68,15 @@ public static ChatClientAgent CreateAIAgent( /// Full set of options to configure the agent. /// Provides a way to customize the creation of the underlying used by the agent. /// Optional logger factory for enabling logging within the agent. + /// An optional to use for resolving services required by the instances being invoked. /// An instance backed by the OpenAI Response service. /// Thrown when or is . public static ChatClientAgent CreateAIAgent( this OpenAIResponseClient client, ChatClientAgentOptions options, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) { Throw.IfNull(client); Throw.IfNull(options); @@ -83,6 +88,6 @@ public static ChatClientAgent CreateAIAgent( chatClient = clientFactory(chatClient); } - return new ChatClientAgent(chatClient, options, loggerFactory); + return new ChatClientAgent(chatClient, options, loggerFactory, services); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs index 56b89d2df8..8d35f7f0b7 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Azure; @@ -726,6 +727,159 @@ public async Task CreateAIAgentAsync_WithEmptyModel_ThrowsArgumentExceptionAsync Assert.Equal("model", exception.ParamName); } + /// + /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void CreateAIAgent_WithServices_PassesServicesToAgent() + { + // Arrange + var client = CreateFakePersistentAgentsClient(); + var serviceProvider = new TestServiceProvider(); + const string Model = "test-model"; + + // Act + var agent = client.CreateAIAgent( + Model, + instructions: "Test instructions", + name: "Test Agent", + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public async Task CreateAIAgentAsync_WithServices_PassesServicesToAgentAsync() + { + // Arrange + var client = CreateFakePersistentAgentsClient(); + var serviceProvider = new TestServiceProvider(); + const string Model = "test-model"; + + // Act + var agent = await client.CreateAIAgentAsync( + Model, + instructions: "Test instructions", + name: "Test Agent", + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that GetAIAgent with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void GetAIAgent_WithServices_PassesServicesToAgent() + { + // Arrange + var client = CreateFakePersistentAgentsClient(); + var serviceProvider = new TestServiceProvider(); + + // Act + var agent = client.GetAIAgent("agent_abc123", services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that GetAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public async Task GetAIAgentAsync_WithServices_PassesServicesToAgentAsync() + { + // Arrange + var client = CreateFakePersistentAgentsClient(); + var serviceProvider = new TestServiceProvider(); + + // Act + var agent = await client.GetAIAgentAsync("agent_abc123", services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgent with both clientFactory and services works correctly. + /// + [Fact] + public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly() + { + // Arrange + var client = CreateFakePersistentAgentsClient(); + var serviceProvider = new TestServiceProvider(); + TestChatClient? testChatClient = null; + const string Model = "test-model"; + + // Act + var agent = client.CreateAIAgent( + Model, + instructions: "Test instructions", + name: "Test Agent", + clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient), + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the custom chat client was applied + var retrievedTestClient = agent.GetService(); + Assert.NotNull(retrievedTestClient); + Assert.Same(testChatClient, retrievedTestClient); + + // Verify the IServiceProvider was passed through + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Uses reflection to access the FunctionInvocationServices property which is not public. + /// + private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client) + { + var property = typeof(FunctionInvokingChatClient).GetProperty( + "FunctionInvocationServices", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return property?.GetValue(client) as IServiceProvider; + } + /// /// Test custom chat client that can be used to verify clientFactory functionality. /// @@ -736,6 +890,14 @@ public TestChatClient(IChatClient innerClient) : base(innerClient) } } + /// + /// A simple test IServiceProvider implementation for testing. + /// + private sealed class TestServiceProvider : IServiceProvider + { + public object? GetService(Type serviceType) => null; + } + public sealed class FakePersistentAgentsAdministrationClient : PersistentAgentsAdministrationClient { public FakePersistentAgentsAdministrationClient() @@ -761,7 +923,7 @@ private static PersistentAgentsClient CreateFakePersistentAgentsClient() { var client = new PersistentAgentsClient("https://any.com", DelegatedTokenCredential.Create((_, _) => new AccessToken())); - ((System.Reflection.TypeInfo)typeof(PersistentAgentsClient)).DeclaredFields.First(f => f.Name == "_client") + ((TypeInfo)typeof(PersistentAgentsClient)).DeclaredFields.First(f => f.Name == "_client") .SetValue(client, new FakePersistentAgentsAdministrationClient()); return client; } diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs index 61e3f5ef57..c14582958b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs @@ -4,6 +4,7 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.IO; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -455,6 +456,162 @@ public async Task GetAIAgentAsync_WithEmptyAgentId_ThrowsArgumentExceptionAsync( Assert.Equal("agentId", exception.ParamName); } + /// + /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void CreateAIAgent_WithServices_PassesServicesToAgent() + { + // Arrange + var assistantClient = new TestAssistantClient(); + var serviceProvider = new TestServiceProvider(); + const string ModelId = "test-model"; + + // Act + var agent = assistantClient.CreateAIAgent( + ModelId, + instructions: "Test instructions", + name: "Test Agent", + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgent with options and services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void CreateAIAgent_WithOptionsAndServices_PassesServicesToAgent() + { + // Arrange + var assistantClient = new TestAssistantClient(); + var serviceProvider = new TestServiceProvider(); + const string ModelId = "test-model"; + var options = new ChatClientAgentOptions + { + Name = "Test Agent", + Instructions = "Test instructions" + }; + + // Act + var agent = assistantClient.CreateAIAgent(ModelId, options, services: serviceProvider); + + // Assert + Assert.NotNull(agent); + Assert.Equal("Test Agent", agent.Name); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that GetAIAgent with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void GetAIAgent_WithServices_PassesServicesToAgent() + { + // Arrange + var assistantClient = new TestAssistantClient(); + var serviceProvider = new TestServiceProvider(); + var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent"}"""))!; + + // Act + var agent = assistantClient.GetAIAgent(assistant, services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that GetAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public async Task GetAIAgentAsync_WithServices_PassesServicesToAgentAsync() + { + // Arrange + var assistantClient = new TestAssistantClient(); + var serviceProvider = new TestServiceProvider(); + + // Act + var agent = await assistantClient.GetAIAgentAsync("asst_abc123", services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgent with both clientFactory and services works correctly. + /// + [Fact] + public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly() + { + // Arrange + var assistantClient = new TestAssistantClient(); + var serviceProvider = new TestServiceProvider(); + var testChatClient = new TestChatClient(assistantClient.AsIChatClient("test-model")); + const string ModelId = "test-model"; + + // Act + var agent = assistantClient.CreateAIAgent( + ModelId, + instructions: "Test instructions", + name: "Test Agent", + clientFactory: (innerClient) => testChatClient, + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the custom chat client was applied + var retrievedTestClient = agent.GetService(); + Assert.NotNull(retrievedTestClient); + Assert.Same(testChatClient, retrievedTestClient); + + // Verify the IServiceProvider was passed through + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Uses reflection to access the FunctionInvocationServices property which is not public. + /// + private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client) + { + var property = typeof(FunctionInvokingChatClient).GetProperty( + "FunctionInvocationServices", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return property?.GetValue(client) as IServiceProvider; + } + /// /// Creates a test AssistantClient implementation for testing. /// @@ -488,6 +645,11 @@ public TestChatClient(IChatClient innerClient) : base(innerClient) } } + private sealed class TestServiceProvider : IServiceProvider + { + public object? GetService(Type serviceType) => null; + } + private sealed class FakePipelineResponse : PipelineResponse { public override int Status => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs index 2612f4bfa9..11c42548da 100644 --- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -167,4 +168,114 @@ public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException() Assert.Equal("options", exception.ParamName); } + + /// + /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void CreateAIAgent_WithServices_PassesServicesToAgent() + { + // Arrange + var responseClient = new TestOpenAIResponseClient(); + var serviceProvider = new TestServiceProvider(); + + // Act + var agent = responseClient.CreateAIAgent( + instructions: "Test instructions", + name: "Test Agent", + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgent with options and services parameter correctly passes it through to the ChatClientAgent. + /// + [Fact] + public void CreateAIAgent_WithOptionsAndServices_PassesServicesToAgent() + { + // Arrange + var responseClient = new TestOpenAIResponseClient(); + var serviceProvider = new TestServiceProvider(); + var options = new ChatClientAgentOptions + { + Name = "Test Agent", + Instructions = "Test instructions" + }; + + // Act + var agent = responseClient.CreateAIAgent(options, services: serviceProvider); + + // Assert + Assert.NotNull(agent); + Assert.Equal("Test Agent", agent.Name); + + // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// Verify that CreateAIAgent with both clientFactory and services works correctly. + /// + [Fact] + public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly() + { + // Arrange + var responseClient = new TestOpenAIResponseClient(); + var serviceProvider = new TestServiceProvider(); + var testChatClient = new TestChatClient(responseClient.AsIChatClient()); + + // Act + var agent = responseClient.CreateAIAgent( + instructions: "Test instructions", + name: "Test Agent", + clientFactory: (innerClient) => testChatClient, + services: serviceProvider); + + // Assert + Assert.NotNull(agent); + + // Verify the custom chat client was applied + var retrievedTestClient = agent.GetService(); + Assert.NotNull(retrievedTestClient); + Assert.Same(testChatClient, retrievedTestClient); + + // Verify the IServiceProvider was passed through + var chatClient = agent.GetService(); + Assert.NotNull(chatClient); + var functionInvokingClient = chatClient.GetService(); + Assert.NotNull(functionInvokingClient); + Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient)); + } + + /// + /// A simple test IServiceProvider implementation for testing. + /// + private sealed class TestServiceProvider : IServiceProvider + { + public object? GetService(Type serviceType) => null; + } + + /// + /// Uses reflection to access the FunctionInvocationServices property which is not public. + /// + private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client) + { + var property = typeof(FunctionInvokingChatClient).GetProperty( + "FunctionInvocationServices", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return property?.GetValue(client) as IServiceProvider; + } }