diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgents/Step06_AzureAIAgent_Functions.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgents/Step06_AzureAIAgent_Functions.cs
new file mode 100644
index 000000000000..58220792a285
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgents/Step06_AzureAIAgent_Functions.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System.ComponentModel;
+using Azure.AI.Projects;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.AzureAI;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Agent = Azure.AI.Projects.Agent;
+
+namespace GettingStarted.AzureAgents;
+
+///
+/// This example demonstrates similarity between using
+/// and (see: Step 2).
+///
+public class Step06_AzureAIAgent_Functions(ITestOutputHelper output) : BaseAgentsTest(output)
+{
+ private const string HostName = "Host";
+ private const string HostInstructions = "Answer questions about the menu.";
+
+ [Fact]
+ public async Task UseSingleAgentWithFunctionToolsAsync()
+ {
+ // Define the agent
+ AzureAIClientProvider clientProvider = this.GetAzureProvider();
+ AgentsClient client = clientProvider.Client.GetAgentsClient();
+
+ // In this sample the function tools are added to the agent this is
+ // important if you want to retrieve the agent later and then dynamically check
+ // what function tools it requires.
+ KernelPlugin plugin = KernelPluginFactory.CreateFromType();
+ var tools = plugin.Select(f => f.ToToolDefinition(plugin.Name));
+
+ Agent definition = await client.CreateAgentAsync(
+ model: TestConfiguration.AzureAI.ChatModelId,
+ name: HostName,
+ description: null,
+ instructions: HostInstructions,
+ tools: tools);
+ Microsoft.SemanticKernel.Agents.AzureAI.AzureAIAgent agent = new(definition, clientProvider)
+ {
+ Kernel = new Kernel(),
+ };
+
+ // Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
+ agent.Kernel.Plugins.Add(plugin);
+
+ // Create a thread for the agent conversation.
+ AgentThread thread = await client.CreateThreadAsync(metadata: AssistantSampleMetadata);
+
+ // Respond to user input
+ try
+ {
+ await InvokeAgentAsync("Hello");
+ await InvokeAgentAsync("What is the special soup and its price?");
+ await InvokeAgentAsync("What is the special drink and its price?");
+ await InvokeAgentAsync("Thank you");
+ }
+ finally
+ {
+ await client.DeleteThreadAsync(thread.Id);
+ await client.DeleteAgentAsync(agent.Id);
+ }
+
+ // Local function to invoke agent and display the conversation messages.
+ async Task InvokeAgentAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ await agent.AddChatMessageAsync(thread.Id, message);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(thread.Id))
+ {
+ this.WriteAgentChatMessage(response);
+ }
+ }
+ }
+
+ private sealed class MenuPlugin
+ {
+ [KernelFunction, Description("Provides a list of specials from the menu.")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
+ public string GetSpecials() =>
+ """
+ Special Soup: Clam Chowder
+ Special Salad: Cobb Salad
+ Special Drink: Chai Tea
+ """;
+
+ [KernelFunction, Description("Provides the price of the requested menu item.")]
+ public string GetItemPrice(
+ [Description("The name of the menu item.")]
+ string menuItem) =>
+ "$9.99";
+ }
+}
diff --git a/dotnet/src/Agents/AzureAI/Extensions/KernelFunctionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/KernelFunctionExtensions.cs
index 0606b73c9cd5..e6b9c722eabb 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/KernelFunctionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/KernelFunctionExtensions.cs
@@ -4,7 +4,10 @@
namespace Microsoft.SemanticKernel.Agents.AzureAI;
-internal static class KernelFunctionExtensions
+///
+/// Extensions for to support Azure AI specific operations.
+///
+public static class KernelFunctionExtensions
{
///
/// Convert to an OpenAI tool model.
diff --git a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
index 3bab33429860..d832f36fb069 100644
--- a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
+++ b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
@@ -149,11 +149,18 @@ public static async IAsyncEnumerable GetMessagesAsync(Agents
{
logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId);
- ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))];
+ List tools = new(agent.Definition.Tools);
+
+ // Add unique functions from the Kernel which are not already present in the agent's tools
+ var functionToolNames = new HashSet(tools.OfType().Select(t => t.Name));
+ var functionTools = kernel.Plugins
+ .SelectMany(kp => kp.Select(kf => kf.ToToolDefinition(kp.Name)))
+ .Where(tool => !functionToolNames.Contains(tool.Name));
+ tools.AddRange(functionTools);
string? instructions = await agent.GetInstructionsAsync(kernel, arguments, cancellationToken).ConfigureAwait(false);
- ThreadRun run = await client.CreateAsync(threadId, agent, instructions, tools, invocationOptions, cancellationToken).ConfigureAwait(false);
+ ThreadRun run = await client.CreateAsync(threadId, agent, instructions, [.. tools], invocationOptions, cancellationToken).ConfigureAwait(false);
logger.LogAzureAIAgentCreatedRun(nameof(InvokeAsync), run.Id, threadId);