builds but doesn't dynamically create the activities - why?

This commit is contained in:
Ryan Sweet
2023-07-17 16:19:59 -07:00
parent 2859a92da5
commit 6c759c28cd
4 changed files with 237 additions and 76 deletions

View File

@@ -25,6 +25,7 @@ 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;
namespace Elsa.SemanticKernel;
@@ -44,47 +45,196 @@ public class SemanticKernelActivityProvider : IActivityProvider
}
public async ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
{
//get a list of skills in the assembly
var skills = await LoadSkillsFromAssemblyAsync("skills");
var descriptors = new List<ActivityDescriptor>();
foreach (var skill in skills)
// get the kernel
var kernel = KernelBuilder();
// get a list of skills in the assembly
var skills = LoadSkillsFromAssemblyAsync("skills", kernel);
SKContext context = new SKContext();
var functionsAvailable = context.Skills.GetFunctionsView();
// create activity descriptors for each skilland function
var activities = new List<ActivityDescriptor>();
foreach (KeyValuePair<string, List<FunctionView>> skill in functionsAvailable.SemanticFunctions)
{
//var descriptor = await CreateActivityDescriptors(skill, cancellationToken);
// descriptors.Add(descriptor);
Console.WriteLine($"Creating Activities for Skill: {skill.Key}");
foreach (FunctionView func in skill.Value)
{
activities.Add(CreateActivityDescriptorFromSkillAndFunction(func, cancellationToken));
}
}
return descriptors;
return activities;
}
/// <summary>
/// Creates an activity descriptor from a skill and function.
/// </summary>
/// <param name="function">The semantic kernel function</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>An activity descriptor.</returns>
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<InputDescriptor>();
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<SemanticKernelSkill>(context);
// Customize the activity type name.
activityInstance.Type = fullTypeName;
// Configure the activity's URL and method properties.
activityInstance.SkillName = new Input<string?>(function.SkillName);
activityInstance.FunctionName = new Input<string?>(function.Name);
return activityInstance;
}
};
}
/// <summary>
/// Creates an input descriptor for a single line string
/// </summary>
/// <param name="name">The name of the input field</param>
/// <param name="description">The description of the input field</param>
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<T> object.
UIHint = InputUIHints.SingleLine,
ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(name),
ValueSetter = (activity, value) => activity.SyntheticProperties[name] = value!,
};
return inputDescriptor;
}
/// <summary>
/// Creates an input descriptor from an sk funciton parameter definition.
/// </summary>
/// <param name="parameter">The function parameter.</param>
/// <returns>An input descriptor.</returns>
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<T> object.
UIHint = InputUIHints.MultiLine,
ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(parameter.Name),
ValueSetter = (activity, value) => activity.SyntheticProperties[parameter.Name] = value!,
};
return inputDescriptor;
}
///<summary>
/// Gets a list of the skills in the assembly
///</summary>
private async Task<IEnumerable<string>> LoadSkillsFromAssemblyAsync(string assemblyName)
private IEnumerable<string> LoadSkillsFromAssemblyAsync(string assemblyName, IKernel kernel)
{
var skills = new List<string>();
var assembly = Assembly.Load(assemblyName);
//IEnumerable<Type> skillTypes = GetTypesInNamespace(assembly, "skills");
Type[] skillTypes = assembly.GetTypes().ToArray();
foreach(Type skillType in skillTypes)
Type[] skillTypes = assembly.GetTypes().ToArray();
foreach (Type skillType in skillTypes)
{
if (skillType.Namespace.Equals("Microsoft.SKDevTeam"))
{
Console.WriteLine($"Found type: {assembly.FullName}.{skillType.Namespace}.{skillType.Name}");
if(skillType.Namespace.Equals("Microsoft.SKDevTeam"))
skills.Add(skillType.Name);
var functions = skillType.GetFields();
foreach (var function in functions)
{
skills.Add(skillType.Name);
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($"Added skill: {skillType.Name}");
Console.WriteLine($"SK Added function: {skfunc.SkillName}.{skfunc.Name}");
}
}
}
}
return skills;
}
private IEnumerable<Type> GetTypesInNamespace(Assembly assembly, string nameSpace)
/// <summary>
/// Gets a semantic kernel instance
/// </summary>
/// <returns>Microsoft.SemanticKernel.IKernel</returns>
private IKernel KernelBuilder()
{
return
assembly.GetTypes()
.Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal));
var kernelSettings = KernelSettings.LoadSettings();
var kernelConfig = new KernelConfig();
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning);
});
var kernel = new KernelBuilder()
.WithLogger(loggerFactory.CreateLogger<IKernel>())
.WithAzureChatCompletionService(kernelSettings.DeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey, true, kernelSettings.ServiceId, true)
.WithConfiguration(kernelConfig)
.Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig
{
MaxRetryCount = KernelSettings.DefaultMaxRetries,
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;
}
}

