Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Avoid sending duplicate function tools when creating a thread #10413

Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// This example demonstrates similarity between using <see cref="AzureAIAgent"/>
/// and <see cref="ChatCompletionAgent"/> (see: Step 2).
/// </summary>
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<MenuPlugin>();
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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace Microsoft.SemanticKernel.Agents.AzureAI;

internal static class KernelFunctionExtensions
/// <summary>
/// Extensions for <see cref="KernelFunction"/> to support Azure AI specific operations.
/// </summary>
public static class KernelFunctionExtensions
{
/// <summary>
/// Convert <see cref="KernelFunction"/> to an OpenAI tool model.
Expand Down
11 changes: 9 additions & 2 deletions dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,18 @@ public static async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync(Agents
{
logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId);

ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))];
List<ToolDefinition> 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<string>(tools.OfType<FunctionToolDefinition>().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);

Expand Down
Loading