mirror of
https://github.com/microsoft/autogen.git
synced 2026-04-20 03:02:16 -04:00
cli calls local skills, instead of http function call
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class SkillsResponse
|
||||
{
|
||||
[JsonPropertyName("response")]
|
||||
public string? Response { get; set; }
|
||||
}
|
||||
@@ -1,10 +1,32 @@
|
||||
using System.CommandLine;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Orchestration;
|
||||
using skills;
|
||||
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var kernelSettings = KernelSettings.LoadSettings();
|
||||
|
||||
var kernelConfig = new KernelConfig();
|
||||
kernelConfig.AddCompletionBackend(kernelSettings);
|
||||
|
||||
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder
|
||||
.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning)
|
||||
.AddConsole()
|
||||
.AddDebug();
|
||||
});
|
||||
|
||||
var kernel = new KernelBuilder()
|
||||
.WithLogger(loggerFactory.CreateLogger<IKernel>())
|
||||
.WithConfiguration(kernelConfig).Build();
|
||||
|
||||
|
||||
var fileOption = new Option<FileInfo?>(
|
||||
name: "--file",
|
||||
description: "The file used for input to the skill function");
|
||||
@@ -14,27 +36,27 @@ class Program
|
||||
|
||||
var doCommand = new Command("do", "Doers :) ");
|
||||
var doItCommand = new Command("it", "Do it!");
|
||||
doItCommand.SetHandler(async (file) => await ChainFunctions(file.FullName), fileOption);
|
||||
doItCommand.SetHandler(async (file) => await ChainFunctions(file.FullName, kernel), fileOption);
|
||||
doCommand.AddCommand(doItCommand);
|
||||
|
||||
var pmCommand = new Command("pm", "Commands for the PM team");
|
||||
var pmReadmeCommand = new Command("readme", "Produce a Readme for a given input");
|
||||
pmReadmeCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.Readme , file.FullName), fileOption);
|
||||
pmReadmeCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.Readme , file.FullName, kernel), fileOption);
|
||||
|
||||
var pmBootstrapCommand = new Command("bootstrap", "Bootstrap a project for a given input");
|
||||
pmBootstrapCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file.FullName), fileOption);
|
||||
pmBootstrapCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file.FullName, kernel), fileOption);
|
||||
|
||||
pmCommand.AddCommand(pmReadmeCommand);
|
||||
pmCommand.AddCommand(pmBootstrapCommand);
|
||||
|
||||
var devleadCommand = new Command("devlead", "Commands for the Dev Lead team");
|
||||
var devleadPlanCommand = new Command("plan", "Plan the work for a given input");
|
||||
devleadPlanCommand.SetHandler(async (file) => await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file.FullName), fileOption);
|
||||
devleadPlanCommand.SetHandler(async (file) => await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file.FullName, kernel), fileOption);
|
||||
devleadCommand.AddCommand(devleadPlanCommand);
|
||||
|
||||
var devCommand = new Command("dev", "Commands for the Dev team");
|
||||
var devPlanCommand = new Command("plan", "Implement the module for a given input");
|
||||
devPlanCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(Developer), Developer.Implement, file.FullName), fileOption);
|
||||
devPlanCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(Developer), Developer.Implement, file.FullName, kernel), fileOption);
|
||||
devCommand.AddCommand(devPlanCommand);
|
||||
|
||||
rootCommand.AddCommand(pmCommand);
|
||||
@@ -45,25 +67,25 @@ class Program
|
||||
await rootCommand.InvokeAsync(args);
|
||||
}
|
||||
|
||||
public static async Task ChainFunctions(string file)
|
||||
public static async Task ChainFunctions(string file, IKernel kernel)
|
||||
{
|
||||
var sandboxSkill = new SandboxSkill();
|
||||
var outputPath = Directory.CreateDirectory("output");
|
||||
|
||||
var readme = await CallWithFile<string>(nameof(PM), PM.Readme , file);
|
||||
var readme = await CallWithFile<string>(nameof(PM), PM.Readme , file, kernel);
|
||||
await SaveToFile(Path.Combine(outputPath.FullName, "README.md"), readme);
|
||||
|
||||
var script = await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file);
|
||||
var script = await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file, kernel);
|
||||
await sandboxSkill.RunInDotnetAlpineAsync(script);
|
||||
await SaveToFile(Path.Combine(outputPath.FullName, "bootstrap.sh"), script);
|
||||
|
||||
var plan = await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file);
|
||||
var plan = await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file, kernel);
|
||||
await SaveToFile(Path.Combine(outputPath.FullName, "plan.json"), JsonSerializer.Serialize(plan));
|
||||
|
||||
var implementationTasks = plan.steps.SelectMany(
|
||||
(step) => step.subtasks.Select(
|
||||
async (subtask) => {
|
||||
var implementationResult = await CallFunction<string>(nameof(Developer), Developer.Implement, subtask.LLM_prompt);
|
||||
var implementationResult = await CallFunction<string>(nameof(Developer), Developer.Implement, subtask.LLM_prompt, kernel);
|
||||
await sandboxSkill.RunInDotnetAlpineAsync(implementationResult);
|
||||
await SaveToFile(Path.Combine(outputPath.FullName, $"{step.step}-{subtask.subtask}.sh"), implementationResult);
|
||||
return implementationResult; }));
|
||||
@@ -75,43 +97,28 @@ class Program
|
||||
await File.WriteAllTextAsync(filePath, content);
|
||||
}
|
||||
|
||||
public static async Task<T> CallWithFile<T>(string skillName, string functionName, string filePath)
|
||||
public static async Task<T> CallWithFile<T>(string skillName, string functionName, string filePath, IKernel kernel)
|
||||
{
|
||||
if(!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}", filePath);
|
||||
var input = File.ReadAllText(filePath);
|
||||
return await CallFunction<T>(skillName, functionName, input);
|
||||
return await CallFunction<T>(skillName, functionName, input, kernel);
|
||||
}
|
||||
|
||||
public static async Task<T> CallFunction<T>(string skillName, string functionName, string input)
|
||||
public static async Task<T> CallFunction<T>(string skillName, string functionName, string input, IKernel kernel)
|
||||
{
|
||||
var variables = new[]
|
||||
{
|
||||
new { key = "input", value = input }
|
||||
};
|
||||
|
||||
var requestBody = new { variables };
|
||||
var requestBodyJson = JsonSerializer.Serialize(requestBody);
|
||||
|
||||
Console.WriteLine($"Calling skill '{skillName}' function '{functionName}' with input '{input}'");
|
||||
Console.WriteLine(requestBodyJson);
|
||||
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);
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
var apiUrl = $"http://localhost:7071/api/skills/{skillName}/functions/{functionName}";
|
||||
var response = await httpClient.PostAsync(apiUrl, new StringContent(requestBodyJson));
|
||||
var context = new ContextVariables();
|
||||
context.Set("input", input);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Error: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var skillResponse = JsonSerializer.Deserialize<SkillsResponse>(responseJson);
|
||||
var result = typeof(T) != typeof(string) ? JsonSerializer.Deserialize<T>(skillResponse.Response) : (T)(object)skillResponse.Response;
|
||||
|
||||
Console.WriteLine(responseJson);
|
||||
var answer = await kernel.RunAsync(context, function).ConfigureAwait(false);
|
||||
var result = typeof(T) != typeof(string) ? JsonSerializer.Deserialize<T>(answer.ToString()) : (T)(object)answer.ToString();
|
||||
Console.WriteLine(answer);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="0.14.547.1-preview" />
|
||||
<PackageReference Include="Testcontainers" Version="3.2.0" />
|
||||
|
||||
27
cli/config/KernelConfigExtensions.cs
Normal file
27
cli/config/KernelConfigExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
internal static class KernelConfigExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a text completion service to the list. It can be either an OpenAI or Azure OpenAI backend service.
|
||||
/// </summary>
|
||||
/// <param name="kernelConfig"></param>
|
||||
/// <param name="kernelSettings"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
internal static void AddCompletionBackend(this KernelConfig kernelConfig, KernelSettings kernelSettings)
|
||||
{
|
||||
switch (kernelSettings.ServiceType.ToUpperInvariant())
|
||||
{
|
||||
case KernelSettings.AzureOpenAI:
|
||||
kernelConfig.AddAzureChatCompletionService(kernelSettings.DeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey);
|
||||
break;
|
||||
|
||||
case KernelSettings.OpenAI:
|
||||
kernelConfig.AddOpenAITextCompletionService(modelId: kernelSettings.DeploymentOrModelId, apiKey: kernelSettings.ApiKey, orgId: kernelSettings.OrgId, serviceId: kernelSettings.ServiceId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Invalid service type value: {kernelSettings.ServiceType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
89
cli/config/KernelSettings.cs
Normal file
89
cli/config/KernelSettings.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
internal class KernelSettings
|
||||
{
|
||||
public const string DefaultConfigFile = "config/appsettings.json";
|
||||
public const string OpenAI = "OPENAI";
|
||||
public const string AzureOpenAI = "AZUREOPENAI";
|
||||
|
||||
[JsonPropertyName("serviceType")]
|
||||
public string ServiceType { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("serviceId")]
|
||||
public string ServiceId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("deploymentOrModelId")]
|
||||
public string DeploymentOrModelId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("endpoint")]
|
||||
public string Endpoint { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("apiKey")]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("orgId")]
|
||||
public string OrgId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("logLevel")]
|
||||
public LogLevel? LogLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Load the kernel settings from settings.json if the file exists and if not attempt to use user secrets.
|
||||
/// </summary>
|
||||
internal static KernelSettings LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(DefaultConfigFile))
|
||||
{
|
||||
return FromFile(DefaultConfigFile);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Semantic kernel settings '{DefaultConfigFile}' not found, attempting to load configuration from user secrets.");
|
||||
|
||||
return FromUserSecrets();
|
||||
}
|
||||
catch (InvalidDataException ide)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"Unable to load semantic kernel settings, please provide configuration settings using instructions in the README.\n" +
|
||||
"Please refer to: https://github.com/microsoft/semantic-kernel-starters/blob/main/sk-csharp-hello-world/README.md#configuring-the-starter"
|
||||
);
|
||||
throw new InvalidOperationException(ide.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the kernel settings from the specified configuration file if it exists.
|
||||
/// </summary>
|
||||
internal static KernelSettings FromFile(string configFile = DefaultConfigFile)
|
||||
{
|
||||
if (!File.Exists(configFile))
|
||||
{
|
||||
throw new FileNotFoundException($"Configuration not found: {configFile}");
|
||||
}
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
|
||||
.AddJsonFile(configFile, optional: true, reloadOnChange: true)
|
||||
.Build();
|
||||
|
||||
return configuration.Get<KernelSettings>()
|
||||
?? throw new InvalidDataException($"Invalid semantic kernel settings in '{configFile}', please provide configuration settings using instructions in the README.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the kernel settings from user secrets.
|
||||
/// </summary>
|
||||
internal static KernelSettings FromUserSecrets()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddUserSecrets<KernelSettings>()
|
||||
.Build();
|
||||
|
||||
return configuration.Get<KernelSettings>()
|
||||
?? throw new InvalidDataException("Invalid semantic kernel settings in user secrets, please provide configuration settings using instructions in the README.");
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class SemanticFunctionConfig
|
||||
public double TopP { get; set; }
|
||||
public double PPenalty { get; set; }
|
||||
public double FPenalty { get; set; }
|
||||
private static SemanticFunctionConfig ForSkillAndFunction(string skillName, string functionName) =>
|
||||
public static SemanticFunctionConfig ForSkillAndFunction(string skillName, string functionName) =>
|
||||
(skillName, functionName) switch
|
||||
{
|
||||
(nameof(PM), nameof(PM.BootstrapProject)) => PM.BootstrapProject,
|
||||
|
||||
Reference in New Issue
Block a user