diff --git a/examples/dotnet/dotnet-02-message-types-demo/Program.cs b/examples/dotnet/dotnet-02-message-types-demo/Program.cs index 3f8d9889..bbbb1f5a 100644 --- a/examples/dotnet/dotnet-02-message-types-demo/Program.cs +++ b/examples/dotnet/dotnet-02-message-types-demo/Program.cs @@ -54,12 +54,11 @@ private static IServiceCollection AddAzureAIContentSafety( IConfiguration config) { var authType = config.GetValue("AuthType"); - var endpoint = config.GetValue("Endpoint"); + var endpoint = new Uri(config.GetValue("Endpoint")!); var apiKey = config.GetValue("ApiKey"); return services.AddSingleton(_ => authType == "AzureIdentity" - ? new ContentSafetyClient(new Uri(endpoint!), new DefaultAzureCredential()) - : new ContentSafetyClient(new Uri(endpoint!), - new AzureKeyCredential(apiKey!))); + ? new ContentSafetyClient(endpoint, new DefaultAzureCredential()) + : new ContentSafetyClient(endpoint, new AzureKeyCredential(apiKey!))); } } diff --git a/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs b/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs index fd905a87..ae1597f5 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 : IAgentConfig +public class MyAgentConfig : AgentConfig { // Define safety and behavioral guardrails. // See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message for more information and examples. @@ -22,198 +22,93 @@ public class MyAgentConfig : IAgentConfig [JsonPropertyName(nameof(this.SystemPromptSafety))] [JsonPropertyOrder(0)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "Safety guardrails")] + [AgentConfigProperty("description", "Instructions used to define safety and behavioral guardrails. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message.")] + [AgentConfigProperty("maxLength", 2048)] + [AgentConfigProperty("default", DefaultPromptSafety)] + [AgentConfigProperty("uischema", "textarea")] public string SystemPromptSafety { get; set; } = DefaultPromptSafety; [JsonPropertyName(nameof(this.SystemPrompt))] [JsonPropertyOrder(1)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "System prompt")] + [AgentConfigProperty("description", "Initial system message used to define the assistant behavior.")] + [AgentConfigProperty("maxLength", 32768)] + [AgentConfigProperty("default", DefaultSystemPrompt)] + [AgentConfigProperty("uischema", "textarea")] public string SystemPrompt { get; set; } = DefaultSystemPrompt; [JsonPropertyName(nameof(this.ReplyToAgents))] [JsonPropertyOrder(10)] + [AgentConfigProperty("type", "boolean")] + [AgentConfigProperty("title", "Reply to other assistants in conversations")] + [AgentConfigProperty("description", "Reply to assistants")] + [AgentConfigProperty("default", false)] public bool ReplyToAgents { get; set; } = false; [JsonPropertyName(nameof(this.CommandsEnabled))] [JsonPropertyOrder(20)] + [AgentConfigProperty("type", "boolean")] + [AgentConfigProperty("title", "Support commands")] + [AgentConfigProperty("description", "Support commands, e.g. /say")] + [AgentConfigProperty("default", false)] public bool CommandsEnabled { get; set; } = false; [JsonPropertyName(nameof(this.MaxMessagesCount))] [JsonPropertyOrder(30)] + [AgentConfigProperty("type", "integer")] + [AgentConfigProperty("title", "Max conversation messages")] + [AgentConfigProperty("description", "How many messages to answer in a conversation before ending and stopping replies.")] + [AgentConfigProperty("minimum", 1)] + [AgentConfigProperty("maximum", int.MaxValue)] + [AgentConfigProperty("default", 100)] public int MaxMessagesCount { get; set; } = 100; [JsonPropertyName(nameof(this.Temperature))] [JsonPropertyOrder(40)] + [AgentConfigProperty("type", "number")] + [AgentConfigProperty("title", "LLM temperature")] + [AgentConfigProperty("description", "The temperature value ranges from 0 to 1. Lower values indicate greater determinism and higher values indicate more randomness.")] + [AgentConfigProperty("minimum", 0.0)] + [AgentConfigProperty("maximum", 1.0)] + [AgentConfigProperty("default", 0.0)] public double Temperature { get; set; } = 0.0; [JsonPropertyName(nameof(this.NucleusSampling))] [JsonPropertyOrder(50)] + [AgentConfigProperty("type", "number")] + [AgentConfigProperty("title", "LLM nucleus sampling")] + [AgentConfigProperty("description", "Nucleus sampling probability ranges from 0 to 1. Lower values result in more deterministic outputs by limiting the choice to the most probable words, and higher values allow for more randomness by including a larger set of potential words.")] + [AgentConfigProperty("minimum", 0.0)] + [AgentConfigProperty("maximum", 1.0)] + [AgentConfigProperty("default", 1.0)] public double NucleusSampling { get; set; } = 1.0; [JsonPropertyName(nameof(this.LLMProvider))] [JsonPropertyOrder(60)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("default", "openai")] + [AgentConfigProperty("enum", new[] { "openai", "azure-openai" })] + [AgentConfigProperty("title", "LLM provider")] + [AgentConfigProperty("description", "AI service providing the LLM.")] + [AgentConfigProperty("uischema", "radiobutton")] public string LLMProvider { get; set; } = "openai"; - // [JsonPropertyName(nameof(this.LLMEndpoint))] - // [JsonPropertyOrder(70)] - // public string LLMEndpoint { get; set; } = "https://api.openai.com/v1"; - [JsonPropertyName(nameof(this.ModelName))] [JsonPropertyOrder(80)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "OpenAI Model (or Azure Deployment)")] + [AgentConfigProperty("description", "Model used to generate text.")] + [AgentConfigProperty("maxLength", 256)] + [AgentConfigProperty("default", "GPT-4o")] public string ModelName { get; set; } = "gpt-4o"; - 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.SystemPrompt = cfg.SystemPrompt; - this.SystemPromptSafety = cfg.SystemPromptSafety; - this.ReplyToAgents = cfg.ReplyToAgents; - this.CommandsEnabled = cfg.CommandsEnabled; - this.MaxMessagesCount = cfg.MaxMessagesCount; - this.Temperature = cfg.Temperature; - this.NucleusSampling = cfg.NucleusSampling; - this.LLMProvider = cfg.LLMProvider; - // this.LLMEndpoint = cfg.LLMEndpoint; - this.ModelName = cfg.ModelName; - } - public string RenderSystemPrompt() { return string.IsNullOrWhiteSpace(this.SystemPromptSafety) ? this.SystemPrompt : $"{this.SystemPromptSafety}\n{this.SystemPrompt}"; } - - public object ToWorkbenchFormat() - { - Dictionary result = new(); - Dictionary defs = new(); - Dictionary properties = new(); - Dictionary jsonSchema = new(); - Dictionary uiSchema = new(); - - // AI Safety configuration. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message - properties[nameof(this.SystemPromptSafety)] = new Dictionary - { - { "type", "string" }, - { "title", "Safety guardrails" }, - { - "description", - "Instructions used to define safety and behavioral guardrails. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message." - }, - { "maxLength", 2048 }, - { "default", DefaultPromptSafety } - }; - ConfigUtils.UseTextAreaFor(nameof(this.SystemPromptSafety), uiSchema); - - // Initial AI instructions, aka System prompt or Meta-prompt. - properties[nameof(this.SystemPrompt)] = new Dictionary - { - { "type", "string" }, - { "title", "System prompt" }, - { "description", "Initial system message used to define the assistant behavior." }, - { "maxLength", 32768 }, - { "default", DefaultSystemPrompt } - }; - ConfigUtils.UseTextAreaFor(nameof(this.SystemPrompt), uiSchema); - - 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.MaxMessagesCount)] = new Dictionary - { - { "type", "integer" }, - { "title", "Max conversation messages" }, - { "description", "How many messages to answer in a conversation before ending and stopping replies." }, - { "minimum", 1 }, - { "maximum", int.MaxValue }, - { "default", 100 } - }; - - properties[nameof(this.Temperature)] = new Dictionary - { - { "type", "number" }, - { "title", "LLM temperature" }, - { - "description", - "The temperature value ranges from 0 to 1. Lower values indicate greater determinism and higher values indicate more randomness." - }, - { "minimum", 0.0 }, - { "maximum", 1.0 }, - { "default", 0.0 } - }; - - properties[nameof(this.NucleusSampling)] = new Dictionary - { - { "type", "number" }, - { "title", "LLM nucleus sampling" }, - { - "description", - "Nucleus sampling probability ranges from 0 to 1. Lower values result in more deterministic outputs by limiting the choice to the most probable words, and higher values allow for more randomness by including a larger set of potential words." - }, - { "minimum", 0.0 }, - { "maximum", 1.0 }, - { "default", 1.0 } - }; - - properties[nameof(this.LLMProvider)] = new Dictionary - { - { "type", "string" }, - { "default", "openai" }, - { "enum", new[] { "openai", "azure-openai" } }, - { "title", "LLM provider" }, - { "description", "AI service providing the LLM." }, - }; - ConfigUtils.UseRadioButtonsFor(nameof(this.LLMProvider), uiSchema); - - // properties[nameof(this.LLMEndpoint)] = new Dictionary - // { - // { "type", "string" }, - // { "title", "LLM endpoint" }, - // { "description", "OpenAI/Azure OpenAI endpoint." }, - // { "maxLength", 256 }, - // { "default", "https://api.openai.com/v1" } - // }; - - properties[nameof(this.ModelName)] = new Dictionary - { - { "type", "string" }, - { "title", "OpenAI Model (or Azure Deployment)" }, - { "description", "Model used to generate text." }, - { "maxLength", 256 }, - { "default", "GPT-4o" } - }; - - 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/dotnet-03-simple-chatbot/Program.cs b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs index 61bc62fb..9a9a328c 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/Program.cs +++ b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs @@ -48,13 +48,12 @@ private static IServiceCollection AddAzureAIContentSafety( IConfiguration config) { var authType = config.GetValue("AuthType"); - var endpoint = config.GetValue("Endpoint"); + var endpoint = new Uri(config.GetValue("Endpoint")!); var apiKey = config.GetValue("ApiKey"); return services.AddSingleton(_ => authType == "AzureIdentity" - ? new ContentSafetyClient(new Uri(endpoint!), new DefaultAzureCredential()) - : new ContentSafetyClient(new Uri(endpoint!), - new AzureKeyCredential(apiKey!))); + ? new ContentSafetyClient(endpoint, new DefaultAzureCredential()) + : new ContentSafetyClient(endpoint, new AzureKeyCredential(apiKey!))); } /* diff --git a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj index 0768b4be..6e47ae56 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj +++ b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj @@ -13,7 +13,6 @@ - @@ -57,4 +56,9 @@ + + + + \ No newline at end of file diff --git a/libraries/dotnet/SemanticWorkbench.sln.DotSettings b/libraries/dotnet/SemanticWorkbench.sln.DotSettings index 67d57727..8732803f 100644 --- a/libraries/dotnet/SemanticWorkbench.sln.DotSettings +++ b/libraries/dotnet/SemanticWorkbench.sln.DotSettings @@ -4,4 +4,5 @@ CORS HTML JSON - LLM \ No newline at end of file + LLM + UI \ No newline at end of file diff --git a/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs new file mode 100644 index 00000000..0c8d9822 --- /dev/null +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs @@ -0,0 +1,77 @@ +// 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 object? ToWorkbenchFormat() + { + Dictionary result = new(); + Dictionary defs = new(); + Dictionary properties = new(); + Dictionary jsonSchema = new(); + Dictionary uiSchema = new(); + + foreach (var property in this.GetType().GetProperties()) + { + var config = new Dictionary(); + var attributes = property.GetCustomAttributes(); + foreach (var attribute in attributes) + { + config[attribute.Name] = attribute.Value; + } + + properties[property.Name] = config; + + if (config.TryGetValue("uischema", out var uiSchemaValue)) + { + switch (uiSchemaValue) + { + case "textarea": + ConfigUtils.UseTextAreaFor(property.Name, uiSchema); + break; + case "radiobutton": + ConfigUtils.UseRadioButtonsFor(property.Name, uiSchema); + break; + default: + break; + } + } + } + + 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; + } + + 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/AgentConfigPropertyAttribute.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigPropertyAttribute.cs new file mode 100644 index 00000000..5d38aa6d --- /dev/null +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigPropertyAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; + +// ReSharper disable once CheckNamespace +namespace Microsoft.SemanticWorkbench.Connector; + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] +public class AgentConfigPropertyAttribute : Attribute +{ + public string Name { get; } + public object Value { get; } + + public AgentConfigPropertyAttribute(string name, object value) + { + this.Name = name; + this.Value = value; + } +} diff --git a/libraries/dotnet/WorkbenchConnector/ConfigUtils.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs similarity index 94% rename from libraries/dotnet/WorkbenchConnector/ConfigUtils.cs rename to libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs index 7c4f7c42..d5194c27 100644 --- a/libraries/dotnet/WorkbenchConnector/ConfigUtils.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; +// ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; public static class ConfigUtils diff --git a/libraries/dotnet/WorkbenchConnector/IAgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs similarity index 84% rename from libraries/dotnet/WorkbenchConnector/IAgentConfig.cs rename to libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs index c4f0122a..5f0f5912 100644 --- a/libraries/dotnet/WorkbenchConnector/IAgentConfig.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +// ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; public interface IAgentConfig diff --git a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj index 34719fc9..ef9c5eea 100644 --- a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj +++ b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj @@ -1,7 +1,7 @@  - 0.1.0 + 0.2.0 Microsoft.SemanticWorkbench.Connector Microsoft.SemanticWorkbench.Connector net8.0 @@ -9,7 +9,7 @@ enable 12 LatestMajor - $(NoWarn);IDE0130;CA2254;CA1812; + $(NoWarn);IDE0130;CA2254;CA1812;CA1813; @@ -107,4 +107,4 @@ - + \ No newline at end of file