[.Net]: Introduce ChatCompletionAgent to AutoGen.SemanticKernel package (#2584)

* WIP add SKAgent to proj

* Fix Unit test

* Remove accidental coommit

* Add version props

* Revert Kludge test changes

* PR comments : executionSettings and use / upgrade SemanticKernelExperimentalVersion

* Add back deleted api and constructor, mark as Obsolete

* PR feedback : Introduce SemanticKernelChatCompletionAgent. Add unit tests and refactor semanticKernelChatMessageContentConnector to be SkSequentialChatMessageContentConnector.cs

* Revert SkSequentialChatMessageContentConnector

* PR comments, remove systemMessage in SemanticKernelChatCompletionAgent

* Fix formatting

* Fix bad merge

* Revert "Fix bad merge"

This reverts commit a189ad9f42.

* Remove accidental commit

---------

Co-authored-by: luongdavid <luongdavid@microsoft.com>
This commit is contained in:
David Luong 2024-05-08 23:50:36 -04:00 committed by GitHub
parent 5be103ab6b
commit b529fe21a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 169 additions and 7 deletions

View File

@ -2,8 +2,8 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AzureOpenAIVersion>1.0.0-beta.17</AzureOpenAIVersion>
<SemanticKernelVersion>1.7.1</SemanticKernelVersion>
<SemanticKernelExperimentalVersion>1.7.1-alpha</SemanticKernelExperimentalVersion>
<SemanticKernelVersion>1.10.0</SemanticKernelVersion>
<SemanticKernelExperimentalVersion>1.10.0-alpha</SemanticKernelExperimentalVersion>
<SystemCodeDomVersion>5.0.0</SystemCodeDomVersion>
<MicrosoftCodeAnalysisVersion>4.3.0</MicrosoftCodeAnalysisVersion>
<ApprovalTestVersion>6.0.0</ApprovalTestVersion>

View File

@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS8981;CS8600;CS8602;CS8604;CS8618;CS0219;SKEXP0054;SKEXP0050</NoWarn>
<NoWarn>$(NoWarn);CS8981;CS8600;CS8602;CS8604;CS8618;CS0219;SKEXP0054;SKEXP0050;SKEXP0110</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -98,5 +98,4 @@ public class SemanticKernelCodeSnippet
}
#endregion register_semantic_kernel_chat_message_content_connector
}
}

View File

