Skip to content

Commit

Permalink
Enhancements, Refactoring, and Documentation Updates (#27)
Browse files Browse the repository at this point in the history
* 🗑️ Remove EmailPlugin and EmailPluginEx classes

Removed the EmailPlugin and EmailPluginEx classes from the SKonsole
Plugins directory. This includes the deletion of SendEmail and
GetEmailAddress functions, as well as the Logger extension method.

* 🔄 Add cancellation token support to plugins

This commit adds support for CancellationToken in the CondensePlugin and
PRPlugin. It ensures that the token is checked during processing of
chunks and paragraphs, allowing for more responsive cancellation of
long-running tasks. This improves the overall performance and
responsiveness of the plugins.

* 🔄 Update logger instantiation in various classes

Replace specific class type with `this.GetType()` when creating loggers
in multiple classes, improving consistency and maintainability.

* 📦 Update GitPlugin with dynamic diff generation

This commit updates the GitPlugin to include dynamic diff generation
capabilities. The plugin now supports generating output based on a git
diff or git show file output for a given instruction. The update also
includes improvements to the GitDiffStaged function and adds a new
GitDiffDynamic function for more flexible diff filtering. The changes
made in this commit will enhance the overall functionality and
flexibility of the GitPlugin.

* 📝 Update README and improve SKonsole description

Update the README file to include a Contributing section and update the
license information. Improve the description of the SKonsole command in
Program.cs for better user understanding.

* 📝 Add validPlugins to contextVariables in StepwisePlannerCommand

This commit updates the StepwisePlannerCommand and LoadOptionSet methods to improve plugin handling and add
support for GitPlugin. The changes include:
- Adding validPlugins list to keep track of imported plugins.
- Modifying RunCreatePlan to pass validPlugins to RunChat.
- Updating RespondTo method to use validPlugins for filtering available functions.
- Implementing GitPlugin support with a new plan for processing 'git diff' output.

* 📦 Update SKonsole to v1.1.0 and reformat csproj

Update the SKonsole version from 1.0.6 to 1.1.0, and reformat the
csproj file for better readability. No changes in package
references or project references.

* 📦 Update SKonsole.csproj and add package metadata

Update SKonsole.csproj with additional package metadata, such as
owners, icon, repository type, tags, readme file, and license
expression. Also, include the icon and readme file in the package.
  • Loading branch information
lemillermicrosoft authored Oct 16, 2023
1 parent e94c656 commit 1a8a850
Show file tree
Hide file tree
Showing 19 changed files with 332 additions and 127 deletions.
4 changes: 3 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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)*
75 changes: 48 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <commitHash>`: 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 <message>`: 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

Expand All @@ -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 <commitHash>`: 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 <message>`: 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_
*Powered by [Microsoft Semantic Kernel](https://github.com/microsoft/semantic-kernel)*
2 changes: 1 addition & 1 deletion apps/SKonsole/Commands/CommitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CommitCommand>();
this._logger = loggerFactory.CreateLogger(this.GetType());
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion apps/SKonsole/Commands/PRCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PRCommand>();
this._logger = loggerFactory.CreateLogger(this.GetType());
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion apps/SKonsole/Commands/PlannerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlannerCommand>();
this._logger = loggerFactory.CreateLogger(this.GetType());
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion apps/SKonsole/Commands/PromptChatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public PromptChatCommand(ConfigurationProvider config, ILogger? logger = null) :
if (logger is null)
{
using var loggerFactory = Logging.GetFactory();
this._logger = loggerFactory.CreateLogger<PromptChatCommand>();
this._logger = loggerFactory.CreateLogger(this.GetType());
}
else
{
Expand Down
78 changes: 69 additions & 9 deletions apps/SKonsole/Commands/StepwisePlannerCommand.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,7 +23,7 @@ public StepwisePlannerCommand(ConfigurationProvider config, ILogger? logger = nu
if (logger is null)
{
using var loggerFactory = Logging.GetFactory();
this._logger = loggerFactory.CreateLogger<StepwisePlannerCommand>();
this._logger = loggerFactory.CreateLogger(this.GetType());
}
else
{
Expand All @@ -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<string>) LoadOptionSet(string optionSet)
{
var kernel = KernelProvider.Instance.Get();
List<string> 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
Expand All @@ -90,9 +135,23 @@ public StepwisePlugin(IKernel kernel)
}

[SKFunction, Description("Respond to a message.")]
public async Task<SKContext?> RespondTo(string message, string history)
public async Task<SKContext?> RespondTo(string message, string history, string validPlugins, CancellationToken cancellationToken = default)
{
var planner = new StepwisePlanner(this._kernel);
var config = new StepwisePlannerConfig();
var plugins = JsonSerializer.Deserialize<List<string>>(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);
Expand All @@ -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();
Expand All @@ -124,14 +183,15 @@ public StepwisePlugin(IKernel kernel)
}
}

private static async Task RunChat(IKernel kernel, ILogger? logger, ISKFunction chatFunction)
private static async Task RunChat(IKernel kernel, IList<string> 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.[/]");
var contextVariables = new ContextVariables();

var history = string.Empty;
contextVariables.Set("history", history);
contextVariables.Set("validPlugins", JsonSerializer.Serialize(validPlugins));

KernelResult botMessage = KernelResult.FromFunctionResults("Hello!", new List<FunctionResult>());

Expand Down
Binary file added apps/SKonsole/Images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/SKonsole/KernelProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public IKernel Get()
.WithLoggerFactory(s_loggerFactory)
.Build();

_kernel.LoggerFactory.CreateLogger<KernelProvider>().LogTrace("KernelProvider.Instance: Added Azure OpenAI backends");
_kernel.LoggerFactory.CreateLogger(this.GetType()).LogTrace("KernelProvider.Instance: Added Azure OpenAI backends");

return _kernel;
}
Expand Down
Loading

0 comments on commit 1a8a850

Please sign in to comment.