diff --git a/assistant-connector/dotnet/README.md b/assistant-connector/dotnet/README.md
index ed8b2fb3..d4aaa1d1 100644
--- a/assistant-connector/dotnet/README.md
+++ b/assistant-connector/dotnet/README.md
@@ -1,3 +1,37 @@
-This project contains a .NET connector allowing to connect .NET agents and assistants to Semantic Workbench.
+# Semantic Workbench
-The repository contains some [examples](../../examples/) using this connector.
+Semantic Workbench is a versatile tool designed for quickly prototyping intelligent assistants.
+Whether you're building new assistants or integrating existing ones, the workbench offers a unified
+interface for managing conversations, configuring settings, and customizing behavior.
+
+# Connector
+
+The Connector allows to seamlessly integrate .NET agents, built with any framework, into Semantic
+Workbench. By using HTTP for communication, the connector enables your agent to handle instructions
+and exchange data with both the frontend and backend of Semantic Workbench.
+
+# Setup Guide
+
+To integrate your agent:
+
+1. Add the `Microsoft.SemanticWorkbench.Connector` nuget to the .NET project containing your agent.
+
+2. **Define an agent configuration**: Create a configuration class for your agent. This can be empty
+ if no configuration is needed from the workbench UI.
+
+3. **Extend Agent Functionality**: Inherit from `Microsoft.SemanticWorkbench.Connector.AgentBase`
+ and implement the `GetDefaultConfig` and `ParseConfig` methods in your agent class. Examples
+ are available in the repository.
+
+4. **Create a Connector**: Implement `Microsoft.SemanticWorkbench.Connector.WorkbenchConnector` and
+ its `CreateAgentAsync` method to allow the workbench to create multiple agent instances.
+
+5. Start a `Microsoft.SemanticWorkbench.Connector.WorkbenchConnector` calling the `ConnectAsync`
+ method.
+
+6. Start a Web service using the endpoints defined in `Microsoft.SemanticWorkbench.Connector.Webservice`.
+
+# Examples
+
+Find sample .NET agents and assistants using this connector in the
+[official repository](https://github.com/microsoft/semanticworkbench/tree/main/examples).
diff --git a/assistant-connector/dotnet/WorkbenchConnector/AgentBase.cs b/assistant-connector/dotnet/WorkbenchConnector/AgentBase.cs
index 8249a2d8..e0b90fe1 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/AgentBase.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/AgentBase.cs
@@ -1,5 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.SemanticWorkbench.Connector;
@@ -16,13 +20,13 @@ public abstract class AgentBase
public IAgentConfig RawConfig { get; protected set; }
// Simple storage layer to persist agents data
- protected readonly IAgentServiceStorage Storage;
+ protected IAgentServiceStorage Storage { get; private set; }
// Reference to agent service
- protected readonly WorkbenchConnector WorkbenchConnector;
+ protected WorkbenchConnector WorkbenchConnector { get; private set; }
// Agent logger
- protected readonly ILogger Log;
+ protected ILogger Log { get; private set; }
///
/// Agent instantiation
@@ -30,7 +34,7 @@ public abstract class AgentBase
/// Semantic Workbench connector
/// Agent data storage
/// Agent logger
- public AgentBase(
+ protected AgentBase(
WorkbenchConnector workbenchConnector,
IAgentServiceStorage storage,
ILogger log)
@@ -68,6 +72,7 @@ public virtual AgentInfo ToDataModel()
///
/// Start the agent
///
+ /// Async task cancellation token
public virtual Task StartAsync(
CancellationToken cancellationToken = default)
{
@@ -77,6 +82,7 @@ public virtual Task StartAsync(
///
/// Stop the agent
///
+ /// Async task cancellation token
public virtual Task StopAsync(
CancellationToken cancellationToken = default)
{
@@ -158,7 +164,6 @@ await Task.WhenAll([
///
/// Delete a conversation
///
- /// Agent instance ID
/// Conversation ID
/// Async task cancellation token
public virtual Task DeleteConversationAsync(
@@ -211,11 +216,11 @@ public virtual async Task AddParticipantAsync(
/// Remove a participant from a conversation
///
/// Conversation ID
- /// Participant information
+ /// Participant information
/// Async task cancellation token
public virtual async Task RemoveParticipantAsync(
string conversationId,
- Participant participantUpdatedEvent,
+ Participant participant,
CancellationToken cancellationToken = default)
{
this.Log.LogDebug("Removing participant from conversation '{0}' on agent '{1}'",
@@ -224,7 +229,7 @@ public virtual async Task RemoveParticipantAsync(
Conversation? conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false);
if (conversation == null) { return; }
- conversation.RemoveParticipant(participantUpdatedEvent);
+ conversation.RemoveParticipant(participant);
await this.Storage.SaveConversationAsync(conversation, cancellationToken).ConfigureAwait(false);
}
@@ -287,7 +292,7 @@ public virtual Task ReceiveNoteAsync(
/// Receive a command, a special type of message
///
/// Conversation ID
- /// Message information
+ /// Command information
/// Async task cancellation token
public virtual Task ReceiveCommandAsync(
string conversationId,
@@ -321,7 +326,7 @@ public virtual Task ReceiveCommandResponseAsync(
/// Remove a message from a conversation
///
/// Conversation ID
- /// Message information
+ /// Message information
/// Async task cancellation token
public virtual async Task DeleteMessageAsync(
string conversationId,
diff --git a/assistant-connector/dotnet/WorkbenchConnector/ConfigUtils.cs b/assistant-connector/dotnet/WorkbenchConnector/ConfigUtils.cs
index 0ebcb9e6..7c4f7c42 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/ConfigUtils.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/ConfigUtils.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+
namespace Microsoft.SemanticWorkbench.Connector;
public static class ConfigUtils
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Constants.cs b/assistant-connector/dotnet/WorkbenchConnector/Constants.cs
index b771d868..fbb233b9 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Constants.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Constants.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Net.Http;
+
namespace Microsoft.SemanticWorkbench.Connector;
public static class Constants
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Models/Command.cs b/assistant-connector/dotnet/WorkbenchConnector/Models/Command.cs
index 3abc36cb..35885410 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Models/Command.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Models/Command.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+
// ReSharper disable once CheckNamespace
namespace Microsoft.SemanticWorkbench.Connector;
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Models/Conversation.cs b/assistant-connector/dotnet/WorkbenchConnector/Models/Conversation.cs
index 34f24439..eb99e4e0 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Models/Conversation.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Models/Conversation.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel.ChatCompletion;
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Models/ConversationEvent.cs b/assistant-connector/dotnet/WorkbenchConnector/Models/ConversationEvent.cs
index 06de4707..daeaff65 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Models/ConversationEvent.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Models/ConversationEvent.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Text.Json.Serialization;
// ReSharper disable once CheckNamespace
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Models/DebugInfo.cs b/assistant-connector/dotnet/WorkbenchConnector/Models/DebugInfo.cs
index 2c549402..91446119 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Models/DebugInfo.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Models/DebugInfo.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+
// ReSharper disable once CheckNamespace
namespace Microsoft.SemanticWorkbench.Connector;
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Models/Message.cs b/assistant-connector/dotnet/WorkbenchConnector/Models/Message.cs
index a11ff1b6..7b4ffba1 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Models/Message.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Models/Message.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Text.Json.Serialization;
// ReSharper disable once CheckNamespace
@@ -30,12 +31,19 @@ public class Message
[JsonPropertyName("metadata")]
public MessageMetadata Metadata { get; set; } = new();
+ ///
+ /// Prepare a chat message instance
///
/// Content types:
/// - text/plain
/// - text/html
/// - application/json (requires "json_schema" metadata)
///
+ ///
+ /// Agent ID
+ /// Chat content
+ /// Optional debugging data
+ /// Message content type
public static Message CreateChatMessage(
string agentId,
string content,
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs b/assistant-connector/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs
index ba996e18..0d0fb2a4 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs
@@ -1,7 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@@ -39,9 +44,9 @@ public AgentServiceStorage(
: "StoragePathLinux") ?? string.Empty;
this._path = Path.Join(tmpPath, connectorId);
- if (this._path.Contains("$tmp"))
+ if (this._path.Contains("$tmp", StringComparison.OrdinalIgnoreCase))
{
- this._path = this._path.Replace("$tmp", Path.GetTempPath());
+ this._path = this._path.Replace("$tmp", Path.GetTempPath(), StringComparison.OrdinalIgnoreCase);
}
this._path = Path.Join(this._path, "agents");
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Storage/IAgentServiceStorage.cs b/assistant-connector/dotnet/WorkbenchConnector/Storage/IAgentServiceStorage.cs
index 6a81cbdf..e43b7d5f 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Storage/IAgentServiceStorage.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Storage/IAgentServiceStorage.cs
@@ -1,5 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
// ReSharper disable once CheckNamespace
namespace Microsoft.SemanticWorkbench.Connector;
diff --git a/assistant-connector/dotnet/WorkbenchConnector/Webservice.cs b/assistant-connector/dotnet/WorkbenchConnector/Webservice.cs
index c990da59..4fc59aa3 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/Webservice.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/Webservice.cs
@@ -1,7 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
using System.Text;
using System.Text.Json;
+using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -295,22 +301,20 @@ public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint(
return Results.NotFound($"State '{insightId}' Not Found");
}
- else
+
+ // TODO: support schemas
+ var result = new
{
- // TODO: support schemas
- var result = new
+ id = insightId,
+ data = new
{
- id = insightId,
- data = new
- {
- content = insight.Content
- },
- json_schema = (object)null!,
- ui_schema = (object)null!
- };
+ content = insight.Content
+ },
+ json_schema = (object)null!,
+ ui_schema = (object)null!
+ };
- return Results.Json(result);
- }
+ return Results.Json(result);
});
return builder;
@@ -546,7 +550,7 @@ private static IEndpointRouteBuilder UseCatchAllEndpoint(
StringBuilder headersStringBuilder = new();
foreach (KeyValuePair header in context.Request.Headers)
{
- headersStringBuilder.AppendLine($"{header.Key}: {header.Value}");
+ headersStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{header.Key}: {header.Value}");
}
// Read body
diff --git a/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConfig.cs b/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConfig.cs
index a1cc706a..dbc14c6d 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConfig.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConfig.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+
namespace Microsoft.SemanticWorkbench.Connector;
public class WorkbenchConfig
diff --git a/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConnector.cs b/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConnector.cs
index 75238d90..83ffa0f3 100644
--- a/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConnector.cs
+++ b/assistant-connector/dotnet/WorkbenchConnector/WorkbenchConnector.cs
@@ -1,7 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@@ -9,15 +15,15 @@ namespace Microsoft.SemanticWorkbench.Connector;
public abstract class WorkbenchConnector : IDisposable
{
- protected readonly IAgentServiceStorage Storage;
- protected readonly WorkbenchConfig Config = new();
- protected readonly HttpClient HttpClient;
- protected readonly ILogger Log;
- protected readonly Dictionary Agents;
+ protected IAgentServiceStorage Storage { get; private set; }
+ protected WorkbenchConfig Config { get; private set; } = new();
+ protected HttpClient HttpClient { get; private set; }
+ protected ILogger Log { get; private set; }
+ protected Dictionary Agents { get; private set; }
private Timer? _pingTimer;
- public WorkbenchConnector(
+ protected WorkbenchConnector(
IConfiguration appConfig,
IAgentServiceStorage storage,
ILogger logger)
@@ -40,9 +46,9 @@ public WorkbenchConnector(
public virtual async Task ConnectAsync(CancellationToken cancellationToken = default)
{
this.Log.LogInformation("Connecting {1} {2} {3}...", this.Config.ConnectorName, this.Config.ConnectorId, this.Config.ConnectorEndpoint);
- #pragma warning disable CS4014 // ping runs in the background without blocking
+#pragma warning disable CS4014 // ping runs in the background without blocking
this._pingTimer ??= new Timer(_ => this.PingSemanticWorkbenchBackendAsync(cancellationToken), null, 0, 10000);
- #pragma warning restore CS4014
+#pragma warning restore CS4014
List agents = await this.Storage.GetAllAgentsAsync(cancellationToken).ConfigureAwait(false);
this.Log.LogInformation("Starting {0} agents", agents.Count);
@@ -56,12 +62,15 @@ public virtual async Task ConnectAsync(CancellationToken cancellationToken = def
/// Disconnect the agent service from the workbench backend
///
/// Async task cancellation token
- public virtual Task DisconnectAsync(CancellationToken cancellationToken = default)
+ public virtual async Task DisconnectAsync(CancellationToken cancellationToken = default)
{
this.Log.LogInformation("Disconnecting {1} {2} ...", this.Config.ConnectorName, this.Config.ConnectorId);
- this._pingTimer?.Dispose();
+ if (this._pingTimer != null)
+ {
+ await this._pingTimer.DisposeAsync().ConfigureAwait(false);
+ }
+
this._pingTimer = null;
- return Task.CompletedTask;
}
///
@@ -69,6 +78,7 @@ public virtual Task DisconnectAsync(CancellationToken cancellationToken = defaul
///
/// Agent instance ID
/// Agent name
+ /// Configuration content
/// Async task cancellation token
public abstract Task CreateAgentAsync(
string agentId,
@@ -110,7 +120,7 @@ public virtual async Task DeleteAgentAsync(
///
/// Agent instance ID
/// Conversation ID
- /// Content. Markdown and HTML are supported.
+ /// Insight content. Markdown and HTML are supported.
/// Async task cancellation token
public virtual async Task UpdateAgentConversationInsightAsync(
string agentId,
@@ -137,8 +147,8 @@ public virtual async Task UpdateAgentConversationInsightAsync(
};
string url = Constants.SendAgentConversationInsightsEvent.Path
- .Replace(Constants.SendAgentConversationInsightsEvent.AgentPlaceholder, agentId)
- .Replace(Constants.SendAgentConversationInsightsEvent.ConversationPlaceholder, conversationId);
+ .Replace(Constants.SendAgentConversationInsightsEvent.AgentPlaceholder, agentId, StringComparison.OrdinalIgnoreCase)
+ .Replace(Constants.SendAgentConversationInsightsEvent.ConversationPlaceholder, conversationId, StringComparison.OrdinalIgnoreCase);
await this.SendAsync(HttpMethod.Post, url, data, agentId, "UpdateAgentConversationInsight", cancellationToken).ConfigureAwait(false);
}
@@ -167,18 +177,17 @@ public virtual async Task SetAgentStatusAsync(
};
string url = Constants.SendAgentStatusMessage.Path
- .Replace(Constants.SendAgentStatusMessage.ConversationPlaceholder, conversationId)
- .Replace(Constants.SendAgentStatusMessage.AgentPlaceholder, agentId);
+ .Replace(Constants.SendAgentStatusMessage.ConversationPlaceholder, conversationId, StringComparison.OrdinalIgnoreCase)
+ .Replace(Constants.SendAgentStatusMessage.AgentPlaceholder, agentId, StringComparison.OrdinalIgnoreCase);
await this.SendAsync(HttpMethod.Put, url, data, agentId, $"SetAgentStatus[{status}]", cancellationToken).ConfigureAwait(false);
}
///
- /// Set a temporary agent status within a conversation
+ /// Reset the temporary agent status within a conversation
///
/// Agent instance ID
/// Conversation ID
- /// Short status description
/// Async task cancellation token
public virtual async Task ResetAgentStatusAsync(
string agentId,
@@ -188,18 +197,18 @@ public virtual async Task ResetAgentStatusAsync(
this.Log.LogDebug("Setting agent status in conversation '{0}' with agent '{1}'",
conversationId.HtmlEncode(), agentId.HtmlEncode());
- string payload = """
- {
- "status": null,
- "active_participant": true
- }
- """;
+ const string Payload = """
+ {
+ "status": null,
+ "active_participant": true
+ }
+ """;
- var data = JsonSerializer.Deserialize