Skip to content

Commit

Permalink
Decouple .NET connector from Semantic Kernel (#211)
Browse files Browse the repository at this point in the history
* Decouple .NET connector from Semantic Kernel: there was just a helper
using SK, that can be moved to extension methods for people using SK. No
need to depend on SK which could cause version conflicts.
* Refactor agent config, replace `IAgentConfig` with `AgentConfigBase`
  • Loading branch information
dluc authored Nov 5, 2024
1 parent e34f2a7 commit 8db5455
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 145 deletions.
78 changes: 78 additions & 0 deletions examples/dotnet/dotnet-03-simple-chatbot/ConnectorExtensions.cs
Original file line number Diff line number Diff line change
@@ -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("<style>");
result.AppendLine("DIV.conversationHistory { padding: 0 20px 60px 20px; }");
result.AppendLine("DIV.conversationHistory P { margin: 0 0 8px 0; }");
result.AppendLine("</style>");
result.AppendLine("<div class='conversationHistory'>");

foreach (var msg in conversation.Messages)
{
result.AppendLine("<p>");
if (msg.Sender.Id == assistantId)
{
result.AppendLine("<b>Assistant</b><br/>");
}
else
{
result
.Append("<b>")
.Append(conversation.GetParticipantName(msg.Sender.Id))
.AppendLine("</b><br/>");
}

result.AppendLine(msg.Content).AppendLine("</p>");
}

result.Append("</div>");

return result.ToString();
}
}
2 changes: 1 addition & 1 deletion examples/dotnet/dotnet-03-simple-chatbot/MyAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private async Task<Message> 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
Expand Down
2 changes: 1 addition & 1 deletion examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion libraries/dotnet/WorkbenchConnector/AgentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Microsoft.SemanticWorkbench.Connector;

public abstract class AgentBase<TAgentConfig> : IAgentBase
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
// Agent instance ID
public string Id { get; protected set; } = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, object> result = new();
Dictionary<string, object> defs = new();
Expand Down Expand Up @@ -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));
}
}
}
10 changes: 0 additions & 10 deletions libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs

This file was deleted.

22 changes: 0 additions & 22 deletions libraries/dotnet/WorkbenchConnector/Models/ChatHistoryExt.cs

This file was deleted.

64 changes: 0 additions & 64 deletions libraries/dotnet/WorkbenchConnector/Models/Conversation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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("<style>");
result.AppendLine("DIV.conversationHistory { padding: 0 20px 60px 20px; }");
result.AppendLine("DIV.conversationHistory P { margin: 0 0 8px 0; }");
result.AppendLine("</style>");
result.AppendLine("<div class='conversationHistory'>");

foreach (var msg in this.Messages)
{
result.AppendLine("<p>");
if (msg.Sender.Id == assistantId)
{
result.AppendLine("<b>Assistant</b><br/>");
}
else
{
result
.Append("<b>")
.Append(this.GetParticipantName(msg.Sender.Id))
.AppendLine("</b><br/>");
}

result.AppendLine(msg.Content).AppendLine("</p>");
}

result.Append("</div>");

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";
}
}
6 changes: 2 additions & 4 deletions libraries/dotnet/WorkbenchConnector/Models/ServiceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
namespace Microsoft.SemanticWorkbench.Connector;

public class ServiceInfo<TAgentConfig>(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;

Expand All @@ -24,5 +22,5 @@ public class ServiceInfo<TAgentConfig>(TAgentConfig cfg)
public Dictionary<string, object> Metadata { get; set; } = new();

[JsonPropertyName("default_config")]
public object DefaultConfiguration => this._cfg.ToWorkbenchFormat() ?? new();
public object DefaultConfiguration => cfg.ToWorkbenchFormat() ?? new();
}
24 changes: 12 additions & 12 deletions libraries/dotnet/WorkbenchConnector/Webservice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private sealed class SemanticWorkbenchWebservice

