diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln
index bbdf5984aec2..6223b71768a6 100644
--- a/dotnet/SK-dotnet.sln
+++ b/dotnet/SK-dotnet.sln
@@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelBuilder", "..\samples\dotnet\KernelBuilder\KernelBuilder.csproj", "{A52818AC-57FB-495F-818F-9E1E7BC5618C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skills.Code", "src\SemanticKernel.Skills\Skills.Code\Skills.Code.csproj", "{0EE82492-0176-43D5-A8E0-F2E2A766B5D5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -110,6 +112,10 @@ Global
{A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0EE82492-0176-43D5-A8E0-F2E2A766B5D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0EE82492-0176-43D5-A8E0-F2E2A766B5D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0EE82492-0176-43D5-A8E0-F2E2A766B5D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0EE82492-0176-43D5-A8E0-F2E2A766B5D5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -130,6 +136,7 @@ Global
{107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974}
{F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D}
{A52818AC-57FB-495F-818F-9E1E7BC5618C} = {FA3720F1-C99A-49B2-9577-A940257098BF}
+ {0EE82492-0176-43D5-A8E0-F2E2A766B5D5} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
diff --git a/dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs b/dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs
new file mode 100644
index 000000000000..5652f2cf67df
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Skills/Skills.Code/CodeSkill.cs
@@ -0,0 +1,187 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+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.KernelExtensions;
+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#)
+///
+public class CodeSkill
+{
+ ///
+ /// Parameter names.
+ ///
+ ///
+ public static class Parameters
+ {
+ ///
+ /// Document file path.
+ ///
+ public const string FilePath = "filePath";
+
+ ///
+ /// Directory to which to extract compressed file's data.
+ ///
+ public const string DestinationDirectoryPath = "destinationDirectoryPath";
+
+ ///
+ /// Name of the memory collection used to store the code summaries.
+ ///
+ public const string MemoryCollectionName = "memoryCollectionName";
+ }
+
+ ///
+ /// The max tokens to process in a single semantic function call.
+ ///
+ private const int MaxTokens = 1024;
+
+ private readonly ISKFunction _summarizeCodeFunction;
+ private readonly IKernel _kernel;
+ private readonly WebFileDownloadSkill _downloadSkill;
+ private readonly DocumentSkill _documentSkill;
+ private readonly ILogger _logger;
+
+ internal const string SummarizeCodeSnippetDefinition =
+ @"BEGIN CONTENT TO SUMMARIZE:
+{{$INPUT}}
+END CONTENT TO SUMMARIZE.
+
+Summarize the content in 'CONTENT TO SUMMARIZE', identifying main points.
+Do not incorporate other general knowledge.
+Summary is in plain text, in complete sentences, with no markup or tags.
+
+BEGIN SUMMARY:
+";
+
+ internal const string MemoryCollectionName = "CodeSkillMemory"; // TODO Should this be configurable
+
+
+ ///
+ /// 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)
+ {
+ this._kernel = kernel;
+ this._downloadSkill = downloadSkill;
+ this._documentSkill = documentSkill;
+ this._logger = logger ?? NullLogger.Instance;
+
+ this._summarizeCodeFunction = kernel.CreateSemanticFunction(
+ SummarizeCodeSnippetDefinition,
+ skillName: nameof(CodeSkill),
+ description: "Given a snippet of code, summarize the part of the file.",
+ maxTokens: MaxTokens,
+ temperature: 0.1,
+ topP: 0.5);
+ }
+
+ ///
+ /// Summarize a code file into an embedding
+ ///
+ /// Path of file to summarize
+ /// Semantic kernal context
+ /// Task
+ public async Task SummarizeCodeFileAsync(string filePath, 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 (code != null && code.Length > 0)
+ {
+ // TODO should we create a new SKContext here?
+ context.Variables.Update(code);
+ await this._summarizeCodeFunction.InvokeAsync(context);
+
+ var result = context.Variables.ToString();
+ // TODO Include the file URI in the text
+ await this._kernel.Memory.SaveInformationAsync(MemoryCollectionName, text: result, id: filePath);
+ }
+ }
+
+ ///
+ /// Summarize the code found under a directory into embeddings (one per file)
+ ///
+ /// Path of directory to summarize
+ /// Semantic kernal context
+ /// Task
+ public async Task SummarizeCodeDirectoryAsync(string directoryPath, SKContext context)
+ {
+ // 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));
+
+ if (filePaths != null && filePaths.Length > 0)
+ {
+ this._logger.LogDebug("Found {0} files to summarize", filePaths.Length);
+
+ foreach (string filePath in filePaths)
+ {
+ await this.SummarizeCodeFileAsync(filePath, context);
+ }
+
+ _ = context.Variables.Update($"Found {filePaths.Length} files to summarize");
+ context.Variables.Set(Parameters.MemoryCollectionName, MemoryCollectionName);
+ }
+ }
+
+ ///
+ /// Summarize the code downloaded from the specified URI.
+ ///
+ /// 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)
+ {
+ // 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()}");
+
+ try
+ {
+ // 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);
+
+ await this.SummarizeCodeDirectoryAsync(directoryPath, context);
+ }
+ finally
+ {
+ // Cleanup downloaded file and also unzipped content
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+ if (Directory.Exists(directoryPath))
+ {
+ Directory.Delete(directoryPath, true);
+ }
+ }
+ }
+}
diff --git a/dotnet/src/SemanticKernel.Skills/Skills.Code/Skills.Code.csproj b/dotnet/src/SemanticKernel.Skills/Skills.Code/Skills.Code.csproj
new file mode 100644
index 000000000000..ba4d1f5c4c3f
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Skills/Skills.Code/Skills.Code.csproj
@@ -0,0 +1,25 @@
+
+
+
+ $([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)'))))
+
+
+
+
+ Microsoft.SemanticKernel.Skills.Code
+ Microsoft.SemanticKernel.Skills.Code
+ netstandard2.1
+
+
+
+
+ Microsoft.SemanticKernel.Skills.Code
+ Semantic Kernel - Code Skill
+
+
+
+
+
+
+
+
diff --git a/samples/apps/github-qna-webapp-react/src/components/GitHubRepoSelection.tsx b/samples/apps/github-qna-webapp-react/src/components/GitHubRepoSelection.tsx
index e547a87434e1..0cda2b7b2762 100644
--- a/samples/apps/github-qna-webapp-react/src/components/GitHubRepoSelection.tsx
+++ b/samples/apps/github-qna-webapp-react/src/components/GitHubRepoSelection.tsx
@@ -2,7 +2,8 @@
import { Body1, Button, Input, Label, Spinner, Subtitle2, Title3 } from '@fluentui/react-components';
import { ArrowDownload16Regular, CheckmarkCircle20Filled } from '@fluentui/react-icons';
-import { FC, useState } from "react";
+import { FC, useState } from 'react';
+
import { useSemanticKernel } from '../hooks/useSemanticKernel';
import { IKeyConfig } from '../model/KeyConfig';
@@ -25,67 +26,102 @@ const GitHubProjectSelection: FC = ({ uri, keyConfig, onLoadProject, onBa
let cleanProjectUri = project?.trim();
if (!cleanProjectUri?.endsWith('/')) {
- cleanProjectUri = `${cleanProjectUri}/`
+ cleanProjectUri = `${cleanProjectUri}/`;
}
const url = `${cleanProjectUri}archive/refs/heads/${branch}.zip`;
- const path = `%temp%\\${branch}_${new Date().getTime()}.zip`;
-
try {
- var result = await sk.invokeAsync(keyConfig, { value: url, inputs: [{ key: 'filePath', value: path }] }, 'WebFileDownloadSkill', 'DownloadToFile');
+ var result = await sk.invokeAsync(
+ keyConfig,
+ { value: url, inputs: [] },
+ 'CodeSkill',
+ 'SummarizeRepository',
+ );
setIsLoaded(true);
+ console.log(result);
} catch {
setIsLoadError(true);
alert('Something went wrong. Please check that the function is running and accessible from this location.');
}
- }
+ };
return (
-
+
Enter in the GitHub Project URL
- Start by entering a GitHub Repository URL. We will pull the public repository into local memory so you can ask any questions about the repository and get help.
+
+ Start by entering a GitHub Repository URL. We will pull the public repository into local memory so you
+ can ask any questions about the repository and get help.{' '}
+
-
+