View File

@@ -33,12 +33,6 @@ namespace Elsa.SemanticKernel;
[PublicAPI]
public class SemanticKernelSkill : CodeActivity<string>
{
//constructor - called by the workflow engine
public SemanticKernelSkill(string? source = default, int? line = default) : base(source, line)
{
}
[Input(
Description = "System Prompt",
UIHint = InputUIHints.MultiLine,
@@ -54,7 +48,7 @@ public class SemanticKernelSkill : CodeActivity<string>
[Input(
Description = "Max retries",
UIHint = InputUIHints.SingleLine,
DefaultValue = 9)]
DefaultValue = KernelSettings.DefaultMaxRetries)]
public Input<int> MaxRetries { get; set; }
[Input(
@@ -98,6 +92,17 @@ public class SemanticKernelSkill : CodeActivity<string>
// get the kernel
var kernel = KernelBuilder();
// load the skill
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);
// set the context (our prompt)
var contextVars = new ContextVariables();
contextVars.Set("input", prompt);
/* var interestingMemories = kernel.Memory.SearchAsync("ImportedMemories", prompt, 2);
var wafContext = "Consider the following contextual snippets:";
await foreach (var memory in interestingMemories)
@@ -105,61 +110,60 @@ public class SemanticKernelSkill : CodeActivity<string>
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<string, List<FunctionView>> 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);
SKContext answer = await kernel.RunAsync(contextVars, function).ConfigureAwait(false);
string result = answer.Result;
workflowContext.SetResult(result);
}
}
/// <summary>
/// Load the skills into the kernel
/// </summary>
private string ListSkillsInKernel(IKernel kernel)
{
var theSkills = LoadSkillsFromAssemblyAsync("skills", kernel);
SKContext context = kernel.CreateNewContext();
var functionsAvailable = context.Skills.GetFunctionsView();
var list = new StringBuilder();
foreach (KeyValuePair<string, List<FunctionView>> 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()}");
return list.ToString();
}
/// <summary>
/// Gets a semantic kernel instance
/// </summary>
@@ -187,7 +191,7 @@ public class SemanticKernelSkill : CodeActivity<string>
.WithConfiguration(kernelConfig)
.Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig
{
MaxRetryCount = maxRetries,
MaxRetryCount = KernelSettings.DefaultMaxRetries,
UseExponentialBackoff = true,
// MinRetryDelay = TimeSpan.FromSeconds(2),
// MaxRetryDelay = TimeSpan.FromSeconds(8),

View File

@@ -9,6 +9,7 @@ internal class KernelSettings
public const string DefaultConfigFile = "config/appsettings.json";
public const string OpenAI = "OPENAI";
public const string AzureOpenAI = "AZUREOPENAI";
public const int DefaultMaxRetries = 9;
[JsonPropertyName("serviceType")]
public string ServiceType { get; set; } = string.Empty;