This commit is contained in:
Kosta Petan
2024-02-23 15:49:46 +01:00
parent 246639912d
commit 7dd7aa6fe2
59 changed files with 67 additions and 2277 deletions

View File

@@ -8,6 +8,7 @@
NewAsk
-> create PM issue
-> create DevLead issue
-> create a branch
```
```
ReadmeGenerated
@@ -25,14 +26,23 @@
CodeGenerated
-> post comment
```
```
ReadmeFinished
-> commit to branch
```
```
SandboxRunFinished
-> commit to branch
```
### AzureOps agent handles:
```
ReadmeFinished
ReadmeChainClosed
-> store
-> ReadmeStored
```
```
CodeFinished
CodeChainClosed
-> store
-> run in sandbox
```

View File

@@ -7,12 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{088E1138-FF7
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "apps", "apps", "{4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cli", "src\apps\cli\cli.csproj", "{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gh-flow", "src\apps\gh-flow\gh-flow.csproj", "{37C45587-DD0C-4BFE-817A-21301567A94A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gh-flow-df", "src\apps\gh-flow-df\gh-flow-df.csproj", "{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "seed-memory", "src\apps\seed-memory\seed-memory.csproj", "{110B2C34-0F48-41CE-BC8F-AE3D24E79627}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowsApp", "src\apps\WorkflowsApp\WorkflowsApp.csproj", "{F288CF45-81EF-4F57-ABAC-D7ABE894E7A0}"
@@ -26,24 +22,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.DevTeam.Skills", "src\libs\Microsoft.AI.DevTeam.Skills\Microsoft.AI.DevTeam.Skills.csproj", "{FF3D28B0-9344-44AF-BEEA-260187BE435E}"
EndProject
Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {30612385-E4F4-4FD9-B648-45AF74CB4915}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4D8667EC-09BE-4CB1-A278-E1CCD83B62B0} = {088E1138-FF7B-4179-AD37-4E3819C56D7E}
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{37C45587-DD0C-4BFE-817A-21301567A94A} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{110B2C34-0F48-41CE-BC8F-AE3D24E79627} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{F288CF45-81EF-4F57-ABAC-D7ABE894E7A0} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{24EB6E3A-C080-4B27-8EA7-F6C91446B840} = {088E1138-FF7B-4179-AD37-4E3819C56D7E}
{1F6EE104-0B3F-49C1-BE3B-B93CAD18BAD0} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
{9FA8DCFB-1726-42FB-B6CD-8AAC027810A1} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
{FF3D28B0-9344-44AF-BEEA-260187BE435E} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
@@ -53,18 +31,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|x64.ActiveCfg = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|x64.Build.0 = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Debug|x86.Build.0 = Debug|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|Any CPU.Build.0 = Release|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|x64.ActiveCfg = Release|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|x64.Build.0 = Release|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|x86.ActiveCfg = Release|Any CPU
{1FE4A9AE-6403-48D6-8D06-A97CCCE608FA}.Release|x86.Build.0 = Release|Any CPU
{37C45587-DD0C-4BFE-817A-21301567A94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37C45587-DD0C-4BFE-817A-21301567A94A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37C45587-DD0C-4BFE-817A-21301567A94A}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -77,18 +43,6 @@ Global
{37C45587-DD0C-4BFE-817A-21301567A94A}.Release|x64.Build.0 = Release|Any CPU
{37C45587-DD0C-4BFE-817A-21301567A94A}.Release|x86.ActiveCfg = Release|Any CPU
{37C45587-DD0C-4BFE-817A-21301567A94A}.Release|x86.Build.0 = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|x64.ActiveCfg = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|x64.Build.0 = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|x86.ActiveCfg = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Debug|x86.Build.0 = Debug|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|Any CPU.Build.0 = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|x64.ActiveCfg = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|x64.Build.0 = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|x86.ActiveCfg = Release|Any CPU
{CF0F7B0D-3CC4-4068-A1A8-64E3151D01E3}.Release|x86.Build.0 = Release|Any CPU
{110B2C34-0F48-41CE-BC8F-AE3D24E79627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{110B2C34-0F48-41CE-BC8F-AE3D24E79627}.Debug|Any CPU.Build.0 = Debug|Any CPU
{110B2C34-0F48-41CE-BC8F-AE3D24E79627}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -150,4 +104,20 @@ Global
{FF3D28B0-9344-44AF-BEEA-260187BE435E}.Release|x86.ActiveCfg = Release|Any CPU
{FF3D28B0-9344-44AF-BEEA-260187BE435E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4D8667EC-09BE-4CB1-A278-E1CCD83B62B0} = {088E1138-FF7B-4179-AD37-4E3819C56D7E}
{37C45587-DD0C-4BFE-817A-21301567A94A} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{110B2C34-0F48-41CE-BC8F-AE3D24E79627} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{F288CF45-81EF-4F57-ABAC-D7ABE894E7A0} = {4D8667EC-09BE-4CB1-A278-E1CCD83B62B0}
{24EB6E3A-C080-4B27-8EA7-F6C91446B840} = {088E1138-FF7B-4179-AD37-4E3819C56D7E}
{1F6EE104-0B3F-49C1-BE3B-B93CAD18BAD0} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
{9FA8DCFB-1726-42FB-B6CD-8AAC027810A1} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
{FF3D28B0-9344-44AF-BEEA-260187BE435E} = {24EB6E3A-C080-4B27-8EA7-F6C91446B840}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {30612385-E4F4-4FD9-B648-45AF74CB4915}
EndGlobalSection
EndGlobal

View File

@@ -1,19 +0,0 @@
using System.Text.Json.Serialization;
public class Subtask
{
public string subtask { get; set; }
public string prompt { get; set; }
}
public class Step
{
public string description { get; set; }
public string step { get; set; }
public List<Subtask> subtasks { get; set; }
}
public class DevLeadPlanResponse
{
public List<Step> steps { get; set; }
}

View File

@@ -1,183 +0,0 @@
using System.CommandLine;
using System.Text.Json;
using Microsoft.AI.DevTeam.Skills;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Plugins.Memory;
class Program
{
static async Task Main(string[] args)
{
var maxRetryOption = new Option<int>(
name: "--maxRetry",
description: "The number of retires to use if throttled",
getDefaultValue: () => 6);
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "The file used for input to the skill function");
var rootCommand = new RootCommand("CLI tool for the AI Dev team");
rootCommand.AddGlobalOption(fileOption);
rootCommand.Add(maxRetryOption);
var doCommand = new Command("do", "Doers :) ");
var doItCommand = new Command("it", "Do it!");
doItCommand.SetHandler(async (file, maxRetry) => await ChainFunctions(file.FullName, maxRetry), fileOption, maxRetryOption);
doCommand.AddCommand(doItCommand);
var pmCommand = new Command("pm", "Commands for the PM team");
var pmReadmeCommand = new Command("readme", "Produce a Readme for a given input");
pmReadmeCommand.SetHandler(async (file, maxRetry) => await CallWithFile<string>(nameof(PM), PM.Readme, file.FullName, maxRetry), fileOption, maxRetryOption);
var pmBootstrapCommand = new Command("bootstrap", "Bootstrap a project for a given input");
pmBootstrapCommand.SetHandler(async (file, maxRetry) => await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file.FullName, maxRetry), fileOption, maxRetryOption);
pmCommand.AddCommand(pmReadmeCommand);
pmCommand.AddCommand(pmBootstrapCommand);
var devleadCommand = new Command("devlead", "Commands for the Dev Lead team");
var devleadPlanCommand = new Command("plan", "Plan the work for a given input");
devleadPlanCommand.SetHandler(async (file, maxRetry) => await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file.FullName, maxRetry), fileOption, maxRetryOption);
devleadCommand.AddCommand(devleadPlanCommand);
var devCommand = new Command("dev", "Commands for the Dev team");
var devPlanCommand = new Command("plan", "Implement the module for a given input");
devPlanCommand.SetHandler(async (file, maxRetry) => await CallWithFile<string>(nameof(Developer), Developer.Implement, file.FullName, maxRetry), fileOption, maxRetryOption);
devCommand.AddCommand(devPlanCommand);
rootCommand.AddCommand(pmCommand);
rootCommand.AddCommand(devleadCommand);
rootCommand.AddCommand(devCommand);
rootCommand.AddCommand(doCommand);
await rootCommand.InvokeAsync(args);
}
public static async Task ChainFunctions(string file, int maxRetry)
{
var sandboxSkill = new SandboxSkill();
var outputPath = Directory.CreateDirectory("output");
Console.WriteLine($"Using output directory: {outputPath}");
var readme = await CallWithFile<string>(nameof(PM), PM.Readme, file, maxRetry);
string readmeFile = Path.Combine(outputPath.FullName, "README.md");
await SaveToFile(readmeFile, readme);
Console.WriteLine($"Saved README to {readmeFile}");
var script = await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file, maxRetry);
await sandboxSkill.RunInDotnetAlpineAsync(script);
await SaveToFile(Path.Combine(outputPath.FullName, "bootstrap.sh"), script);
Console.WriteLine($"Saved bootstrap script to {outputPath.FullName}bootstrap.sh");
var plan = await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, readmeFile, maxRetry);
await SaveToFile(Path.Combine(outputPath.FullName, "plan.json"), JsonSerializer.Serialize(plan));
Console.WriteLine($"Using Plan: \n {plan}");
var implementationTasks = plan.steps.SelectMany(
(step) => step.subtasks.Select(
async (subtask) =>
{
Console.WriteLine($"Implementing {step.step}-{subtask.subtask}");
var implementationResult = string.Empty;
while (true)
{
try
{
implementationResult = await CallFunction<string>(nameof(Developer), Developer.Implement, subtask.prompt, maxRetry);
break;
}
catch (Exception ex)
{
if (ex.Message.Contains("TooMany"))
{
Console.WriteLine("Throttled, retrying...");
continue;
}
throw;
}
}
await sandboxSkill.RunInDotnetAlpineAsync(implementationResult);
await SaveToFile(Path.Combine(outputPath.FullName, $"{step.step}-{subtask.subtask}.sh"), implementationResult);
return implementationResult;
}));
await Task.WhenAll(implementationTasks);
}
public static async Task SaveToFile(string filePath, string content)
{
await File.WriteAllTextAsync(filePath, content);
}
public static async Task<T> CallWithFile<T>(string skillName, string functionName, string filePath, int maxRetry)
{
if (!File.Exists(filePath))
throw new FileNotFoundException($"File not found: {filePath}", filePath);
var input = File.ReadAllText(filePath);
return await CallFunction<T>(skillName, functionName, input, maxRetry);
}
public static async Task<T> CallFunction<T>(string skillName, string functionName, string input, int maxRetry)
{
var kernelSettings = KernelSettings.LoadSettings();
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning)
.AddConsole()
.AddDebug();
});
var memoryStore = new QdrantMemoryStore(new QdrantVectorDbClient("http://qdrant:6333", 1536));
var embedingGeneration = new AzureTextEmbeddingGeneration(kernelSettings.EmbeddingDeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey);
var semanticTextMemory = new SemanticTextMemory(memoryStore, embedingGeneration);
var memoryBuilder = new MemoryBuilder();
var memory = memoryBuilder.WithLoggerFactory(loggerFactory)
.WithQdrantMemoryStore("http://qdrant:6333", 1536)
.WithAzureTextEmbeddingGenerationService(kernelSettings.EmbeddingDeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey)
.Build();
var kernel = new KernelBuilder()
.WithLoggerFactory(loggerFactory)
.WithAzureChatCompletionService(kernelSettings.DeploymentOrModelId, kernelSettings.Endpoint, kernelSettings.ApiKey, true, kernelSettings.ServiceId, true)
.Build();
//Console.WriteLine($"Calling skill '{skillName}' function '{functionName}' with input '{input}'");
var interestingMemories = memory.SearchAsync("waf-pages", input, 2);
var wafContext = "Consider the following architectural guidelines:";
await foreach (var m in interestingMemories)
{
wafContext += $"\n {m.Metadata.Text}";
}
var promptTemplate = Skills.ForSkillAndFunction(skillName, functionName);
var function = kernel.CreateSemanticFunction(promptTemplate);
var context = new ContextVariables();
context.Set("input", input);
context.Set("wafContext", wafContext);
var answer = await kernel.RunAsync(context, function).ConfigureAwait(false);
var result = typeof(T) != typeof(string) ? JsonSerializer.Deserialize<T>(answer.ToString()) : (T)(object)answer.ToString();
//Console.WriteLine(answer);
return result;
}
}
public static class PM { public static string Readme = "Readme"; public static string BootstrapProject = "BootstrapProject"; }
public static class DevLead { public static string Plan = "Plan"; }
public static class Developer { public static string Implement = "Implement"; public static string Improve = "Improve"; }

View File

@@ -1,37 +0,0 @@
using DotNet.Testcontainers.Builders;
public class SandboxSkill
{
public async Task<string> RunInAlpineAsync(string input)
{
return await RunInContainer(input, "alpine");
}
public async Task<string> RunInDotnetAlpineAsync(string input)
{
return await RunInContainer(input, "mcr.microsoft.com/dotnet/sdk:7.0");
}
private async Task<string> RunInContainer(string input, string image)
{
var tempScriptFile = $"{Guid.NewGuid().ToString()}.sh";
var tempScriptPath = $"./output/{tempScriptFile}";
await File.WriteAllTextAsync(tempScriptPath, input);
Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(),"output", "src"));
var dotnetContainer = new ContainerBuilder()
.WithName(Guid.NewGuid().ToString("D"))
.WithImage(image)
.WithBindMount(Path.Combine(Directory.GetCurrentDirectory(),"output", "src"), "/src")
.WithBindMount(Path.Combine(Directory.GetCurrentDirectory(), tempScriptPath), $"/src/{tempScriptFile}")
.WithWorkingDirectory("/src")
.WithCommand("sh", tempScriptFile)
.Build();
await dotnetContainer.StartAsync()
.ConfigureAwait(false);
// Cleanup
File.Delete(tempScriptPath);
File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "output", "src", tempScriptFile));
return "";
}
}

View File

@@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta5" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Qdrant" Version="1.0.0-beta5" />
<PackageReference Include="Testcontainers" Version="3.2.0" />
<ProjectReference Include="..\..\libs\Microsoft.AI.DevTeam.Skills\Microsoft.AI.DevTeam.Skills.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,93 +0,0 @@
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
internal class KernelSettings
{
public const string DefaultConfigFile = "config/appsettings.json";
public const string OpenAI = "OPENAI";
public const string AzureOpenAI = "AZUREOPENAI";
[JsonPropertyName("serviceType")]
public string ServiceType { get; set; } = string.Empty;
[JsonPropertyName("serviceId")]
public string ServiceId { get; set; } = string.Empty;
[JsonPropertyName("deploymentOrModelId")]
public string DeploymentOrModelId { get; set; } = string.Empty;
[JsonPropertyName("embeddingDeploymentOrModelId")]
public string EmbeddingDeploymentOrModelId { get; set; } = string.Empty;
[JsonPropertyName("endpoint")]
public string Endpoint { get; set; } = string.Empty;
[JsonPropertyName("apiKey")]
public string ApiKey { get; set; } = string.Empty;
[JsonPropertyName("orgId")]
public string OrgId { get; set; } = string.Empty;
[JsonPropertyName("logLevel")]
public LogLevel? LogLevel { get; set; }
/// <summary>
/// Load the kernel settings from settings.json if the file exists and if not attempt to use user secrets.
/// </summary>
internal static KernelSettings LoadSettings()
{
try
{
if (File.Exists(DefaultConfigFile))
{
return FromFile(DefaultConfigFile);
}
Console.WriteLine($"Semantic kernel settings '{DefaultConfigFile}' not found, attempting to load configuration from user secrets.");
return FromUserSecrets();
}
catch (InvalidDataException ide)
{
Console.Error.WriteLine(
"Unable to load semantic kernel settings, please provide configuration settings using instructions in the README.\n" +
"Please refer to: https://github.com/microsoft/semantic-kernel-starters/blob/main/sk-csharp-hello-world/README.md#configuring-the-starter"
);
throw new InvalidOperationException(ide.Message);
}
}
/// <summary>
/// Load the kernel settings from the specified configuration file if it exists.
/// </summary>
internal static KernelSettings FromFile(string configFile = DefaultConfigFile)
{
if (!File.Exists(configFile))
{
throw new FileNotFoundException($"Configuration not found: {configFile}");
}
var configuration = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile(configFile, optional: true, reloadOnChange: true)
.Build();
return configuration.Get<KernelSettings>()
?? throw new InvalidDataException($"Invalid semantic kernel settings in '{configFile}', please provide configuration settings using instructions in the README.");
}
/// <summary>
/// Load the kernel settings from user secrets.
/// </summary>
internal static KernelSettings FromUserSecrets()
{
var configuration = new ConfigurationBuilder()
.AddUserSecrets<KernelSettings>()
.AddEnvironmentVariables()
.Build();
return configuration.Get<KernelSettings>()
?? throw new InvalidDataException("Invalid semantic kernel settings in user secrets, please provide configuration settings using instructions in the README.");
}
}

View File

@@ -1,7 +0,0 @@
Please write a bash script with the commands that would be required to generate two new .NET applications as follows:
The first application will be a web service called "skdtwebapi" with a two methods: /prompt, which accepts a PUT request with a text body of no more than 2048k, and /skills which accepts a GET request and lists the semantic skills it has.
The second application will be a command line client of the web service and is called "skdt" and will accept one argument, a file name. The command line application will PUT the contents of the text file to the /prompt method of the first application.
You may add comments to the script and the generated output but do not add any other text except the bash script.
You may include commands to build the applications but do not run them.
Use .NET 7.
Configuration parameters required for the webapi applicaton will include AzureOpenAIServiceEndpoint, AIServiceKey, AIModel.

View File

@@ -1,128 +0,0 @@
#!/bin/bash
# Create skdtwebapi web service
dotnet new webapi -n skdtwebapi
cd skdtwebapi
# Add required NuGet packages
dotnet add package Microsoft.AspNetCore.OData
dotnet add package Microsoft.Azure.CognitiveServices.Language
# Add configuration parameters to appsettings.json
echo '{
"AzureOpenAIServiceEndpoint": "",
"AIServiceKey": "",
"AIModel": ""
}' > appsettings.json
# Add /prompt and /skills methods to ValuesController.cs
echo 'using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Extensions.Configuration;
namespace skdtwebapi.Controllers
{
[ApiController]
[Route("[controller]")]
public class ValuesController : ControllerBase
{
private readonly IConfiguration _config;
public ValuesController(IConfiguration config)
{
_config = config;
}
[HttpGet("skills")]
public async Task<ActionResult<IEnumerable<string>>> GetSkills()
{
var credentials = new ApiKeyServiceClientCredentials(_config["AIServiceKey"]);
var client = new TextAnalyticsClient(credentials)
{
Endpoint = _config["AzureOpenAIServiceEndpoint"]
};
var result = await client.EntitiesRecognitionGeneralAsync("en", "I am a software developer");
return result.Entities.Select(e => e.Name).ToList();
}
[HttpPut("prompt")]
public async Task<ActionResult> Prompt([FromBody] string prompt)
{
var credentials = new ApiKeyServiceClientCredentials(_config["AIServiceKey"]);
var client = new TextAnalyticsClient(credentials)
{
Endpoint = _config["AzureOpenAIServiceEndpoint"]
};
var result = await client.EntitiesRecognitionGeneralAsync("en", prompt);
return Ok();
}
}
}' > Controllers/ValuesController.cs
# Create skdt command line client
cd ..
dotnet new console -n skdt
cd skdt
# Add required NuGet packages
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Http
dotnet add package Microsoft.Net.Http.Headers
# Add configuration parameters to appsettings.json
echo '{
"WebApiUrl": "https://localhost:5001",
"AIServiceKey": "",
"AIModel": ""
}' > appsettings.json
# Add code to Program.cs to PUT contents of text file to /prompt method
echo 'using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
namespace skdt
{
class Program
{
static async Task Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddHttpClient()
.BuildServiceProvider();
var clientFactory = serviceProvider.GetService<IHttpClientFactory>();
var client = clientFactory.CreateClient();
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var fileContents = await File.ReadAllTextAsync(args[0]);
var response = await client.PutAsync($"{configuration["WebApiUrl"]}/prompt", new StringContent(fileContents));
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Prompt sent successfully.");
}
else
{
Console.WriteLine($"Error sending prompt: {response.StatusCode}");
}
}
}
}' > Program.cs

View File

@@ -1,36 +0,0 @@
Calling skill 'PM' function 'Readme' with file 'util/ToDoListSamplePrompt.txt'
{"variables":[{"key":"input","value":"I\u0027d like to build a typical Todo List Application: a simple productivity tool that allows users to create, manage, and track tasks or to-do items. \nKey features of the Todo List application include the ability to add, edit, and delete tasks, set due dates and reminders, categorize tasks by project or priority, and mark tasks as complete. \nThe Todo List applications also offer collaboration features, such as sharing tasks with others or assigning tasks to team members.\nAdditionally, the Todo List application will offer offer mobile and web-based interfaces, allowing users to access their tasks from anywhere."}]}
# Todo List Application
The Todo List Application is a simple productivity tool designed to help users create, manage, and track tasks or to-do items. With both mobile and web-based interfaces, users can access their tasks from anywhere, making it easy to stay organized and on top of their work.
## Features
- **Add, Edit, and Delete Tasks**: Users can easily create new tasks, modify existing ones, and remove tasks when they are no longer needed.
- **Set Due Dates and Reminders**: Users can set due dates for tasks and receive reminders to ensure they stay on track and complete tasks on time.
- **Categorize Tasks**: Users can organize tasks by project or priority, making it easy to focus on what's most important.
- **Mark Tasks as Complete**: Users can mark tasks as complete, providing a sense of accomplishment and helping to track progress.
- **Collaboration Features**: Users can share tasks with others or assign tasks to team members, making it easy to collaborate and work together on projects.
- **Mobile and Web-Based Interfaces**: Users can access their tasks from anywhere, whether they're on their phone or using a web browser.
## Architecture
The Todo List Application is organized into the following components:
- **Frontend**: The frontend is responsible for displaying the user interface and handling user interactions. It is built using a combination of HTML, CSS, and JavaScript, and communicates with the backend through a RESTful API.
- **Backend**: The backend is responsible for processing user requests, managing the database, and handling business logic. It is built using a server-side programming language (e.g., Node.js, Python, or Ruby) and a database management system (e.g., MySQL, PostgreSQL, or MongoDB).
- **Database**: The database stores all the data related to tasks, projects, and users. It is organized into tables or collections, depending on the chosen database management system.
- **API**: The API provides a set of endpoints for the frontend to interact with the backend. It follows the RESTful architecture, using standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations on tasks, projects, and users.
- **Authentication and Authorization**: The application includes user authentication and authorization features, ensuring that only authorized users can access and modify their tasks and projects.
## Running the Application
To run the Todo List Application, follow these steps:
1. Clone the repository to your local machine.
2. Install the required dependencies using the package manager for your chosen programming language (e.g., `npm install` for Node.js, `pip install -r requirements.txt` for Python, or `bundle install` for Ruby).
3. Set up the database by following the instructions provided in the `database_setup.md` file.
4. Start the backend server by running the appropriate command for your chosen programming language (e.g., `node server.js` for Node.js, `python manage.py runserver` for Python, or `rails server` for Ruby).
5. Open the frontend in your web browser by navigating to the provided URL (e.g., `http://localhost:3000`).
For more detailed instructions, refer to the `installation.md` and `usage.md` files included in the repository.

