Ask questions about the repository
@@ -100,8 +108,9 @@ const QnA: FC
= ({ uri, project, branch, keyConfig, onBack }) => {
{
setIsBusy(true);
+ setChatHistory([...chatHistory, m]);
const response = await getResponse(m);
- setChatHistory([...chatHistory, m, response!]);
+ setResponse(response!);
setIsBusy(false);
}}
/>
diff --git a/samples/dotnet/KernelHttpServer/Extensions.cs b/samples/dotnet/KernelHttpServer/Extensions.cs
index b2aa536326be..c820e0ca14ac 100644
--- a/samples/dotnet/KernelHttpServer/Extensions.cs
+++ b/samples/dotnet/KernelHttpServer/Extensions.cs
@@ -17,7 +17,6 @@
using Microsoft.SemanticKernel.KernelExtensions;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
-using Microsoft.SemanticKernel.Skills.Code;
using Microsoft.SemanticKernel.Skills.Document;
using Microsoft.SemanticKernel.Skills.Document.FileSystem;
using Microsoft.SemanticKernel.Skills.Document.OpenXml;
@@ -172,12 +171,12 @@ internal static void RegisterNativeSkills(this IKernel kernel, IEnumerable
-
+
diff --git a/dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs b/samples/dotnet/github-skills/GitHubSkill.cs
similarity index 51%
rename from dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs
rename to samples/dotnet/github-skills/GitHubSkill.cs
index 5652f2cf67df..f62f40d5f183 100644
--- a/dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs
+++ b/samples/dotnet/github-skills/GitHubSkill.cs
@@ -3,22 +3,21 @@
using System;
using System.IO;
using System.IO.Compression;
-using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.KernelExtensions;
+using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.Skills.Document;
using Microsoft.SemanticKernel.Skills.Web;
-namespace Microsoft.SemanticKernel.Skills.Code;
-
///
-/// Skill for interacting with code files (e.g. C#)
+/// Skill for interacting with a GitHub repository.
///
-public class CodeSkill
+public class GitHubSkill
{
///
/// Parameter names.
@@ -26,6 +25,16 @@ public class CodeSkill
///
public static class Parameters
{
+ ///
+ /// Name of the repository repositoryBranch which will be downloaded and summarized.
+ ///
+ public const string RepositoryBranch = "repositoryBranch";
+
+ ///
+ /// The search string to match against the names of files in the repository.
+ ///
+ public const string SearchPattern = "searchPattern";
+
///
/// Document file path.
///
@@ -47,11 +56,16 @@ public static class Parameters
///
private const int MaxTokens = 1024;
+ ///
+ /// The max file size to send directly to memory.
+ ///
+ private const int MaxFileSize = 2048;
+
private readonly ISKFunction _summarizeCodeFunction;
private readonly IKernel _kernel;
private readonly WebFileDownloadSkill _downloadSkill;
private readonly DocumentSkill _documentSkill;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
internal const string SummarizeCodeSnippetDefinition =
@"BEGIN CONTENT TO SUMMARIZE:
@@ -65,26 +79,26 @@ Do not incorporate other general knowledge.
BEGIN SUMMARY:
";
- internal const string MemoryCollectionName = "CodeSkillMemory"; // TODO Should this be configurable
+ internal const string MemoryCollectionName = "GitHubSkillMemory"; // TODO Should this be configurable
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Kernel instance
/// Instance of WebFileDownloadSkill used to download web files
/// Instance of DocumentSkill used to read files
/// Optional logger
- public CodeSkill(IKernel kernel, WebFileDownloadSkill downloadSkill, DocumentSkill documentSkill, ILogger? logger = null)
+ public GitHubSkill(IKernel kernel, WebFileDownloadSkill downloadSkill, DocumentSkill documentSkill, ILogger? logger = null)
{
this._kernel = kernel;
this._downloadSkill = downloadSkill;
this._documentSkill = documentSkill;
- this._logger = logger ?? NullLogger.Instance;
+ this._logger = logger ?? NullLogger.Instance;
this._summarizeCodeFunction = kernel.CreateSemanticFunction(
SummarizeCodeSnippetDefinition,
- skillName: nameof(CodeSkill),
+ skillName: nameof(GitHubSkill),
description: "Given a snippet of code, summarize the part of the file.",
maxTokens: MaxTokens,
temperature: 0.1,
@@ -92,96 +106,107 @@ public CodeSkill(IKernel kernel, WebFileDownloadSkill downloadSkill, DocumentSki
}
///
- /// Summarize a code file into an embedding
+ /// Summarize the code downloaded from the specified URI.
///
- /// Path of file to summarize
+ /// URI to download the respository content to be summarized
/// Semantic kernal context
/// Task
- public async Task SummarizeCodeFileAsync(string filePath, SKContext context)
+ [SKFunction("Downloads a repository and summarizes the content")]
+ [SKFunctionName("SummarizeRepository")]
+ [SKFunctionInput(Description = "URL of the GitHub repository to summarize")]
+ [SKFunctionContextParameter(Name = Parameters.RepositoryBranch, Description = "Name of the repository repositoryBranch which will be downloaded and summarized")]
+ [SKFunctionContextParameter(Name = Parameters.SearchPattern, Description = "The search string to match against the names of files in the repository")]
+ public async Task SummarizeRepositoryAsync(string source, SKContext context)
{
- // TODO do we need to extend the DocumentSkill to read raw content?
- // string code = await this._documentSkill.ReadTextAsync(filePath, context);
- string code = File.ReadAllText(filePath);
+ if (!context.Variables.Get(Parameters.RepositoryBranch, out string repositoryBranch) || string.IsNullOrEmpty(repositoryBranch))
+ {
+ repositoryBranch = "main";
+ }
+ if (!context.Variables.Get(Parameters.SearchPattern, out string searchPattern) || string.IsNullOrEmpty(searchPattern))
+ {
+ searchPattern = "*.md";
+ }
- if (code != null && code.Length > 0)
+ string tempPath = Path.GetTempPath();
+ string directoryPath = Path.Combine(tempPath, $"SK-{Guid.NewGuid()}");
+ string filePath = Path.Combine(tempPath, $"SK-{Guid.NewGuid()}.zip");
+
+ try
{
- // TODO should we create a new SKContext here?
- context.Variables.Update(code);
- await this._summarizeCodeFunction.InvokeAsync(context);
+ var repositoryUri = source.Trim(new char[] {' ', '/'});
+ var context1 = new SKContext(new ContextVariables(), NullMemory.Instance, null, context.Log);
+ context1.Variables.Set(Parameters.FilePath, filePath);
+ await this._downloadSkill.DownloadToFileAsync($"{repositoryUri}/archive/refs/heads/{repositoryBranch}.zip", context1);
+
+ ZipFile.ExtractToDirectory(filePath, directoryPath);
+
+ await this.SummarizeCodeDirectoryAsync(directoryPath, searchPattern, repositoryUri, repositoryBranch, context);
- var result = context.Variables.ToString();
- // TODO Include the file URI in the text
- await this._kernel.Memory.SaveInformationAsync(MemoryCollectionName, text: result, id: filePath);
+ context.Variables.Set(Parameters.MemoryCollectionName, MemoryCollectionName);
+ }
+ finally
+ {
+ // Cleanup downloaded file and also unzipped content
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+ if (Directory.Exists(directoryPath))
+ {
+ Directory.Delete(directoryPath, true);
+ }
}
}
///
- /// Summarize the code found under a directory into embeddings (one per file)
+ /// Summarize a code file into an embedding
///
- /// Path of directory to summarize
- /// Semantic kernal context
- /// Task
- public async Task SummarizeCodeDirectoryAsync(string directoryPath, SKContext context)
+ private async Task SummarizeCodeFileAsync(string filePath, string repositoryUri, string repositoryBranch, string fileUri)
{
- // TODO Use the document skill for recursion
- // TODO Allow the wildcard match to be configurable
- string[] filePaths = await Task.FromResult(Directory.GetFiles(directoryPath, "*.md", SearchOption.AllDirectories));
+ string code = File.ReadAllText(filePath);
- if (filePaths != null && filePaths.Length > 0)
+ if (code != null && code.Length > 0)
{
- this._logger.LogDebug("Found {0} files to summarize", filePaths.Length);
-
- foreach (string filePath in filePaths)
+ string text;
+ if (code.Length > MaxFileSize)
{
- await this.SummarizeCodeFileAsync(filePath, context);
+ var context = await this._summarizeCodeFunction.InvokeAsync(code);
+ var result = context.Variables.ToString();
+ text = $"{result} File:{repositoryUri}/blob/{repositoryBranch}/{fileUri}";
}
-
- _ = context.Variables.Update($"Found {filePaths.Length} files to summarize");
- context.Variables.Set(Parameters.MemoryCollectionName, MemoryCollectionName);
+ else
+ {
+ text = $"{code} File:{repositoryUri}/blob/{repositoryBranch}/{fileUri}";
+ }
+ await this._kernel.Memory.SaveInformationAsync(MemoryCollectionName, text: text, id: fileUri);
}
}
///
- /// Summarize the code downloaded from the specified URI.
+ /// Summarize the code found under a directory into embeddings (one per file)
///
- /// URI to download the respository content to be summarized
- /// Semantic kernal context
- /// Task
- [SKFunction("Downloads a repository and summarizes the content")]
- [SKFunctionName("SummarizeRepository")]
- [SKFunctionInput(Description = "URL of repository to summarize")]
- public async Task SummarizeRepositoryAsync(string source, SKContext context)
+ private async Task SummarizeCodeDirectoryAsync(string directoryPath, string searchPattern, string repositoryUri, string repositoryBranch, SKContext context)
{
- // TODO Accept the repo uri and branch as separate parameters
- // 1. Down URI would be calculated in the Skill rather than the client
- // 2. We cna compute file URI's so the responses can include a link to the relevant file
- string filePath = Environment.ExpandEnvironmentVariables($"%temp%\\SK-{Guid.NewGuid()}.zip");
- string directoryPath = Environment.ExpandEnvironmentVariables($"%temp%\\SK-{Guid.NewGuid()}");
+ string[] filePaths = await Task.FromResult(Directory.GetFiles(directoryPath, searchPattern, SearchOption.AllDirectories));
- try
+ if (filePaths != null && filePaths.Length > 0)
{
- // TODO should we create a new SKContext here?
- context.Variables.Set(Parameters.FilePath, filePath);
- await this._downloadSkill.DownloadToFileAsync(source, context);
- context.Variables.Set(Parameters.FilePath, null);
-
- filePath = Environment.ExpandEnvironmentVariables(filePath);
- // TODO Use the file compression skill
- ZipFile.ExtractToDirectory(filePath, directoryPath);
+ this._logger.LogDebug("Found {0} files to summarize", filePaths.Length);
- await this.SummarizeCodeDirectoryAsync(directoryPath, context);
- }
- finally
- {
- // Cleanup downloaded file and also unzipped content
- if (File.Exists(filePath))
- {
- File.Delete(filePath);
- }
- if (Directory.Exists(directoryPath))
+ foreach (string filePath in filePaths)
{
- Directory.Delete(directoryPath, true);
+ var fileUri = this.BuildFileUri(directoryPath, filePath, repositoryUri, repositoryBranch);
+ await this.SummarizeCodeFileAsync(filePath, repositoryUri, repositoryBranch, fileUri);
}
}
}
+
+ ///
+ /// Build the file uri corresponding to the file path.
+ ///
+ private string BuildFileUri(string directoryPath, string filePath, string repositoryUri, string repositoryBranch)
+ {
+ var repositoryBranchName = $"{repositoryUri.Trim('/').Substring(repositoryUri.LastIndexOf('/'))}-{repositoryBranch}";
+ return filePath.Substring(directoryPath.Length + repositoryBranchName.Length + 1).Replace('\\', '/');
+ }
}
diff --git a/samples/dotnet/github-skills/GitHubSkillsExample.csproj b/samples/dotnet/github-skills/GitHubSkillsExample.csproj
new file mode 100644
index 000000000000..d84d552a3ab8
--- /dev/null
+++ b/samples/dotnet/github-skills/GitHubSkillsExample.csproj
@@ -0,0 +1,13 @@
+
+
+
+ GitHubSkillsExample
+ netstandard2.1
+
+
+
+
+
+
+
+