Skip to content

.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

Merged
Show file tree
Hide file tree
Changes from 4 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
168 changes: 168 additions & 0 deletions dotnet/samples/GettingStartedWithAgents/Step08_AgentHandOff.cs
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);
}
}
88 changes: 88 additions & 0 deletions dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs
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.
/// </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

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'

Check failure on line 23 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'End tag 'param' does not match the start tag 'see'.'
/// <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

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, ubuntu-latest, Release, true, integration)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Debug)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'

Check failure on line 29 in dotnet/src/Agents/Core/Functions/AgentKernelFunctionFactory.cs

View workflow job for this annotation

GitHub Actions / dotnet-build-and-test (8.0, windows-latest, Release)

XML comment has badly formed XML -- 'Expected an end tag for element 'param'.'
[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));

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>>) },
};

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
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@ public static string ProcessFunctionResult(object functionResult)
return chatMessageContent.ToString();
}

// Same optimization but for a enumerable of CjatMessageContent
if (functionResult is IEnumerable<ChatMessageContent> chatMessageContents)
{
return string.Join(",", chatMessageContents.Select(c => c.ToString()));
}

return JsonSerializer.Serialize(functionResult, s_functionResultSerializerOptions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public abstract class BaseTest : TextWriter
/// and <see cref="TestConfiguration.AzureOpenAI"/> are defined.
/// If 'false', Azure takes precedence.
/// </summary>
protected virtual bool ForceOpenAI { get; } = false;
protected virtual bool ForceOpenAI { get; set; } = false;

protected ITestOutputHelper Output { get; }

Expand Down
Loading