@ -5,6 +5,10 @@
<RootNamespace>AutoGen.SemanticKernel</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);SKEXP0110</NoWarn>
</PropertyGroup>
<Import Project="$(RepoRoot)/nuget/nuget-package.props" />
<PropertyGroup>
@ -18,6 +22,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="$(AzureOpenAIVersion)" />
<PackageReference Include="Microsoft.SemanticKernel" Version="$(SemanticKernelVersion)" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" Version="$(SemanticKernelExperimentalVersion)" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SemanticKernelChatCompletionAgent.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
namespace AutoGen.SemanticKernel;
public class SemanticKernelChatCompletionAgent : IAgent
{
public string Name { get; }
private readonly ChatCompletionAgent _chatCompletionAgent;
public SemanticKernelChatCompletionAgent(ChatCompletionAgent chatCompletionAgent)
{
this.Name = chatCompletionAgent.Name ?? throw new ArgumentNullException(nameof(chatCompletionAgent.Name));
this._chatCompletionAgent = chatCompletionAgent;
}
public async Task<IMessage> GenerateReplyAsync(IEnumerable<IMessage> messages, GenerateReplyOptions? options = null,
CancellationToken cancellationToken = default)
{
ChatMessageContent[] reply = await _chatCompletionAgent
.InvokeAsync(BuildChatHistory(messages), cancellationToken)
.ToArrayAsync(cancellationToken: cancellationToken);
return reply.Length > 1
? throw new InvalidOperationException("ResultsPerPrompt greater than 1 is not supported in this semantic kernel agent")
: new MessageEnvelope<ChatMessageContent>(reply[0], from: this.Name);
}
private ChatHistory BuildChatHistory(IEnumerable<IMessage> messages)
{
return new ChatHistory(ProcessMessage(messages));
}
private IEnumerable<ChatMessageContent> ProcessMessage(IEnumerable<IMessage> messages)
{
return messages.Select(m => m switch
{
IMessage<ChatMessageContent> cmc => cmc.Content,
_ => throw new ArgumentException("Invalid message type")
});
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>$(TestTargetFramework)</TargetFramework>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>$(NoWarn);xUnit1013</NoWarn>
<NoWarn>$(NoWarn);xUnit1013;SKEXP0110</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -8,7 +8,9 @@ using AutoGen.SemanticKernel;
using AutoGen.SemanticKernel.Extension;
using FluentAssertions;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace AutoGen.Tests;
@ -69,8 +71,7 @@ public partial class SemanticKernelAgentTest
var messages = new IMessage[]
{
MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")),
new TextMessage(Role.Assistant, "Hello", from: "user"),
new MultiModalMessage(Role.Assistant,
new TextMessage(Role.Assistant, "Hello", from: "user"), new MultiModalMessage(Role.Assistant,
[
new TextMessage(Role.Assistant, "Hello", from: "user"),
],
@ -128,4 +129,110 @@ public partial class SemanticKernelAgentTest
reply.GetContent()!.ToLower().Should().Contain("seattle");
reply.GetContent()!.ToLower().Should().Contain("sunny");
}
[ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")]
public async Task BasicSkChatCompletionAgentConversationTestAsync()
{
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable.");
var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable.");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key);
var kernel = builder.Build();
var agent = new ChatCompletionAgent()
{
Kernel = kernel,
Name = "assistant",
Instructions = "You are a helpful AI assistant"
};
var skAgent = new SemanticKernelChatCompletionAgent(agent);
var chatMessageContent = MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello"));
var reply = await skAgent.SendAsync(chatMessageContent);
reply.Should().BeOfType<MessageEnvelope<ChatMessageContent>>();
reply.As<MessageEnvelope<ChatMessageContent>>().From.Should().Be("assistant");
}
[ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")]
public async Task SkChatCompletionAgentChatMessageContentConnectorTestAsync()
{
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable.");
var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable.");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key);
var kernel = builder.Build();
var connector = new SemanticKernelChatMessageContentConnector();
var agent = new ChatCompletionAgent()
{
Kernel = kernel,
Name = "assistant",
Instructions = "You are a helpful AI assistant"
};
var skAgent = new SemanticKernelChatCompletionAgent(agent)
.RegisterMiddleware(connector);
var messages = new IMessage[]
{
MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")),
new TextMessage(Role.Assistant, "Hello", from: "user"), new MultiModalMessage(Role.Assistant,
[
new TextMessage(Role.Assistant, "Hello", from: "user"),
],
from: "user"),
};
foreach (var message in messages)
{
var reply = await skAgent.SendAsync(message);
reply.Should().BeOfType<TextMessage>();
reply.As<TextMessage>().From.Should().Be("assistant");
}
}
[ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")]
public async Task SkChatCompletionAgentPluginTestAsync()
{
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable.");
var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable.");
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key);
var parameters = this.GetWeatherAsyncFunctionContract.Parameters!.Select(p => new KernelParameterMetadata(p.Name!)
{
Description = p.Description,
DefaultValue = p.DefaultValue,
IsRequired = p.IsRequired,
ParameterType = p.ParameterType,
});
var function = KernelFunctionFactory.CreateFromMethod(this.GetWeatherAsync, this.GetWeatherAsyncFunctionContract.Name, this.GetWeatherAsyncFunctionContract.Description, parameters);
builder.Plugins.AddFromFunctions("plugins", [function]);
var kernel = builder.Build();
var agent = new ChatCompletionAgent()
{
Kernel = kernel,
Name = "assistant",
Instructions = "You are a helpful AI assistant",
ExecutionSettings =
new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
}
};
var skAgent =
new SemanticKernelChatCompletionAgent(agent).RegisterMiddleware(
new SemanticKernelChatMessageContentConnector());
var question = "What is the weather in Seattle?";
var reply = await skAgent.SendAsync(question);
reply.GetContent()!.ToLower().Should().Contain("seattle");
reply.GetContent()!.ToLower().Should().Contain("sunny");
}
}