Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,37 @@ public async Task UseAzureAgentWithPluginEnumParameter()
}
}

[Fact]
public async Task UseAzureAgentWithPromptFunction()
{
// Define prompt function
KernelFunction promptFunction =
KernelFunctionFactory.CreateFromPrompt(
promptTemplate:
"""
Count the number of vowels in INPUT and report as a markdown table.

INPUT:
{{$input}}
""",
description: "Counts the number of vowels");

// Define the agent
AzureAIAgent agent =
await CreateAzureAgentAsync(
KernelPluginFactory.CreateFromFunctions("AgentPlugin", [promptFunction]),
instructions: "You job is to only and always analyze the vowels in the user input without confirmation.");

// Add a filter to the agent's kernel to log function invocations.
agent.Kernel.FunctionInvocationFilters.Add(new PromptFunctionFilter());

// Create the chat history thread to capture the agent interaction.
AzureAIAgentThread thread = new(agent.Client);

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Who would know naught of art must learn, act, and then take his ease.");
}

private async Task<AzureAIAgent> CreateAzureAgentAsync(KernelPlugin plugin, string? instructions = null, string? name = null)
{
// Define the agent
Expand All @@ -71,7 +102,11 @@ private async Task<AzureAIAgent> CreateAzureAgentAsync(KernelPlugin plugin, stri
null,
instructions);

AzureAIAgent agent = new(definition, this.Client);
AzureAIAgent agent =
new(definition, this.Client)
{
Kernel = this.CreateKernelWithChatCompletion(),
};

// Add to the agent's Kernel
if (plugin != null)
Expand All @@ -93,4 +128,14 @@ private async Task InvokeAgentAsync(AzureAIAgent agent, AgentThread thread, stri
this.WriteAgentChatMessage(response);
}
}

private sealed class PromptFunctionFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"\nINVOKING: {context.Function.Name}");
await next.Invoke(context);
System.Console.WriteLine($"\nRESULT: {context.Result}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public MenuItem[] GetMenu()
[KernelFunction, Description("Provides a list of specials from the menu.")]
public MenuItem[] GetSpecials()
{
return s_menuItems.Where(i => i.IsSpecial).ToArray();
return [.. s_menuItems.Where(i => i.IsSpecial)];
}

[KernelFunction, Description("Provides the price of the requested menu item.")]
Expand Down
108 changes: 93 additions & 15 deletions dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public async Task UseChatCompletionWithPlugin(bool useChatClient)
name: "Host",
useChatClient: useChatClient);

/// Create the chat history thread to capture the agent interaction.
AgentThread thread = new ChatHistoryAgentThread();
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Hello");
Expand All @@ -45,13 +45,46 @@ public async Task UseChatCompletionWithPluginEnumParameter(bool useChatClient)
KernelPluginFactory.CreateFromType<WidgetFactory>(),
useChatClient: useChatClient);

/// Create the chat history thread to capture the agent interaction.
AgentThread thread = new ChatHistoryAgentThread();
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithPromptFunction(bool useChatClient)
{
// Define prompt function
KernelFunction promptFunction =
KernelFunctionFactory.CreateFromPrompt(
promptTemplate:
"""
Count the number of vowels in INPUT and report as a markdown table.

INPUT:
{{$input}}
""",
description: "Counts the number of vowels");

// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
KernelPluginFactory.CreateFromFunctions("AgentPlugin", [promptFunction]),
instructions: "You job is to only and always analyze the vowels in the user input without confirmation.",
useChatClient: useChatClient);

// Add a filter to the agent's kernel to log function invocations.
agent.Kernel.FunctionInvocationFilters.Add(new PromptFunctionFilter());

// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Who would know naught of art must learn, act, and then take his ease.");
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand All @@ -72,45 +105,90 @@ public async Task UseChatCompletionWithTemplateExecutionSettings(bool useChatCli

agent.Kernel.Plugins.AddFromType<WidgetFactory>();

/// Create the chat history thread to capture the agent interaction.
AgentThread thread = new ChatHistoryAgentThread();
// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "Create a beautiful red colored widget for me.");

chatClient?.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task UseChatCompletionWithManualFunctionCalling(bool useChatClient)
{
// Define the agent
ChatCompletionAgent agent = CreateAgentWithPlugin(
KernelPluginFactory.CreateFromType<MenuPlugin>(),
functionChoiceBehavior: FunctionChoiceBehavior.Auto(autoInvoke: false),
useChatClient: useChatClient);

/// Create the chat history thread to capture the agent interaction.
ChatHistoryAgentThread thread = new();

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, thread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, thread, "What is the special drink and its price?");
}

private ChatCompletionAgent CreateAgentWithPlugin(
KernelPlugin plugin,
string? instructions = null,
string? name = null,
FunctionChoiceBehavior? functionChoiceBehavior = null,
bool useChatClient = false)
{
ChatCompletionAgent agent =
new()
{
Instructions = instructions,
Name = name,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out _),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};
new()
{
Instructions = instructions,
Name = name,
Kernel = this.CreateKernelWithChatCompletion(useChatClient, out _),
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = functionChoiceBehavior ?? FunctionChoiceBehavior.Auto() }),
};

// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(plugin);

return agent;
}

// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(ChatCompletionAgent agent, AgentThread thread, string input)
private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistoryAgentThread thread, string input)
{
ChatMessageContent message = new(AuthorRole.User, input);
this.WriteAgentChatMessage(message);

await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
{
this.WriteAgentChatMessage(response);

Task<FunctionResultContent>[] functionResults = await ProcessFunctionCalls(response, agent.Kernel).ToArrayAsync();
thread.ChatHistory.Add(response);
foreach (ChatMessageContent functionResult in functionResults.Select(result => result.Result.ToChatMessage()))
{
this.WriteAgentChatMessage(functionResult);
thread.ChatHistory.Add(functionResult);
}
}
}

private async IAsyncEnumerable<Task<FunctionResultContent>> ProcessFunctionCalls(ChatMessageContent response, Kernel kernel)
{
foreach (FunctionCallContent functionCall in response.Items.OfType<FunctionCallContent>())
{
yield return functionCall.InvokeAsync(kernel);
}
}

private sealed class PromptFunctionFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
System.Console.WriteLine($"INVOKING: {context.Function.Name}");
await next.Invoke(context);
System.Console.WriteLine($"RESULT: {context.Result}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ protected IChatClient AddChatClientToKernel(IKernelBuilder builder)
.AsIChatClient();
}

var functionCallingChatClient = chatClient!.AsBuilder().UseKernelFunctionInvocation().Build();
builder.Services.AddTransient<IChatClient>((sp) => functionCallingChatClient);
IChatClient functionCallingChatClient = chatClient.AsBuilder().UseKernelFunctionInvocation().Build();
builder.Services.AddSingleton(functionCallingChatClient);
return functionCallingChatClient;
#pragma warning restore CA2000 // Dispose objects before losing scope
}
Expand Down
Loading