View File

@@ -1,8 +0,0 @@
I'd like to build a typical Chat Message Applicaton: a simple chat app in React Native that allows the user to manage multiple chat threads.
Each thred is a different conversation. The app can look very similar to Apple iMessage.
Key features of the Chat Message application include the ability to create a new chat thread, view the messages in each thread, and to edite and send a new message.
The frontend for the app will be in typescript and React Native, the backend will be in C# as an Azure Function.
The backend will use a mock function to generate random responses. The backend will store the chats in an Azure CosmosDB.
The application will have a setup for local testing and development that allows testing without deployment to Azure.
Use bicep scripts to create the Azure resources.
The front end should be able to run entirely in the browser.

View File

@@ -1,6 +0,0 @@
I'd like to build a typical Todo List Application: a simple productivity tool that allows users to create, manage, and track tasks or to-do items.
Key features of the Todo List application include the ability to add, edit, and delete tasks, set due dates and reminders, categorize tasks by project or priority, and mark tasks as complete.
The Todo List applications also offer collaboration features, such as sharing tasks with others or assigning tasks to team members.
Additionally, the Todo List application will offer offer mobile and web-based interfaces, allowing users to access their tasks from anywhere.
Use C# as the language.
The app needs to be deployed to Azure, be highly performant, cost effective and secure, following the rules of Well Architected Framework.

