using Elsa.Extensions; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Reliability; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SKDevTeam; using System; using System.Text; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Elsa.SemanticKernel; /// /// Invoke a Semantic Kernel skill. /// [Activity("Elsa", "AI Chat", "Invoke a Semantic Kernel skill. ", DisplayName = "Semantic Kernel Skill", Kind = ActivityKind.Task)] [PublicAPI] public class SemanticKernelSkill : CodeActivity { //constructor - called by the workflow engine public SemanticKernelSkill(string? source = default, int? line = default) : base(source, line) { } [Input( Description = "System Prompt", UIHint = InputUIHints.MultiLine, DefaultValue = PromptDefaults.SystemPrompt)] public Input SysPrompt { get; set; } = default!; [Input( Description = "User Input Prompt", UIHint = InputUIHints.MultiLine, DefaultValue = PromptDefaults.UserPrompt)] public Input Prompt { get; set; } [Input( Description = "Max retries", UIHint = InputUIHints.SingleLine, DefaultValue = 9)] public Input MaxRetries { get; set; } [Input( Description = "The skill to invoke from the semantic kernel", UIHint = InputUIHints.SingleLine, DefaultValue = "Chat")] public Input SkillName { get; set; } [Input( Description = "The function to invoke from the skill", UIHint = InputUIHints.SingleLine, DefaultValue = "ChatCompletion")] public Input FunctionName { get; set; } /* [Input( Description = "Mockup - don't actually call the AI, just output the prompts", UIHint = InputUIHints.Checkbox, DefaultValue = false)] public Input Mockup { get; set; } */ /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext workflowContext) { var test = SkillName.Get(workflowContext); var skillName = SkillName.Get(workflowContext); var functionName = FunctionName.Get(workflowContext); var systemPrompt = SysPrompt.Get(workflowContext); var maxRetries = MaxRetries.Get(workflowContext); var prompt = Prompt.Get(workflowContext); //var mockup = Mockup.Get(workflowContext); var mockup = false; string info = ($"#################\nSkill: {skillName}\nFunction: {functionName}\nPrompt: {prompt}\n#################\n\n"); if (mockup) { workflowContext.SetResult(info); } else { // get the kernel var kernel = KernelBuilder(); /* var interestingMemories = kernel.Memory.SearchAsync("ImportedMemories", prompt, 2); var wafContext = "Consider the following contextual snippets:"; await foreach (var memory in interestingMemories) { wafContext += $"\n {memory.Metadata.Text}"; } */ var skillConfig = SemanticFunctionConfig.ForSkillAndFunction(skillName, functionName); /* var function = kernel.CreateSemanticFunction(skillConfig.PromptTemplate, skillConfig.Name, skillConfig.SkillName, skillConfig.Description, skillConfig.MaxTokens, skillConfig.Temperature, skillConfig.TopP, skillConfig.PPenalty, skillConfig.FPenalty); */ var contextVars = new ContextVariables(); contextVars.Set("input", prompt); SKContext context = kernel.CreateNewContext(); var theSkills = LoadSkillsFromAssemblyAsync("skills", kernel); var functionsAvailable = context.Skills.GetFunctionsView(); var list = new StringBuilder(); foreach (KeyValuePair> skill in functionsAvailable.SemanticFunctions) { Console.WriteLine($"Skill: {skill.Key}"); foreach (FunctionView func in skill.Value) { // Function description if (func.Description != null) { list.AppendLine($"// {func.Description}"); } else { Console.WriteLine("{0}.{1} is missing a description", func.SkillName, func.Name); list.AppendLine($"// Function {func.SkillName}.{func.Name}."); } // Function name list.AppendLine($"{func.SkillName}.{func.Name}"); // Function parameters foreach (var p in func.Parameters) { var description = string.IsNullOrEmpty(p.Description) ? p.Name : p.Description; var defaultValueString = string.IsNullOrEmpty(p.DefaultValue) ? string.Empty : $" (default value: {p.DefaultValue})"; list.AppendLine($"Parameter \"{p.Name}\": {description} {defaultValueString}"); } } } Console.WriteLine($"List of all skills ----- {list.ToString()}"); //context.Set("wafContext", wafContext); SKContext answer = await kernel.RunAsync(contextVars, functionName).ConfigureAwait(false); string result = answer.Result; workflowContext.SetResult(result); } } /// /// Gets a semantic kernel instance /// /// Microsoft.SemanticKernel.IKernel private IKernel KernelBuilder() { var kernelSettings = KernelSettings.LoadSettings(); var kernelConfig = new KernelConfig(); using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning); }); /* var memoryStore = new QdrantMemoryStore(new QdrantVectorDbClient("http://qdrant", 1536, port: 6333)); var embedingGeneration = new AzureTextEmbeddingGeneration(kernelSettings.EmbeddingDeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey); var semanticTextMemory = new SemanticTextMemory(memoryStore, embedingGeneration); */ var kernel = new KernelBuilder() .WithLogger(loggerFactory.CreateLogger()) .WithAzureChatCompletionService(kernelSettings.DeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey, true, kernelSettings.ServiceId, true) //.WithMemory(semanticTextMemory) .WithConfiguration(kernelConfig) .Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig { MaxRetryCount = maxRetries, UseExponentialBackoff = true, // MinRetryDelay = TimeSpan.FromSeconds(2), // MaxRetryDelay = TimeSpan.FromSeconds(8), MaxTotalRetryTime = TimeSpan.FromSeconds(300), // RetryableStatusCodes = new[] { HttpStatusCode.TooManyRequests, HttpStatusCode.RequestTimeout }, // RetryableExceptions = new[] { typeof(HttpRequestException) } })) .Build(); return kernel; } /// /// Gets a list of the skills in the assembly /// private IEnumerable LoadSkillsFromAssemblyAsync(string assemblyName, IKernel kernel) { var skills = new List(); var assembly = Assembly.Load(assemblyName); Type[] skillTypes = assembly.GetTypes().ToArray(); foreach (Type skillType in skillTypes) { if (skillType.Namespace.Equals("Microsoft.SKDevTeam")) { skills.Add(skillType.Name); var functions = skillType.GetFields(); foreach (var function in functions) { string field = function.FieldType.ToString(); if (field.Equals("Microsoft.SKDevTeam.SemanticFunctionConfig")) { var skillConfig = SemanticFunctionConfig.ForSkillAndFunction(skillType.Name, function.Name); var skfunc = kernel.CreateSemanticFunction( skillConfig.PromptTemplate, skillConfig.Name, skillConfig.SkillName, skillConfig.Description, skillConfig.MaxTokens, skillConfig.Temperature, skillConfig.TopP, skillConfig.PPenalty, skillConfig.FPenalty); Console.WriteLine($"SK Added function: {skfunc.SkillName}.{skfunc.Name}"); } } } } return skills; } }