-
Notifications
You must be signed in to change notification settings - Fork 4k
.Net: Add an experimental AgentKernelFunctionFactory #11136
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
Changes from 4 commits
03fd50b
298aabc
f8cf996
a05fcaf
7bbcc0a
d8e11e3
baa8922
46582af
f72f3e3
8f8dfa2
67eb374
3f086a8
62d4bf8
fc716e5
a39f401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
using System.ComponentModel; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Agents; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
|
||
namespace GettingStarted; | ||
|
||
/// <summary> | ||
/// Demonstrate creation of <see cref="ChatCompletionAgent"/> and | ||
/// eliciting its response to three explicit user messages. | ||
/// </summary> | ||
public class Step08_AgentHandOff : BaseAgentsTest | ||
{ | ||
public Step08_AgentHandOff(ITestOutputHelper output) : base(output) | ||
{ | ||
this.ForceOpenAI = true; | ||
} | ||
|
||
[Fact] | ||
public async Task SalesAssistantAgentAsync() | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
kernel.Plugins.AddFromType<OrderPlugin>(); | ||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
// Define the agent | ||
ChatCompletionAgent agent = | ||
new() | ||
{ | ||
Name = "SalesAssistant", | ||
Instructions = "You are a sales assistant. Place orders for items the user requests.", | ||
Kernel = kernel, | ||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), | ||
}; | ||
|
||
// Invoke the agent and display the responses | ||
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "Place an order for a black boot.")); | ||
await foreach (ChatMessageContent responseItem in responseItems) | ||
{ | ||
this.WriteAgentChatMessage(responseItem); | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task RefundAgentAsync() | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
kernel.Plugins.AddFromType<RefundPlugin>(); | ||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
// Define the agent | ||
ChatCompletionAgent agent = | ||
new() | ||
{ | ||
Name = "RefundAgent", | ||
Instructions = "You are a refund agent. Help the user with refunds.", | ||
Kernel = kernel, | ||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), | ||
}; | ||
|
||
// Invoke the agent and display the responses | ||
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "I want a refund for a black boot.")); | ||
await foreach (ChatMessageContent responseItem in responseItems) | ||
{ | ||
this.WriteAgentChatMessage(responseItem); | ||
} | ||
} | ||
|
||
[Fact] | ||
public async Task MultipleAgentsAsync() | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
var agentPlugin = KernelPluginFactory.CreateFromFunctions("AgentPlugin", | ||
[ | ||
AgentKernelFunctionFactory.CreateFromAgent(this.CreateSalesAssistant()), | ||
AgentKernelFunctionFactory.CreateFromAgent(this.CreateRefundAgent()) | ||
]); | ||
kernel.Plugins.Add(agentPlugin); | ||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
// Define the agent | ||
ChatCompletionAgent agent = | ||
new() | ||
{ | ||
Name = "ShoppingAssistant", | ||
Instructions = "You are a sales assistant. Delegate to the provided agents to help the user with placing orders and requesting refunds.", | ||
Kernel = kernel, | ||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), | ||
}; | ||
|
||
// Invoke the agent and display the responses | ||
string[] messages = | ||
[ | ||
"Place an order for a black boot.", | ||
"Now I want a refund for the black boot." | ||
]; | ||
|
||
AgentThread? agentThread = null; | ||
foreach (var message in messages) | ||
{ | ||
var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, message), agentThread); | ||
await foreach (var responseItem in responseItems) | ||
{ | ||
agentThread = responseItem.Thread; | ||
this.WriteAgentChatMessage(responseItem.Message); | ||
} | ||
} | ||
} | ||
|
||
#region private | ||
private ChatCompletionAgent CreateSalesAssistant() | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
kernel.Plugins.AddFromType<OrderPlugin>(); | ||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
// Define the agent | ||
return new() | ||
{ | ||
Name = "SalesAssistant", | ||
Instructions = "You are a sales assistant. Place orders for items the user requests.", | ||
Description = "Agent to invoke to place orders for items the user requests.", | ||
Kernel = kernel, | ||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), | ||
}; | ||
} | ||
|
||
private ChatCompletionAgent CreateRefundAgent() | ||
{ | ||
Kernel kernel = this.CreateKernelWithChatCompletion(); | ||
kernel.Plugins.AddFromType<RefundPlugin>(); | ||
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
// Define the agent | ||
return new() | ||
{ | ||
Name = "RefundAgent", | ||
Instructions = "You are a refund agent. Help the user with refunds.", | ||
Description = "Agent to invoke to execute a refund an item on behalf of the user.", | ||
Kernel = kernel, | ||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), | ||
}; | ||
} | ||
#endregion | ||
} | ||
|
||
public sealed class OrderPlugin | ||
{ | ||
[KernelFunction, Description("Place an order for the specified item.")] | ||
public string PlaceOrder([Description("The name of the item to be ordered.")] string itemName) => "success"; | ||
} | ||
|
||
public sealed class RefundPlugin | ||
{ | ||
[KernelFunction, Description("Execute a refund for the specified item.")] | ||
public string ExecuteRefund(string itemName) => "success"; | ||
} | ||
|
||
public sealed class AutoFunctionInvocationFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter | ||
{ | ||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next) | ||
{ | ||
output.WriteLine($"Invoke: {context.Function.Name}"); | ||
|
||
await next(context); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.SemanticKernel.Agents.Extensions; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
|
||
namespace Microsoft.SemanticKernel.Agents; | ||
|
||
/// <summary> | ||
/// Provides factory methods for creating implementations of <see cref="KernelFunction"/> backed by an <see cref="Agent" />. | ||
/// </summary> | ||
[Experimental("SKEXP0110")] | ||
public static class AgentKernelFunctionFactory | ||
{ | ||
/// <summary> | ||
/// Creates a <see cref="KernelFunction"/> instance for a method, specified via a delegate. | ||
markwallace-microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// </summary> | ||
/// <param name="agent">The <see cref="Agent"> to be represented via the created <see cref="KernelFunction"/>.</param> | ||
Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs
|
||
/// <param name="functionName">The name to use for the function. If null, it will default to the agent name.</param> | ||
/// <param name="description">The description to use for the function. If null, it will default to agent description.</param> | ||
/// <param name="parameters">Optional parameter descriptions. If null, it will default to one derived from the method represented by <paramref name="method"/>.</param> | ||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param> | ||
/// <returns>The created <see cref="KernelFunction"/> for invoking <paramref name="method"/>.</returns> | ||
[RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] | ||
Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs
|
||
[RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] | ||
public static KernelFunction CreateFromAgent( | ||
Agent agent, | ||
string? functionName = null, | ||
string? description = null, | ||
IEnumerable<KernelParameterMetadata>? parameters = null, | ||
KernelReturnParameterMetadata? returnParameter = null, | ||
ILoggerFactory? loggerFactory = null) | ||
{ | ||
Verify.NotNull(agent, nameof(agent)); | ||
markwallace-microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
async Task<FunctionResult> InvokeAgentAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken) | ||
{ | ||
arguments.TryGetValue("query", out var query); | ||
var queryString = query?.ToString() ?? string.Empty; | ||
|
||
AgentInvokeOptions? options = null; | ||
|
||
if (arguments.TryGetValue("instructions", out var instructions) && instructions is not null) | ||
{ | ||
options = new() | ||
{ | ||
AdditionalInstructions = instructions?.ToString() ?? string.Empty | ||
}; | ||
} | ||
|
||
var response = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, queryString), null, options, cancellationToken); | ||
var responseItems = await response.ToArrayAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | ||
var chatMessages = responseItems.Select(i => i.Message).ToArray(); | ||
return new FunctionResult(function, chatMessages, kernel.Culture); | ||
} | ||
|
||
KernelFunctionFromMethodOptions options = new() | ||
{ | ||
FunctionName = functionName ?? agent.GetName(), | ||
Description = description ?? agent.Description, | ||
Parameters = parameters ?? GetDefaultKernelParameterMetadata(), | ||
ReturnParameter = new() { ParameterType = typeof(IAsyncEnumerable<AgentResponseItem<ChatMessageContent>>) }, | ||
markwallace-microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
return KernelFunctionFactory.CreateFromMethod( | ||
InvokeAgentAsync, | ||
options); | ||
} | ||
|
||
#region private | ||
[RequiresUnreferencedCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] | ||
[RequiresDynamicCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] | ||
private static IEnumerable<KernelParameterMetadata> GetDefaultKernelParameterMetadata() | ||
{ | ||
return s_kernelParameterMetadata ??= [ | ||
new KernelParameterMetadata("query") { Description = "Available information that will guide in performing this operation.", ParameterType = typeof(string), IsRequired = true }, | ||
new KernelParameterMetadata("instructions") { Description = "Additional instructions for the agent.", ParameterType = typeof(string), IsRequired = true }, | ||
]; | ||
} | ||
|
||
private static IEnumerable<KernelParameterMetadata>? s_kernelParameterMetadata; | ||
#endregion | ||
} |
Uh oh!
There was an error while loading. Please reload this page.