View File

@@ -1,201 +0,0 @@
{
"skillName": "FunSkill",
"functionName": "Excuses",
"description": "Turn a scenario into a creative or humorous excuse to send your boss",
"filePath": "skills/FunSkill/Excuses/skprompt.txt",
"results": [
{
"completionConfig": {
"label": "gpt-4",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "gpt-4",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "chat-completion",
"chatSystemMessage": "You are an AI assistant that helps people find information."
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "bob"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:bob",
"durationInMilliseconds": 3171.3077,
"inputTokens": 46,
"outputTokens": 24,
"result": "Excuse: Bob was busy teaching a group of squirrels how to knit sweaters for the upcoming winter fashion show.",
"chatCompletionResult": "Excuse: Bob was busy teaching a group of squirrels how to knit sweaters for the upcoming winter fashion show."
}
},
{
"completionConfig": {
"label": "text-davinci-003",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "text-davinci-003",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "text-completion"
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "bob"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:bob",
"durationInMilliseconds": 2507.7456,
"inputTokens": 55,
"outputTokens": 26,
"result": " didn't show up to work\nExcuse:Bob was abducted by aliens and they needed him to help them fix their spaceship.",
"textCompletionResult": " didn't show up to work\nExcuse:Bob was abducted by aliens and they needed him to help them fix their spaceship."
}
},
{
"completionConfig": {
"label": "text-davinci-003",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "text-davinci-003",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "text-completion"
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "bob"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:bob",
"durationInMilliseconds": 1427.7162,
"inputTokens": 55,
"outputTokens": 26,
"result": " didn't show up to work\nExcuse:Bob was abducted by aliens and they needed him to help them fix their spaceship.",
"textCompletionResult": " didn't show up to work\nExcuse:Bob was abducted by aliens and they needed him to help them fix their spaceship."
}
},
{
"completionConfig": {
"label": "gpt-4",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "gpt-4",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "text-completion"
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "bob"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:bob",
"durationInMilliseconds": 725.1863,
"inputTokens": 46,
"outputTokens": 1,
"result": "bob",
"textCompletionResult": "bob"
}
},
{
"completionConfig": {
"label": "gpt-4",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "gpt-4",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "text-completion"
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "I'm in pain"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:I'm in pain",
"durationInMilliseconds": 725.0451,
"inputTokens": 48,
"outputTokens": 4,
"result": "I'm in pain",
"textCompletionResult": "I'm in pain"
}
},
{
"completionConfig": {
"label": "gpt-4",
"aiService": "AzureOpenAI",
"deploymentOrModelId": "gpt-4",
"endpoint": "https://lightspeed-team-shared-openai-eastus.openai.azure.com/",
"useCustomEndpoint": false,
"endpointType": "text-completion"
},
"requestSettings": {
"temperature": 0,
"topP": 0,
"presencePenalty": 0,
"frequencyPenalty": 0,
"maxTokens": 0,
"stopSequences": []
},
"variables": [
{
"key": "input",
"value": "I'm in pain"
}
],
"output": {
"prompt": "Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild.\n\nEvent:I am running late.\nExcuse:I was being held ransom by giraffe gangsters. \n\nEvent:I'm in pain",
"durationInMilliseconds": 1296.1323,
"inputTokens": 48,
"outputTokens": 4,
"result": "I'm in pain",
"textCompletionResult": "I'm in pain"
}
}
]
}

View File

@@ -1,7 +0,0 @@
{
"recommendations": [
"ms-azuretools.vscode-azurefunctions",
"ms-dotnettools.csharp",
"ms-semantic-kernel.semantic-kernel"
]
}

View File

@@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to .NET Functions",
"type": "coreclr",
"request": "attach",
"processId": "${command:azureFunctions.pickProcess}"
}
]
}

View File

@@ -1,7 +0,0 @@
{
"azureFunctions.deploySubpath": "bin/Release/net6.0/publish",
"azureFunctions.projectLanguage": "C#",
"azureFunctions.projectRuntime": "~4",
"debug.internalConsoleOptions": "neverOpen",
"azureFunctions.preDeployTask": "publish (functions)"
}

