Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SKonsole configuration command and Spectre.Console package #3

Merged
merged 5 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions apps/SKonsole/Commands/ConfigCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Newtonsoft.Json.Linq;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SKonsole.Commands
{
public class ConfigCommand : Command
{
private readonly ConfigurationProvider _config;

public ConfigCommand(ConfigurationProvider config) : base("config", "skonsole configuration")
{
this._config = config;
Add(ConfigGetCommand());
Add(ConfigSetCommand());

this.SetHandler(async context => await RunConfigAsync(context.GetCancellationToken()));
}

Command ConfigGetCommand()
{
var keyArgument = new Argument<string>("key", "configuration key");

var getCommand = new Command("get", "get configuration value");

getCommand.AddArgument(keyArgument);

getCommand.SetHandler((key) =>
{
var value = _config.Get(key);
if (value == null)
{
AnsiConsole.MarkupLine($"[red]Configuration key '{key}' not found.[/]");
}
else
{
AnsiConsole.MarkupLine($"[green]{key}[/]: {value}");
}
}, keyArgument);
return getCommand;
}


Command ConfigSetCommand()
{
var keyArgument = new Argument<string>("key", "configuration key");
var valueArgument = new Argument<string>("value", "configuration value");

var setCommand = new Command("set", "set configuration value");
setCommand.AddArgument(keyArgument);
setCommand.AddArgument(valueArgument);

setCommand.SetHandler(_config.SaveConfig, keyArgument, valueArgument);
return setCommand;
}

static async Task RunConfigAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var keys = new[]
{
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME",
"AZURE_OPENAI_API_ENDPOINT",
"AZURE_OPENAI_API_KEY"
};

var config = new ConfigurationProvider();
var configKey = await new SelectionPrompt<string>()
.Title("Select key to config:")
.AddChoices(keys)
.ShowAsync(AnsiConsole.Console, token);

var currentValue = config.Get(configKey);

var value = await new TextPrompt<string>($"Set value for [green]{configKey}[/]")
.DefaultValue(currentValue ?? string.Empty)
.HideDefaultValue()
.Validate((value) =>
{
if (string.IsNullOrWhiteSpace(value))
{
return ValidationResult.Error("[red]Value cannot be empty[/]");
}
return ValidationResult.Success();
})
.AllowEmpty()
.ShowAsync(AnsiConsole.Console, token);
if (!string.IsNullOrWhiteSpace(value))
{
await config.SaveConfig(configKey, value.Trim());
}
}
}
}

}
60 changes: 60 additions & 0 deletions apps/SKonsole/ConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json;

public class ConfigurationProvider
{
public static ConfigurationProvider Instance = new();

const string _file = ".skonsole";
xbotter marked this conversation as resolved.
Show resolved Hide resolved

private readonly string _path;
private readonly IConfiguration _configuration;
private readonly Dictionary<string, string?> _config = new();

public ConfigurationProvider()
{
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
_path = Path.Combine(userProfile, _file);

if (File.Exists(_path))
{
_config = FromJson<Dictionary<string, string?>>(File.ReadAllText(_path)) ?? new();
}

_configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddInMemoryCollection(_config)
.Build();
}

public async Task SaveConfig(string key, string value)
{
_config[key] = value;

await File.WriteAllTextAsync(_path, ToJson(_config));
}

public string? Get(string key)
{
return _configuration[key];
}

private static string ToJson<T>(T obj)
{
return JsonSerializer.Serialize(obj);
}

private static T? FromJson<T>(string json)
{
if (json == null)
return default;
try
{
return JsonSerializer.Deserialize<T>(json);
}
catch
{
return default;
}
}
}
42 changes: 29 additions & 13 deletions apps/SKonsole/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.CommandLine;
using System.CommandLine.Builder;
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
Expand All @@ -10,6 +11,7 @@
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.Skills.Web;
using Microsoft.SemanticKernel.Skills.Web.Bing;
using SKonsole.Commands;
using SKonsole.Skills;

