From 9016adb90d5ae2f50efafe2d28f3e679d277593d Mon Sep 17 00:00:00 2001
From: Devis Lucato
Date: Tue, 30 Jul 2024 13:39:05 +0200
Subject: [PATCH] Add .NET example showing content types, Azure AI Content
Safety and debugging
---
README.md | 5 +-
dotnet/SemanticWorkbench.sln | 6 +
dotnet/SemanticWorkbench.sln.DotSettings | 5 +-
examples/{dotnet-example01 => }/.editorconfig | 0
.../dotnet-example02/AgentExample02.csproj | 38 ++
examples/dotnet-example02/MyAgent.cs | 594 ++++++++++++++++++
examples/dotnet-example02/MyAgentConfig.cs | 90 +++
.../dotnet-example02/MyWorkbenchConnector.cs | 49 ++
examples/dotnet-example02/Program.cs | 56 ++
examples/dotnet-example02/README.md | 50 ++
examples/dotnet-example02/appsettings.json | 67 ++
examples/dotnet-example02/docs/abc.png | Bin 0 -> 195865 bytes
examples/dotnet-example02/docs/code.png | Bin 0 -> 321959 bytes
examples/dotnet-example02/docs/config.png | Bin 0 -> 221183 bytes
examples/dotnet-example02/docs/echo.png | Bin 0 -> 341457 bytes
examples/dotnet-example02/docs/markdown.png | Bin 0 -> 219802 bytes
examples/dotnet-example02/docs/mermaid.png | Bin 0 -> 156315 bytes
examples/dotnet-example02/docs/reverse.png | Bin 0 -> 253609 bytes
.../dotnet-example02/docs/safety-check.png | Bin 0 -> 176807 bytes
19 files changed, 958 insertions(+), 2 deletions(-)
rename examples/{dotnet-example01 => }/.editorconfig (100%)
create mode 100644 examples/dotnet-example02/AgentExample02.csproj
create mode 100644 examples/dotnet-example02/MyAgent.cs
create mode 100644 examples/dotnet-example02/MyAgentConfig.cs
create mode 100644 examples/dotnet-example02/MyWorkbenchConnector.cs
create mode 100644 examples/dotnet-example02/Program.cs
create mode 100644 examples/dotnet-example02/README.md
create mode 100644 examples/dotnet-example02/appsettings.json
create mode 100644 examples/dotnet-example02/docs/abc.png
create mode 100644 examples/dotnet-example02/docs/code.png
create mode 100644 examples/dotnet-example02/docs/config.png
create mode 100644 examples/dotnet-example02/docs/echo.png
create mode 100644 examples/dotnet-example02/docs/markdown.png
create mode 100644 examples/dotnet-example02/docs/mermaid.png
create mode 100644 examples/dotnet-example02/docs/reverse.png
create mode 100644 examples/dotnet-example02/docs/safety-check.png
diff --git a/README.md b/README.md
index 46bd4cdd..73626c80 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,10 @@ Designed to be agnostic of any agent framework, language, or platform, the Seman
To develop new agents and connect existing ones, see the [Assistant Development Guide](docs/ASSISTANT_DEVELOPMENT_GUIDE.md)
-The repository contains a [Python Canonical Assistant](semantic-workbench/v1/service/semantic-workbench-assistant/semantic_workbench_assistant/canonical.py) and a [.NET Agent Example](examples/dotnet-example01) that can be used as starting points to create custom agents.
+The repository contains a [Python Canonical Assistant](semantic-workbench/v1/service/semantic-workbench-assistant/semantic_workbench_assistant/canonical.py) and some [.NET Agent Examples](examples) that can be used as starting points to create custom agents.
+
+
+
# Workbench setup
diff --git a/dotnet/SemanticWorkbench.sln b/dotnet/SemanticWorkbench.sln
index e7c88213..a32e46dd 100644
--- a/dotnet/SemanticWorkbench.sln
+++ b/dotnet/SemanticWorkbench.sln
@@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkbenchConnector", "Workb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgentExample01", "..\examples\dotnet-example01\AgentExample01.csproj", "{3A6FE36E-B186-458C-984B-C1BBF4BFB440}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgentExample02", "..\examples\dotnet-example02\AgentExample02.csproj", "{46BC33EC-AA35-428D-A8B4-2C0E693C7C51}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -18,5 +20,9 @@ Global
{3A6FE36E-B186-458C-984B-C1BBF4BFB440}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A6FE36E-B186-458C-984B-C1BBF4BFB440}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A6FE36E-B186-458C-984B-C1BBF4BFB440}.Release|Any CPU.Build.0 = Release|Any CPU
+ {46BC33EC-AA35-428D-A8B4-2C0E693C7C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46BC33EC-AA35-428D-A8B4-2C0E693C7C51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46BC33EC-AA35-428D-A8B4-2C0E693C7C51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46BC33EC-AA35-428D-A8B4-2C0E693C7C51}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/dotnet/SemanticWorkbench.sln.DotSettings b/dotnet/SemanticWorkbench.sln.DotSettings
index 496120c1..171966c5 100644
--- a/dotnet/SemanticWorkbench.sln.DotSettings
+++ b/dotnet/SemanticWorkbench.sln.DotSettings
@@ -1,2 +1,5 @@
- CORS
\ No newline at end of file
+ ABC
+ CORS
+ HTML
+ JSON
\ No newline at end of file
diff --git a/examples/dotnet-example01/.editorconfig b/examples/.editorconfig
similarity index 100%
rename from examples/dotnet-example01/.editorconfig
rename to examples/.editorconfig
diff --git a/examples/dotnet-example02/AgentExample02.csproj b/examples/dotnet-example02/AgentExample02.csproj
new file mode 100644
index 00000000..f09b0fde
--- /dev/null
+++ b/examples/dotnet-example02/AgentExample02.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net8.0
+ enable
+ enable
+ AgentExample02
+ AgentExample02
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/examples/dotnet-example02/MyAgent.cs b/examples/dotnet-example02/MyAgent.cs
new file mode 100644
index 00000000..974e5bfc
--- /dev/null
+++ b/examples/dotnet-example02/MyAgent.cs
@@ -0,0 +1,594 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Azure;
+using Azure.AI.ContentSafety;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.SemanticWorkbench.Connector;
+
+namespace AgentExample02;
+
+public class MyAgent : AgentBase
+{
+ // Agent settings
+ public MyAgentConfig Config
+ {
+ get { return (MyAgentConfig)this.RawConfig; }
+ private set { this.RawConfig = value; }
+ }
+
+ // Azure Content Safety
+ private readonly ContentSafetyClient _contentSafety;
+
+ ///
+ /// Create a new agent instance
+ ///
+ /// Agent instance ID
+ /// Agent name
+ /// Agent configuration
+ /// Service containing the agent, used to communicate with Workbench backend
+ /// Agent data storage
+ /// Azure content safety
+ /// Semantic Kernel
+ /// App logger factory
+ public MyAgent(
+ string agentId,
+ string agentName,
+ MyAgentConfig? agentConfig,
+ WorkbenchConnector workbenchConnector,
+ IAgentServiceStorage storage,
+ ContentSafetyClient contentSafety,
+ ILoggerFactory? loggerFactory = null)
+ : base(
+ workbenchConnector,
+ storage,
+ loggerFactory?.CreateLogger() ?? new NullLogger())
+ {
+ this.Id = agentId;
+ this.Name = agentName;
+ this.Config = agentConfig ?? new MyAgentConfig();
+ this._contentSafety = contentSafety;
+ }
+
+ ///
+ public override IAgentConfig GetDefaultConfig()
+ {
+ return new MyAgentConfig();
+ }
+
+ ///
+ public override IAgentConfig? ParseConfig(object data)
+ {
+ return JsonSerializer.Deserialize(JsonSerializer.Serialize(data));
+ }
+
+ ///
+ public override async Task ReceiveCommandAsync(
+ string conversationId,
+ Command command,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ if (!this.Config.CommandsEnabled) { return; }
+
+ // Support only the "say" command
+ if (command.CommandName.ToLowerInvariant() != "say") { return; }
+
+ // Update the chat history to include the message received
+ await base.ReceiveMessageAsync(conversationId, command, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && command.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ var answer = Message.CreateChatMessage(this.Id, command.CommandParams);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public override Task ReceiveMessageAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ switch (this.Config.Behavior.ToLowerInvariant())
+ {
+ case "echo": return this.EchoExampleAsync(conversationId, message, cancellationToken);
+ case "reverse": return this.ReverseExampleAsync(conversationId, message, cancellationToken);
+ case "safety check": return this.SafetyCheckExampleAsync(conversationId, message, cancellationToken);
+ case "markdown sample": return this.MarkdownExampleAsync(conversationId, message, cancellationToken);
+ case "html sample": return this.HTMLExampleAsync(conversationId, message, cancellationToken);
+ case "code sample": return this.CodeExampleAsync(conversationId, message, cancellationToken);
+ case "json sample": return this.JSONExampleAsync(conversationId, message, cancellationToken);
+ case "mermaid sample": return this.MermaidExampleAsync(conversationId, message, cancellationToken);
+ case "music sample": return this.MusicExampleAsync(conversationId, message, cancellationToken);
+ case "none": return this.NoneExampleAsync(conversationId, message, cancellationToken);
+ default: return this.NoneExampleAsync(conversationId, message, cancellationToken);
+ }
+ }
+
+ // Check text with Azure Content Safety
+ private async Task<(bool isSafe, object report)> IsSafeAsync(
+ string? text,
+ CancellationToken cancellationToken)
+ {
+ Response? result = await this._contentSafety.AnalyzeTextAsync(text, cancellationToken).ConfigureAwait(false);
+
+ bool isSafe = result.HasValue && result.Value.CategoriesAnalysis.All(x => x.Severity is 0);
+ IEnumerable report = result.HasValue ? result.Value.CategoriesAnalysis.Select(x => $"{x.Category}: {x.Severity}") : Array.Empty();
+
+ return (isSafe, report);
+ }
+
+ private async Task EchoExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Ignore empty messages
+ if (string.IsNullOrWhiteSpace(message.Content)) { return; }
+
+ // Create the answer content
+ var (inputIsSafe, report) = await this.IsSafeAsync(message.Content, cancellationToken).ConfigureAwait(false);
+ var answer = inputIsSafe
+ ? Message.CreateChatMessage(this.Id, message.Content)
+ : Message.CreateChatMessage(this.Id, "I'm not sure how to respond to that.", report);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ReverseExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Ignore empty messages
+ if (string.IsNullOrWhiteSpace(message.Content)) { return; }
+
+ // Create the answer content
+ var (inputIsSafe, report) = await this.IsSafeAsync(message.Content, cancellationToken).ConfigureAwait(false);
+ var answer = inputIsSafe
+ ? Message.CreateChatMessage(this.Id, $"{new string(message.Content.Reverse().ToArray())}")
+ : Message.CreateChatMessage(this.Id, "I'm not sure how to respond to that.", report);
+
+ // Check the output too
+ var (outputIsSafe, reportOut) = await this.IsSafeAsync(answer.Content, cancellationToken).ConfigureAwait(false);
+ if (!outputIsSafe)
+ {
+ answer = Message.CreateChatMessage(this.Id, "Sorry I won't process that.", reportOut);
+ }
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private Task LogChatHistoryAsInsight(
+ Conversation conversation,
+ CancellationToken cancellationToken)
+ {
+ var insight = new Insight("history", "Chat History", conversation.ToHtmlString(this.Id));
+ return this.SetConversationInsightAsync(conversation.Id, insight, cancellationToken);
+ }
+
+ private async Task SafetyCheckExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ Message answer;
+ Response? result = await this._contentSafety.AnalyzeTextAsync(message.Content, cancellationToken).ConfigureAwait(false);
+ if (!result.HasValue)
+ {
+ answer = Message.CreateChatMessage(
+ this.Id,
+ "Sorry, something went wrong, I couldn't analyze the message.",
+ "The request to Azure Content Safety failed and returned NULL");
+ }
+ else
+ {
+ bool isOffensive = result.Value.CategoriesAnalysis.Any(x => x.Severity is > 0);
+ IEnumerable report = result.Value.CategoriesAnalysis.Select(x => $"{x.Category}: {x.Severity}");
+
+ answer = Message.CreateChatMessage(
+ this.Id,
+ isOffensive ? "Offensive content detected" : "OK",
+ report);
+ }
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task MarkdownExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Prepare answer using Markdown syntax
+ const string MarkdownContent = """
+ # Using Semantic Workbench with .NET Agents
+
+ This project provides an example of testing your agent within the **Semantic Workbench**.
+
+ ## Project Overview
+
+ The sample project utilizes the `WorkbenchConnector` library, enabling you to focus on agent development and testing.
+
+ Semantic Workbench allows mixing agents from different frameworks and multiple instances of the same agent.
+ The connector can manage multiple agent instances if needed, or you can work with a single instance if preferred.
+ To integrate agents developed with other frameworks, we recommend isolating each agent type with a dedicated web service, ie a dedicated project.
+ """;
+ var answer = Message.CreateChatMessage(this.Id, MarkdownContent);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task HTMLExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ const string HTMLExample = """
+ Using Semantic Workbench with .NET Agents
+
+ This project provides an example of testing your agent within the Semantic Workbench.
+
+ Project Overview
+
+ The sample project utilizes the
WorkbenchConnector
library, enabling you to focus on agent development and testing.
+
+ Semantic Workbench allows mixing agents from different frameworks and multiple instances of the same agent.
+ The connector can manage multiple agent instances if needed, or you can work with a single instance if preferred.
+ To integrate agents developed with other frameworks, we recommend isolating each agent type with a dedicated web service, ie a dedicated project.
+ """;
+ var answer = Message.CreateChatMessage(this.Id, HTMLExample, contentType: "text/html");
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task CodeExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ const string CodeExample = """
+ How to instantiate SK with OpenAI:
+
+ ```csharp
+ // Semantic Kernel with OpenAI
+ var openAIKey = appBuilder.Configuration.GetSection("OpenAI").GetValue("ApiKey")
+ ?? throw new ArgumentNullException("OpenAI config not found");
+ var openAIModel = appBuilder.Configuration.GetSection("OpenAI").GetValue("Model")
+ ?? throw new ArgumentNullException("OpenAI config not found");
+ appBuilder.Services.AddSingleton(_ => Kernel.CreateBuilder()
+ .AddOpenAIChatCompletion(openAIModel, openAIKey)
+ .Build());
+ ```
+ """;
+ var answer = Message.CreateChatMessage(this.Id, CodeExample);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task MermaidExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ const string MermaidContentExample = """
+ ```mermaid
+ gitGraph:
+ commit "Ashish"
+ branch newbranch
+ checkout newbranch
+ commit id:"1111"
+ commit tag:"test"
+ checkout main
+ commit type: HIGHLIGHT
+ commit
+ merge newbranch
+ commit
+ branch b2
+ commit
+ ```
+ """;
+ var answer = Message.CreateChatMessage(this.Id, MermaidContentExample);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task MusicExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Create the answer content
+ const string ABCContentExample = """
+ ```abc
+ X:1
+ T:Twinkle, Twinkle, Little Star
+ M:4/4
+ L:1/4
+ K:C
+ C C G G | A A G2 | F F E E | D D C2 |
+ G G F F | E E D2 | G G F F | E E D2 |
+ C C G G | A A G2 | F F E E | D D C2 |
+ ```
+ """;
+ var answer = Message.CreateChatMessage(this.Id, ABCContentExample);
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task JSONExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Check if we're replying to other agents
+ if (!this.Config.ReplyToAgents && message.Sender.Role == "assistant") { return; }
+
+ // Ignore empty messages
+ if (string.IsNullOrWhiteSpace(message.Content)) { return; }
+
+ // Create the answer content
+ const string JSONExample = """
+ {
+ "name": "Devis",
+ "age": 30,
+ "email": "noreply@some.email",
+ "address": {
+ "street": "123 Main St",
+ "city": "Anytown",
+ "state": "CA",
+ "zip": "123456"
+ }
+ }
+ """;
+ var answer = Message.CreateChatMessage(this.Id, JSONExample, contentType: "application/json");
+
+ // Update the chat history to include the outgoing message
+ this.Log.LogTrace("Store new message");
+ await this.AddMessageToHistoryAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+
+ // Send the message to workbench backend
+ this.Log.LogTrace("Send new message");
+ await this.SendTextMessageAsync(conversationId, answer, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task NoneExampleAsync(
+ string conversationId,
+ Message message,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Show some status while working...
+ await this.SetAgentStatusAsync(conversationId, "Thinking...", cancellationToken).ConfigureAwait(false);
+
+ // Update the chat history to include the message received
+ var conversation = await base.AddMessageToHistoryAsync(conversationId, message, cancellationToken).ConfigureAwait(false);
+
+ // Exit without doing anything
+ }
+ finally
+ {
+ this.Log.LogTrace("Reset agent status");
+ await this.ResetAgentStatusAsync(conversationId, cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/examples/dotnet-example02/MyAgentConfig.cs b/examples/dotnet-example02/MyAgentConfig.cs
new file mode 100644
index 00000000..4a8548fc
--- /dev/null
+++ b/examples/dotnet-example02/MyAgentConfig.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using Microsoft.SemanticWorkbench.Connector;
+
+namespace AgentExample02;
+
+public class MyAgentConfig : IAgentConfig
+{
+ [JsonPropertyName(nameof(this.ReplyToAgents))]
+ [JsonPropertyOrder(10)]
+ public bool ReplyToAgents { get; set; } = false;
+
+ [JsonPropertyName(nameof(this.CommandsEnabled))]
+ [JsonPropertyOrder(20)]
+ public bool CommandsEnabled { get; set; } = false;
+
+ [JsonPropertyName(nameof(this.Behavior))]
+ [JsonPropertyOrder(30)]
+ public string Behavior { get; set; } = "none";
+
+ public void Update(object? config)
+ {
+ if (config == null)
+ {
+ throw new ArgumentException("Incompatible or empty configuration");
+ }
+
+ if (config is not MyAgentConfig cfg)
+ {
+ throw new ArgumentException("Incompatible configuration type");
+ }
+
+ this.ReplyToAgents = cfg.ReplyToAgents;
+ this.CommandsEnabled = cfg.CommandsEnabled;
+ this.Behavior = cfg.Behavior;
+ }
+
+ public object ToWorkbenchFormat()
+ {
+ Dictionary result = new();
+ Dictionary defs = new();
+ Dictionary properties = new();
+ Dictionary jsonSchema = new();
+ Dictionary uiSchema = new();
+
+ properties[nameof(this.ReplyToAgents)] = new Dictionary
+ {
+ { "type", "boolean" },
+ { "title", "Reply to other assistants in conversations" },
+ { "description", "Reply to assistants" },
+ { "default", false }
+ };
+
+ properties[nameof(this.CommandsEnabled)] = new Dictionary
+ {
+ { "type", "boolean" },
+ { "title", "Support commands" },
+ { "description", "Support commands, e.g. /say" },
+ { "default", false }
+ };
+
+ properties[nameof(this.Behavior)] = new Dictionary
+ {
+ { "type", "string" },
+ { "default", "echo" },
+ { "enum", new[] { "echo", "reverse", "safety check", "markdown sample", "code sample", "json sample", "mermaid sample", "html sample", "music sample", "none" } },
+ { "title", "How to reply" },
+ { "description", "How to reply to messages, what logic to use." },
+ };
+
+ // Use "list of radio buttons" instead of default "select box"
+ uiSchema[nameof(this.Behavior)] = new Dictionary
+ {
+ { "ui:widget", "radio" }
+ };
+
+ jsonSchema["type"] = "object";
+ jsonSchema["title"] = "ConfigStateModel";
+ jsonSchema["additionalProperties"] = false;
+ jsonSchema["properties"] = properties;
+ jsonSchema["$defs"] = defs;
+
+ result["json_schema"] = jsonSchema;
+ result["ui_schema"] = uiSchema;
+ result["config"] = this;
+
+ return result;
+ }
+}
diff --git a/examples/dotnet-example02/MyWorkbenchConnector.cs b/examples/dotnet-example02/MyWorkbenchConnector.cs
new file mode 100644
index 00000000..9f9c0931
--- /dev/null
+++ b/examples/dotnet-example02/MyWorkbenchConnector.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.SemanticWorkbench.Connector;
+
+namespace AgentExample02;
+
+public sealed class MyWorkbenchConnector : WorkbenchConnector
+{
+ private readonly MyAgentConfig _defaultAgentConfig = new();
+ private readonly IServiceProvider _sp;
+
+ public MyWorkbenchConnector(
+ IServiceProvider sp,
+ IConfiguration appConfig,
+ IAgentServiceStorage storage,
+ ILoggerFactory? loggerFactory = null)
+ : base(appConfig, storage, loggerFactory?.CreateLogger() ?? new NullLogger())
+ {
+ appConfig.GetSection("Agent").Bind(this._defaultAgentConfig);
+ this._sp = sp;
+ }
+
+ ///
+ public override async Task CreateAgentAsync(
+ string agentId,
+ string? name,
+ object? configData,
+ CancellationToken cancellationToken = default)
+ {
+ if (this.GetAgent(agentId) != null) { return; }
+
+ this.Log.LogDebug("Creating agent '{0}'", agentId);
+
+ MyAgentConfig config = this._defaultAgentConfig;
+ if (configData != null)
+ {
+ var newCfg = JsonSerializer.Deserialize(JsonSerializer.Serialize(configData));
+ if (newCfg != null) { config = newCfg; }
+ }
+
+ // Instantiate using .NET Service Provider so that dependencies are automatically injected
+ var agent = ActivatorUtilities.CreateInstance(this._sp, agentId, name ?? agentId, config);
+
+ await agent.StartAsync(cancellationToken).ConfigureAwait(false);
+ this.Agents.TryAdd(agentId, agent);
+ }
+}
diff --git a/examples/dotnet-example02/Program.cs b/examples/dotnet-example02/Program.cs
new file mode 100644
index 00000000..c1a7f000
--- /dev/null
+++ b/examples/dotnet-example02/Program.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure;
+using Azure.AI.ContentSafety;
+using Azure.Identity;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticWorkbench.Connector;
+
+namespace AgentExample02;
+
+internal static class Program
+{
+ private const string CORSPolicyName = "MY-CORS";
+
+ internal static async Task Main(string[] args)
+ {
+ // Setup
+ var appBuilder = WebApplication.CreateBuilder(args);
+
+ // Load settings from files and env vars
+ appBuilder.Configuration
+ .AddJsonFile("appsettings.json")
+ .AddJsonFile("appsettings.Development.json", optional: true)
+ .AddEnvironmentVariables();
+
+ // Storage layer to persist agents configuration and conversations
+ appBuilder.Services.AddSingleton();
+
+ // Agent service to support multiple agent instances
+ appBuilder.Services.AddSingleton();
+
+ // Azure AI Content Safety, used for demo
+ var azureContentSafetyAuthType = appBuilder.Configuration.GetSection("AzureContentSafety").GetValue("AuthType");
+ var azureContentSafetyEndpoint = appBuilder.Configuration.GetSection("AzureContentSafety").GetValue("Endpoint");
+ var azureContentSafetyApiKey = appBuilder.Configuration.GetSection("AzureContentSafety").GetValue("ApiKey");
+ appBuilder.Services.AddSingleton(_ => azureContentSafetyAuthType == "AzureIdentity"
+ ? new ContentSafetyClient(new Uri(azureContentSafetyEndpoint!), new DefaultAzureCredential())
+ : new ContentSafetyClient(new Uri(azureContentSafetyEndpoint!), new AzureKeyCredential(azureContentSafetyApiKey!)));
+
+ // Misc
+ appBuilder.Services.AddLogging()
+ .AddCors(opt => opt.AddPolicy(CORSPolicyName, pol => pol.WithMethods("GET", "POST", "PUT", "DELETE")));
+
+ // Build
+ WebApplication app = appBuilder.Build();
+ app.UseCors(CORSPolicyName);
+
+ // Connect to workbench backend, keep alive, and accept incoming requests
+ var connectorEndpoint = app.Configuration.GetSection("Workbench").Get()!.ConnectorEndpoint;
+ using var agentService = app.UseAgentWebservice(connectorEndpoint, true);
+ await agentService.ConnectAsync().ConfigureAwait(false);
+
+ // Start app and webservice
+ await app.RunAsync().ConfigureAwait(false);
+ }
+}
diff --git a/examples/dotnet-example02/README.md b/examples/dotnet-example02/README.md
new file mode 100644
index 00000000..29537665
--- /dev/null
+++ b/examples/dotnet-example02/README.md
@@ -0,0 +1,50 @@
+# Example 2 - Content Types, Content Safety, Debugging
+
+This project provides an example of an agent with a configurable behavior, showing also Semantic Workbench support for **multiple content types**, such as Markdown, HTML, Mermaid graphs, JSON, etc.
+
+The agent demonstrates also a simple **integration with [Azure AI Content Safety](https://azure.microsoft.com/products/ai-services/ai-content-safety)**, to test user input and LLM models output.
+
+The example shows also how to leverage Semantic Workbench UI to **inspect agents' result, by including debugging information** readily available in the conversation.
+
+## Project Overview
+
+The sample project utilizes the `WorkbenchConnector` library, enabling you to focus on agent development and testing.
+
+Differently from [example 1](../dotnet-example01), this agent has a configurable `behavior` to show different output types.
+All the logic starts from `MyAgent.ReceiveMessageAsync()` method as seen in the previous example.
+
+
+
+## Agent output types
+
+* **echo**: echoes the user message back, only if the content is considered safe, after checking with Azure AI Content Safety.
+
+
+
+* **reverse**: echoes the user message back, reversing the string, only if the content is considered safe, and only if the output is considered safe.
+
+
+
+* **safety check**: check if the user message is safe, returning debugging details.
+
+
+
+* **markdown sample**: returns a fixed Markdown content example.
+
+
+
+* **code sample**: returns a fixed Code content example.
+
+
+
+* **json sample**: returns a fixed JSON content example.
+* **mermaid sample**: returns a fixed [Mermaid Graph](https://mermaid.js.org/syntax/examples.html) example.
+
+
+
+* **html sample**: returns a fixed HTML content example.
+* **music sample**: returns a fixed ABC Music example that can be played from the UI.
+
+
+* **none**: configures the agent not to reply to any message.
+
diff --git a/examples/dotnet-example02/appsettings.json b/examples/dotnet-example02/appsettings.json
new file mode 100644
index 00000000..a2f3558a
--- /dev/null
+++ b/examples/dotnet-example02/appsettings.json
@@ -0,0 +1,67 @@
+{
+ // Semantic Workbench connector settings
+ "Workbench": {
+ // Semantic Workbench endpoint.
+ "WorkbenchEndpoint": "http://127.0.0.1:3000",
+ // The endpoint of your service, where semantic workbench will send communications too.
+ // This should match hostname, port, protocol and path of the web service. You can use
+ // this also to route semantic workbench through a proxy or a gateway if needed.
+ "ConnectorEndpoint": "http://127.0.0.1:9101/myagents",
+ // Unique ID of the service. Semantic Workbench will store this event to identify the server
+ // so you should keep the value fixed to match the conversations tracked across service restarts.
+ "ConnectorId": "AgentExample02",
+ // Name of your agent service
+ "ConnectorName": ".NET Multi Agent Service 02",
+ // Description of your agent service.
+ "ConnectorDescription": "Multi-agent service for .NET agents",
+ // Where to store agents settings and conversations
+ // See AgentServiceStorage class.
+ "StoragePathLinux": "/tmp/.sw/AgentExample02",
+ "StoragePathWindows": "$tmp\\.sw\\AgentExample02"
+ },
+ // You agent settings
+ "Agent": {
+ "Name": "Agent2",
+ "ReplyToAgents": false,
+ "CommandsEnabled": true,
+ "Behavior": "none"
+ },
+ // Azure Content Safety settings
+ "AzureContentSafety": {
+ "Endpoint": "https://....cognitiveservices.azure.com/",
+ "AuthType": "ApiKey",
+ "ApiKey": "..."
+ },
+ // Web service settings
+ "AllowedHosts": "*",
+ "Kestrel": {
+ "Endpoints": {
+ "Http": {
+ "Url": "http://*:9101"
+ }
+ // "Https": {
+ // "Url": "https://*:9102"
+ // }
+ }
+ },
+ // .NET Logger settings
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Information"
+ },
+ "Console": {
+ "LogToStandardErrorThreshold": "Critical",
+ "FormatterName": "simple",
+ "FormatterOptions": {
+ "TimestampFormat": "[HH:mm:ss.fff] ",
+ "SingleLine": true,
+ "UseUtcTimestamp": false,
+ "IncludeScopes": false,
+ "JsonWriterOptions": {
+ "Indented": true
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/dotnet-example02/docs/abc.png b/examples/dotnet-example02/docs/abc.png
new file mode 100644
index 0000000000000000000000000000000000000000..59f88b85ef643b104138ce715b095f0a7f7bd892
GIT binary patch
literal 195865
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