View File

@@ -1,105 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "clean (functions)",
"command": "dotnet",
"args": [
"clean",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"problemMatcher": "$msCompile"
},
{
"label": "build (functions)",
"command": "dotnet",
"args": [
"build",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"dependsOn": "clean (functions)",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$msCompile"
},
{
"label": "clean release (functions)",
"command": "dotnet",
"args": [
"clean",
"--configuration",
"Release",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"problemMatcher": "$msCompile"
},
{
"label": "publish (functions)",
"command": "dotnet",
"args": [
"publish",
"--configuration",
"Release",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"type": "process",
"dependsOn": "clean release (functions)",
"problemMatcher": "$msCompile"
},
{
"type": "func",
"dependsOn": "build (functions)",
"options": {
"cwd": "${workspaceFolder}/bin/Debug/net7.0"
},
"command": "host start",
"isBackground": true,
"problemMatcher": "$func-dotnet-watch"
},
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/sk-csharp-azure-functions.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/sk-csharp-azure-functions.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/sk-csharp-azure-functions.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,76 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask.Client;
using Octokit;
namespace SK.DevTeam
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public class IssuesActivities
{
private readonly GithubService _ghService;
public IssuesActivities(GithubService githubService)
{
_ghService = githubService;
}
[Function(nameof(CreateIssue))]
public async Task<NewIssueResponse> CreateIssue([ActivityTrigger] NewIssueRequest request, FunctionContext executionContext)
{
var ghClient = await _ghService.GetGitHubClient();
var newIssue = new NewIssue($"{request.Function} chain for #{request.IssueRequest.Number}")
{
Body = request.IssueRequest.Input,
};
newIssue.Labels.Add($"{request.Skill}.{request.Function}");
var issue = await ghClient.Issue.Create(request.IssueRequest.Org, request.IssueRequest.Repo, newIssue);
var commentBody = $" - [ ] #{issue.Number} - tracks {request.Skill}.{request.Function}";
var comment = await ghClient.Issue.Comment.Create(request.IssueRequest.Org, request.IssueRequest.Repo, (int)request.IssueRequest.Number, commentBody);
return new NewIssueResponse
{
Number = issue.Number,
CommentId = comment.Id
};
}
[Function("CloseSubOrchestration")]
public async Task Close(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "close")] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var request = await req.ReadFromJsonAsync<CloseIssueRequest>();
var ghClient = await _ghService.GetGitHubClient();
var comment = await ghClient.Issue.Comment.Get(request.Org, request.Repo, request.CommentId);
var updatedComment = comment.Body.Replace("[ ]", "[x]");
await ghClient.Issue.Comment.Update(request.Org, request.Repo, request.CommentId, updatedComment);
await client.RaiseEventAsync(request.InstanceId, SubIssueOrchestration.IssueClosed, true);
}
[Function(nameof(GetLastComment))]
public async Task<string> GetLastComment([ActivityTrigger] IssueOrchestrationRequest request, FunctionContext executionContext)
{
var ghClient = await _ghService.GetGitHubClient();
var icOptions = new IssueCommentRequest
{
Direction = SortDirection.Descending
};
var apiOptions = new ApiOptions
{
PageCount = 1,
PageSize = 1,
StartPage = 1
};
var comments = await ghClient.Issue.Comment.GetAllForIssue(request.Org, request.Repo, (int)request.Number, icOptions, apiOptions);
return comments.First().Body;
}
}
}

View File

@@ -1,44 +0,0 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using Azure.Data.Tables;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace SK.DevTeam
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public static class MetadataActivities
{
[Function(nameof(GetMetadata))]
public static async Task<IssueMetadata> GetMetadata(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "metadata/{key}")] HttpRequestData req, string key,
[TableInput("Metadata", Connection = "AzureWebJobsStorage")] TableClient client,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger<SKWebHookEventProcessor>();
logger.LogInformation($"Getting metadata for {key}");
var metadataResponse = await client.GetEntityAsync<IssueMetadata>(key, key);
var metadata = metadataResponse.Value;
logger.LogInformation($"Metadata result {JsonSerializer.Serialize(metadata)}");
return metadata;
}
[Function(nameof(SaveMetadata))]
public static async Task<IssueMetadata> SaveMetadata(
[ActivityTrigger] IssueMetadata metadata,
[TableInput("Metadata", Connection = "AzureWebJobsStorage")] TableClient client,
FunctionContext executionContext)
{
await client.UpsertEntityAsync(metadata);
return metadata;
}
}
}

View File

@@ -1,239 +0,0 @@
using System.Text;
using Azure;
using Azure.Core;
using Azure.Data.Tables;
using Azure.Identity;
using Azure.ResourceManager;
using Azure.ResourceManager.ContainerInstance;
using Azure.ResourceManager.ContainerInstance.Models;
using Azure.ResourceManager.Resources;
using Azure.Storage.Files.Shares;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Octokit;
using Octokit.Helpers;
namespace SK.DevTeam
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public class PullRequestActivities
{
private readonly AzureOptions _azSettings;
private readonly GithubService _ghService;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<PullRequestActivities> logger;
public PullRequestActivities(IOptions<AzureOptions> azOptions, GithubService ghService, IHttpClientFactory httpClientFactory, ILogger<PullRequestActivities> logger)
{
_azSettings = azOptions.Value;
_ghService = ghService;
_httpClientFactory = httpClientFactory;
this.logger = logger;
}
[Function(nameof(SaveOutput))]
public async Task<bool> SaveOutput([ActivityTrigger] SaveOutputRequest request, FunctionContext executionContext)
{
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={_azSettings.FilesAccountName};AccountKey={_azSettings.FilesAccountKey};EndpointSuffix=core.windows.net";
var parentDirName = $"{request.Directory}/{request.IssueOrchestrationId}";
var fileName = $"{request.FileName}.{request.Extension}";
var share = new ShareClient(connectionString, _azSettings.FilesShareName);
await share.CreateIfNotExistsAsync();
await share.GetDirectoryClient($"{request.Directory}").CreateIfNotExistsAsync();
var parentDir = share.GetDirectoryClient(parentDirName);
await parentDir.CreateIfNotExistsAsync();
var directory = parentDir.GetSubdirectoryClient(request.SubOrchestrationId);
await directory.CreateIfNotExistsAsync();
var file = directory.GetFileClient(fileName);
// hack to enable script to save files in the same directory
var cwdHack = "#!/bin/bash\n cd $(dirname $0)";
var output = request.Extension == "sh" ? request.Output.Replace("#!/bin/bash", cwdHack) : request.Output;
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(output)))
{
await file.CreateAsync(stream.Length);
await file.UploadRangeAsync(
new HttpRange(0, stream.Length),
stream);
}
return true;
}
[Function(nameof(CreateBranch))]
public async Task<bool> CreateBranch([ActivityTrigger] GHNewBranch request, FunctionContext executionContext)
{
var ghClient = await _ghService.GetGitHubClient();
var repo = await ghClient.Repository.Get(request.Org, request.Repo);
await ghClient.Git.Reference.CreateBranch(request.Org, request.Repo, request.Branch, repo.DefaultBranch);
return true;
}
[Function(nameof(CreatePR))]
public async Task<bool> CreatePR([ActivityTrigger] GHNewBranch request, FunctionContext executionContext)
{
var ghClient = await _ghService.GetGitHubClient();
var repo = await ghClient.Repository.Get(request.Org, request.Repo);
await ghClient.PullRequest.Create(request.Org, request.Repo, new NewPullRequest($"New app #{request.Number}", request.Branch, repo.DefaultBranch));
return true;
}
[Function(nameof(RunInSandbox))]
public async Task<bool> RunInSandbox(
[ActivityTrigger] RunInSandboxRequest request,
[TableInput("ContainersMetadata", Connection = "AzureWebJobsStorage")] TableClient tableClient,
FunctionContext executionContext)
{
var client = new ArmClient(new DefaultAzureCredential());
var containerGroupName = $"sk-sandbox-{request.PrRequest.SubOrchestrationId}";
var containerName = $"sk-sandbox-{request.PrRequest.SubOrchestrationId}";
var image = Environment.GetEnvironmentVariable("SANDBOX_IMAGE", EnvironmentVariableTarget.Process);
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
var resourceGroupResource = client.GetResourceGroupResource(resourceGroupResourceId);
var scriptPath = $"/azfiles/output/{request.PrRequest.IssueOrchestrationId}/{request.PrRequest.SubOrchestrationId}/run.sh";
var collection = resourceGroupResource.GetContainerGroups();
var data = new ContainerGroupData(new AzureLocation(_azSettings.Location), new ContainerInstanceContainer[]
{
new ContainerInstanceContainer(containerName,image,new ContainerResourceRequirements(new ContainerResourceRequestsContent(1.5,1)))
{
Command = { "/bin/bash", $"{scriptPath}" },
VolumeMounts =
{
new ContainerVolumeMount("azfiles","/azfiles/")
{
IsReadOnly = false,
}
},
}}, ContainerInstanceOperatingSystemType.Linux)
{
Volumes =
{
new ContainerVolume("azfiles")
{
AzureFile = new ContainerInstanceAzureFileVolume(_azSettings.FilesShareName,_azSettings.FilesAccountName)
{
StorageAccountKey = _azSettings.FilesAccountKey
},
},
},
RestartPolicy = ContainerGroupRestartPolicy.Never,
Sku = ContainerGroupSku.Standard,
Priority = ContainerGroupPriority.Regular
};
await collection.CreateOrUpdateAsync(WaitUntil.Completed, containerGroupName, data);
var metadata = new ContainerInstanceMetadata
{
PartitionKey = containerGroupName,
RowKey = containerGroupName,
SubOrchestrationId = request.SanboxOrchestrationId,
Processed = false
};
await tableClient.UpsertEntityAsync(metadata);
return true;
}
[Function(nameof(CommitToGithub))]
public async Task<bool> CommitToGithub([ActivityTrigger] GHCommitRequest request, FunctionContext executionContext)
{
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={_azSettings.FilesAccountName};AccountKey={_azSettings.FilesAccountKey};EndpointSuffix=core.windows.net";
var ghClient = await _ghService.GetGitHubClient();
var dirName = $"{request.Directory}/{request.IssueOrchestrationId}/{request.SubOrchestrationId}";
var share = new ShareClient(connectionString, _azSettings.FilesShareName);
var directory = share.GetDirectoryClient(dirName);
var remaining = new Queue<ShareDirectoryClient>();
remaining.Enqueue(directory);
while (remaining.Count > 0)
{
var dir = remaining.Dequeue();
await foreach (var item in dir.GetFilesAndDirectoriesAsync())
{
if (!item.IsDirectory && item.Name != "run.sh") // we don't want the generated script in the PR
{
try
{
var file = dir.GetFileClient(item.Name);
var filePath = file.Path.Replace($"{_azSettings.FilesShareName}/", "")
.Replace($"{dirName}/", "");
var fileStream = await file.OpenReadAsync();
using (var reader = new StreamReader(fileStream, Encoding.UTF8))
{
var value = reader.ReadToEnd();
await ghClient.Repository.Content.CreateFile(
request.Org, request.Repo, filePath,
new CreateFileRequest($"Commit message", value, request.Branch)); // TODO: add more meaningfull commit message
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Error while uploading file {item.Name}");
}
}
else if (item.IsDirectory)
{
remaining.Enqueue(dir.GetSubdirectoryClient(item.Name));
}
}
}
return true;
}
[Function(nameof(Terminated))]
public async Task<ContainerInstanceMetadata> Terminated(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "container/{name}/terminate")] HttpRequestData req, string name,
[TableInput("ContainersMetadata", Connection = "AzureWebJobsStorage")] TableClient tableClient,
[DurableClient] DurableTaskClient client)
{
var metadataResponse = await tableClient.GetEntityAsync<ContainerInstanceMetadata>(name, name);
var metadata = metadataResponse.Value;
if (!metadata.Processed)
{
await client.RaiseEventAsync(metadata.SubOrchestrationId, SubIssueOrchestration.ContainerTerminated, true);
metadata.Processed = true;
await tableClient.UpdateEntityAsync(metadata, metadata.ETag, TableUpdateMode.Replace);
}
return metadata;
}
[Function(nameof(CleanContainers))]
public async Task CleanContainers(
[TimerTrigger("*/30 * * * * *")] TimerInfo myTimer,
FunctionContext executionContext)
{
var httpClient = _httpClientFactory.CreateClient("FunctionsClient");
var client = new ArmClient(new DefaultAzureCredential());
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
var resourceGroupResource = client.GetResourceGroupResource(resourceGroupResourceId);
var collection = resourceGroupResource.GetContainerGroups();
foreach (var cg in collection.GetAll())
{
var c = await cg.GetAsync();
if (c.Value.Data.ProvisioningState == "Succeeded"
&& c.Value.Data.Containers.First().InstanceView.CurrentState.State == "Terminated")
{
await cg.DeleteAsync(WaitUntil.Started);
await httpClient.PostAsync($"container/{cg.Data.Name}/terminate", default);
}
}
}
}
}

