mirror of
https://github.com/microsoft/autogen.git
synced 2026-04-20 03:02:16 -04:00
remove Dapr sample/implementation (#345)
Co-authored-by: Kosta Petan <Kosta.Petan@microsoft.com>
This commit is contained in:
@@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{290F9824-BAD
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AI.Agents", "src\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj", "{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AI.Agents.Dapr", "src\Microsoft.AI.Agents.Dapr\Microsoft.AI.Agents.Dapr.csproj", "{1972846E-4C21-4E40-B448-D78B73806BD9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AI.Agents.Orleans", "src\Microsoft.AI.Agents.Orleans\Microsoft.AI.Agents.Orleans.csproj", "{A4AE4656-4919-45E2-9680-C317FBCF7693}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{943853E7-513D-45EA-870F-549CFC0AF8E8}"
|
||||
@@ -19,8 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{50809508-F83
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AI.DevTeam", "samples\gh-flow\src\Microsoft.AI.DevTeam\Microsoft.AI.DevTeam.csproj", "{79981945-61F7-4E1A-8949-7808FD75471B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AI.DevTeam.Dapr", "samples\gh-flow\src\Microsoft.AI.DevTeam.Dapr\Microsoft.AI.DevTeam.Dapr.csproj", "{A7677950-18F1-42FF-8018-870395417465}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "seed-memory", "samples\gh-flow\src\seed-memory\seed-memory.csproj", "{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E6A25E68-EA75-4294-9B45-3CF2BB3B4ACD}"
|
||||
@@ -66,10 +62,6 @@ Global
|
||||
{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1972846E-4C21-4E40-B448-D78B73806BD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1972846E-4C21-4E40-B448-D78B73806BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1972846E-4C21-4E40-B448-D78B73806BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1972846E-4C21-4E40-B448-D78B73806BD9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A4AE4656-4919-45E2-9680-C317FBCF7693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A4AE4656-4919-45E2-9680-C317FBCF7693}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A4AE4656-4919-45E2-9680-C317FBCF7693}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -78,10 +70,6 @@ Global
|
||||
{79981945-61F7-4E1A-8949-7808FD75471B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{79981945-61F7-4E1A-8949-7808FD75471B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{79981945-61F7-4E1A-8949-7808FD75471B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A7677950-18F1-42FF-8018-870395417465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A7677950-18F1-42FF-8018-870395417465}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7677950-18F1-42FF-8018-870395417465}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7677950-18F1-42FF-8018-870395417465}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -140,12 +128,10 @@ Global
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20} = {290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}
|
||||
{1972846E-4C21-4E40-B448-D78B73806BD9} = {290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}
|
||||
{A4AE4656-4919-45E2-9680-C317FBCF7693} = {290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}
|
||||
{E0E93575-7187-4975-8D72-6F285CD01767} = {943853E7-513D-45EA-870F-549CFC0AF8E8}
|
||||
{50809508-F830-4553-9C4E-C802E0A0F690} = {E0E93575-7187-4975-8D72-6F285CD01767}
|
||||
{79981945-61F7-4E1A-8949-7808FD75471B} = {50809508-F830-4553-9C4E-C802E0A0F690}
|
||||
{A7677950-18F1-42FF-8018-870395417465} = {50809508-F830-4553-9C4E-C802E0A0F690}
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8} = {943853E7-513D-45EA-870F-549CFC0AF8E8}
|
||||
{20E5C8C3-CE40-4FC3-96F8-B4A2C51936E9} = {290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}
|
||||
{B9188ADC-D322-4B38-B3D6-95338E89C34B} = {290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
<PackageVersion Include="Azure.ResourceManager.ContainerInstance" Version="1.2.1" />
|
||||
<PackageVersion Include="Azure.Storage.Files.Shares" Version="12.18.0" />
|
||||
<PackageVersion Include="CloudNative.CloudEvents.SystemTextJson" Version="2.7.1" />
|
||||
<PackageVersion Include="Dapr.Actors" Version="1.13.1" />
|
||||
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.13.1" />
|
||||
<PackageVersion Include="Dapr.AspNetCore" Version="1.13.1" />
|
||||
<PackageVersion Include="Dapr.Client" Version="1.13.1" />
|
||||
<PackageVersion Include="Elsa" Version="3.1.3" />
|
||||
<PackageVersion Include="Elsa.EntityFrameworkCore" Version="3.1.3" />
|
||||
<PackageVersion Include="Elsa.EntityFrameworkCore.Sqlite" Version="3.1.3" />
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: agents-pubsub
|
||||
spec:
|
||||
type: pubsub.redis
|
||||
version: v1
|
||||
metadata:
|
||||
- name: redisHost
|
||||
value: localhost:6379
|
||||
- name: redisPassword
|
||||
value: ""
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: agents-statestore
|
||||
spec:
|
||||
type: state.redis
|
||||
version: v1
|
||||
metadata:
|
||||
- name: redisHost
|
||||
value: localhost:6379
|
||||
- name: redisPassword
|
||||
value: ""
|
||||
- name: actorStateStore
|
||||
value: "true"
|
||||
@@ -1,28 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
// The architect has Org+Repo scope and is holding the knowledge of the high level architecture of the project
|
||||
public class Architect : AiAgent<ArchitectState>, IDaprAgent
|
||||
{
|
||||
public Architect(ActorHost host, DaprClient client, ISemanticTextMemory memory, Kernel kernel)
|
||||
: base(host, client, memory, kernel)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task HandleEvent(Event item)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class ArchitectState
|
||||
{
|
||||
public string? FilesTree { get; set; }
|
||||
public string? HighLevelArchitecture { get; set; }
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public class AzureGenie : Agent, IDaprAgent
|
||||
{
|
||||
private readonly IManageAzure _azureService;
|
||||
|
||||
public AzureGenie(ActorHost host, DaprClient client, IManageAzure azureService) : base(host, client)
|
||||
{
|
||||
_azureService = azureService;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.ReadmeCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await Store(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber, "readme", "md", "output", item.Data["readme"]);
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.ReadmeStored),
|
||||
Subject = context.Subject,
|
||||
Data = context.ToData()
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(GithubFlowEventType.CodeCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await Store(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber, "run", "sh", "output", item.Data["code"]);
|
||||
await RunInSandbox(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber);
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.SandboxRunCreated),
|
||||
Subject = context.Subject,
|
||||
Data = context.ToData()
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Store(string org, string repo, long parentIssueNumber, long issueNumber, string filename, string extension, string dir, string output)
|
||||
{
|
||||
await _azureService.Store(org, repo, parentIssueNumber, issueNumber, filename, extension, dir, output);
|
||||
}
|
||||
|
||||
public async Task RunInSandbox(string org, string repo, long parentIssueNumber, long issueNumber)
|
||||
{
|
||||
await _azureService.RunInSandbox(org, repo, parentIssueNumber, issueNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class Dev : AiAgent<DeveloperState>, IDaprAgent
|
||||
{
|
||||
private readonly ILogger<Dev> _logger;
|
||||
|
||||
public Dev(ActorHost host, DaprClient client, Kernel kernel, ISemanticTextMemory memory, ILogger<Dev> logger)
|
||||
: base(host, client, memory, kernel)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.CodeGenerationRequested):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var code = await GenerateCode(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"] = code;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.CodeGenerated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.CodeChainClosed):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var lastCode = state.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["code"] = lastCode;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.CodeCreated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GenerateCode(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: ask the architect for the high level architecture as well as the files structure of the project
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(DeveloperSkills.Implement, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error generating code");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DeveloperState
|
||||
{
|
||||
public string? Understanding { get; set; }
|
||||
}
|
||||
|
||||
public class UnderstandingResult
|
||||
{
|
||||
public required string NewUnderstanding { get; set; }
|
||||
public required string Explanation { get; set; }
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class DeveloperSkills
|
||||
{
|
||||
public const string Implement = """
|
||||
You are a Developer for an application.
|
||||
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}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public const string Improve = """
|
||||
You are a Developer for an application. Your job is to imrove the code that you are given in the input below.
|
||||
Please output a new version of code that fixes any problems with this version.
|
||||
If there is an error message in the input you should fix that error in the code.
|
||||
Wrap the code output up in a bash script that creates the necessary files by overwriting any previous 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}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public const string Explain = """
|
||||
You are an experienced software developer, with strong experience in Azure and Microsoft technologies.
|
||||
Extract the key features and capabilities of the code file below, with the intent to build an understanding of an entire code repository.
|
||||
You can include references or documentation links in your explanation. Also where appropriate please output a list of keywords to describe the code or its capabilities.
|
||||
Example:
|
||||
Keywords: Azure, networking, security, authentication
|
||||
|
||||
===code===
|
||||
{{$input}}
|
||||
===end-code===
|
||||
Only include the points in a bullet point format and DON'T add anything outside of the bulleted list.
|
||||
Be short and concise.
|
||||
If the code's purpose is not clear output an error:
|
||||
Error: The model could not determine the purpose of the code.
|
||||
""";
|
||||
|
||||
public const string ConsolidateUnderstanding = """
|
||||
You are an experienced software developer, with strong experience in Azure and Microsoft technologies.
|
||||
You are trying to build an understanding of the codebase from code files. This is the current understanding of the project:
|
||||
===current-understanding===
|
||||
{{$input}}
|
||||
===end-current-understanding===
|
||||
and this is the new information that surfaced
|
||||
===new-understanding===
|
||||
{{$newUnderstanding}}
|
||||
===end-new-understanding===
|
||||
Your job is to update your current understanding with the new information.
|
||||
Only include the points in a bullet point format and DON'T add anything outside of the bulleted list.
|
||||
Be short and concise.
|
||||
""";
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class DevLeadSkills
|
||||
{
|
||||
public const string Plan = """
|
||||
You are a Dev Lead for an application team, building the application described below.
|
||||
Please break down the steps and modules required to develop the complete application, describe each step in detail.
|
||||
Make prescriptive architecture, language, and frameowrk choices, do not provide a range of choices.
|
||||
For each step or module then break down the steps or subtasks required to complete that step or module.
|
||||
For each subtask write an LLM prompt that would be used to tell a model to write the coee that will accomplish that subtask. If the subtask involves taking action/running commands tell the model to write the script that will run those commands.
|
||||
In each LLM prompt restrict the model from outputting other text that is not in the form of code or code comments.
|
||||
Please output a JSON array data structure, in the precise schema shown below, with a list of steps and a description of each step, and the steps or subtasks that each requires, and the LLM prompts for each subtask.
|
||||
Example:
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"step": "1",
|
||||
"description": "This is the first step",
|
||||
"subtasks": [
|
||||
{
|
||||
"subtask": "Subtask 1",
|
||||
"description": "This is the first subtask",
|
||||
"prompt": "Write the code to do the first subtask"
|
||||
},
|
||||
{
|
||||
"subtask": "Subtask 2",
|
||||
"description": "This is the second subtask",
|
||||
"prompt": "Write the code to do the second subtask"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Do not output any other text.
|
||||
Do not wrap the JSON in any other text, output the JSON format described above, making sure it's a valid JSON.
|
||||
Input: {{$input}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public const string Explain = """
|
||||
You are a Dev Lead.
|
||||
Please explain the code that is in the input below. You can include references or documentation links in your explanation.
|
||||
Also where appropriate please output a list of keywords to describe the code or its capabilities.
|
||||
example:
|
||||
Keywords: Azure, networking, security, authentication
|
||||
|
||||
If the code's purpose is not clear output an error:
|
||||
Error: The model could not determine the purpose of the code.
|
||||
|
||||
--
|
||||
Input: {{$input}}
|
||||
""";
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class DeveloperLead : AiAgent<DeveloperLeadState>, IDaprAgent
|
||||
{
|
||||
private readonly ILogger<DeveloperLead> _logger;
|
||||
|
||||
public DeveloperLead(ActorHost host, DaprClient client, Kernel kernel, ISemanticTextMemory memory, ILogger<DeveloperLead> logger)
|
||||
: base(host, client, memory, kernel)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.DevPlanRequested):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var plan = await CreatePlan(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"] = plan;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.DevPlanGenerated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.DevPlanChainClosed):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var latestPlan = state.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["plan"] = latestPlan;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.DevPlanCreated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
public async Task<string> CreatePlan(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Ask the architect for the existing high level architecture
|
||||
// as well as the file structure
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(DevLeadSkills.Plan, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating development plan");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DevLeadPlanResponse
|
||||
{
|
||||
public required List<StepDescription> Steps { get; set; }
|
||||
}
|
||||
|
||||
public class StepDescription
|
||||
{
|
||||
public required string Description { get; set; }
|
||||
public required string Step { get; set; }
|
||||
public required List<SubtaskDescription> subtasks { get; set; }
|
||||
}
|
||||
|
||||
public class SubtaskDescription
|
||||
{
|
||||
public required string Subtask { get; set; }
|
||||
public required string Prompt { get; set; }
|
||||
}
|
||||
|
||||
public class DeveloperLeadState
|
||||
{
|
||||
public string? Plan { get; set; }
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public class Hubber : Agent, IDaprAgent
|
||||
{
|
||||
private readonly IManageGithub _ghService;
|
||||
|
||||
public Hubber(ActorHost host, DaprClient client, IManageGithub ghService) : base(host, client)
|
||||
{
|
||||
_ghService = ghService;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.NewAsk):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var pmIssue = await CreateIssue(context.Org, context.Repo, item.Data["input"], "PM.Readme", context.IssueNumber);
|
||||
var devLeadIssue = await CreateIssue(context.Org, context.Repo, item.Data["input"], "DevLead.Plan", context.IssueNumber);
|
||||
await PostComment(context.Org, context.Repo, context.IssueNumber, $" - #{pmIssue} - tracks PM.Readme");
|
||||
await PostComment(context.Org, context.Repo, context.IssueNumber, $" - #{devLeadIssue} - tracks DevLead.Plan");
|
||||
await CreateBranch(context.Org, context.Repo, $"sk-{context.IssueNumber}");
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeGenerated):
|
||||
case nameof(GithubFlowEventType.DevPlanGenerated):
|
||||
case nameof(GithubFlowEventType.CodeGenerated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var result = item.Data["result"];
|
||||
var contents = string.IsNullOrEmpty(result) ? "Sorry, I got tired, can you try again please? " : result;
|
||||
await PostComment(context.Org, context.Repo, context.IssueNumber, contents);
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(GithubFlowEventType.DevPlanCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var plan = JsonSerializer.Deserialize<DevLeadPlanResponse>(item.Data["plan"]);
|
||||
var prompts = plan!.Steps.SelectMany(s => s.subtasks.Select(st => st.Prompt));
|
||||
|
||||
foreach (var prompt in prompts)
|
||||
{
|
||||
var functionName = "Developer.Implement";
|
||||
var issue = await CreateIssue(context.Org, context.Repo, prompt, functionName, context.ParentNumber ?? 0);
|
||||
var commentBody = $" - #{issue} - tracks {functionName}";
|
||||
await PostComment(context.Org, context.Repo, context.ParentNumber ?? 0 , commentBody);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeStored):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber, "output", branch);
|
||||
await CreatePullRequest(context.Org, context.Repo, context.ParentNumber ?? 0, branch);
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.SandboxRunFinished):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber, "output", branch);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CreateIssue(string org, string repo, string input, string function, long parentNumber)
|
||||
{
|
||||
return await _ghService.CreateIssue(org, repo, input, function, parentNumber);
|
||||
}
|
||||
public async Task PostComment(string org, string repo, long issueNumber, string comment)
|
||||
{
|
||||
await _ghService.PostComment(org, repo, issueNumber, comment);
|
||||
}
|
||||
public async Task CreateBranch(string org, string repo, string branch)
|
||||
{
|
||||
await _ghService.CreateBranch(org, repo, branch);
|
||||
}
|
||||
public async Task CreatePullRequest(string org, string repo, long issueNumber, string branch)
|
||||
{
|
||||
await _ghService.CreatePR(org, repo, issueNumber, branch);
|
||||
}
|
||||
public async Task CommitToBranch(string org, string repo, long parentNumber, long issueNumber, string rootDir, string branch)
|
||||
{
|
||||
await _ghService.CommitToBranch(org, repo, parentNumber, issueNumber, rootDir, branch);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class PMSkills
|
||||
{
|
||||
public const string BootstrapProject = """
|
||||
Please write a bash script with the commands that would be required to generate applications as described in the following input.
|
||||
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.
|
||||
Do not include any git commands.
|
||||
Input: {{$input}}
|
||||
{{$waf}}
|
||||
""";
|
||||
public const string Readme = """
|
||||
You are a program manager on a software development team. You are working on an app described below.
|
||||
Based on the input below, and any dialog or other context, please output a raw README.MD markdown file documenting the main features of the app and the architecture or code organization.
|
||||
Do not describe how to create the application.
|
||||
Write the README as if it were documenting the features and architecture of the application. You may include instructions for how to run the application.
|
||||
Input: {{$input}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public const string Explain = """
|
||||
You are a Product Manager.
|
||||
Please explain the code that is in the input below. You can include references or documentation links in your explanation.
|
||||
Also where appropriate please output a list of keywords to describe the code or its capabilities.
|
||||
example:
|
||||
Keywords: Azure, networking, security, authentication
|
||||
|
||||
If the code's purpose is not clear output an error:
|
||||
Error: The model could not determine the purpose of the code.
|
||||
|
||||
--
|
||||
Input: {{$input}}
|
||||
""";
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public class ProductManager : AiAgent<ProductManagerState>, IDaprAgent
|
||||
{
|
||||
private readonly ILogger<ProductManager> _logger;
|
||||
|
||||
public ProductManager(ActorHost host, DaprClient client, Kernel kernel, ISemanticTextMemory memory, ILogger<ProductManager> logger)
|
||||
: base(host, client, memory, kernel)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.ReadmeRequested):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var readme = await CreateReadme(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"] = readme;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.ReadmeGenerated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeChainClosed):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var lastReadme = state.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["readme"] = lastReadme;
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.ReadmeCreated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreateReadme(string ask)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(PMSkills.Readme, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating readme");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductManagerState
|
||||
{
|
||||
public string? Capabilities { get; set; }
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class Sandbox : Agent, IDaprAgent, IRemindable
|
||||
{
|
||||
private const string ReminderName = "SandboxRunReminder";
|
||||
public const string StateStore = "agents-statestore";
|
||||
private readonly IManageAzure _azService;
|
||||
|
||||
public Sandbox(ActorHost host, DaprClient client, IManageAzure azService) : base(host, client)
|
||||
{
|
||||
_azService = azService;
|
||||
}
|
||||
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.SandboxRunCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await ScheduleCommitSandboxRun(context.Org, context.Repo, context.ParentNumber ?? 0, context.IssueNumber);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ScheduleCommitSandboxRun(string org, string repo, long parentIssueNumber, long issueNumber)
|
||||
{
|
||||
await StoreState(org, repo, parentIssueNumber, issueNumber);
|
||||
await this.RegisterReminderAsync(
|
||||
ReminderName,
|
||||
null,
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
private async Task StoreState(string org, string repo, long parentIssueNumber, long issueNumber)
|
||||
{
|
||||
var state = new SandboxMetadata
|
||||
{
|
||||
Org = org,
|
||||
Repo = repo,
|
||||
IssueNumber = issueNumber,
|
||||
ParentIssueNumber = parentIssueNumber,
|
||||
IsCompleted = false
|
||||
};
|
||||
await StateManager.SetStateAsync(
|
||||
StateStore,
|
||||
state);
|
||||
}
|
||||
private async Task Cleanup()
|
||||
{
|
||||
var agentState = await StateManager.GetStateAsync<SandboxMetadata>(StateStore);
|
||||
agentState.IsCompleted = true;
|
||||
await UnregisterReminderAsync(ReminderName);
|
||||
await StateManager.SetStateAsync(
|
||||
StateStore,
|
||||
agentState);
|
||||
}
|
||||
|
||||
public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
|
||||
{
|
||||
var agentState = await StateManager.GetStateAsync<SandboxMetadata>(StateStore);
|
||||
if (!agentState.IsCompleted)
|
||||
{
|
||||
var sandboxId = $"sk-sandbox-{agentState.Org}-{agentState.Repo}-{agentState.ParentIssueNumber}-{agentState.IssueNumber}";
|
||||
if (await _azService.IsSandboxCompleted(sandboxId))
|
||||
{
|
||||
await _azService.DeleteSandbox(sandboxId);
|
||||
var data = new Dictionary<string, string> {
|
||||
{ "org", agentState.Org },
|
||||
{ "repo", agentState.Repo },
|
||||
{ "issueNumber", agentState.IssueNumber.ToString() },
|
||||
{ "parentNumber", agentState.ParentIssueNumber.ToString() }
|
||||
};
|
||||
var subject = $"{agentState.Org}-{agentState.Repo}-{agentState.IssueNumber}";
|
||||
await PublishEvent(new Event
|
||||
{
|
||||
Namespace = Consts.MainTopic,
|
||||
Type = nameof(GithubFlowEventType.SandboxRunFinished),
|
||||
Subject = subject,
|
||||
Data = data
|
||||
});
|
||||
await Cleanup();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SandboxMetadata
|
||||
{
|
||||
public required string Org { get; set; }
|
||||
public required string Repo { get; set; }
|
||||
public long ParentIssueNumber { get; set; }
|
||||
public long IssueNumber { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 5274
|
||||
EXPOSE 11111
|
||||
EXPOSE 30000
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:5274
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
ARG configuration=Release
|
||||
COPY . .
|
||||
RUN dotnet restore "src/apps/gh-flow/gh-flow.csproj"
|
||||
WORKDIR "/src/apps/gh-flow"
|
||||
RUN dotnet build "gh-flow.csproj" -c $configuration -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG configuration=Release
|
||||
RUN dotnet publish "gh-flow.csproj" -c $configuration -o /app/publish
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "gh-flow.dll"]
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr.Events;
|
||||
|
||||
public enum GithubFlowEventType
|
||||
{
|
||||
NewAsk,
|
||||
ReadmeChainClosed,
|
||||
CodeChainClosed,
|
||||
CodeGenerationRequested,
|
||||
DevPlanRequested,
|
||||
ReadmeGenerated,
|
||||
DevPlanGenerated,
|
||||
CodeGenerated,
|
||||
DevPlanChainClosed,
|
||||
ReadmeRequested,
|
||||
ReadmeStored,
|
||||
SandboxRunFinished,
|
||||
ReadmeCreated,
|
||||
CodeCreated,
|
||||
DevPlanCreated,
|
||||
SandboxRunCreated
|
||||
}
|
||||
|
||||
public static class EventExtensions
|
||||
{
|
||||
public static GithubContext ToGithubContext(this Event evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
return new GithubContext
|
||||
{
|
||||
Org = evt.Data["org"],
|
||||
Repo = evt.Data["repo"],
|
||||
IssueNumber = long.Parse(evt.Data["issueNumber"]),
|
||||
ParentNumber = string.IsNullOrEmpty(evt.Data["parentNumber"]) ? default : long.Parse(evt.Data["parentNumber"])
|
||||
};
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> ToData(this GithubContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return new Dictionary<string, string> {
|
||||
{ "org", context.Org },
|
||||
{ "repo", context.Repo },
|
||||
{ "issueNumber", $"{context.IssueNumber}" },
|
||||
{ "parentNumber", context.ParentNumber?.ToString(CultureInfo.InvariantCulture) ?? ""}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class GithubContext
|
||||
{
|
||||
public required string Org { get; set; }
|
||||
public required string Repo { get; set; }
|
||||
public long IssueNumber { get; set; }
|
||||
public long? ParentNumber { get; set; }
|
||||
|
||||
public string Subject => $"{Org}-{Repo}-{IssueNumber}";
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class EventEnvelope
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public required Event Data { get; set; }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
<UserSecretsId>c073c86e-8483-4956-942f-331fd09172d4</UserSecretsId>
|
||||
<AnalysisMode>All</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit.Webhooks.AspNetCore" />
|
||||
<PackageReference Include="Octokit" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" />
|
||||
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Azure" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
||||
<PackageReference Include="Azure.ResourceManager.ContainerInstance" />
|
||||
<PackageReference Include="Azure.Storage.Files.Shares" />
|
||||
<PackageReference Include="Azure.Data.Tables" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
|
||||
<PackageReference Include="Dapr.Actors.AspNetCore" />
|
||||
<PackageReference Include="Dapr.AspNetCore" />
|
||||
<PackageReference Include="CloudNative.CloudEvents.SystemTextJson" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.AI.Agents.Dapr\Microsoft.AI.Agents.Dapr.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class AzureOptions
|
||||
{
|
||||
[Required]
|
||||
public required string SubscriptionId { get; set; }
|
||||
[Required]
|
||||
public required string Location { get; set; }
|
||||
[Required]
|
||||
public required string ContainerInstancesResourceGroup { get; set; }
|
||||
[Required]
|
||||
public required string FilesShareName { get; set; }
|
||||
[Required]
|
||||
public required string FilesAccountName { get; set; }
|
||||
[Required]
|
||||
public required string FilesAccountKey { get; set; }
|
||||
[Required]
|
||||
public required string SandboxImage { get; set; }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public static class Consts
|
||||
{
|
||||
public const string MainTopic = "DevPersonas";
|
||||
public const string PubSub = "agents-pubsub";
|
||||
public const string AppId = "dev-agents";
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class GithubOptions
|
||||
{
|
||||
[Required]
|
||||
public required string AppKey { get; set; }
|
||||
[Required]
|
||||
public int AppId { get; set; }
|
||||
[Required]
|
||||
public long InstallationId { get; set; }
|
||||
[Required]
|
||||
public required string WebhookSecret { get; set; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class OpenAIOptions
|
||||
{
|
||||
[Required]
|
||||
public required string ServiceType { get; set; }
|
||||
[Required]
|
||||
public required string ServiceId { get; set; }
|
||||
[Required]
|
||||
public required string DeploymentOrModelId { get; set; }
|
||||
[Required]
|
||||
public required string EmbeddingDeploymentOrModelId { get; set; }
|
||||
[Required]
|
||||
public required string Endpoint { get; set; }
|
||||
[Required]
|
||||
public required string ApiKey { get; set; }
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class QdrantOptions
|
||||
{
|
||||
[Required]
|
||||
public required string Endpoint { get; set; }
|
||||
[Required]
|
||||
public int VectorSize { get; set; }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class ServiceOptions
|
||||
{
|
||||
[Required]
|
||||
public required Uri IngesterUrl { get; set; }
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Octokit.Webhooks;
|
||||
using Octokit.Webhooks.AspNetCore;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.Azure;
|
||||
using Microsoft.Extensions.Http.Resilience;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using Microsoft.SemanticKernel.Connectors.Qdrant;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Dapr;
|
||||
using Dapr.Actors.Client;
|
||||
using Dapr.Actors;
|
||||
using Microsoft.AI.DevTeam.Dapr;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddSingleton<WebhookEventProcessor, GithubWebHookProcessor>();
|
||||
builder.Services.AddTransient(CreateKernel);
|
||||
builder.Services.AddTransient(CreateMemory);
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
builder.Services.AddSingleton(s =>
|
||||
{
|
||||
var ghOptions = s.GetRequiredService<IOptions<GithubOptions>>();
|
||||
var logger = s.GetRequiredService<ILogger<GithubAuthService>>();
|
||||
var ghService = new GithubAuthService(ghOptions, logger);
|
||||
var client = ghService.GetGitHubClient();
|
||||
return client;
|
||||
});
|
||||
|
||||
builder.Services.AddAzureClients(clientBuilder =>
|
||||
{
|
||||
clientBuilder.UseCredential(new DefaultAzureCredential());
|
||||
clientBuilder.AddArmClient(default);
|
||||
});
|
||||
|
||||
builder.Services.AddDaprClient();
|
||||
|
||||
builder.Services.AddActors(
|
||||
options =>
|
||||
{
|
||||
options.UseJsonSerialization = true;
|
||||
options.JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||
options.ReentrancyConfig = new ActorReentrancyConfig
|
||||
{
|
||||
Enabled = true
|
||||
};
|
||||
options.Actors.RegisterActor<Dev>();
|
||||
options.Actors.RegisterActor<DeveloperLead>();
|
||||
options.Actors.RegisterActor<ProductManager>();
|
||||
options.Actors.RegisterActor<AzureGenie>();
|
||||
options.Actors.RegisterActor<Hubber>();
|
||||
options.Actors.RegisterActor<Sandbox>();
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<GithubAuthService>();
|
||||
|
||||
builder.Services.AddApplicationInsightsTelemetry();
|
||||
builder.Services.AddOptions<GithubOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(GithubOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<AzureOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(AzureOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<OpenAIOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(OpenAIOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<QdrantOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(QdrantOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<ServiceOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(ServiceOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddSingleton<IManageAzure, AzureService>();
|
||||
builder.Services.AddSingleton<IManageGithub, GithubService>();
|
||||
builder.Services.AddSingleton<IAnalyzeCode, CodeAnalyzer>();
|
||||
|
||||
builder.Services.Configure<JsonSerializerOptions>(options =>
|
||||
{
|
||||
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
});
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseRouting()
|
||||
.UseEndpoints(endpoints =>
|
||||
{
|
||||
var ghOptions = app.Services.GetRequiredService<IOptions<GithubOptions>>().Value;
|
||||
endpoints.MapGitHubWebhooks(secret: ghOptions.WebhookSecret);
|
||||
endpoints.MapActorsHandlers();
|
||||
endpoints.MapSubscribeHandler();
|
||||
});
|
||||
|
||||
app.UseCloudEvents();
|
||||
|
||||
app.MapPost("/developers", [Topic(Consts.PubSub, Consts.MainTopic,
|
||||
$"(event.type ==\"{nameof(GithubFlowEventType.CodeGenerationRequested)}\") || (event.type ==\"{nameof(GithubFlowEventType.CodeGenerationRequested)}\")", 1)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(Dev), nameof(Dev.HandleEvent), evt));
|
||||
|
||||
app.MapPost("/devleads", [Topic(Consts.PubSub, Consts.MainTopic,
|
||||
$"(event.type ==\"{nameof(GithubFlowEventType.DevPlanRequested)}\") || (event.type ==\"{nameof(GithubFlowEventType.DevPlanChainClosed)}\")", 2)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(DeveloperLead), nameof(DeveloperLead.HandleEvent), evt));
|
||||
|
||||
app.MapPost("/productmanagers", [Topic(Consts.PubSub, Consts.MainTopic,
|
||||
$"(event.type ==\"{nameof(GithubFlowEventType.ReadmeRequested)}\") || (event.type ==\"{nameof(GithubFlowEventType.ReadmeChainClosed)}\")", 3)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(ProductManager), nameof(ProductManager.HandleEvent), evt));
|
||||
|
||||
app.MapPost("/hubbers", [Topic(Consts.PubSub, Consts.MainTopic,
|
||||
$"(event.type ==\"{nameof(GithubFlowEventType.NewAsk)}\") || (event.type ==\"{nameof(GithubFlowEventType.ReadmeGenerated)}\") || (event.type ==\"{nameof(GithubFlowEventType.DevPlanGenerated)}\") || (event.type ==\"{nameof(GithubFlowEventType.CodeGenerated)}\") || (event.type ==\"{nameof(GithubFlowEventType.DevPlanCreated)}\") || (event.type ==\"{nameof(GithubFlowEventType.ReadmeStored)}\") || (event.type ==\"{nameof(GithubFlowEventType.SandboxRunFinished)}\")", 4)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(Hubber), nameof(Hubber.HandleEvent), evt));
|
||||
|
||||
app.MapPost("/azuregenies", [Topic(Consts.PubSub, Consts.MainTopic,
|
||||
$"(event.type ==\"{nameof(GithubFlowEventType.ReadmeCreated)}\") || (event.type ==\"{nameof(GithubFlowEventType.CodeCreated)}\")", 5)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(AzureGenie), nameof(AzureGenie.HandleEvent), evt));
|
||||
|
||||
app.MapPost("/sandboxes", [Topic(Consts.PubSub, Consts.MainTopic, $"(event.type ==\"{nameof(GithubFlowEventType.SandboxRunCreated)}\")", 6)]
|
||||
async (IActorProxyFactory proxyFactory, EventEnvelope evt) => await HandleEvent(proxyFactory, nameof(Sandbox), nameof(Sandbox.HandleEvent), evt));
|
||||
|
||||
app.Run();
|
||||
|
||||
static async Task HandleEvent(IActorProxyFactory proxyFactory, string type, string method, EventEnvelope evt)
|
||||
{
|
||||
var proxyOptions = new ActorProxyOptions
|
||||
{
|
||||
RequestTimeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
var proxy = proxyFactory.Create(new ActorId(evt.Data.Subject), type, proxyOptions);
|
||||
await proxy.InvokeMethodAsync(method, evt.Data);
|
||||
}
|
||||
|
||||
static ISemanticTextMemory CreateMemory(IServiceProvider provider)
|
||||
{
|
||||
var openAiConfig = provider.GetRequiredService<IOptions<OpenAIOptions>>().Value;
|
||||
var qdrantConfig = provider.GetRequiredService<IOptions<QdrantOptions>>().Value;
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder
|
||||
.SetMinimumLevel(LogLevel.Debug)
|
||||
.AddConsole()
|
||||
.AddDebug();
|
||||
});
|
||||
|
||||
var memoryBuilder = new MemoryBuilder();
|
||||
return memoryBuilder.WithLoggerFactory(loggerFactory)
|
||||
.WithQdrantMemoryStore(qdrantConfig.Endpoint, qdrantConfig.VectorSize)
|
||||
.WithAzureOpenAITextEmbeddingGeneration(openAiConfig.EmbeddingDeploymentOrModelId, openAiConfig.Endpoint, openAiConfig.ApiKey)
|
||||
.Build();
|
||||
}
|
||||
|
||||
static Kernel CreateKernel(IServiceProvider provider)
|
||||
{
|
||||
var openAiConfig = provider.GetRequiredService<IOptions<OpenAIOptions>>().Value;
|
||||
var clientOptions = new OpenAIClientOptions();
|
||||
clientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(5);
|
||||
var openAIClient = new OpenAIClient(new Uri(openAiConfig.Endpoint), new AzureKeyCredential(openAiConfig.ApiKey), clientOptions);
|
||||
var builder = Kernel.CreateBuilder();
|
||||
builder.Services.AddLogging(c => c.AddConsole().AddDebug().SetMinimumLevel(LogLevel.Debug));
|
||||
builder.Services.AddAzureOpenAIChatCompletion(openAiConfig.DeploymentOrModelId, openAIClient);
|
||||
builder.Services.ConfigureHttpClientDefaults(c =>
|
||||
{
|
||||
c.AddStandardResilienceHandler().Configure(o =>
|
||||
{
|
||||
o.Retry.MaxRetryAttempts = 5;
|
||||
o.Retry.BackoffType = Polly.DelayBackoffType.Exponential;
|
||||
});
|
||||
});
|
||||
return builder.Build();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:59668",
|
||||
"sslPort": 44354
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5244",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7227;http://localhost:5244",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using System.Text;
|
||||
using Azure;
|
||||
using Azure.Core;
|
||||
using Azure.ResourceManager;
|
||||
using Azure.ResourceManager.ContainerInstance;
|
||||
using Azure.ResourceManager.ContainerInstance.Models;
|
||||
using Azure.ResourceManager.Resources;
|
||||
using Azure.Storage.Files.Shares;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public class AzureService : IManageAzure
|
||||
{
|
||||
private readonly AzureOptions _azSettings;
|
||||
private readonly ILogger<AzureService> _logger;
|
||||
private readonly ArmClient _client;
|
||||
|
||||
public AzureService(IOptions<AzureOptions> azOptions, ILogger<AzureService> logger, ArmClient client)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(azOptions);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
_azSettings = azOptions.Value;
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task DeleteSandbox(string sandboxId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
||||
var resourceGroupResource = _client.GetResourceGroupResource(resourceGroupResourceId);
|
||||
|
||||
var collection = resourceGroupResource.GetContainerGroups();
|
||||
var containerGroup = await collection.GetAsync(sandboxId);
|
||||
await containerGroup.Value.DeleteAsync(WaitUntil.Started);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting sandbox");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> IsSandboxCompleted(string sandboxId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
||||
var resourceGroupResource = _client.GetResourceGroupResource(resourceGroupResourceId);
|
||||
|
||||
var collection = resourceGroupResource.GetContainerGroups();
|
||||
var containerGroup = await collection.GetAsync(sandboxId);
|
||||
return containerGroup.Value.Data.ProvisioningState == "Succeeded"
|
||||
&& containerGroup.Value.Data.Containers.First().InstanceView.CurrentState.State == "Terminated";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking sandbox status");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunInSandbox(string org, string repo, long parentIssueNumber, long issueNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runId = $"sk-sandbox-{org}-{repo}-{parentIssueNumber}-{issueNumber}";
|
||||
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
||||
var resourceGroupResource = _client.GetResourceGroupResource(resourceGroupResourceId);
|
||||
var scriptPath = $"/azfiles/output/{org}-{repo}/{parentIssueNumber}/{issueNumber}/run.sh";
|
||||
var collection = resourceGroupResource.GetContainerGroups();
|
||||
var data = new ContainerGroupData(new AzureLocation(_azSettings.Location), new ContainerInstanceContainer[]
|
||||
{
|
||||
new ContainerInstanceContainer(runId,_azSettings.SandboxImage,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, runId, data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error running sandbox");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task Store(string org, string repo, long parentIssueNumber, long issueNumber, string filename, string extension, string dir, string output)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(output);
|
||||
try
|
||||
{
|
||||
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={_azSettings.FilesAccountName};AccountKey={_azSettings.FilesAccountKey};EndpointSuffix=core.windows.net";
|
||||
var parentDirName = $"{dir}/{org}-{repo}";
|
||||
|
||||
var fileName = $"{filename}.{extension}";
|
||||
|
||||
var share = new ShareClient(connectionString, _azSettings.FilesShareName);
|
||||
await share.CreateIfNotExistsAsync();
|
||||
await share.GetDirectoryClient($"{dir}").CreateIfNotExistsAsync(); ;
|
||||
|
||||
var parentDir = share.GetDirectoryClient(parentDirName);
|
||||
await parentDir.CreateIfNotExistsAsync();
|
||||
|
||||
var parentIssueDir = parentDir.GetSubdirectoryClient($"{parentIssueNumber}");
|
||||
await parentIssueDir.CreateIfNotExistsAsync();
|
||||
|
||||
var directory = parentIssueDir.GetSubdirectoryClient($"{issueNumber}");
|
||||
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 contents = extension == "sh" ? output.Replace("#!/bin/bash", cwdHack, StringComparison.InvariantCulture) : output;
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
{
|
||||
await file.CreateAsync(stream.Length);
|
||||
await file.UploadRangeAsync(
|
||||
new HttpRange(0, stream.Length),
|
||||
stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error storing output");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IManageAzure
|
||||
{
|
||||
Task Store(string org, string repo, long parentIssueNumber, long issueNumber, string filename, string extension, string dir, string output);
|
||||
Task RunInSandbox(string org, string repo, long parentIssueNumber, long issueNumber);
|
||||
Task<bool> IsSandboxCompleted(string sandboxId);
|
||||
Task DeleteSandbox(string sandboxId);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public interface IAnalyzeCode
|
||||
{
|
||||
Task<IEnumerable<CodeAnalysisResult>> Analyze(string content);
|
||||
}
|
||||
public class CodeAnalyzer : IAnalyzeCode
|
||||
{
|
||||
private readonly ServiceOptions _serviceOptions;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<CodeAnalyzer> _logger;
|
||||
|
||||
public CodeAnalyzer(IOptions<ServiceOptions> serviceOptions, HttpClient httpClient, ILogger<CodeAnalyzer> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(serviceOptions);
|
||||
ArgumentNullException.ThrowIfNull(httpClient);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_serviceOptions = serviceOptions.Value;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_httpClient.BaseAddress = _serviceOptions.IngesterUrl;
|
||||
|
||||
}
|
||||
public async Task<IEnumerable<CodeAnalysisResult>> Analyze(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new CodeAnalysisRequest { Content = content };
|
||||
var response = await _httpClient.PostAsJsonAsync("api/AnalyzeCode", request);
|
||||
var stringResult = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<IEnumerable<CodeAnalysisResult>>(stringResult);
|
||||
return result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error analyzing code");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CodeAnalysisRequest
|
||||
{
|
||||
public required string Content { get; set; }
|
||||
}
|
||||
|
||||
public class CodeAnalysisResult
|
||||
{
|
||||
public required string Meaning { get; set; }
|
||||
public required string CodeBlock { get; set; }
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Octokit;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class GithubAuthService
|
||||
{
|
||||
private readonly GithubOptions _githubSettings;
|
||||
private readonly ILogger<GithubAuthService> _logger;
|
||||
|
||||
public GithubAuthService(IOptions<GithubOptions> ghOptions, ILogger<GithubAuthService> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ghOptions);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
_githubSettings = ghOptions.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GenerateJwtToken(string appId, string appKey, int minutes)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(appKey);
|
||||
var securityKey = new RsaSecurityKey(rsa);
|
||||
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var iat = new DateTimeOffset(now).ToUnixTimeSeconds();
|
||||
var exp = new DateTimeOffset(now.AddMinutes(minutes)).ToUnixTimeSeconds();
|
||||
|
||||
var claims = new[] {
|
||||
new Claim(JwtRegisteredClaimNames.Iat, iat.ToString(), ClaimValueTypes.Integer64),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, exp.ToString(), ClaimValueTypes.Integer64)
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: appId,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddMinutes(10),
|
||||
signingCredentials: credentials
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public GitHubClient GetGitHubClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
var jwtToken = GenerateJwtToken(_githubSettings.AppId.ToString(), _githubSettings.AppKey, 10);
|
||||
var appClient = new GitHubClient(new ProductHeaderValue("SK-DEV-APP"))
|
||||
{
|
||||
Credentials = new Credentials(jwtToken, AuthenticationType.Bearer)
|
||||
};
|
||||
var response = appClient.GitHubApps.CreateInstallationToken(_githubSettings.InstallationId).Result;
|
||||
return new GitHubClient(new ProductHeaderValue($"SK-DEV-APP-Installation{_githubSettings.InstallationId}"))
|
||||
{
|
||||
Credentials = new Credentials(response.Token)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting GitHub client");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
using System.Text;
|
||||
using Azure.Storage.Files.Shares;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Octokit;
|
||||
using Octokit.Helpers;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public class GithubService : IManageGithub
|
||||
{
|
||||
private readonly GitHubClient _ghClient;
|
||||
private readonly AzureOptions _azSettings;
|
||||
private readonly ILogger<GithubService> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public GithubService(IOptions<AzureOptions> azOptions, GitHubClient ghClient, ILogger<GithubService> logger, HttpClient httpClient)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(azOptions);
|
||||
ArgumentNullException.ThrowIfNull(ghClient);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
ArgumentNullException.ThrowIfNull(httpClient);
|
||||
_ghClient = ghClient;
|
||||
_azSettings = azOptions.Value;
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task CommitToBranch(string org, string repo, long parentNumber, long issueNumber, string rootDir, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={_azSettings.FilesAccountName};AccountKey={_azSettings.FilesAccountKey};EndpointSuffix=core.windows.net";
|
||||
|
||||
var dirName = $"{rootDir}/{org}-{repo}/{parentNumber}/{issueNumber}";
|
||||
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}/", "", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace($"{dirName}/", "", StringComparison.OrdinalIgnoreCase);
|
||||
var fileStream = await file.OpenReadAsync();
|
||||
using (var reader = new StreamReader(fileStream, Encoding.UTF8))
|
||||
{
|
||||
var value = await reader.ReadToEndAsync();
|
||||
|
||||
await _ghClient.Repository.Content.CreateFile(
|
||||
org, repo, filePath,
|
||||
new CreateFileRequest($"Commit message", value, branch)); // TODO: add more meaningfull commit message
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while uploading file '{FileName}'.", item.Name);
|
||||
}
|
||||
}
|
||||
else if (item.IsDirectory)
|
||||
{
|
||||
remaining.Enqueue(dir.GetSubdirectoryClient(item.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error committing to branch");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateBranch(string org, string repo, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ghRepo = await _ghClient.Repository.Get(org, repo);
|
||||
if (ghRepo.Size == 0)
|
||||
{
|
||||
// Create a new file and commit it to the repository
|
||||
var createChangeSet = await _ghClient.Repository.Content.CreateFile(
|
||||
org,
|
||||
repo,
|
||||
"README.md",
|
||||
new CreateFileRequest("Initial commit", "# Readme")
|
||||
);
|
||||
}
|
||||
|
||||
await _ghClient.Git.Reference.CreateBranch(org, repo, branch, ghRepo.DefaultBranch);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating branch");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetMainLanguage(string org, string repo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var languages = await _ghClient.Repository.GetAllLanguages(org, repo);
|
||||
var mainLanguage = languages.OrderByDescending(l => l.NumberOfBytes).First();
|
||||
return mainLanguage.Name;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting main language");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CreateIssue(string org, string repo, string input, string function, long parentNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newIssue = new NewIssue($"{function} chain for #{parentNumber}")
|
||||
{
|
||||
Body = input,
|
||||
};
|
||||
newIssue.Labels.Add(function);
|
||||
newIssue.Labels.Add($"Parent.{parentNumber}");
|
||||
var issue = await _ghClient.Issue.Create(org, repo, newIssue);
|
||||
return issue.Number;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating issue");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreatePR(string org, string repo, long number, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ghRepo = await _ghClient.Repository.Get(org, repo);
|
||||
await _ghClient.PullRequest.Create(org, repo, new NewPullRequest($"New app #{number}", branch, ghRepo.DefaultBranch));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating PR");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostComment(string org, string repo, long issueNumber, string comment)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ghClient.Issue.Comment.Create(org, repo, (int)issueNumber, comment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error posting comment");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FileResponse>> GetFiles(string org, string repo, string branch, Func<RepositoryContent, bool> filter)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
|
||||
try
|
||||
{
|
||||
var items = await _ghClient.Repository.Content.GetAllContentsByRef(org, repo, branch);
|
||||
return await CollectFiles(org, repo, branch, items, filter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting files");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<FileResponse>> CollectFiles(string org, string repo, string branch, IReadOnlyList<RepositoryContent> items, Func<RepositoryContent, bool> filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new List<FileResponse>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Type == ContentType.File && filter(item))
|
||||
{
|
||||
var content = await _httpClient.GetStringAsync(new Uri(item.DownloadUrl));
|
||||
result.Add(new FileResponse
|
||||
{
|
||||
Name = item.Name,
|
||||
Content = content
|
||||
});
|
||||
}
|
||||
else if (item.Type == ContentType.Dir)
|
||||
{
|
||||
var subItems = await _ghClient.Repository.Content.GetAllContentsByRef(org, repo, item.Path, branch);
|
||||
result.AddRange(await CollectFiles(org, repo, branch, subItems, filter));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error collecting files");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileResponse
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string Content { get; set; }
|
||||
}
|
||||
|
||||
public interface IManageGithub
|
||||
{
|
||||
Task<int> CreateIssue(string org, string repo, string input, string functionName, long parentNumber);
|
||||
Task CreatePR(string org, string repo, long number, string branch);
|
||||
Task CreateBranch(string org, string repo, string branch);
|
||||
Task CommitToBranch(string org, string repo, long parentNumber, long issueNumber, string rootDir, string branch);
|
||||
|
||||
Task PostComment(string org, string repo, long issueNumber, string comment);
|
||||
Task<IEnumerable<FileResponse>> GetFiles(string org, string repo, string branch, Func<RepositoryContent, bool> filter);
|
||||
Task<string> GetMainLanguage(string org, string repo);
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
using System.Globalization;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.DevTeam.Dapr.Events;
|
||||
using Octokit.Webhooks;
|
||||
using Octokit.Webhooks.Events;
|
||||
using Octokit.Webhooks.Events.IssueComment;
|
||||
using Octokit.Webhooks.Events.Issues;
|
||||
using Octokit.Webhooks.Models;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public sealed class GithubWebHookProcessor : WebhookEventProcessor
|
||||
{
|
||||
private readonly DaprClient _daprClient;
|
||||
private readonly ILogger<GithubWebHookProcessor> _logger;
|
||||
|
||||
public GithubWebHookProcessor(DaprClient daprClient, ILogger<GithubWebHookProcessor> logger)
|
||||
{
|
||||
_daprClient = daprClient;
|
||||
_logger = logger;
|
||||
}
|
||||
protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(headers);
|
||||
ArgumentNullException.ThrowIfNull(issuesEvent);
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Processing issue event");
|
||||
var org = issuesEvent.Repository!.Owner.Login;
|
||||
var repo = issuesEvent.Repository.Name;
|
||||
var issueNumber = issuesEvent.Issue.Number;
|
||||
var input = issuesEvent.Issue.Body;
|
||||
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
|
||||
// Also, we've introduced the Parent label, that ties the sub-issue with the parent issue
|
||||
var labels = issuesEvent.Issue.Labels
|
||||
.Select(l => l.Name.Split('.'))
|
||||
.Where(parts => parts.Length == 2)
|
||||
.ToDictionary(parts => parts[0], parts => parts[1]);
|
||||
var skillName = labels.Keys.First(k => k != "Parent");
|
||||
long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value, CultureInfo.InvariantCulture) : null;
|
||||
|
||||
var suffix = $"{org}-{repo}";
|
||||
if (issuesEvent.Action == IssuesAction.Opened)
|
||||
{
|
||||
_logger.LogInformation("Processing HandleNewAsk");
|
||||
await HandleNewAsk(issueNumber, parentNumber, skillName, labels[skillName], input!, org, repo);
|
||||
}
|
||||
else if (issuesEvent.Action == IssuesAction.Closed && issuesEvent.Issue.User.Type.Value == UserType.Bot)
|
||||
{
|
||||
_logger.LogInformation("Processing HandleClosingIssue");
|
||||
await HandleClosingIssue(issueNumber, parentNumber, skillName, labels[skillName], org, repo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Processing issue event");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ProcessIssueCommentWebhookAsync(
|
||||
WebhookHeaders headers,
|
||||
IssueCommentEvent issueCommentEvent,
|
||||
IssueCommentAction action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(headers);
|
||||
ArgumentNullException.ThrowIfNull(issueCommentEvent);
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Processing issue comment event");
|
||||
var org = issueCommentEvent.Repository!.Owner.Login;
|
||||
var repo = issueCommentEvent.Repository.Name;
|
||||
var issueNumber = issueCommentEvent.Issue.Number;
|
||||
var input = issueCommentEvent.Comment.Body;
|
||||
// Assumes the label follows the following convention: Skill.Function example: PM.Readme
|
||||
var labels = issueCommentEvent.Issue.Labels
|
||||
.Select(l => l.Name.Split('.'))
|
||||
.Where(parts => parts.Length == 2)
|
||||
.ToDictionary(parts => parts[0], parts => parts[1]);
|
||||
var skillName = labels.Keys.First(k => k != "Parent");
|
||||
long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value, CultureInfo.InvariantCulture) : null;
|
||||
var suffix = $"{org}-{repo}";
|
||||
|
||||
// we only respond to non-bot comments
|
||||
if (issueCommentEvent.Sender!.Type.Value != UserType.Bot)
|
||||
{
|
||||
await HandleNewAsk(issueNumber, parentNumber, skillName, labels[skillName], input, org, repo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Processing issue comment event");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task HandleClosingIssue(long issueNumber, long? parentNumber, string skillName, string functionName, string org, string repo)
|
||||
{
|
||||
var subject = $"{org}-{repo}-{issueNumber}";
|
||||
var eventType = (skillName, functionName) switch
|
||||
{
|
||||
("PM", "Readme") => nameof(GithubFlowEventType.ReadmeChainClosed),
|
||||
("DevLead", "Plan") => nameof(GithubFlowEventType.DevPlanChainClosed),
|
||||
("Developer", "Implement") => nameof(GithubFlowEventType.CodeChainClosed),
|
||||
_ => nameof(GithubFlowEventType.NewAsk)
|
||||
};
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "org", org },
|
||||
{ "repo", repo },
|
||||
{ "issueNumber", issueNumber.ToString() },
|
||||
{ "parentNumber", parentNumber?.ToString(CultureInfo.InvariantCulture) ?? "" }
|
||||
};
|
||||
|
||||
var evt = new Event
|
||||
{
|
||||
Namespace = subject,
|
||||
Type = eventType,
|
||||
Subject = subject,
|
||||
Data = data
|
||||
};
|
||||
await PublishEvent(evt);
|
||||
}
|
||||
|
||||
private async Task HandleNewAsk(long issueNumber, long? parentNumber, string skillName, string functionName, string input, string org, string repo)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Handling new ask");
|
||||
var subject = $"{org}-{repo}-{issueNumber}";
|
||||
var eventType = (skillName, functionName) switch
|
||||
{
|
||||
("Do", "It") => nameof(GithubFlowEventType.NewAsk),
|
||||
("PM", "Readme") => nameof(GithubFlowEventType.ReadmeRequested),
|
||||
("DevLead", "Plan") => nameof(GithubFlowEventType.DevPlanRequested),
|
||||
("Developer", "Implement") => nameof(GithubFlowEventType.CodeGenerationRequested),
|
||||
_ => nameof(GithubFlowEventType.NewAsk)
|
||||
};
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "org", org },
|
||||
{ "repo", repo },
|
||||
{ "issueNumber", issueNumber.ToString() },
|
||||
{ "parentNumber", parentNumber ?.ToString(CultureInfo.InvariantCulture) ?? ""},
|
||||
{ "input" , input}
|
||||
};
|
||||
var evt = new Event
|
||||
{
|
||||
Namespace = subject,
|
||||
Type = eventType,
|
||||
Subject = subject,
|
||||
Data = data
|
||||
};
|
||||
await PublishEvent(evt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Handling new ask");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishEvent(Event evt)
|
||||
{
|
||||
var metadata = new Dictionary<string, string>() {
|
||||
{ "cloudevent.Type", evt.Type },
|
||||
{ "cloudevent.Subject", evt.Subject ?? "" },
|
||||
{ "cloudevent.id", Guid.NewGuid().ToString()}
|
||||
};
|
||||
|
||||
await _daprClient.PublishEventAsync(Consts.PubSub, Consts.MainTopic, evt, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Information",
|
||||
"Orleans.Streams": "Information"
|
||||
}
|
||||
},
|
||||
"ApplicationInsights": {
|
||||
"ConnectionString": ""
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SANDBOX_IMAGE" : "mcr.microsoft.com/dotnet/sdk:7.0",
|
||||
"GithubOptions" : {
|
||||
"AppKey": "",
|
||||
"AppId": "",
|
||||
"InstallationId": "",
|
||||
"WebhookSecret": ""
|
||||
},
|
||||
"AzureOptions" : {
|
||||
"SubscriptionId":"",
|
||||
"Location":"",
|
||||
"ContainerInstancesResourceGroup":"",
|
||||
"FilesShareName":"",
|
||||
"FilesAccountName":"",
|
||||
"FilesAccountKey":"",
|
||||
"SandboxImage" : "mcr.microsoft.com/dotnet/sdk:7.0",
|
||||
"ManagedIdentity": ""
|
||||
},
|
||||
"OpenAIOptions" : {
|
||||
"ServiceType":"AzureOpenAI",
|
||||
"ServiceId":"gpt-4",
|
||||
"DeploymentOrModelId":"gpt-4",
|
||||
"EmbeddingDeploymentOrModelId":"text-embedding-ada-002",
|
||||
"Endpoint":"",
|
||||
"ApiKey":""
|
||||
},
|
||||
"QdrantOptions" : {
|
||||
"Endpoint" : "http://qdrant:6333",
|
||||
"VectorSize" : "1536"
|
||||
},
|
||||
"ServiceOptions" : {
|
||||
"IngesterUrl" : "http://localhost:7071"
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Information",
|
||||
"Orleans.Streams": "Information"
|
||||
}
|
||||
},
|
||||
"ApplicationInsights": {
|
||||
"ConnectionString": ""
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SANDBOX_IMAGE" : "mcr.microsoft.com/dotnet/sdk:7.0",
|
||||
"GithubOptions" : {
|
||||
"AppKey": "",
|
||||
"AppId": "",
|
||||
"InstallationId": "",
|
||||
"WebhookSecret": ""
|
||||
},
|
||||
"AzureOptions" : {
|
||||
"SubscriptionId":"",
|
||||
"Location":"",
|
||||
"ContainerInstancesResourceGroup":"",
|
||||
"FilesShareName":"",
|
||||
"FilesAccountName":"",
|
||||
"FilesAccountKey":"",
|
||||
"SandboxImage" : "mcr.microsoft.com/dotnet/sdk:7.0",
|
||||
"ManagedIdentity": ""
|
||||
},
|
||||
"OpenAIOptions" : {
|
||||
"ServiceType":"AzureOpenAI",
|
||||
"ServiceId":"gpt-4",
|
||||
"DeploymentOrModelId":"gpt-4",
|
||||
"EmbeddingDeploymentOrModelId":"text-embedding-ada-002",
|
||||
"Endpoint":"",
|
||||
"ApiKey":""
|
||||
},
|
||||
"QdrantOptions" : {
|
||||
"Endpoint" : "http://qdrant:6333",
|
||||
"VectorSize" : "1536"
|
||||
},
|
||||
"ServiceOptions" : {
|
||||
"IngesterUrl" : "http://localhost:7071"
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
namespace Microsoft.AI.Agents.Dapr;
|
||||
|
||||
public abstract class Agent : Actor, IAgent
|
||||
{
|
||||
private readonly DaprClient _daprClient;
|
||||
|
||||
protected Agent(ActorHost host, DaprClient daprClient) : base(host)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
}
|
||||
public abstract Task HandleEvent(Event item);
|
||||
|
||||
public async Task PublishEvent(Event item)
|
||||
{
|
||||
var metadata = new Dictionary<string, string>()
|
||||
{
|
||||
["cloudevent.Type"] = item.Type,
|
||||
["cloudevent.Subject"] = item.Subject,
|
||||
["cloudevent.id"] = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
await _daprClient.PublishEventAsync("default", item.Namespace, item, metadata).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
namespace Microsoft.AI.Agents.Dapr;
|
||||
|
||||
public abstract class AiAgent<T> : Agent, IAiAgent where T : class, new()
|
||||
{
|
||||
public string StateStore = "agents-statestore";
|
||||
public AiAgent(ActorHost host, DaprClient client, ISemanticTextMemory memory, Kernel kernel)
|
||||
: base(host, client)
|
||||
{
|
||||
_memory = memory;
|
||||
_kernel = kernel;
|
||||
}
|
||||
|
||||
private readonly ISemanticTextMemory _memory;
|
||||
private readonly Kernel _kernel;
|
||||
|
||||
protected AgentState<T> state = default!;
|
||||
|
||||
protected override async Task OnActivateAsync()
|
||||
{
|
||||
state = await StateManager.GetOrAddStateAsync(StateStore, new AgentState<T>());
|
||||
}
|
||||
|
||||
public void AddToHistory(string message, ChatUserType userType)
|
||||
{
|
||||
if (state.History == null)
|
||||
{
|
||||
state.History = new List<ChatHistoryItem>();
|
||||
}
|
||||
|
||||
state.History.Add(new ChatHistoryItem
|
||||
{
|
||||
Message = message,
|
||||
Order = state.History.Count + 1,
|
||||
UserType = userType
|
||||
});
|
||||
}
|
||||
|
||||
public string AppendChatHistory(string ask)
|
||||
{
|
||||
AddToHistory(ask, ChatUserType.User);
|
||||
return string.Join("\n", state.History.Select(message => $"{message.UserType}: {message.Message}"));
|
||||
}
|
||||
|
||||
public virtual async Task<string> CallFunction(string template, KernelArguments arguments, OpenAIPromptExecutionSettings? settings = null)
|
||||
{
|
||||
var propmptSettings = settings ?? new OpenAIPromptExecutionSettings { MaxTokens = 18000, Temperature = 0.8, TopP = 1 };
|
||||
var function = _kernel.CreateFunctionFromPrompt(template, propmptSettings);
|
||||
var result = (await _kernel.InvokeAsync(function, arguments)).ToString();
|
||||
AddToHistory(result, ChatUserType.Agent);
|
||||
await StateManager.SetStateAsync(
|
||||
StateStore,
|
||||
state);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds knowledge to the
|
||||
/// </summary>
|
||||
/// <param name="instruction">The instruction string that uses the value of !index! as a placeholder to inject the data. Example:"Consider the following architectural guidelines: {waf}" </param>
|
||||
/// <param name="index">Knowledge index</param>
|
||||
/// <param name="arguments">The sk arguments, "input" is the argument </param>
|
||||
/// <returns></returns>
|
||||
public async Task<KernelArguments> AddKnowledge(string instruction, string index, KernelArguments arguments)
|
||||
{
|
||||
var documents = _memory.SearchAsync(index, arguments["input"]?.ToString()!, 5);
|
||||
var kbStringBuilder = new StringBuilder();
|
||||
await foreach (var doc in documents)
|
||||
{
|
||||
kbStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{doc.Metadata.Text}");
|
||||
}
|
||||
arguments[index] = instruction.Replace($"!{index}!", $"{kbStringBuilder}");
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Dapr.Actors;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
namespace Microsoft.AI.Agents.Dapr;
|
||||
|
||||
public interface IDaprAgent : IActor
|
||||
{
|
||||
Task HandleEvent(Event item);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapr.Client" />
|
||||
<PackageReference Include="Dapr.Actors" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user