public static WorkbenchConnector<TAgentConfig> UseAgentWebservice<TAgentConfig>(
this IEndpointRouteBuilder builder, string endpoint, bool enableCatchAll = false)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
WorkbenchConnector<TAgentConfig>? workbenchConnector = builder.ServiceProvider.GetService<WorkbenchConnector<TAgentConfig>>();
if (workbenchConnector == null)
Expand Down Expand Up @@ -60,7 +60,7 @@ public static WorkbenchConnector<TAgentConfig> UseAgentWebservice<TAgentConfig>(
// Return service details and default agent configuration
public static IEndpointRouteBuilder UseFetchServiceInfo<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapGet(prefix + "/", (
[FromServicesAttribute] WorkbenchConnector<TAgentConfig> workbenchConnector,
Expand All @@ -76,7 +76,7 @@ public static IEndpointRouteBuilder UseFetchServiceInfo<TAgentConfig>(
// Create new agent instance
public static IEndpointRouteBuilder UseCreateAgentEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapPut(prefix + "/{agentId}",
async (
Expand Down Expand Up @@ -110,7 +110,7 @@ await workbenchConnector.CreateAgentAsync(agentId, name, null, cancellationToken
// Delete agent instance
public static IEndpointRouteBuilder UseDeleteAgentEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapDelete(prefix + "/{agentId}",
async (
Expand All @@ -130,7 +130,7 @@ public static IEndpointRouteBuilder UseDeleteAgentEndpoint<TAgentConfig>(
// Fetch agent configuration
public static IEndpointRouteBuilder UseFetchAgentConfigEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapGet(prefix + "/{agentId}/config",
(
Expand All @@ -155,7 +155,7 @@ public static IEndpointRouteBuilder UseFetchAgentConfigEndpoint<TAgentConfig>(
// Save agent configuration
public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapPut(prefix + "/{agentId}/config",
async (
Expand All @@ -171,7 +171,7 @@ public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint<TAgentConfig>(
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);
Expand All @@ -186,7 +186,7 @@ public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint<TAgentConfig>(
// Create new conversation
private static IEndpointRouteBuilder UseCreateConversationEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapPut(prefix + "/{agentId}/conversations/{conversationId}",
async (
Expand Down Expand Up @@ -214,7 +214,7 @@ private static IEndpointRouteBuilder UseCreateConversationEndpoint<TAgentConfig>
// Fetch conversation states
public static IEndpointRouteBuilder UseFetchConversationStatesEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapGet(prefix + "/{agentId}/conversations/{conversationId}/states",
async (
Expand Down Expand Up @@ -285,7 +285,7 @@ end of content
// Fetch conversation states
public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapGet(prefix + "/{agentId}/conversations/{conversationId}/states/{insightId}",
async (
Expand Down Expand Up @@ -348,7 +348,7 @@ public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint<TAgentCo
// New conversation event
private static IEndpointRouteBuilder UseCreateConversationEventEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapPost(prefix + "/{agentId}/conversations/{conversationId}/events",
async (
Expand Down Expand Up @@ -539,7 +539,7 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint<TAgentCo
// Delete conversation
public static IEndpointRouteBuilder UseDeleteConversationEndpoint<TAgentConfig>(
this IEndpointRouteBuilder builder, string prefix)
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
builder.MapDelete(prefix + "/{agentId}/conversations/{conversationId}",
async (
Expand Down
2 changes: 1 addition & 1 deletion libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Microsoft.SemanticWorkbench.Connector;

public abstract class WorkbenchConnector<TAgentConfig> : IDisposable
where TAgentConfig : IAgentConfig, new()
where TAgentConfig : AgentConfigBase, new()
{
protected IAgentServiceStorage Storage { get; private set; }
protected WorkbenchConfig WorkbenchConfig { get; private set; }
Expand Down
Loading

0 comments on commit 8db5455

Please sign in to comment.