View File

@@ -1,33 +0,0 @@
<Project>
<PropertyGroup>
<!-- Default properties inherited by all projects. Projects can override. -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<AnalysisLevel>latest</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<NoWarn>CS1591,CA1852,CA1050</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- Disable NuGet packaging by default. Projects can override. -->
<IsPackable>disable</IsPackable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup>
<RepoRoot>$([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)'))))</RepoRoot>
</PropertyGroup>
</Project>

View File

@@ -1,25 +0,0 @@
<Project>
<!-- Direct all packages under 'dotnet' to get versions from Directory.Packages.props -->
<!-- using Central Package Management feature -->
<!-- https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
<!-- Only run 'dotnet format' on dev machines, Release builds. Skip on GitHub Actions -->
<!-- as this runs in its own Actions job. -->
<Target Name="DotnetFormatOnBuild" BeforeTargets="Build"
Condition=" '$(Configuration)' == 'Release' AND '$(GITHUB_ACTIONS)' == '' ">
<Message Text="Running dotnet format" Importance="high" />
<Exec Command="dotnet format --no-restore -v diag $(ProjectFileName)" />
</Target>
<Target Name="AddInternalsVisibleTo" BeforeTargets="BeforeCompile">
<!-- Handle Add InternalsVisibleTo to any targets that don't support it. -->
<ItemGroup Condition="'@(InternalsVisibleTo->Count())' &gt; 0 AND $([MSBuild]::VersionLessThan($(NETCoreSdkVersion), '5.0.0'))">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1 Condition="'%(InternalsVisibleTo.PublicKey)' != ''">%(InternalsVisibleTo.Identity), PublicKey="%(InternalsVisibleTo.PublicKey)</_Parameter1>
<_Parameter1 Condition="'%(InternalsVisibleTo.PublicKey)' == ''">%(InternalsVisibleTo.Identity)</_Parameter1>
<_Parameter1_TypeName>System.String</_Parameter1_TypeName>
</AssemblyAttribute>
</ItemGroup>
</Target>
</Project>

View File

@@ -1,10 +0,0 @@
public class AddToPRRequest
{
public string Output { get; set; }
public string IssueOrchestrationId { get; set; }
public string SubOrchestrationId { get; set; }
public string PrSubOrchestrationId { get; set; }
public string Extension { get; set; }
public bool RunInSandbox { get; set; }
public IssueOrchestrationRequest Request { get; set; }
}

View File

@@ -1,7 +0,0 @@
public class CloseIssueRequest
{
public string InstanceId { get; set; }
public int CommentId { get; set; }
public string Org { get; set; }
public string Repo { get; set; }
}

View File

@@ -1,12 +0,0 @@
using Azure;
using Azure.Data.Tables;
public class ContainerInstanceMetadata : ITableEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string SubOrchestrationId { get; set; }
public bool Processed { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
}

View File

@@ -1,17 +0,0 @@
public class Subtask
{
public string subtask { get; set; }
public string prompt { get; set; }
}
public class Step
{
public string description { get; set; }
public string step { get; set; }
public List<Subtask> subtasks { get; set; }
}
public class DevLeadPlanResponse
{
public List<Step> steps { get; set; }
}

View File

@@ -1,12 +0,0 @@
namespace SK.DevTeam
{
public class GHCommitRequest
{
public object IssueOrchestrationId { get; set; }
public object SubOrchestrationId { get; set; }
public string Org { get; set; }
public string Repo { get; set; }
public object Directory { get; set; }
public string Branch { get; set; }
}
}

View File

@@ -1,10 +0,0 @@
namespace SK.DevTeam
{
public class GHNewBranch
{
public string Org { get; set; }
public string Repo { get; set; }
public string Branch { get; set; }
public object Number { get; set; }
}
}

View File

@@ -1,19 +0,0 @@
using Azure;
using Azure.Data.Tables;
public class IssueMetadata : ITableEntity
{
public long? Number { get; set; }
public int? CommentId { get; set; }
public string? InstanceId { get; set; }
public string? Id { get; set; }
public string? Org { get; set; }
public string? Repo { get; set; }
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
}

View File

@@ -1,8 +0,0 @@
public class IssueOrchestrationRequest
{
public string Org { get; set; }
public string Repo { get; set; }
public long Number { get; set; }
public string Input { get; set; }
public string Branch => $"sk-{Number}";
}

View File

@@ -1,6 +0,0 @@
public class NewIssueRequest
{
public IssueOrchestrationRequest IssueRequest { get; set; }
public string Skill { get; set; }
public string Function { get; set; }
}

View File

@@ -1,5 +0,0 @@
public class NewIssueResponse
{
public long Number { get; set; }
public int CommentId { get; set; }
}

View File

@@ -1,6 +0,0 @@
public class RunAndSaveRequest
{
public IssueOrchestrationRequest Request { get; set; }
public string InstanceId { get; set; }
}

View File

@@ -1,8 +0,0 @@
namespace SK.DevTeam
{
public class RunInSandboxRequest
{
public AddToPRRequest PrRequest { get; set; }
public string SanboxOrchestrationId { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
namespace SK.DevTeam
{
public class SaveOutputRequest
{
public string IssueOrchestrationId { get; set; }
public string SubOrchestrationId { get; set; }
public string Output { get; set; }
public string Extension { get; set; }
public string Directory { get; set; }
public string FileName { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
namespace SK.DevTeam
{
public class SkillRequest
{
public IssueOrchestrationRequest IssueRequest { get; set; }
public string Skill { get; set; }
public string Function { get; set; }
}
}

View File

@@ -1,5 +0,0 @@
public class SkillResponse<T>
{
public T Output { get; set; }
public string SuborchestrationId { get; set; }
}

View File

@@ -1,75 +0,0 @@
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;
using static SK.DevTeam.SubIssueOrchestration;
namespace SK.DevTeam
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public static class IssueOrchestration
{
[Function("IssueOrchestrationStart")]
public static async Task<string> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "doit")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("IssueOrchestration_HttpStart");
var request = await req.ReadFromJsonAsync<IssueOrchestrationRequest>();
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(IssueOrchestration), request);
logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
return "";
}
[Function(nameof(IssueOrchestration))]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context, IssueOrchestrationRequest request)
{
var logger = context.CreateReplaySafeLogger(nameof(IssueOrchestration));
var outputs = new List<string>();
var newGHBranchRequest = new GHNewBranch
{
Org = request.Org,
Repo = request.Repo,
Branch = request.Branch,
Number = request.Number
};
var newBranch = await context.CallActivityAsync<bool>(nameof(PullRequestActivities.CreateBranch), newGHBranchRequest);
var readmeTask = await context.CallSubOrchestratorAsync<bool>(nameof(ReadmeAndSave), new RunAndSaveRequest
{
Request = request,
InstanceId = context.InstanceId
});
var newPR = await context.CallActivityAsync<bool>(nameof(PullRequestActivities.CreatePR), newGHBranchRequest);
var planTask = await context.CallSubOrchestratorAsync<SkillResponse<string>>(nameof(CreatePlan), request);
var plan = JsonSerializer.Deserialize<DevLeadPlanResponse>(planTask.Output);
var implementationTasks = plan.steps.SelectMany(s => s.subtasks.Select(st =>
context.CallSubOrchestratorAsync<bool>(nameof(ImplementAndSave), new RunAndSaveRequest
{
Request = new IssueOrchestrationRequest
{
Number = request.Number,
Org = request.Org,
Repo = request.Repo,
Input = st.prompt,
},
InstanceId = context.InstanceId
})));
await Task.WhenAll(implementationTasks);
return outputs;
}
}
}

View File

@@ -1,154 +0,0 @@
using Microsoft.AI.DevTeam.Skills;
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
namespace SK.DevTeam
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public static class SubIssueOrchestration
{
public static string IssueClosed = "IssueClosed";
public static string ContainerTerminated = "ContainerTerminated";
private static async Task<SkillResponse<string>> CallSkill(TaskOrchestrationContext context, SkillRequest request)
{
var newIssueResponse = await context.CallActivityAsync<NewIssueResponse>(nameof(IssuesActivities.CreateIssue), new NewIssueRequest
{
IssueRequest = request.IssueRequest,
Skill = request.Skill,
Function = request.Function
});
var metadata = await context.CallActivityAsync<IssueMetadata>(nameof(MetadataActivities.SaveMetadata), new IssueMetadata
{
Number = newIssueResponse.Number,
InstanceId = context.InstanceId,
Id = Guid.NewGuid().ToString(),
CommentId = newIssueResponse.CommentId,
Org = request.IssueRequest.Org,
Repo = request.IssueRequest.Repo,
PartitionKey = $"{request.IssueRequest.Org}{request.IssueRequest.Repo}{newIssueResponse.Number}",
RowKey = $"{request.IssueRequest.Org}{request.IssueRequest.Repo}{newIssueResponse.Number}",
Timestamp = DateTimeOffset.UtcNow
});
bool issueClosed = await context.WaitForExternalEvent<bool>(IssueClosed);
var lastComment = await context.CallActivityAsync<string>(nameof(IssuesActivities.GetLastComment), new IssueOrchestrationRequest
{
Org = request.IssueRequest.Org,
Repo = request.IssueRequest.Repo,
Number = newIssueResponse.Number
});
return new SkillResponse<string> { Output = lastComment, SuborchestrationId = context.InstanceId };
}
[Function(nameof(CreateReadme))]
public static async Task<SkillResponse<string>> CreateReadme(
[OrchestrationTrigger] TaskOrchestrationContext context, IssueOrchestrationRequest request)
{
return await CallSkill(context, new SkillRequest
{
IssueRequest = request,
Skill = nameof(PM),
Function = nameof(PM.Readme)
});
}
[Function(nameof(CreatePlan))]
public static async Task<SkillResponse<string>> CreatePlan(
[OrchestrationTrigger] TaskOrchestrationContext context, IssueOrchestrationRequest request)
{
return await CallSkill(context, new SkillRequest
{
IssueRequest = request,
Skill = nameof(DevLead),
Function = nameof(DevLead.Plan)
});
}
[Function(nameof(Implement))]
public static async Task<SkillResponse<string>> Implement(
[OrchestrationTrigger] TaskOrchestrationContext context, IssueOrchestrationRequest request)
{
return await CallSkill(context, new SkillRequest
{
IssueRequest = request,
Skill = nameof(Developer),
Function = nameof(Developer.Implement)
});
}
[Function(nameof(ImplementAndSave))]
public static async Task<bool> ImplementAndSave(
[OrchestrationTrigger] TaskOrchestrationContext context, RunAndSaveRequest request)
{
var implementResult = await context.CallSubOrchestratorAsync<SkillResponse<string>>(nameof(Implement), request.Request);
await context.CallSubOrchestratorAsync<string>(nameof(AddToPR), new AddToPRRequest
{
Output = implementResult.Output,
IssueOrchestrationId = request.InstanceId,
SubOrchestrationId = implementResult.SuborchestrationId,
Extension = "sh",
RunInSandbox = true,
Request = request.Request
});
return true;
}
[Function(nameof(ReadmeAndSave))]
public static async Task<bool> ReadmeAndSave(
[OrchestrationTrigger] TaskOrchestrationContext context, RunAndSaveRequest request)
{
var readmeResult = await context.CallSubOrchestratorAsync<SkillResponse<string>>(nameof(CreateReadme), request.Request);
context.CallSubOrchestratorAsync<string>(nameof(AddToPR), new AddToPRRequest
{
Output = readmeResult.Output,
IssueOrchestrationId = request.InstanceId,
SubOrchestrationId = readmeResult.SuborchestrationId,
Extension = "md",
RunInSandbox = false,
Request = request.Request
});
return true;
}
[Function(nameof(AddToPR))]
public static async Task<string> AddToPR(
[OrchestrationTrigger] TaskOrchestrationContext context, AddToPRRequest request)
{
var saveScriptResponse = await context.CallActivityAsync<bool>(nameof(PullRequestActivities.SaveOutput), new SaveOutputRequest
{
Output = request.Output,
IssueOrchestrationId = request.IssueOrchestrationId,
SubOrchestrationId = request.SubOrchestrationId,
Extension = request.Extension,
Directory = "output",
FileName = request.RunInSandbox ? "run" : "readme"
});
if (request.RunInSandbox)
{
var newRequest = new RunInSandboxRequest
{
PrRequest = request,
SanboxOrchestrationId = context.InstanceId
};
var runScriptResponse = await context.CallActivityAsync<bool>(nameof(PullRequestActivities.RunInSandbox), newRequest);
bool containerTerminated = await context.WaitForExternalEvent<bool>(ContainerTerminated);
}
// this is not ideal, as the script might be still running and there might be files that are not yet generated
var commitResponse = await context.CallActivityAsync<bool>(nameof(PullRequestActivities.CommitToGithub), new GHCommitRequest
{
IssueOrchestrationId = request.IssueOrchestrationId,
SubOrchestrationId = request.SubOrchestrationId,
Directory = "output",
Org = request.Request.Org,
Repo = request.Request.Repo,
Branch = request.Request.Branch
});
return default;
}
}
}

View File

@@ -1,113 +0,0 @@
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Memory;
using Octokit.Webhooks;
using Octokit.Webhooks.AzureFunctions;
namespace KernelHttpServer;
public static class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureGitHubWebhooks()
.ConfigureAppConfiguration(configuration =>
{
var config = configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
var builtConfig = config.Build();
})
.ConfigureServices(services =>
{
services.AddTransient((provider) => CreateKernel(provider));
services.AddScoped<GithubService>();
services.AddScoped<WebhookEventProcessor, SKWebHookEventProcessor>();
services.AddOptions<GithubOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("GithubOptions").Bind(settings);
});
services.AddOptions<AzureOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("AzureOptions").Bind(settings);
});
services.AddOptions<OpenAIOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("OpenAIOptions").Bind(settings);
});
services.AddOptions<QdrantOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("QdrantOptions").Bind(settings);
});
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
// return JSON with expected lowercase naming
services.Configure<JsonSerializerOptions>(options =>
{
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
services.AddHttpClient("FunctionsClient", client =>
{
var fqdn = Environment.GetEnvironmentVariable("FUNCTIONS_FQDN", EnvironmentVariableTarget.Process);
client.BaseAddress = new Uri($"{fqdn}/api/");
});
})
.ConfigureLogging(logging =>
{
logging.Services.Configure<LoggerFilterOptions>(options =>
{
LoggerFilterRule defaultRule = options.Rules.FirstOrDefault(rule => rule.ProviderName
== "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");
if (defaultRule is not null)
{
options.Rules.Remove(defaultRule);
}
});
})
.Build();
host.Run();
}
private static IKernel CreateKernel(IServiceProvider provider)
{
var openAiConfig = provider.GetService<IOptions<OpenAIOptions>>().Value;
var qdrantConfig = provider.GetService<IOptions<QdrantOptions>>().Value;
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(LogLevel.Debug)
.AddConsole()
.AddDebug();
});
var memoryStore = new QdrantMemoryStore(new QdrantVectorDbClient(qdrantConfig.Endpoint, qdrantConfig.VectorSize));
var embedingGeneration = new AzureTextEmbeddingGeneration(openAiConfig.EmbeddingDeploymentOrModelId, openAiConfig.Endpoint, openAiConfig.ApiKey);
var semanticTextMemory = new SemanticTextMemory(memoryStore, embedingGeneration);
return new KernelBuilder()
.WithLoggerFactory(loggerFactory)
.WithAzureChatCompletionService(openAiConfig.DeploymentOrModelId, openAiConfig.Endpoint, openAiConfig.ApiKey, true, openAiConfig.ServiceId, true)
.WithMemory(semanticTextMemory)
.Build();
}
}