Console.OutputEncoding = Encoding.Unicode;
Expand Down Expand Up @@ -37,27 +39,25 @@
UseExponentialBackoff = true,
});
})
// .WithAzureTextCompletionService(
// EnvVar("AZURE_OPENAI_DEPLOYMENT_NAME"),
// EnvVar("AZURE_OPENAI_API_ENDPOINT"),
// EnvVar("AZURE_OPENAI_API_KEY"),
// EnvVar("AZURE_OPENAI_DEPLOYMENT_LABEL"))
.WithAzureChatCompletionService(
EnvVar("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"),
EnvVar("AZURE_OPENAI_API_ENDPOINT"),
EnvVar("AZURE_OPENAI_API_KEY"))
ConfigVar("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"),
ConfigVar("AZURE_OPENAI_API_ENDPOINT"),
ConfigVar("AZURE_OPENAI_API_KEY"))
.WithLogger(kernelLogger)
.Build();

_kernel.Logger.LogTrace("KernelSingleton.Instance: adding Azure OpenAI backends");


var rootCommand = new RootCommand();
var commitCommand = new Command("commit", "Commit subcommand");
var prCommand = new Command("pr", "Pull Request feedback subcommand");
var prFeedbackCommand = new Command("feedback", "Pull Request feedback subcommand");
var prDescriptionCommand = new Command("description", "Pull Request description subcommand");
var plannerCommand = new Command("createPlan", "Planner subcommand");
var promptChatCommand = new Command("promptChat", "Prompt chat subcommand");


var messageArgument = new Argument<string>
("message", "An argument that is parsed as a string.");

Expand All @@ -79,6 +79,7 @@
prCommand.Add(prFeedbackCommand);
prCommand.Add(prDescriptionCommand);

rootCommand.Add(new ConfigCommand(ConfigurationProvider.Instance));
rootCommand.Add(commitCommand);
rootCommand.Add(prCommand);
rootCommand.Add(plannerCommand);
Expand Down Expand Up @@ -211,7 +212,7 @@ static async Task RunCreatePlan(IKernel kernel, ILogger? logger, string message)

kernel.ImportSkill(new WriterSkill(kernel), "writer");

var bingConnector = new BingConnector(EnvVar("BING_API_KEY"));
var bingConnector = new BingConnector(ConfigVar("BING_API_KEY"));
var bing = new WebSearchEngineSkill(bingConnector);
var search = kernel.ImportSkill(bing, "bing");

Expand Down Expand Up @@ -247,10 +248,11 @@ static async Task RunPromptChat(IKernel kernel, ILogger? logger)
await RunChat(kernel, logger, chatFunction);
}

static string EnvVar(string name)
static string ConfigVar(string name)
{
var value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value)) throw new Exception($"Env var not set: {name}");
var provider = ConfigurationProvider.Instance;
var value = provider.Get(name);
if (string.IsNullOrEmpty(value)) throw new Exception($"Configuration var not set: {name}.Please run `skonsole config` to set it.");
return value;
}

Expand All @@ -270,12 +272,26 @@ static async Task RunChat(IKernel kernel, ILogger? logger, ISKFunction chatFunct
(logger ?? kernel.Logger).LogInformation("{botMessage}", botMessageFormatted);
(logger ?? kernel.Logger).LogInformation(">>>");

userMessage = Console.ReadLine(); // TODO -- How to support multi-line input?

userMessage = ReadMutiLineInput();
if (userMessage == "exit") break;

history += $"{botMessageFormatted}Human: {userMessage}\nAI:";
contextVariables.Set("history", history);

botMessage = await kernel.RunAsync(contextVariables, chatFunction);
}
}

static string ReadMutiLineInput()
{
var input = new StringBuilder();
var line = string.Empty;

while ((line = Console.ReadLine()) != string.Empty)
{
input.AppendLine(line);
}

return input.ToString().Trim();
}
2 changes: 2 additions & 0 deletions apps/SKonsole/SKonsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
<ToolCommandName>skonsole</ToolCommandName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.19.230804.2-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.Web" Version="0.19.230804.2-preview" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
Expand Down