diff --git a/examples/dotnet/dotnet-03-simple-chatbot/ConnectorExtensions.cs b/examples/dotnet/dotnet-03-simple-chatbot/ConnectorExtensions.cs new file mode 100644 index 00000000..882df91a --- /dev/null +++ b/examples/dotnet/dotnet-03-simple-chatbot/ConnectorExtensions.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticWorkbench.Connector; + +namespace AgentExample; + +public static class ConnectorExtensions +{ + // TODO: the list of participants is incomplete, because agents see only participants being added + public static string GetParticipantName(this Conversation conversation, string id) + { + if (conversation.Participants.TryGetValue(id, out Participant? participant)) + { + return participant.Name; + } + + return "Unknown"; + } + + public static ChatHistory ToSemanticKernelChatHistory( + this Conversation conversation, + string assistantId, + string systemPrompt) + { + var result = new ChatHistory(systemPrompt); + + foreach (Message msg in conversation.Messages) + { + if (msg.Sender.Id == assistantId) + { + result.AddAssistantMessage(msg.Content!); + } + else + { + result.AddUserMessage( + $"[{conversation.GetParticipantName(msg.Sender.Id)}] {msg.Content}"); + } + } + + return result; + } + + public static string ToHtmlString( + this Conversation conversation, + string assistantId) + { + var result = new StringBuilder(); + result.AppendLine(""); + result.AppendLine("
"); + + foreach (var msg in conversation.Messages) + { + result.AppendLine("

"); + if (msg.Sender.Id == assistantId) + { + result.AppendLine("Assistant
"); + } + else + { + result + .Append("") + .Append(conversation.GetParticipantName(msg.Sender.Id)) + .AppendLine("
"); + } + + result.AppendLine(msg.Content).AppendLine("

"); + } + + result.Append("
"); + + return result.ToString(); + } +} diff --git a/examples/dotnet/dotnet-03-simple-chatbot/MyAgent.cs b/examples/dotnet/dotnet-03-simple-chatbot/MyAgent.cs index 926d7a15..a25ede73 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/MyAgent.cs +++ b/examples/dotnet/dotnet-03-simple-chatbot/MyAgent.cs @@ -171,7 +171,7 @@ private async Task PrepareAnswerAsync(Conversation conversation, Messag if (inputIsSafe) { - var chatHistory = conversation.ToChatHistory(this.Id, this.Config.RenderSystemPrompt()); + var chatHistory = conversation.ToSemanticKernelChatHistory(this.Id, this.Config.RenderSystemPrompt()); debugInfo.Add("lastChatMsg", chatHistory.Last().Content); // Show chat history in workbench side panel diff --git a/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs b/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs index ae1597f5..e22a2629 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs +++ b/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs @@ -5,7 +5,7 @@ namespace AgentExample; -public class MyAgentConfig : AgentConfig +public class MyAgentConfig : AgentConfigBase { // Define safety and behavioral guardrails. // See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message for more information and examples. diff --git a/libraries/dotnet/WorkbenchConnector/AgentBase.cs b/libraries/dotnet/WorkbenchConnector/AgentBase.cs index 45a89166..4cacdc36 100644 --- a/libraries/dotnet/WorkbenchConnector/AgentBase.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentBase.cs @@ -10,7 +10,7 @@ namespace Microsoft.SemanticWorkbench.Connector; public abstract class AgentBase : IAgentBase - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { // Agent instance ID public string Id { get; protected set; } = string.Empty; diff --git a/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigBase.cs similarity index 75% rename from libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs rename to libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigBase.cs index a54746be..739d0b9b 100644 --- a/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigBase.cs @@ -1,19 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; using System.Reflection; // ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; -public class AgentConfig : IAgentConfig +public abstract class AgentConfigBase { - public AgentConfig() - { - } - - public object? ToWorkbenchFormat() + public object ToWorkbenchFormat() { Dictionary result = new(); Dictionary defs = new(); @@ -60,22 +55,4 @@ public AgentConfig() return result; } - - public void Update(object? config) - { - if (config == null) - { - throw new ArgumentException("Empty configuration"); - } - - if (config is not AgentConfig cfg) - { - throw new ArgumentException("Incompatible configuration type"); - } - - foreach (var property in this.GetType().GetProperties()) - { - property.SetValue(this, property.GetValue(cfg)); - } - } } diff --git a/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs deleted file mode 100644 index 5f0f5912..00000000 --- a/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -// ReSharper disable once CheckNamespace -namespace Microsoft.SemanticWorkbench.Connector; - -public interface IAgentConfig -{ - public object? ToWorkbenchFormat(); - public void Update(object? config); -} diff --git a/libraries/dotnet/WorkbenchConnector/Models/ChatHistoryExt.cs b/libraries/dotnet/WorkbenchConnector/Models/ChatHistoryExt.cs deleted file mode 100644 index 7af37e02..00000000 --- a/libraries/dotnet/WorkbenchConnector/Models/ChatHistoryExt.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Text; -using Microsoft.SemanticKernel.ChatCompletion; - -// ReSharper disable once CheckNamespace -namespace Microsoft.SemanticWorkbench.Connector; - -public static class ChatHistoryExt -{ - public static string AsString(this ChatHistory history) - { - var result = new StringBuilder(); - foreach (var msg in history) - { - result.Append("[**").Append(msg.Role).Append("**] "); - result.AppendLine(msg.Content); - } - - return result.ToString(); - } -} diff --git a/libraries/dotnet/WorkbenchConnector/Models/Conversation.cs b/libraries/dotnet/WorkbenchConnector/Models/Conversation.cs index eb99e4e0..e437ff26 100644 --- a/libraries/dotnet/WorkbenchConnector/Models/Conversation.cs +++ b/libraries/dotnet/WorkbenchConnector/Models/Conversation.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.Json.Serialization; -using Microsoft.SemanticKernel.ChatCompletion; // ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; @@ -60,66 +58,4 @@ public void RemoveMessage(Message? msg) this.Messages = this.Messages.Where(x => x.Id != msg.Id).ToList(); } - - public ChatHistory ToChatHistory(string assistantId, string systemPrompt) - { - var result = new ChatHistory(systemPrompt); - - foreach (Message msg in this.Messages) - { - if (msg.Sender.Id == assistantId) - { - result.AddAssistantMessage(msg.Content!); - } - else - { - result.AddUserMessage($"[{this.GetParticipantName(msg.Sender.Id)}] {msg.Content}"); - } - } - - return result; - } - - public string ToHtmlString(string assistantId) - { - var result = new StringBuilder(); - result.AppendLine(""); - result.AppendLine("
"); - - foreach (var msg in this.Messages) - { - result.AppendLine("

"); - if (msg.Sender.Id == assistantId) - { - result.AppendLine("Assistant
"); - } - else - { - result - .Append("") - .Append(this.GetParticipantName(msg.Sender.Id)) - .AppendLine("
"); - } - - result.AppendLine(msg.Content).AppendLine("

"); - } - - result.Append("
"); - - return result.ToString(); - } - - // TODO: the list of participants is incomplete, because agents see only participants being added - private string GetParticipantName(string id) - { - if (this.Participants.TryGetValue(id, out Participant? participant)) - { - return participant.Name; - } - - return "Unknown"; - } } diff --git a/libraries/dotnet/WorkbenchConnector/Models/ServiceInfo.cs b/libraries/dotnet/WorkbenchConnector/Models/ServiceInfo.cs index 8468dc9b..9d1e2402 100644 --- a/libraries/dotnet/WorkbenchConnector/Models/ServiceInfo.cs +++ b/libraries/dotnet/WorkbenchConnector/Models/ServiceInfo.cs @@ -7,10 +7,8 @@ namespace Microsoft.SemanticWorkbench.Connector; public class ServiceInfo(TAgentConfig cfg) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { - private readonly TAgentConfig _cfg = cfg; - [JsonPropertyName("assistant_service_id")] public string ServiceId { get; set; } = string.Empty; @@ -24,5 +22,5 @@ public class ServiceInfo(TAgentConfig cfg) public Dictionary Metadata { get; set; } = new(); [JsonPropertyName("default_config")] - public object DefaultConfiguration => this._cfg.ToWorkbenchFormat() ?? new(); + public object DefaultConfiguration => cfg.ToWorkbenchFormat() ?? new(); } diff --git a/libraries/dotnet/WorkbenchConnector/Webservice.cs b/libraries/dotnet/WorkbenchConnector/Webservice.cs index cd3a29f2..811a7fe7 100644 --- a/libraries/dotnet/WorkbenchConnector/Webservice.cs +++ b/libraries/dotnet/WorkbenchConnector/Webservice.cs @@ -27,7 +27,7 @@ private sealed class SemanticWorkbenchWebservice public static WorkbenchConnector UseAgentWebservice( this IEndpointRouteBuilder builder, string endpoint, bool enableCatchAll = false) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { WorkbenchConnector? workbenchConnector = builder.ServiceProvider.GetService>(); if (workbenchConnector == null) @@ -60,7 +60,7 @@ public static WorkbenchConnector UseAgentWebservice( // Return service details and default agent configuration public static IEndpointRouteBuilder UseFetchServiceInfo( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapGet(prefix + "/", ( [FromServicesAttribute] WorkbenchConnector workbenchConnector, @@ -76,7 +76,7 @@ public static IEndpointRouteBuilder UseFetchServiceInfo( // Create new agent instance public static IEndpointRouteBuilder UseCreateAgentEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapPut(prefix + "/{agentId}", async ( @@ -110,7 +110,7 @@ await workbenchConnector.CreateAgentAsync(agentId, name, null, cancellationToken // Delete agent instance public static IEndpointRouteBuilder UseDeleteAgentEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapDelete(prefix + "/{agentId}", async ( @@ -130,7 +130,7 @@ public static IEndpointRouteBuilder UseDeleteAgentEndpoint( // Fetch agent configuration public static IEndpointRouteBuilder UseFetchAgentConfigEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapGet(prefix + "/{agentId}/config", ( @@ -155,7 +155,7 @@ public static IEndpointRouteBuilder UseFetchAgentConfigEndpoint( // Save agent configuration public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapPut(prefix + "/{agentId}/config", async ( @@ -171,7 +171,7 @@ public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint( if (agent == null) { return Results.NotFound("Agent Not Found"); } var config = agent.ParseConfig(data["config"]); - IAgentConfig newConfig = + AgentConfigBase newConfig = await agent.UpdateAgentConfigAsync(config, cancellationToken).ConfigureAwait(false); var tmp = workbenchConnector.GetAgent(agentId); @@ -186,7 +186,7 @@ public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint( // Create new conversation private static IEndpointRouteBuilder UseCreateConversationEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapPut(prefix + "/{agentId}/conversations/{conversationId}", async ( @@ -214,7 +214,7 @@ private static IEndpointRouteBuilder UseCreateConversationEndpoint // Fetch conversation states public static IEndpointRouteBuilder UseFetchConversationStatesEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapGet(prefix + "/{agentId}/conversations/{conversationId}/states", async ( @@ -285,7 +285,7 @@ end of content // Fetch conversation states public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapGet(prefix + "/{agentId}/conversations/{conversationId}/states/{insightId}", async ( @@ -348,7 +348,7 @@ public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapPost(prefix + "/{agentId}/conversations/{conversationId}/events", async ( @@ -539,7 +539,7 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint( this IEndpointRouteBuilder builder, string prefix) - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { builder.MapDelete(prefix + "/{agentId}/conversations/{conversationId}", async ( diff --git a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs index 9405b4cc..c67d0934 100644 --- a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs +++ b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs @@ -13,7 +13,7 @@ namespace Microsoft.SemanticWorkbench.Connector; public abstract class WorkbenchConnector : IDisposable - where TAgentConfig : IAgentConfig, new() + where TAgentConfig : AgentConfigBase, new() { protected IAgentServiceStorage Storage { get; private set; } protected WorkbenchConfig WorkbenchConfig { get; private set; } diff --git a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj index 3837d11c..b51381ae 100644 --- a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj +++ b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj @@ -16,10 +16,6 @@ - - - - true true