View File

@@ -1,5 +0,0 @@
# Demo
## How do I get started?
Check - [Getting started](../docs/github-flow-getting-started.md)

View File

@@ -1,36 +0,0 @@
using Microsoft.Extensions.Options;
using Octokit;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public class GithubService
{
private readonly GithubOptions _githubSettings;
public GithubService(IOptions<GithubOptions> ghOptions)
{
_githubSettings = ghOptions.Value;
}
public async Task<GitHubClient> GetGitHubClient()
{
// Use GitHubJwt library to create the GitHubApp Jwt Token using our private certificate PEM file
var generator = new GitHubJwt.GitHubJwtFactory(
new GitHubJwt.StringPrivateKeySource(_githubSettings.AppKey),
new GitHubJwt.GitHubJwtFactoryOptions
{
AppIntegrationId = _githubSettings.AppId, // The GitHub App Id
ExpirationSeconds = 600 // 10 minutes is the maximum time allowed
}
);
var jwtToken = generator.CreateEncodedJwtToken();
var appClient = new GitHubClient(new ProductHeaderValue("SK-DEV-APP"))
{
Credentials = new Credentials(jwtToken, AuthenticationType.Bearer)
};
var response = await appClient.GitHubApps.CreateInstallationToken(_githubSettings.InstallationId);
return new GitHubClient(new ProductHeaderValue($"SK-DEV-APP-Installation{_githubSettings.InstallationId}"))
{
Credentials = new Credentials(response.Token)
};
}
}

