From bd167ee476b931c4bfe29c6c78c8234293a765f2 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:10:03 +0100 Subject: [PATCH] .NET: AI Agent with plugin (#1009) * add sample to demonstrate plugins for ai agent * Update dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove lang version property * address review comments * rename functionInvocationServices to services * restore the solution file corrupted during the merge with lates main --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dotnet/agent-framework-dotnet.slnx | 5 +- .../Agent_Step14_Middleware.csproj | 3 +- .../Agent_Step15_Plugins.csproj | 24 ++++ .../Agents/Agent_Step15_Plugins/Program.cs | 135 ++++++++++++++++++ .../samples/GettingStarted/Agents/README.md | 1 + .../Extensions/OpenAIChatClientExtensions.cs | 13 +- .../ChatClient/ChatClientAgent.cs | 12 +- .../ChatClient/ChatClientExtensions.cs | 4 +- 8 files changed, 181 insertions(+), 16 deletions(-) create mode 100644 dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj create mode 100644 dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index eea5eba27c..dafc4544dc 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -48,8 +48,9 @@ - + + @@ -295,4 +296,4 @@ - + \ No newline at end of file diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj index f65635790d..266e373153 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj @@ -3,8 +3,7 @@ Exe net9.0 - 12 - + enable disable VSTHRD200;CA1707 diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj new file mode 100644 index 0000000000..82e92ba913 --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj @@ -0,0 +1,24 @@ + + + + Exe + net9.0 + + enable + disable + VSTHRD200;CA1707;CA1812 + Agent_Step15_Plugins + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs new file mode 100644 index 0000000000..af28433fca --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to use plugins with an AI agent. Plugin classes can +// depend on other services that need to be injected. In this sample, the +// AgentPlugin class uses the WeatherProvider and CurrentTimeProvider classes +// to get weather and current time information. Both services are registered +// in the service collection and injected into the plugin. +// Plugin classes may have many methods, but only some are intended to be used +// as AI functions. The AsAITools method of the plugin class shows how to specify +// which methods should be exposed to the AI agent. + +using System; +using System.Collections.Generic; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using OpenAI; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + +// Create a service collection to hold the agent plugin and its dependencies. +ServiceCollection services = new(); +services.AddSingleton(); +services.AddSingleton(); +services.AddSingleton(); // The plugin depends on WeatherProvider and CurrentTimeProvider registered above. + +IServiceProvider serviceProvider = services.BuildServiceProvider(); + +const string AgentName = "Assistant"; +const string AgentInstructions = "You are a helpful assistant that helps people find information."; + +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetChatClient(deploymentName) + .CreateAIAgent( + instructions: AgentInstructions, + name: AgentName, + tools: [.. serviceProvider.GetRequiredService().AsAITools()], + services: serviceProvider); // Pass the service provider to the agent so it will be available to plugin functions to resolve dependencies. + +Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.")); + +/// +/// The agent plugin that provides weather and current time information. +/// +/// The weather provider to get weather information. +internal sealed class AgentPlugin(WeatherProvider weatherProvider) +{ + /// + /// Gets the weather information for the specified location. + /// + /// + /// This method demonstrates how to use the dependency that was injected into the plugin class. + /// + /// The location to get the weather for. + /// The weather information for the specified location. + public string GetWeather(string location) + { + return weatherProvider.GetWeather(location); + } + + /// + /// Gets the current date and time for the specified location. + /// + /// + /// This method demonstrates how to resolve a dependency using the service provider passed to the method. + /// + /// The service provider to resolve the . + /// The location to get the current time for. + /// The current date and time as a . + public DateTimeOffset GetCurrentTime(IServiceProvider sp, string location) + { + // Resolve the CurrentTimeProvider from the service provider + var currentTimeProvider = sp.GetRequiredService(); + + return currentTimeProvider.GetCurrentTime(location); + } + + /// + /// Returns the functions provided by this plugin. + /// + /// + /// In real world scenarios, a class may have many methods and only a subset of them may be intended to be exposed as AI functions. + /// This method demonstrates how to explicitly specify which methods should be exposed to the AI agent. + /// + /// The functions provided by this plugin. + public IEnumerable AsAITools() + { + yield return AIFunctionFactory.Create(this.GetWeather); + yield return AIFunctionFactory.Create(this.GetCurrentTime); + } +} + +/// +/// The weather provider that returns weather information. +/// +internal sealed class WeatherProvider +{ + /// + /// Gets the weather information for the specified location. + /// + /// + /// The weather information is hardcoded for demonstration purposes. + /// In a real application, this could call a weather API to get actual weather data. + /// + /// The location to get the weather for. + /// The weather information for the specified location. + public string GetWeather(string location) + { + return $"The weather in {location} is cloudy with a high of 15°C."; + } +} + +/// +/// Provides the current date and time. +/// +/// +/// This class returns the current date and time using the system's clock. +/// +internal sealed class CurrentTimeProvider +{ + /// + /// Gets the current date and time. + /// + /// The location to get the current time for (not used in this implementation). + /// The current date and time as a . + public DateTimeOffset GetCurrentTime(string location) + { + return DateTimeOffset.Now; + } +} diff --git a/dotnet/samples/GettingStarted/Agents/README.md b/dotnet/samples/GettingStarted/Agents/README.md index 18e59ceb01..0ba0383166 100644 --- a/dotnet/samples/GettingStarted/Agents/README.md +++ b/dotnet/samples/GettingStarted/Agents/README.md @@ -40,6 +40,7 @@ Before you begin, ensure you have the following prerequisites: |[Exposing a simple agent as a function tool](./Agent_Step12_AsFunctionTool/)|This sample demonstrates how to expose an agent as a function tool| |[Using memory with an agent](./Agent_Step13_Memory/)|This sample demonstrates how to create a simple memory component and use it with an agent| |[Using middleware with an agent](./Agent_Step14_Middleware/)|This sample demonstrates how to use middleware with an agent| +|[Using plugins with an agent](./Agent_Step15_Plugins/)|This sample demonstrates how to use plugins with an agent| ## Running the samples from the console diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs index 6b48c04182..3fc7bb7de0 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs @@ -30,6 +30,7 @@ public static class OpenAIChatClientExtensions /// 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 Chat Completion service. /// Thrown when is . public static AIAgent CreateAIAgent( @@ -39,7 +40,8 @@ public static class OpenAIChatClientExtensions string? description = null, IList? tools = null, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) => + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) => client.CreateAIAgent( new ChatClientAgentOptions() { @@ -52,7 +54,8 @@ public static class OpenAIChatClientExtensions } }, clientFactory, - loggerFactory); + loggerFactory, + services); /// /// Creates an AI agent from an using the OpenAI Chat Completion API. @@ -61,13 +64,15 @@ public static class OpenAIChatClientExtensions /// 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 Chat Completion service. /// Thrown when or is . public static AIAgent CreateAIAgent( this ChatClient client, ChatClientAgentOptions options, Func? clientFactory = null, - ILoggerFactory? loggerFactory = null) + ILoggerFactory? loggerFactory = null, + IServiceProvider? services = null) { Throw.IfNull(client); Throw.IfNull(options); @@ -79,6 +84,6 @@ public static class OpenAIChatClientExtensions chatClient = clientFactory(chatClient); } - return new ChatClientAgent(chatClient, options, loggerFactory); + return new ChatClientAgent(chatClient, options, loggerFactory, services); } } diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index a357a4e8bd..fe73ee1257 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -35,8 +35,8 @@ public sealed class ChatClientAgent : AIAgent /// Optional description for the agent. /// Optional list of tools that the agent can use during invocation. /// Optional logger factory to use for logging. - /// An optional to use for resolving services required by the instances being invoked. - public ChatClientAgent(IChatClient chatClient, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null, IServiceProvider? functionInvocationServices = null) + /// An optional to use for resolving services required by the instances being invoked. + public ChatClientAgent(IChatClient chatClient, string? instructions = null, string? name = null, string? description = null, IList? tools = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null) : this( chatClient, new ChatClientAgentOptions @@ -50,7 +50,7 @@ public sealed class ChatClientAgent : AIAgent } }, loggerFactory, - functionInvocationServices) + services) { } @@ -60,8 +60,8 @@ public sealed class ChatClientAgent : AIAgent /// The chat client to use for invoking the agent. /// Full set of options to configure the agent. /// Optional logger factory to use for logging. - /// An optional to use for resolving services required by the instances being invoked. - public ChatClientAgent(IChatClient chatClient, ChatClientAgentOptions? options, ILoggerFactory? loggerFactory = null, IServiceProvider? functionInvocationServices = null) + /// An optional to use for resolving services required by the instances being invoked. + public ChatClientAgent(IChatClient chatClient, ChatClientAgentOptions? options, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null) { _ = Throw.IfNull(chatClient); @@ -74,7 +74,7 @@ public sealed class ChatClientAgent : AIAgent this._chatClientType = chatClient.GetType(); // If the user has not opted out of using our default decorators, we wrap the chat client. - this.ChatClient = options?.UseProvidedChatClientAsIs is true ? chatClient : chatClient.WithDefaultAgentMiddleware(options, functionInvocationServices); + this.ChatClient = options?.UseProvidedChatClientAsIs is true ? chatClient : chatClient.WithDefaultAgentMiddleware(options, services); this._logger = (loggerFactory ?? chatClient.GetService() ?? NullLoggerFactory.Instance).CreateLogger(); } diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs index 67cd7c0d54..5b4dc016cd 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.AI; internal static class ChatClientExtensions { - internal static IChatClient WithDefaultAgentMiddleware(this IChatClient chatClient, ChatClientAgentOptions? options, IServiceProvider? functionInvocationServices = null) + internal static IChatClient WithDefaultAgentMiddleware(this IChatClient chatClient, ChatClientAgentOptions? options, IServiceProvider? services = null) { var chatBuilder = chatClient.AsBuilder(); @@ -25,7 +25,7 @@ internal static class ChatClientExtensions }); } - var agentChatClient = chatBuilder.Build(functionInvocationServices); + var agentChatClient = chatBuilder.Build(services); if (options?.ChatOptions?.Tools is { Count: > 0 }) {