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 })
{