View File

@@ -1,117 +0,0 @@
using System.Net.Http.Json;
using System.Text;
using Microsoft.AI.DevTeam.Skills;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using Microsoft.SemanticKernel.Orchestration;
using Newtonsoft.Json;
using Octokit.Webhooks;
using Octokit.Webhooks.Events;
using Octokit.Webhooks.Events.IssueComment;
using Octokit.Webhooks.Events.Issues;
using Octokit.Webhooks.Models;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2007: Do not directly await a Task", Justification = "Durable functions")]
public class SKWebHookEventProcessor : WebhookEventProcessor
{
private readonly IKernel _kernel;
private readonly ILogger<SKWebHookEventProcessor> _logger;
private readonly GithubService _ghService;
private readonly IHttpClientFactory _httpClientFactory;
public SKWebHookEventProcessor(IKernel kernel, ILogger<SKWebHookEventProcessor> logger, GithubService ghService, IHttpClientFactory httpContextFactory)
{
_kernel = kernel;
_logger = logger;
_ghService = ghService;
_httpClientFactory = httpContextFactory;
}
protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
{
var ghClient = await _ghService.GetGitHubClient();
var org = issuesEvent.Organization.Login;
var repo = issuesEvent.Repository.Name;
var issueNumber = issuesEvent.Issue.Number;
var input = issuesEvent.Issue.Body;
if (issuesEvent.Action == IssuesAction.Opened)
{
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
var labels = issuesEvent.Issue.Labels.First().Name.Split(".");
var skillName = labels[0];
var functionName = labels[1];
if (skillName == "Do" && functionName == "It")
{
var issueOrchestrationRequest = new IssueOrchestrationRequest
{
Number = issueNumber,
Org = org,
Repo = repo,
Input = input
};
var content = new StringContent(JsonConvert.SerializeObject(issueOrchestrationRequest), Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient("FunctionsClient");
await httpClient.PostAsync("doit", content);
}
else
{
var result = await RunSkill(skillName, functionName, input);
await ghClient.Issue.Comment.Create(org, repo, (int)issueNumber, result);
}
}
else if (issuesEvent.Action == IssuesAction.Closed && issuesEvent.Issue.User.Type.Value == UserType.Bot)
{
var httpClient = _httpClientFactory.CreateClient("FunctionsClient");
var metadata = await httpClient.GetFromJsonAsync<IssueMetadata>($"metadata/{org}{repo}{issueNumber}");
var closeIssueRequest = new CloseIssueRequest { InstanceId = metadata.InstanceId, CommentId = metadata.CommentId.Value, Org = org, Repo = repo };
var content = new StringContent(JsonConvert.SerializeObject(closeIssueRequest), Encoding.UTF8, "application/json");
_ = await httpClient.PostAsync("close", content);
}
}
protected override async Task ProcessIssueCommentWebhookAsync(
WebhookHeaders headers,
IssueCommentEvent issueCommentEvent,
IssueCommentAction action)
{
// we only resond to non-bot comments
if (issueCommentEvent.Sender.Type.Value != UserType.Bot)
{
var ghClient = await _ghService.GetGitHubClient();
var org = issueCommentEvent.Organization.Login;
var repo = issueCommentEvent.Repository.Name;
var issueId = issueCommentEvent.Issue.Number;
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
var labels = issueCommentEvent.Issue.Labels.First().Name.Split(".");
var skillName = labels[0];
var functionName = labels[1];
var input = issueCommentEvent.Comment.Body;
var result = await RunSkill(skillName, functionName, input);
await ghClient.Issue.Comment.Create(org, repo, (int)issueId, result);
}
}
private async Task<string> RunSkill(string skillName, string functionName, string input)
{
var prompt = Skills.ForSkillAndFunction(skillName, functionName);
var function = _kernel.CreateSemanticFunction(prompt, new OpenAIRequestSettings { MaxTokens = 8000, Temperature = 0.4, TopP = 1 });
var interestingMemories = _kernel.Memory.SearchAsync("waf-pages", input, 2);
var wafContext = "Consider the following architectural guidelines:";
await foreach (var memory in interestingMemories)
{
wafContext += $"\n {memory.Metadata.Text}";
}
var context = new ContextVariables();
context.Set("input", input);
context.Set("wafContext", wafContext);
var result = await _kernel.RunAsync(context, function);
return result.ToString();
}
}

View File

@@ -1,9 +0,0 @@
public class AzureOptions
{
public string SubscriptionId { get; set; }
public string Location { get; set; }
public string ContainerInstancesResourceGroup { get; set; }
public string FilesShareName { get; set; }
public string FilesAccountName { get; set; }
public string FilesAccountKey { get; set; }
}

View File

@@ -1,6 +0,0 @@
public class GithubOptions
{
public string AppKey { get; set; }
public int AppId { get; set; }
public long InstallationId { get; set; }
}

View File

@@ -1,9 +0,0 @@
public class OpenAIOptions
{
public string ServiceType { get; set; }
public string ServiceId { get; set; }
public string DeploymentOrModelId { get; set; }
public string EmbeddingDeploymentOrModelId { get; set; }
public string Endpoint { get; set; }
public string ApiKey { get; set; }
}

View File

@@ -1,5 +0,0 @@
public class QdrantOptions
{
public string Endpoint { get; set; }
public int VectorSize { get; set; }
}

View File

@@ -1,57 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RepoRoot>$([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)'))))</RepoRoot>
<UserSecretsId>b4703849-9b3a-41c3-b1bb-5ede533963c4</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace></RootNamespace>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.0.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.0.0-preview4" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.14.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.19.0" />
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.21.0" />
<PackageReference Include="Octokit.Webhooks.AzureFunctions" Version="2.0.3" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta5" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Qdrant" Version="1.0.0-beta5" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Tables" Version="1.2.0" />
<PackageReference Include="Octokit" Version="7.1.0" />
<PackageReference Include="GitHubJwt" Version="0.0.6" />
<PackageReference Include="Azure.ResourceManager.ContainerInstance" Version="1.2.0-beta.1" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.16.0-beta.1" />
<PackageReference Include="Azure.Data.Tables" Version="12.8.1" />
<PackageReference Include="Azure.Identity" Version="1.10.0-beta.1" />
<ProjectReference Include="..\..\libs\Microsoft.AI.DevTeam.Skills\Microsoft.AI.DevTeam.Skills.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
<None Update="config\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -1,18 +0,0 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensions": {
"durableTask": {
"storageProvider": {
"type": "AzureStorage"
}
}
}
}

View File

@@ -1,31 +0,0 @@
{
"IsEncrypted": false,
"Host": {
"CORS": "*"
},
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"FUNCTIONS_FQDN": "http://localhost:7071",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsFeatureFlags": "EnableHttpProxying",
"SANDBOX_IMAGE" : "mcr.microsoft.com/dotnet/sdk:7.0",
"GithubOptions:AppKey": "",
"GithubOptions:AppId": "",
"GithubOptions:InstallationId": "",
"AzureOptions:SubscriptionId":"",
"AzureOptions:Location":"",
"AzureOptions:ContainerInstancesResourceGroup":"",
"AzureOptions:FilesShareName":"",
"AzureOptions:FilesAccountName":"",
"AzureOptions:FilesAccountKey":"",
"OpenAIOptions:ServiceType":"",
"OpenAIOptions:ServiceId":"",
"OpenAIOptions:DeploymentOrModelId":"",
"OpenAIOptions:EmbeddingDeploymentOrModelId":"",
"OpenAIOptions:Endpoint":"",
"OpenAIOptions:ApiKey":"",
"QdrantOptions:Endpoint":"http://qdrant:6333",
"QdrantOptions:VectorSize":"1536"
}
}

View File

