Skip to content

Commit

Permalink
Chaining the whole team (#1)
Browse files Browse the repository at this point in the history
* do it all :)

* save implementation as it comes

* add sandbox skill

* generate files via the sandbox skill in output/src

---------

Co-authored-by: Kosta Petan <[email protected]>
  • Loading branch information
kostapetan and Kosta Petan authored Jun 8, 2023
1 parent 0b920fc commit 1fcac01
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
# Mono auto generated files
mono_crash.*

output

# Build results
[Dd]ebug/
[Dd]ebugPublic/
Expand Down
18 changes: 6 additions & 12 deletions cli/Models/DevLeadPlanResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,18 @@

public class Subtask
{
[JsonPropertyName("subtask")]
public string Name { get; set; }
[JsonPropertyName("LLM_prompt")]
public string LLMPrompt { get; set; }
public string subtask { get; set; }
public string LLM_prompt { get; set; }
}

public class Step
{
[JsonPropertyName("step")]
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("subtasks")]
public List<Subtask> Subtasks { get; set; }
public string description { get; set; }
public string step { get; set; }
public List<Subtask> subtasks { get; set; }
}

public class DevLeadPlanResponse
{
[JsonPropertyName("steps")]
public List<Step> Steps { get; set; }
public List<Step> steps { get; set; }
}
66 changes: 50 additions & 16 deletions cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System;
using System.CommandLine;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
Expand All @@ -16,50 +12,88 @@ static async Task Main(string[] args)
var rootCommand = new RootCommand("CLI tool for the AI Dev team");
rootCommand.AddGlobalOption(fileOption);

var doCommand = new Command("do", "Doers :) ");
var doItCommand = new Command("it", "Do it!");
doItCommand.SetHandler(async (file) => await ChainFunctions(file.FullName), fileOption);
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) => await CallFunction<string>(nameof(PM), PM.Readme , file.FullName), fileOption);
pmReadmeCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.Readme , file.FullName), fileOption);

var pmBootstrapCommand = new Command("bootstrap", "Bootstrap a project for a given input");
pmBootstrapCommand.SetHandler(async (file) => await CallFunction<string>(nameof(PM), PM.BootstrapProject, file.FullName), fileOption);
pmBootstrapCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file.FullName), fileOption);

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) => await CallFunction<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file.FullName), fileOption);
devleadPlanCommand.SetHandler(async (file) => await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file.FullName), fileOption);
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) => await CallFunction<string>(nameof(Developer), Developer.Implement, file.FullName), fileOption);
devPlanCommand.SetHandler(async (file) => await CallWithFile<string>(nameof(Developer), Developer.Implement, file.FullName), fileOption);
devCommand.AddCommand(devPlanCommand);

rootCommand.AddCommand(pmCommand);
rootCommand.AddCommand(devleadCommand);
rootCommand.AddCommand(devCommand);
rootCommand.AddCommand(doCommand);

await rootCommand.InvokeAsync(args);
}

public static async Task<T> CallFunction<T>(string skillName, string functionName, string file)
public static async Task ChainFunctions(string file)
{
if (!File.Exists(file))
{
Console.WriteLine($"File not found: {file}");
return default;
}
var sandboxSkill = new SandboxSkill();
var outputPath = Directory.CreateDirectory("output");

var readme = await CallWithFile<string>(nameof(PM), PM.Readme , file);
await SaveToFile(Path.Combine(outputPath.FullName, "README.md"), readme);

var script = await CallWithFile<string>(nameof(PM), PM.BootstrapProject, file);
await sandboxSkill.RunInDotnetAlpineAsync(script);
await SaveToFile(Path.Combine(outputPath.FullName, "bootstrap.sh"), script);

var plan = await CallWithFile<DevLeadPlanResponse>(nameof(DevLead), DevLead.Plan, file);
await SaveToFile(Path.Combine(outputPath.FullName, "plan.json"), JsonSerializer.Serialize(plan));

var implementationTasks = plan.steps.SelectMany(
(step) => step.subtasks.Select(
async (subtask) => {
var implementationResult = await CallFunction<string>(nameof(Developer), Developer.Implement, subtask.LLM_prompt);
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)
{
if(!File.Exists(filePath))
throw new FileNotFoundException($"File not found: {filePath}", filePath);
var input = File.ReadAllText(filePath);
return await CallFunction<T>(skillName, functionName, input);
}

public static async Task<T> CallFunction<T>(string skillName, string functionName, string input)
{
var variables = new[]
{
new { key = "input", value = File.ReadAllText(file) }
new { key = "input", value = input }
};

var requestBody = new { variables };
var requestBodyJson = JsonSerializer.Serialize(requestBody);

Console.WriteLine($"Calling skill '{skillName}' function '{functionName}' with file '{file}'");
Console.WriteLine($"Calling skill '{skillName}' function '{functionName}' with input '{input}'");
Console.WriteLine(requestBodyJson);

using var httpClient = new HttpClient();
Expand Down
44 changes: 44 additions & 0 deletions cli/SandboxSkill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using DotNet.Testcontainers.Builders;
using Microsoft.SemanticKernel.SkillDefinition;

public class SandboxSkill
{
[SKFunction("Run a script in Alpine sandbox")]
[SKFunctionInput(Description = "The script to be executed")]
[SKFunctionName("RunInAlpine")]
public async Task<string> RunInAlpineAsync(string input)
{
return await RunInContainer(input, "alpine");
}

[SKFunction("Run a script in dotnet alpine sandbox")]
[SKFunctionInput(Description = "The script to be executed")]
[SKFunctionName("RunInDotnetAlpine")]
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 "";
}
}
2 changes: 2 additions & 0 deletions cli/cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.14.547.1-preview" />
<PackageReference Include="Testcontainers" Version="3.2.0" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion cli/util/ToDoListSamplePrompt.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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.
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.
3 changes: 2 additions & 1 deletion sk-azfunc-server/skills/Developer/Implement/skprompt.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
You are a Developer for an application.
Please output the code or script required to accomplish the task assigned to you below.
Please output the code required to accomplish the task assigned to you below and wrap it in a bash script that creates the files.
Do not use any IDE commands and do not build and run the code.
Make specific choices about implementation. Do not offer a range of options.
Use comments in the code to describe the intent. Do not include other text other than code and code comments.
Input: {{$input}}

0 comments on commit 1fcac01

Please sign in to comment.