using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Elsa.Extensions; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; using Microsoft.AI.DevTeam.Skills; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SkillDefinition; namespace Elsa.SemanticKernel; // // Loads the Semantic Kernel skills and then generates activites for each skill // public class SemanticKernelActivityProvider : IActivityProvider { private readonly IActivityFactory _activityFactory; private readonly IActivityDescriber _activityDescriber; public SemanticKernelActivityProvider(IActivityFactory activityFactory, IActivityDescriber activityDescriber) { _activityFactory = activityFactory; _activityDescriber = activityDescriber; } public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) { // get the kernel var kernel = KernelBuilder(); // get a list of skills in the assembly var skills = LoadSkillsFromAssemblyAsync("skills", kernel); SKContext context = kernel.CreateNewContext(); var functionsAvailable = context.Skills.GetFunctionsView(); // create activity descriptors for each skilland function var activities = new List(); foreach (KeyValuePair> skill in functionsAvailable.SemanticFunctions) { Console.WriteLine($"Creating Activities for Skill: {skill.Key}"); foreach (FunctionView func in skill.Value) { activities.Add(CreateActivityDescriptorFromSkillAndFunction(func, cancellationToken)); } } return activities; } /// /// Creates an activity descriptor from a skill and function. /// /// The semantic kernel function /// An optional cancellation token. /// An activity descriptor. private ActivityDescriptor CreateActivityDescriptorFromSkillAndFunction(FunctionView function, CancellationToken cancellationToken = default) { // Create a fully qualified type name for the activity var thisNamespace = GetType().Namespace; var fullTypeName = $"{thisNamespace}.{function.SkillName}.{function.Name}"; Console.WriteLine($"Creating Activity: {fullTypeName}"); // create inputs from the function parameters - the SemanticKernelSkill activity will be the base for each activity var inputs = new List(); foreach (var p in function.Parameters) { inputs.Add(CreateInputDescriptorFromSKParameter(p)); } inputs.Add(CreateInputDescriptor(typeof(string), "SkillName", function.SkillName, "The name of the skill to use (generated, do not change)")); inputs.Add(CreateInputDescriptor(typeof(string), "FunctionName", function.Name, "The name of the function to use (generated, do not change)")); inputs.Add(CreateInputDescriptor(typeof(int), "MaxRetries", KernelSettings.DefaultMaxRetries, "Max Retries to contact AI Service")); return new ActivityDescriptor { Kind = ActivityKind.Task, Category = "Semantic Kernel", Description = function.Description, Name = function.Name, TypeName = fullTypeName, Namespace = $"{thisNamespace}.{function.SkillName}", DisplayName = $"{function.SkillName}.{function.Name}", Inputs = inputs, Outputs = new[] {new OutputDescriptor()}, Constructor = context => { // The constructor is called when an activity instance of this type is requested. // Create the activity instance. var activityInstance = _activityFactory.Create(context); // Customize the activity type name. activityInstance.Type = fullTypeName; // Configure the activity's URL and method properties. activityInstance.SkillName = new Input(function.SkillName); activityInstance.FunctionName = new Input(function.Name); return activityInstance; } }; } /// /// Creates an input descriptor for a single line string /// /// The name of the input field /// The description of the input field private InputDescriptor CreateInputDescriptor(Type inputType, string name, Object defaultValue, string description) { var inputDescriptor = new InputDescriptor { Description = description, DefaultValue = defaultValue, Type = inputType, Name = name, DisplayName = name, IsSynthetic = true, // This is a synthetic property, i.e. it is not part of the activity's .NET type. IsWrapped = true, // This property is wrapped within an Input object. UIHint = InputUIHints.SingleLine, ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(name), ValueSetter = (activity, value) => activity.SyntheticProperties[name] = value!, }; return inputDescriptor; } /// /// Creates an input descriptor from an sk funciton parameter definition. /// /// The function parameter. /// An input descriptor. private InputDescriptor CreateInputDescriptorFromSKParameter(ParameterView parameter) { var inputDescriptor = new InputDescriptor { Description = string.IsNullOrEmpty(parameter.Description) ? parameter.Name : parameter.Description, DefaultValue = string.IsNullOrEmpty(parameter.DefaultValue) ? string.Empty : parameter.DefaultValue, Type = typeof(string), Name = parameter.Name, DisplayName = parameter.Name, IsSynthetic = true, // This is a synthetic property, i.e. it is not part of the activity's .NET type. IsWrapped = true, // This property is wrapped within an Input object. UIHint = InputUIHints.MultiLine, ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(parameter.Name), ValueSetter = (activity, value) => activity.SyntheticProperties[parameter.Name] = value!, }; return inputDescriptor; } /// /// 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($"SKActivityProvider Added SK function: {skfunc.SkillName}.{skfunc.Name}"); } } } } return skills; } /// /// Gets a semantic kernel instance /// /// Microsoft.SemanticKernel.IKernel private IKernel KernelBuilder() { var kernelSettings = KernelSettings.LoadSettings(); using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning); }); var kernel = new KernelBuilder() .WithLoggerFactory(loggerFactory) .WithAzureChatCompletionService(kernelSettings.DeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey, true, kernelSettings.ServiceId, true) .Build(); return kernel; } }