@@ -1,52 +0,0 @@
{
"IsEncrypted": false,
"Host": {
"CORS": "*"
},
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
// For local development, keep the default value
// for Azure deployment, it will be injected as a variable in the bicep template
"FUNCTIONS_FQDN": "localhost:7071",
// For local development, keep the default value
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsFeatureFlags": "EnableHttpProxying",
// This is the container image used as a base for the sandbox
"SANDBOX_IMAGE" : "mcr.microsoft.com/devcontainers/universal:2-linux",
// The private key generated for the Github App
"GithubOptions__AppKey": "",
// The App Id for the Github App
"GithubOptions__AppId": "",
// The instalation ID for the Github App (once installed to a repo or an org)
"GithubOptions__InstallationId": "",
// Azure subscription id
"AzureOptions__SubscriptionId":"",
// Location for the deployed resources in Azure
"AzureOptions__Location":"",
// Resource group in Azure, where ACI sandbox instances are going to be created
"AzureOptions__ContainerInstancesResourceGroup":"",
// Azure storage file share name (doesn't work with Azurite)
"AzureOptions__FilesShareName":"",
// Azure storage file share account name
"AzureOptions__FilesAccountName":"",
// Azure storage file share account key
"AzureOptions__FilesAccountKey":"",
// If using Azure - AzureOpenAI
"OpenAIOptions__ServiceType":"",
// the service id of the OpenAI model you want to use
"OpenAIOptions__ServiceId":"",
// the deployment id of the OpenAI model you want to use
"OpenAIOptions__DeploymentOrModelId":"",
// the embedding deployment id for the semantic memory
"OpenAIOptions__EmbeddingDeploymentOrModelId":"",
// the endpoint for the provisioned OpenAI service
"OpenAIOptions__Endpoint":"",
// the key for the provisioned OpenAI service
"OpenAIOptions__ApiKey":"",
// if using Codespaces, keep the default value
"QdrantOptions__Endpoint":"http://qdrant:6333",
// keep default
"QdrantOptions__VectorSize":"1536"
}
}

View File

@@ -7,17 +7,14 @@ using Orleans.Streams;
namespace Microsoft.AI.DevTeam;
public abstract class Agent : Grain, IGrainWithStringKey
public abstract class AiAgent : Agent
{
public Agent(
public AiAgent(
[PersistentState("state", "messages")] IPersistentState<AgentState> state)
{
_state = state;
}
protected readonly IPersistentState<AgentState> _state;
public abstract Task HandleEvent(Event item, StreamSequenceToken? token);
protected async Task<ContextVariables> CreateWafContext(ISemanticTextMemory memory, string ask)
{
var context = new ContextVariables();
@@ -53,6 +50,21 @@ public abstract class Agent : Grain, IGrainWithStringKey
await _state.WriteStateAsync();
return result;
}
}
public abstract class Agent : Grain, IGrainWithStringKey
{
public abstract Task HandleEvent(Event item, StreamSequenceToken? token);
protected async Task PublishEvent(string ns, string id, Event item)
{
var streamProvider = this.GetStreamProvider("StreamProvider");
var streamId = StreamId.Create(ns, id);
var stream = streamProvider.GetStream<Event>(streamId);
await stream.OnNextAsync(item);
}
}
[Serializable]

View File

@@ -6,7 +6,7 @@ namespace Microsoft.AI.DevTeam;
[StatelessWorker]
[ImplicitStreamSubscription("AzureGenie")]
public class AzureGenie : Grain, IGrainWithStringKey
public class AzureGenie : Agent
{
private readonly IManageAzure _azureService;
@@ -24,12 +24,13 @@ public class AzureGenie : Grain, IGrainWithStringKey
await stream.SubscribeAsync(HandleEvent);
}
public async Task HandleEvent(Event item, StreamSequenceToken? token)
public override async Task HandleEvent(Event item, StreamSequenceToken? token)
{
switch (item.Type)
{
case EventType.ReadmeChainClosed:
//_azureService.Store();
// postEvent ReadmeStored
break;
case EventType.CodeChainClosed:
// _azureService.Store();

View File

@@ -1,93 +0,0 @@
using Microsoft.AI.DevTeam.Skills;
using Orleans.Runtime;
using Orleans.Streams;
namespace Microsoft.AI.DevTeam;
public class Conductor : Grain
{
private readonly IManageGithub _ghService;
public Conductor(IManageGithub ghService)
{
_ghService = ghService;
}
public async Task InitialFlow(string input, string org, string repo, long parentNumber)
{
// await _ghService.CreateBranch(org, repo, $"sk-{parentNumber}");
// var pmIssue = await _ghService.CreateIssue(org, repo, input, $"{nameof(PM)}.{nameof(PM.Readme)}", parentNumber);
// var devLeadIssue = await _ghService.CreateIssue(org, repo, input, $"{nameof(DevLead)}.{nameof(DevLead.Plan)}", parentNumber);
// var suffix = $"{org}-{repo}";
// var lookup = GrainFactory.GetGrain<ILookupMetadata>(suffix);
// var metadataList = new List<StoreMetadataPairs>{
// new StoreMetadataPairs
// {
// Key = pmIssue.IssueNumber,
// Value = new NewIssueResponse { CommentId = pmIssue.CommentId, IssueNumber = (int)parentNumber}
// },
// new StoreMetadataPairs
// {
// Key = devLeadIssue.IssueNumber,
// Value = new NewIssueResponse { CommentId = devLeadIssue.CommentId, IssueNumber = (int)parentNumber}
// }
// };
// await lookup.StoreMetadata(metadataList);
// await githubActor.CreatePR(); // TODO: this should happen when all tasks are done?
}
public async Task ImplementationFlow(DevLeadPlanResponse plan, string org, string repo, int parentNumber)
{
// var suffix = $"{org}-{repo}";
// var prompts = plan.steps.SelectMany(s => s.subtasks.Select(st => st.prompt));
// var lookup = GrainFactory.GetGrain<ILookupMetadata>(suffix);
// var metadataList = new List<StoreMetadataPairs>();
// foreach (var prompt in prompts)
// {
// var issue = await _ghService.CreateIssue(org, repo, prompt, $"{nameof(Developer)}.{nameof(Developer.Implement)}", parentNumber);
// metadataList.Add(new StoreMetadataPairs
// {
// Key = issue.IssueNumber,
// Value = new NewIssueResponse { CommentId = issue.CommentId, IssueNumber = (int)parentNumber }
// });
// }
// await lookup.StoreMetadata(metadataList);
}
}
/*
Events:
NewAsk - Org, Repo, IssueNumber
-> PM subscribes -> check label,
if Do It, create an issue, mark it as PM.Readme
if PM.Readme send the prompt and create a comment
-> DevLead subscribes -> check label,
if Do It, create an issue, mark it as Devlead.Plan
if DevLead.Plan, send the prompt and create a comment
-> Developer subscribes -> check label,
if Developer.Implement, send the prompt and create a comment
ChainClosed - Org, Repo, IssueNumber
-> PM subscribes -> check label,
if PM.Readme send ReadmeCreated event
-> DevLead subscribes -> check label,
if DevLead.Plan, send PlanSubstepCreated event
-> Developer subscribes -> check label,
if Developer.Implement, send ImplementSubstepCreated event
PlanSubstepCreated - Org, Repo, IssueNumber, Substep
-> Developer subscribes -> create an issue, mark it as Developer.Implement
ReadmeCreated - store readme, commit to PR branch
ImplementSubstepCreated - store code, run in sandbox, commit to PR branch
*/

View File

@@ -11,7 +11,7 @@ namespace Microsoft.AI.DevTeam;
//[RegexImplicitStreamSubscription("")]
[ImplicitStreamSubscription("developers")]
public class Dev : Agent
public class Dev : AiAgent
{
private readonly IKernel _kernel;
private readonly ISemanticTextMemory _memory;
@@ -50,6 +50,7 @@ public class Dev : Agent
break;
}
}
public async Task<string> GenerateCode(string ask)
{
try
@@ -62,7 +63,7 @@ public class Dev : Agent
return default;
}
}
public Task<string> ReviewPlan(string plan)
{
throw new NotImplementedException();

View File

@@ -10,7 +10,7 @@ using System.Text.Json;
namespace Microsoft.AI.DevTeam;
[ImplicitStreamSubscription("DevPersonas")]
public class DeveloperLead : Agent
public class DeveloperLead : AiAgent
{
private readonly IKernel _kernel;
private readonly ISemanticTextMemory _memory;

View File

@@ -8,7 +8,7 @@ namespace Microsoft.AI.DevTeam;
[StatelessWorker]
[ImplicitStreamSubscription("Hubber")]
public class Hubber : Grain, IGrainWithStringKey
public class Hubber : Agent
{
private readonly IManageGithub _ghService;
@@ -25,7 +25,7 @@ public class Hubber : Grain, IGrainWithStringKey
await stream.SubscribeAsync(HandleEvent);
}
public async Task HandleEvent(Event item, StreamSequenceToken? token)
public override async Task HandleEvent(Event item, StreamSequenceToken? token)
{
switch (item.Type)
{
@@ -47,6 +47,13 @@ public class Hubber : Grain, IGrainWithStringKey
CreateIssue(item.Data["org"], item.Data["repo"], p, $"{nameof(Developer)}.{nameof(Developer.Implement)}", long.Parse(item.Data["parentNumber"])));
Task.WaitAll(devTasks.ToArray());
break;
case EventType.ReadmeStored:
break;
case EventType.SandboxRunFinished:
// _azureService.Store();
// _azureService.RunInSandbox();
break;
default:
break;
}
@@ -66,10 +73,10 @@ public class Hubber : Grain, IGrainWithStringKey
}
public async Task CreatePullRequest()
{
// await _ghService.CreatePR();
//await _ghService.CreatePR();
}
public async Task CommitToBranch()
{
// await _ghService.CommitToBranch()
//await _ghService.CommitToBranch()
}
}

View File

@@ -10,7 +10,7 @@ using Orleans.Streams;
namespace Microsoft.AI.DevTeam;
[ImplicitStreamSubscription("DevPersonas")]
public class ProductManager : Agent
public class ProductManager : AiAgent
{
private readonly IKernel _kernel;
private readonly ISemanticTextMemory _memory;

View File

@@ -22,5 +22,7 @@ public enum EventType
DevPlanGenerated,
CodeGenerated,
DevPlanChainClosed,
ReadmeRequested
ReadmeRequested,
ReadmeStored,
SandboxRunFinished
}