diff --git a/.vscode/launch.json b/.vscode/launch.json index dfd58b6..d4e8b1f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,9 @@ "request": "launch", "preLaunchTask": "build", "program": "${workspaceFolder}/apps/SKonsole/bin/Debug/net7.0/SKonsole.dll", - "args": ["stepwise", "optionset", "bing++"], + "args": ["stepwise", "optionset", "git"], + // "args": ["git"], + "cwd": "${workspaceFolder}", "console": "integratedTerminal", "stopAtEntry": false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bc010a9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +We welcome contributions to improve SKonsole. If you have any suggestions or bug reports, please feel free to open an issue or submit a pull request. + +## Structure + +The repository is organized into the following main directories: + +- `apps`: Contains the SKonsole application. +- `plugins`: Contains the plugins for the SKonsole application, including the CondensePlugin and PRPlugin. + +## Getting Started + +To get started with the SKonsole application, follow these steps: + +1. Clone the repository. + +### Using Visual Studio +2. Open the `skonsole.sln` solution file in Visual Studio. +3. Build and run the SKonsole application. + +### Using Terminal + +*apps\SKonsole* +```Copy code +dotnet build +dotnet run +``` +This should build and run the SKonsole app. Note that you may need to configure your environment variables with your Azure OpenAI credentials before running the app. + +## Structure + +The repository is organized into the following main directories: + +- `apps`: Contains the SKonsole application. +- `plugins`: Contains the plugins for the SKonsole application, including the CondensePlugin and PRPlugin. + +## Projects and Classes +This repository contains several projects and classes, including: + +- PRPlugin: A plugin that can generate feedback, commit messages, and pull request descriptions based on git diff or git show output. The PRPlugin uses the CondensePlugin as a dependency and implements chunking and aggregation mechanisms to handle large inputs. + +- CondensePlugin: A plugin built on the Semantic Kernel that can condense multiple chunks of text into a single chunk. The plugin uses a semantic function defined with prompt templates and a completion engine. + +- CommitParser and CommitChunker: Two utility classes that split and parse the input based on commit and file information. These classes are useful for generating content and responses based on large text results. + +## Dependencies +This project requires the following dependencies: + +- [Semantic Kernel](https://github.com/microsoft/semantic-kernel) + +*Powered by [Microsoft Semantic Kernel](https://github.com/microsoft/semantic-kernel)* \ No newline at end of file diff --git a/README.md b/README.md index 00385cb..5837843 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,44 @@ - # SKonsole - A Console App built on Semantic Plugins -This repository contains a demo of a console application called SKonsole, which uses the Semantic Kernel to run various plugins. The app currently supports two plugins: generating commit messages and generating pull request feedback. The app uses environment variables to configure the Azure OpenAI backend. +Welcome to the SKonsole repository! SKonsole is a powerful command-line tool that leverages AI to assist you with various tasks. It provides a simple interface to interact with the AI model and perform operations like reading and writing files, searching for files, and even sending emails. The repository contains the source code for the SKonsole application and its plugins. + +## Features + +- AI-powered command-line interface +- Customizable configuration +- Generate commit messages, pull request descriptions, and feedback. +- Engage in chat conversations that are powered by various Plugins. + +## Usage + +### General Commands + +These commands will execute and return a result from the LLM. + +`skonsole commit `: Generate commit messages based on the provided commit hash. -## Getting Started -To get started, simply run the following commands in the terminal: +`skonsole pr feedback`: Generate valuable feedback for pull requests using git diff or git show output. -```Copy code -apps\SKonsole> dotnet build -apps\SKonsole> dotnet run -``` -This should build and run the SKonsole app. Note that you may need to configure your environment variables with your Azure OpenAI credentials before running the app. +`skonsole pr description`: Generate detailed descriptions for pull requests using git diff or git show output. -## Projects and Classes -This repository contains several projects and classes, including: +### Chat Commands -- PRPlugin: A plugin that can generate feedback, commit messages, and pull request descriptions based on git diff or git show output. The PRPlugin uses the CondensePlugin as a dependency and implements chunking and aggregation mechanisms to handle large inputs. +These commands will start a chat conversation with the LLM. -- CondensePlugin: A plugin built on the Semantic Kernel that can condense multiple chunks of text into a single chunk. The plugin uses a semantic function defined with prompt templates and a completion engine. +`skonsole stepwise [options]`: Engage in a StepwisePlanner powered chat session. Use `optionSet` option to specify which optionSets should be used for planning. -- CommitParser and CommitChunker: Two utility classes that split and parse the input based on commit and file information. These classes are useful for generating pull request descriptions and feedback based on the git diff output. +`skonsole createPlan `: Create plans using a Planner by providing a message and then execute the plan. + +`skonsole promptChat`: Engage in interactive prompt chat sessions for building semantic prompts using the LLM. + +### Other Commands + +These commands are other utilities that do not directly leverage LLMs. + +`skonsole config [command] [options]`: Configure SKonsole application settings like LLM endpoints, keys, etc. + +## Configuration + +You can customize the behavior of SKonsole by modifying the configuration settings. In addition to the `config` command, the configuration file is located at `.skonsole` in your user profile directory. You can also set environment variables to override the default settings. ## Installing SKonsole Tool @@ -36,27 +56,28 @@ Install the SKonsole Tool globally with a few quick steps: skonsole --version ``` -## Available Commands +## Plugins + +The repository includes the following plugins: -- `skonsole commit `: Generate commit messages based on the provided commit hash. +### CondensePlugin -- `skonsole pr feedback`: Generate valuable feedback for pull requests using git diff or git show output. +The CondensePlugin is designed to help condense text by using the LLM to merge multiple chunks of text. -- `skonsole pr description`: Generate detailed descriptions for pull requests using git diff or git show output. +### PRPlugin -- `skonsole createPlan `: Create plans using the Planner subcommand by providing a message. +The PRPlugin is designed to help generate pull request summaries and change lists from `git diff` output. -- `skonsole promptChat`: Engage in interactive prompt chat sessions. +### SuperFileIOPlugin -## Dependencies -This project requires the following dependencies: +The SuperFileIOPlugin is an extension of the FileIOPlugin in Semantic Kernel. It includes additional capabilities for reading and writing from the file system. -- [Semantic Kernel](https://github.com/microsoft/semantic-kernel) -## Future Work -In the future, the SKonsole app could be expanded to support more plugins and to parse arguments on launch. Additionally, the repository could include instructions for setting up NuGet credentials and using a GitHub Package source. +## Contributing +See [Contributing](CONTRIBUTING.md). -I hope this README is helpful for you and others who may use your repository in the future. Let me know if there's anything else I can do to help! +## License +SKonsole is licensed under the MIT License. -####_This README was generated using Semantic Kernel_ \ No newline at end of file +*Powered by [Microsoft Semantic Kernel](https://github.com/microsoft/semantic-kernel)* \ No newline at end of file diff --git a/apps/SKonsole/Commands/CommitCommand.cs b/apps/SKonsole/Commands/CommitCommand.cs index bac715f..de60008 100644 --- a/apps/SKonsole/Commands/CommitCommand.cs +++ b/apps/SKonsole/Commands/CommitCommand.cs @@ -14,7 +14,7 @@ public CommitCommand(ConfigurationProvider config, ILogger? logger = null) : bas if (logger is null) { using var loggerFactory = Logging.GetFactory(); - this._logger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(this.GetType()); } else { diff --git a/apps/SKonsole/Commands/PRCommand.cs b/apps/SKonsole/Commands/PRCommand.cs index 18a0a7c..f75e20b 100644 --- a/apps/SKonsole/Commands/PRCommand.cs +++ b/apps/SKonsole/Commands/PRCommand.cs @@ -14,7 +14,7 @@ public PRCommand(ConfigurationProvider config, ILogger? logger = null) : base("p if (logger is null) { using var loggerFactory = Logging.GetFactory(); - this._logger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(this.GetType()); } else { diff --git a/apps/SKonsole/Commands/PlannerCommand.cs b/apps/SKonsole/Commands/PlannerCommand.cs index 25e203e..71e8eb7 100644 --- a/apps/SKonsole/Commands/PlannerCommand.cs +++ b/apps/SKonsole/Commands/PlannerCommand.cs @@ -15,7 +15,7 @@ public PlannerCommand(ConfigurationProvider config, ILogger? logger = null) : ba if (logger is null) { using var loggerFactory = Logging.GetFactory(); - this._logger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(this.GetType()); } else { diff --git a/apps/SKonsole/Commands/PromptChatCommand.cs b/apps/SKonsole/Commands/PromptChatCommand.cs index edc4328..b1feed6 100644 --- a/apps/SKonsole/Commands/PromptChatCommand.cs +++ b/apps/SKonsole/Commands/PromptChatCommand.cs @@ -17,7 +17,7 @@ public PromptChatCommand(ConfigurationProvider config, ILogger? logger = null) : if (logger is null) { using var loggerFactory = Logging.GetFactory(); - this._logger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(this.GetType()); } else { diff --git a/apps/SKonsole/Commands/StepwisePlannerCommand.cs b/apps/SKonsole/Commands/StepwisePlannerCommand.cs index 14851a2..c5b4583 100644 --- a/apps/SKonsole/Commands/StepwisePlannerCommand.cs +++ b/apps/SKonsole/Commands/StepwisePlannerCommand.cs @@ -1,10 +1,12 @@ using System.CommandLine; using System.ComponentModel; using System.Text; +using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Planners; +using Microsoft.SemanticKernel.Planning; using Microsoft.SemanticKernel.Plugins.Core; using Microsoft.SemanticKernel.Plugins.Web; using Microsoft.SemanticKernel.Plugins.Web.Bing; @@ -21,7 +23,7 @@ public StepwisePlannerCommand(ConfigurationProvider config, ILogger? logger = nu if (logger is null) { using var loggerFactory = Logging.GetFactory(); - this._logger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(this.GetType()); } else { @@ -35,50 +37,93 @@ public StepwisePlannerCommand(ConfigurationProvider config, ILogger? logger = nu private static async Task RunCreatePlan(CancellationToken token, ILogger logger, string message = "", string optionSet = "") { - IKernel kernel = LoadOptionSet(optionSet); + (IKernel kernel, var validPlugins) = LoadOptionSet(optionSet); var stepKernel = KernelProvider.Instance.Get(); var functions = stepKernel.ImportFunctions(new StepwisePlugin(kernel), "stepwise"); - await RunChat(stepKernel, null, functions["RespondTo"]).ConfigureAwait(false); + await RunChat(stepKernel, validPlugins, null, functions["RespondTo"]).ConfigureAwait(false); } - private static IKernel LoadOptionSet(string optionSet) + private static (IKernel, List) LoadOptionSet(string optionSet) { var kernel = KernelProvider.Instance.Get(); + List validPlugins = new(); if (optionSet.Contains("bing")) { var bingConnector = new BingConnector(Configuration.ConfigVar("BING_API_KEY")); var bing = new WebSearchEnginePlugin(bingConnector); var search = kernel.ImportFunctions(bing, "bing"); + validPlugins.Add("bing"); } if (optionSet.Contains("++")) { kernel.ImportFunctions(new TimePlugin(), "time"); + validPlugins.Add("time"); kernel.ImportFunctions(new ConversationSummaryPlugin(kernel), "summary"); + validPlugins.Add("summary"); kernel.ImportFunctions(new SuperFileIOPlugin(), "file"); + validPlugins.Add("file"); } else { if (optionSet.Contains("time")) { kernel.ImportFunctions(new TimePlugin(), "time"); + validPlugins.Add("time"); } if (optionSet.Contains("summary")) { kernel.ImportFunctions(new ConversationSummaryPlugin(kernel), "summary"); + validPlugins.Add("summary"); } if (optionSet.Contains("file")) { kernel.ImportFunctions(new SuperFileIOPlugin(), "file"); + validPlugins.Add("file"); + } + + if (optionSet.Contains("git")) + { + var gitPlugin = kernel.ImportFunctions(new GitPlugin(kernel), "git"); + + Plan gitProcessPlan = new("Execute a 'git diff' command and execute semantic reasoning over the output."); + gitProcessPlan.Name = "GenerateDynamicGitDiffResult"; + + // It'd be nice if I could even just use this to set default values. An option -- doesn't work right now. + // gitProcessPlan.Parameters.Set("filter", "-- . \":!*.md\" \":!*skprompt.txt\" \":!*encoder.json\" \":!*vocab.bpe\" \":!*dict.txt\""); + // gitProcessPlan.Parameters.Set("target", "HEAD"); + // gitProcessPlan.Parameters.Set("source", "4b825dc642cb6eb9a060e54bf8d69288fbee4904"); + // gitProcessPlan.Parameters.Set("instructions", ""); + gitProcessPlan.Parameters.Set("filter", ""); + gitProcessPlan.Parameters.Set("target", ""); + gitProcessPlan.Parameters.Set("source", ""); + gitProcessPlan.Parameters.Set("instructions", ""); + + gitProcessPlan.AddSteps(gitPlugin["GitDiffDynamic"]); + gitProcessPlan.Steps[0].Outputs.Add("gitDiffResult"); + + gitProcessPlan.AddSteps(gitPlugin["GenerateDynamic"]); + + // This is the only way to connect to parameters. It's not great. + // Since they are optional, if nothing is supplied `Plan` will not replace the parameter. Should file an issue. + gitProcessPlan.Steps[0].Parameters.Set("filter", "$filter"); + gitProcessPlan.Steps[0].Parameters.Set("target", "$target"); + gitProcessPlan.Steps[0].Parameters.Set("source", "$source"); + gitProcessPlan.Steps[1].Parameters.Set("fullDiff", "$gitDiffResult"); + gitProcessPlan.Steps[1].Parameters.Set("instructions", "$instructions"); + + var p = gitProcessPlan.Describe(); + kernel.ImportPlan(gitProcessPlan); + validPlugins.Add("Plan"); } } - return kernel; + return (kernel, validPlugins); } public class StepwisePlugin @@ -90,9 +135,23 @@ public StepwisePlugin(IKernel kernel) } [SKFunction, Description("Respond to a message.")] - public async Task RespondTo(string message, string history) + public async Task RespondTo(string message, string history, string validPlugins, CancellationToken cancellationToken = default) { - var planner = new StepwisePlanner(this._kernel); + var config = new StepwisePlannerConfig(); + var plugins = JsonSerializer.Deserialize>(validPlugins); + + config.GetAvailableFunctionsAsync = (plannerConfig, filter, token) => + { + var functions = this._kernel.Functions; + var functionViews = functions.GetFunctionViews(); + + var filteredViews = functionViews + .Where(f => plugins is null || plugins.Contains(f.PluginName)) + .OrderBy(f => $"{f.PluginName}.{f.Name}"); + + return Task.FromResult(filteredViews); + }; + var planner = new StepwisePlanner(this._kernel, config); // Option 1 - Respond to just the message // var plan = planner.CreatePlan(message); @@ -104,7 +163,7 @@ public StepwisePlugin(IKernel kernel) // Option 3 - Respond to the history with prompt var plan = planner.CreatePlan($"{history}\n---\nGiven the conversation history, respond to the most recent message."); - var result = await this._kernel.RunAsync(plan); + var result = await this._kernel.RunAsync(plan, cancellationToken: cancellationToken); // Extract metadata and result string into new SKContext -- Is there a better way? var functionResult = result?.FunctionResults?.FirstOrDefault(); @@ -124,7 +183,7 @@ public StepwisePlugin(IKernel kernel) } } - private static async Task RunChat(IKernel kernel, ILogger? logger, ISKFunction chatFunction) + private static async Task RunChat(IKernel kernel, IList validPlugins, ILogger? logger, ISKFunction chatFunction, CancellationToken cancellationToken = default) { AnsiConsole.MarkupLine("[grey]Press Enter twice to send a message.[/]"); AnsiConsole.MarkupLine("[grey]Enter 'exit' to exit.[/]"); @@ -132,6 +191,7 @@ private static async Task RunChat(IKernel kernel, ILogger? logger, ISKFunction c var history = string.Empty; contextVariables.Set("history", history); + contextVariables.Set("validPlugins", JsonSerializer.Serialize(validPlugins)); KernelResult botMessage = KernelResult.FromFunctionResults("Hello!", new List()); diff --git a/apps/SKonsole/Images/icon.png b/apps/SKonsole/Images/icon.png new file mode 100644 index 0000000..ba60404 Binary files /dev/null and b/apps/SKonsole/Images/icon.png differ diff --git a/apps/SKonsole/KernelProvider.cs b/apps/SKonsole/KernelProvider.cs index 24e2679..068017c 100644 --- a/apps/SKonsole/KernelProvider.cs +++ b/apps/SKonsole/KernelProvider.cs @@ -34,7 +34,7 @@ public IKernel Get() .WithLoggerFactory(s_loggerFactory) .Build(); - _kernel.LoggerFactory.CreateLogger().LogTrace("KernelProvider.Instance: Added Azure OpenAI backends"); + _kernel.LoggerFactory.CreateLogger(this.GetType()).LogTrace("KernelProvider.Instance: Added Azure OpenAI backends"); return _kernel; } diff --git a/apps/SKonsole/Plugins/EmailPlugin.cs b/apps/SKonsole/Plugins/EmailPlugin.cs deleted file mode 100644 index a961d7d..0000000 --- a/apps/SKonsole/Plugins/EmailPlugin.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.ComponentModel; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Orchestration; - -namespace SKonsole.Plugins; - -internal sealed class EmailPlugin -{ - [SKFunction, Description("Given an e-mail and message body, send an email")] - public static Task SendEmail( - [Description("The body of the email message to send.")] - string input, - [Description("The email address to send email to.")] - string email_address, - SKContext context, - CancellationToken cancellationToken = default) - { - context.Variables.Update($"Sent email to: {email_address}.\n\n{input}"); - return Task.FromResult(context); - } - - [SKFunction, Description("Given a name, find email address")] - public static Task GetEmailAddress([Description("The name of the person to email.")] string input, SKContext context, - CancellationToken cancellationToken = default) - { - context.Logger().LogDebug("Returning hard coded email for {input}", input); - context.Variables.Update("johndoe1234@example.com"); - return Task.FromResult(context); - } -} diff --git a/apps/SKonsole/Plugins/EmailPluginEx.cs b/apps/SKonsole/Plugins/EmailPluginEx.cs deleted file mode 100644 index af2d3d3..0000000 --- a/apps/SKonsole/Plugins/EmailPluginEx.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Orchestration; - -namespace SKonsole.Plugins; - -internal static class EmailPluginEx -{ - public static ILogger Logger(this SKContext context) - { - return context.LoggerFactory.CreateLogger(); - } -} diff --git a/apps/SKonsole/Plugins/GitPlugin.cs b/apps/SKonsole/Plugins/GitPlugin.cs index d2c1810..1b0157b 100644 --- a/apps/SKonsole/Plugins/GitPlugin.cs +++ b/apps/SKonsole/Plugins/GitPlugin.cs @@ -1,12 +1,59 @@ using System.ComponentModel; using System.Diagnostics; +using CondensePluginLib; +using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Orchestration; +using PRPlugin; +using PRPlugin.Utils; +using static PRPlugin.FunctionEx; namespace SKonsole.Plugins; -internal sealed class GitPlugin +public class GitPlugin { + private const int CHUNK_SIZE = 8000; // Eventually this should come from the kernel + + private readonly IKernel _kernel; + + private readonly ILogger _logger; + + private readonly CondensePlugin _condensePlugin; + + private readonly Dictionary _functions = new(); + + public GitPlugin(IKernel kernel) + { + this._logger = kernel.LoggerFactory.CreateLogger(this.GetType()); + this._condensePlugin = new CondensePlugin(kernel); + + this._kernel = Kernel.Builder + .WithAIService(null, new RedirectTextCompletion(), true) + .Build(); + + this.ImportSemanticFunctions(); + } + + private void ImportSemanticFunctions() + { + var promptTemplate = @"[GITDIFFCONTENT] +{{$input}} +[END GITDIFFCONTENT] + +[GITDIFFCONTENT] is part or all of the output of `git diff`. + +Use[GITDIFFCONTENT] as knowledge for completing tasks. + +Task: +{{$instructions}} + +Result: +"; + var function = this._kernel.CreateSemanticFunction(promptTemplate, "DynamicGenerator", "DynamicResult"); + this._functions.Add("DynamicGenerator", function); + } + [SKFunction, Description("Run 'git diff --staged' and return it's output.")] public static async Task GitDiffStaged(SKContext context, CancellationToken cancellationToken = default) @@ -29,4 +76,54 @@ public static async Task GitDiffStaged(SKContext context, context.Variables.Update(output); return context; } + + [SKFunction, Description("Run 'git diff' with filter, target, source options and return its output.")] + public async Task GitDiffDynamic( + SKContext context, + [Description("The filter to apply to the diff.")] + string filter = "-- . \":!*.md\" \":!*skprompt.txt\" \":!*encoder.json\" \":!*vocab.bpe\" \":!*dict.txt\"", + [Description("The target commit hash.")] + string target = "HEAD", + [Description("The source commit hash. Default is the empty tree SHA.")] + string source = "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + CancellationToken cancellationToken = default) + { + // Workaround due to inability to pass null or empty string to SKFunction via parameterized Plan + filter = filter == "$filter" ? "-- . \":!*.md\" \":!*skprompt.txt\" \":!*encoder.json\" \":!*vocab.bpe\" \":!*dict.txt\"" : filter; + target = target == "$target" ? "HEAD" : target; + source = source == "$source" ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : source; + + this._logger.LogDebug("GitDiffDynamic called:\n\tfilter:{filter}\n\ttarget:{target}\n\tsource:{source}", filter, target, source); + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "git", + Arguments = $"diff {source}..{target} {filter}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + process.Start(); + var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); + + context.Variables.Update(output); + return context; + } + + [SKFunction, Description("Generate an output based on a git diff or git show file output for a given instruction.")] + public async Task GenerateDynamic( + [Description("Output of a `git diff` or `git show` command.")] + string fullDiff, + [Description("Instructions to generate a specific output.")] + string instructions, + SKContext context, + CancellationToken cancellationToken = default) + { + this._logger.LogDebug("GenerateDynamic called:\n\tfullDiff:{fullDiff}\n\tinstructions:{instructions}", fullDiff.Substring(0, Math.Max(0, Math.Min(250, fullDiff.Length - 1))), instructions); + var chunkedInput = CommitChunker.ChunkCommitInfo(fullDiff, CHUNK_SIZE, cancellationToken); + return await this._functions["DynamicGenerator"].CondenseChunkProcess(this._condensePlugin, chunkedInput, instructions, context, "DynamicResult", cancellationToken); + } } diff --git a/apps/SKonsole/Program.cs b/apps/SKonsole/Program.cs index 458b9fe..d9e5f68 100644 --- a/apps/SKonsole/Program.cs +++ b/apps/SKonsole/Program.cs @@ -6,7 +6,7 @@ Console.OutputEncoding = Encoding.Unicode; -var rootCommand = new RootCommand +var rootCommand = new RootCommand(description: "SKonsole is a powerful command-line tool that leverages AI to assist you with various tasks.") { new ConfigCommand(ConfigurationProvider.Instance), new CommitCommand(ConfigurationProvider.Instance), diff --git a/apps/SKonsole/SKonsole.csproj b/apps/SKonsole/SKonsole.csproj index c0a96af..3b9ea38 100644 --- a/apps/SKonsole/SKonsole.csproj +++ b/apps/SKonsole/SKonsole.csproj @@ -1,30 +1,41 @@ - - Exe - SKonsole - 1.0.6 - Lee Miller - Microsoft - A console app built on Semantic Plugins - https://github.com/lemillermicrosoft/skonsole - net7.0 - enable - enable - true - skonsole - - - - - - - - - - - - - - - + + Exe + SKonsole + 1.1.0 + Lee Miller + lemiller + icon.png + Microsoft + A console app built on Semantic Plugins + https://github.com/lemillermicrosoft/skonsole + github + net7.0 + enable + enable + semantickernel gpt AI llms command-line cli + README.md + true + skonsole + MIT + https://github.com/lemillermicrosoft/skonsole/blob/main/README.md + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/SKonsole/Utils/Logging.cs b/apps/SKonsole/Utils/Logging.cs index 142cd35..b21a2ff 100644 --- a/apps/SKonsole/Utils/Logging.cs +++ b/apps/SKonsole/Utils/Logging.cs @@ -11,6 +11,8 @@ internal static ILoggerFactory GetFactory() builder .SetMinimumLevel(LogLevel.Information) // .AddFilter("Microsoft.SemanticKernel.Planners.StepwisePlanner", LogLevel.Information) // Toggle to see chain of thought + // .AddFilter("SKonsole.Plugins.GitPlugin", LogLevel.Debug) + // .AddFilter("PRPlugin.PullRequestPlugin", LogLevel.Debug) .AddFilter("Microsoft", LogLevel.Error) .AddFilter("AzureChatCompletion", LogLevel.Error) .AddFilter("System", LogLevel.Error) diff --git a/plugins/CondensePlugin/CondensePlugin.cs b/plugins/CondensePlugin/CondensePlugin.cs index c87716c..2310253 100644 --- a/plugins/CondensePlugin/CondensePlugin.cs +++ b/plugins/CondensePlugin/CondensePlugin.cs @@ -23,7 +23,7 @@ public CondensePlugin(IKernel kernel) // Load semantic plugin defined with prompt templates var folder = CondensePluginPath(); var condensePlugin = kernel.ImportSemanticFunctionsFromDirectory(folder, SEMANTIC_FUNCTION_PATH); - this._logger = kernel.LoggerFactory.CreateLogger(); + this._logger = kernel.LoggerFactory.CreateLogger(this.GetType()); } catch (Exception e) { @@ -41,13 +41,14 @@ public async Task Condense( CancellationToken cancellationToken = default) { var condenser = context.Functions.GetFunction(SEMANTIC_FUNCTION_PATH, "Condenser"); - + cancellationToken.ThrowIfCancellationRequested(); List lines = TextChunker.SplitPlainTextLines(input, CHUNK_SIZE / 8, EnglishRobertaTokenizer.Counter); List paragraphs = TextChunker.SplitPlainTextParagraphs(lines, CHUNK_SIZE, 100, tokenCounter: EnglishRobertaTokenizer.Counter); var condenseResult = new List(); foreach (var paragraph in paragraphs) { + cancellationToken.ThrowIfCancellationRequested(); context.Variables.Update(paragraph + separator); var result = await context.Runner.RunAsync(condenser, context.Variables, cancellationToken: cancellationToken); condenseResult.Add(result.GetValue()); diff --git a/plugins/PRPlugin/PullRequestPlugin.cs b/plugins/PRPlugin/PullRequestPlugin.cs index 9538c06..337b024 100644 --- a/plugins/PRPlugin/PullRequestPlugin.cs +++ b/plugins/PRPlugin/PullRequestPlugin.cs @@ -28,13 +28,14 @@ public static async Task RollingChunkProcess(this ISKFunction func, L return context; } - public static async Task CondenseChunkProcess(this ISKFunction func, CondensePlugin condensePlugin, List chunkedInput, string prompt, SKContext context, string resultTag) + public static async Task CondenseChunkProcess(this ISKFunction func, CondensePlugin condensePlugin, List chunkedInput, string prompt, SKContext context, string resultTag, CancellationToken cancellationToken = default) { var results = new List(); foreach (var chunk in chunkedInput) { + cancellationToken.ThrowIfCancellationRequested(); context.Variables.Update(chunk); - var result = await context.Runner.RunAsync(func, context.Variables); + var result = await context.Runner.RunAsync(func, context.Variables, cancellationToken); results.Add(result.GetValue()); } @@ -47,7 +48,7 @@ public static async Task CondenseChunkProcess(this ISKFunction func, // update memory with serialized list of results context.Variables.Set("prompt", prompt); - return await condensePlugin.Condense(context, string.Join($"\n ====={resultTag}=====\n", results) + $"\n ====={resultTag}=====\n"); + return await condensePlugin.Condense(context, string.Join($"\n ====={resultTag}=====\n", results) + $"\n ====={resultTag}=====\n", cancellationToken: cancellationToken); } public static async Task AggregateChunkProcess(this ISKFunction func, List chunkedInput, SKContext context) @@ -90,7 +91,7 @@ public PullRequestPlugin(IKernel kernel) .Build(); this._kernel.ImportSemanticFunctionsFromDirectory(folder, SEMANTIC_FUNCTION_PATH); - this._logger = this._kernel.LoggerFactory.CreateLogger(); + this._logger = kernel.LoggerFactory.CreateLogger(this.GetType()); } catch (Exception e) { @@ -125,8 +126,8 @@ public async Task GenerateCommitMessage( var commitGeneratorCapture = this._kernel.Functions.GetFunction(SEMANTIC_FUNCTION_PATH, "CommitMessageGenerator"); var prompt = (await this._kernel.RunAsync(commitGeneratorCapture, cancellationToken: cancellationToken)).GetValue(); - var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE); - return await commitGenerator.CondenseChunkProcess(this._condensePlugin, chunkedInput, prompt, context, "CommitMessageResult"); + var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE, cancellationToken); + return await commitGenerator.CondenseChunkProcess(this._condensePlugin, chunkedInput, prompt, context, "CommitMessageResult", cancellationToken); } [SKFunction, Description("Generate a pull request description based on a git diff or git show file output using a rolling query mechanism.")] @@ -137,7 +138,7 @@ public async Task GeneratePR_Rolling( CancellationToken cancellationToken = default) { var prGenerator_Rolling = context.Functions.GetFunction(SEMANTIC_FUNCTION_PATH, "PullRequestDescriptionGenerator_Rolling"); - var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE); + var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE, cancellationToken); return await prGenerator_Rolling.RollingChunkProcess(chunkedInput, context); } @@ -155,8 +156,8 @@ public async Task GeneratePR( contextVariablesWithoutInput.Set("input", ""); var prompt = (await this._kernel.RunAsync(prGeneratorCapture, contextVariablesWithoutInput, cancellationToken: cancellationToken)).GetValue(); - var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE); - return await prGenerator.CondenseChunkProcess(this._condensePlugin, chunkedInput, prompt, context, "PullRequestDescriptionResult"); + var chunkedInput = CommitChunker.ChunkCommitInfo(input, CHUNK_SIZE, cancellationToken); + return await prGenerator.CondenseChunkProcess(this._condensePlugin, chunkedInput, prompt, context, "PullRequestDescriptionResult", cancellationToken); } #region MISC diff --git a/plugins/PRPlugin/Utils/CommitChunker.cs b/plugins/PRPlugin/Utils/CommitChunker.cs index 82c8bc8..1168413 100644 --- a/plugins/PRPlugin/Utils/CommitChunker.cs +++ b/plugins/PRPlugin/Utils/CommitChunker.cs @@ -4,7 +4,7 @@ namespace PRPlugin.Utils; public static class CommitChunker { - public static List ChunkCommitInfo(string input, int chunkSize) + public static List ChunkCommitInfo(string input, int chunkSize, CancellationToken token = default) { var commits = input.SplitCommitInfo(); @@ -13,6 +13,7 @@ public static List ChunkCommitInfo(string input, int chunkSize) foreach (var commit in commits) { + token.ThrowIfCancellationRequested(); if (currChunk.Length + commit.commit.Length > chunkSize) { chunkedInput.Add(currChunk);