mirror of https://github.com/microsoft/autogen.git
Dapr implementation (#47)
* replace Event to CloudEvent WIP * switch to CloudEvents * dapr implementation WIP * fix namespaces and dapr sdk weirdness * WIP * dapr support WIP * dapr WIP * dapr WIP * dapr semi-working implementation * fix mapping bug * dapr reminders in sandbox * fix prompts * merge almost done * switch to Newtnsoft serialization for Marketing * add event surogate for Orleans serialization * remove newtnsoft serialization (not needed) * cleanup appsettings in marketing sample
This commit is contained in:
parent
949520bba6
commit
e9a7a07e13
|
@ -15,7 +15,8 @@
|
|||
"ghcr.io/azure/azure-dev/azd:latest": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/azure/azure-dev/azd:0": {},
|
||||
"ghcr.io/stuartleeks/dev-container-features/dev-tunnels:0": {}
|
||||
"ghcr.io/stuartleeks/dev-container-features/dev-tunnels:0": {},
|
||||
"ghcr.io/dapr/cli/dapr-cli:0": {}
|
||||
},
|
||||
"postCreateCommand": "bash .devcontainer/startup.sh",
|
||||
"hostRequirements": {
|
||||
|
@ -48,10 +49,11 @@
|
|||
"ms-semantic-kernel.semantic-kernel",
|
||||
"GitHub.copilot-chat",
|
||||
"GitHub.vscode-github-actions",
|
||||
"ms-azuretools.azure-dev",
|
||||
"ms-azuretools.vscode-azurefunctions",
|
||||
"ms-azuretools.vscode-bicep",
|
||||
"ms-dotnettools.vscode-dotnet-runtime"
|
||||
"ms-azuretools.azure-dev",
|
||||
"ms-azuretools.vscode-azurefunctions",
|
||||
"ms-azuretools.vscode-bicep",
|
||||
"ms-dotnettools.vscode-dotnet-runtime",
|
||||
"ms-azuretools.vscode-dapr"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,19 +10,21 @@ services:
|
|||
- ..:/workspace:cached
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
network_mode: service:cosmos
|
||||
network_mode: service:qdrant
|
||||
depends_on:
|
||||
- cosmos
|
||||
cosmos:
|
||||
image: mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator
|
||||
mem_limit: "3g"
|
||||
cpu_count: 2
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
AZURE_COSMOS_EMULATOR_PARTITION_COUNT: "10"
|
||||
AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE: "true"
|
||||
- qdrant
|
||||
qdrant:
|
||||
image: qdrant/qdrant
|
||||
ports:
|
||||
- 6333:6333
|
||||
redis:
|
||||
image: redis:alpine
|
||||
network_mode: service:qdrant
|
||||
zipkin:
|
||||
image: openzipkin/zipkin-slim:latest
|
||||
ports:
|
||||
- 9411:9411
|
||||
dapr-placement:
|
||||
image: "daprio/dapr"
|
||||
command: ["./placement", "--port", "50005"]
|
||||
network_mode: service:qdrant
|
|
@ -4,5 +4,6 @@
|
|||
# sudo cp ~/emulatorcert.crt /usr/local/share/ca-certificates/
|
||||
# sudo update-ca-certificates
|
||||
# sleep 10
|
||||
dotnet restore sk-dev-team.sln
|
||||
# dotnet build util/seed-memory/seed-memory.csproj && dotnet util/seed-memory/bin/Debug/net7.0/seed-memory.dll
|
||||
dotnet restore Microsoft.AI.Agents.sln
|
||||
|
||||
dapr init --slim
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ms-azuretools.vscode-azurefunctions",
|
||||
"ms-dotnettools.csharp"
|
||||
]
|
||||
}
|
|
@ -1,18 +1,12 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to .NET Functions",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:azureFunctions.pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "Attach to Node Functions",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"preLaunchTask": "func: host start"
|
||||
}
|
||||
]
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C#: Microsoft.AI.DevTeam.Dapr [Default Configuration] with Dapr",
|
||||
"type": "dotnet",
|
||||
"request": "launch",
|
||||
"projectPath": "${workspaceFolder}/samples/gh-flow/src/Microsoft.AI.DevTeam.Dapr/Microsoft.AI.DevTeam.Dapr.csproj",
|
||||
"preLaunchTask": "dapr-debug",
|
||||
"postDebugTask": "daprd-down"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,11 +1,4 @@
|
|||
{
|
||||
"dotnet.defaultSolution": "sk-dev-team.sln",
|
||||
"azureFunctions.deploySubpath": "src/apps/ingest",
|
||||
"azureFunctions.projectLanguage": "JavaScript",
|
||||
"azureFunctions.projectRuntime": "~4",
|
||||
"dotnet.defaultSolution": "Microsoft.AI.Agents.sln",
|
||||
"debug.internalConsoleOptions": "neverOpen",
|
||||
"azureFunctions.preDeployTask": "npm prune (functions)",
|
||||
"azureFunctions.postDeployTask": "npm install (functions)",
|
||||
"azureFunctions.projectLanguageModel": 4,
|
||||
"azureFunctions.projectSubpath": "src/apps/ingest"
|
||||
}
|
|
@ -1,99 +1,19 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "clean (functions)",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"clean",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"type": "process",
|
||||
"problemMatcher": "$msCompile",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/sk-azfunc-server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "build (functions)",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"type": "process",
|
||||
"dependsOn": "clean (functions)",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$msCompile",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/sk-azfunc-server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "clean release (functions)",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"clean",
|
||||
"--configuration",
|
||||
"Release",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"type": "process",
|
||||
"problemMatcher": "$msCompile",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/sk-azfunc-server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "publish (functions)",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"publish",
|
||||
"--configuration",
|
||||
"Release",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"type": "process",
|
||||
"dependsOn": "clean release (functions)",
|
||||
"problemMatcher": "$msCompile",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/sk-azfunc-server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "func",
|
||||
"label": "func: host start",
|
||||
"command": "host start",
|
||||
"problemMatcher": "$func-node-watch",
|
||||
"isBackground": true,
|
||||
"dependsOn": "npm install (functions)",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src/apps/ingest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "npm install (functions)",
|
||||
"command": "npm install",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src/apps/ingest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "npm prune (functions)",
|
||||
"command": "npm prune --production",
|
||||
"problemMatcher": [],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src/apps/ingest"
|
||||
}
|
||||
}
|
||||
]
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"appId": "dev-agents",
|
||||
"grpcPort": 50001,
|
||||
"httpPort": 3500,
|
||||
"appPort": 5244,
|
||||
"componentsPath": "samples/gh-flow/components",
|
||||
"label": "dapr-debug",
|
||||
"type": "dapr"
|
||||
},
|
||||
{
|
||||
"appId": "dev-agents",
|
||||
"label": "daprd-down",
|
||||
"type": "daprd-down"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,29 +1,32 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7EC6823E-99FB-403F-9941-0CB08C6BFFD4}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{290F9824-BAD3-4703-B9B7-FE9C4BE3A1CF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Agents", "src\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj", "{160AE89B-16BE-4FB8-8FEC-255E11D6E860}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Agents", "src\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj", "{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{17FFA72B-390A-4BF6-A277-2099897D2132}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Agents.Dapr", "src\Microsoft.AI.Agents.Dapr\Microsoft.AI.Agents.Dapr.csproj", "{1972846E-4C21-4E40-B448-D78B73806BD9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gh-flow", "gh-flow", "{0C7D5C39-2067-4EF1-9C10-72A847FF9CB3}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}") = "src", "src", "{DADD59B8-4B8B-4F2B-BFEB-0F93B8A6AA55}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{943853E7-513D-45EA-870F-549CFC0AF8E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.DevTeam", "samples\gh-flow\src\Microsoft.AI.DevTeam\Microsoft.AI.DevTeam.csproj", "{F02CC33B-B479-480B-B886-25ED0E18494A}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gh-flow", "gh-flow", "{E0E93575-7187-4975-8D72-6F285CD01767}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "seed-memory", "samples\seed-memory\seed-memory.csproj", "{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{50809508-F830-4553-9C4E-C802E0A0F690}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowsApp", "samples\WorkflowsApp\WorkflowsApp.csproj", "{3EE3F061-CC63-41B0-84BA-C95A256E18A9}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.DevTeam", "samples\gh-flow\src\Microsoft.AI.DevTeam\Microsoft.AI.DevTeam.csproj", "{79981945-61F7-4E1A-8949-7808FD75471B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Agents.Orleans", "src\Microsoft.AI.Agents.Orleans\Microsoft.AI.Agents.Orleans.csproj", "{12381ECC-7ECD-4D13-911B-83A07BB58134}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.DevTeam.Dapr", "samples\gh-flow\src\Microsoft.AI.DevTeam.Dapr\Microsoft.AI.DevTeam.Dapr.csproj", "{A7677950-18F1-42FF-8018-870395417465}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AI.Agents.Dapr", "src\Microsoft.AI.Agents.Dapr\Microsoft.AI.Agents.Dapr.csproj", "{6CCA9961-D498-4E59-8781-D94AE8228200}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "marketing", "marketing", "{1FF691E4-E27D-4A7E-861C-4D6291B6EE35}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marketing", "samples\marketing\src\backend\Marketing.csproj", "{71D20402-E03C-43EA-BFA2-DF16B32D623C}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4225F3BA-A39D-4680-945E-F2869E98AEA2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marketing", "samples\marketing\src\backend\Marketing.csproj", "{62F276F3-9184-4908-A7FB-065B4E491BE2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "seed-memory", "samples\gh-flow\src\seed-memory\seed-memory.csproj", "{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -31,47 +34,49 @@ Global
|
|||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{160AE89B-16BE-4FB8-8FEC-255E11D6E860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{160AE89B-16BE-4FB8-8FEC-255E11D6E860}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{160AE89B-16BE-4FB8-8FEC-255E11D6E860}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{160AE89B-16BE-4FB8-8FEC-255E11D6E860}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F02CC33B-B479-480B-B886-25ED0E18494A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F02CC33B-B479-480B-B886-25ED0E18494A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F02CC33B-B479-480B-B886-25ED0E18494A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F02CC33B-B479-480B-B886-25ED0E18494A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3EE3F061-CC63-41B0-84BA-C95A256E18A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3EE3F061-CC63-41B0-84BA-C95A256E18A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3EE3F061-CC63-41B0-84BA-C95A256E18A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3EE3F061-CC63-41B0-84BA-C95A256E18A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12381ECC-7ECD-4D13-911B-83A07BB58134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12381ECC-7ECD-4D13-911B-83A07BB58134}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12381ECC-7ECD-4D13-911B-83A07BB58134}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12381ECC-7ECD-4D13-911B-83A07BB58134}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6CCA9961-D498-4E59-8781-D94AE8228200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CCA9961-D498-4E59-8781-D94AE8228200}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CCA9961-D498-4E59-8781-D94AE8228200}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6CCA9961-D498-4E59-8781-D94AE8228200}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{71D20402-E03C-43EA-BFA2-DF16B32D623C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{71D20402-E03C-43EA-BFA2-DF16B32D623C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{71D20402-E03C-43EA-BFA2-DF16B32D623C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{71D20402-E03C-43EA-BFA2-DF16B32D623C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0BF1CD6-34E3-4ED4-9B2A-9B8781E9BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
{A4AE4656-4919-45E2-9680-C317FBCF7693}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{79981945-61F7-4E1A-8949-7808FD75471B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
{62F276F3-9184-4908-A7FB-065B4E491BE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{62F276F3-9184-4908-A7FB-065B4E491BE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{62F276F3-9184-4908-A7FB-065B4E491BE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{62F276F3-9184-4908-A7FB-065B4E491BE2}.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
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{160AE89B-16BE-4FB8-8FEC-255E11D6E860} = {7EC6823E-99FB-403F-9941-0CB08C6BFFD4}
|
||||
{0C7D5C39-2067-4EF1-9C10-72A847FF9CB3} = {17FFA72B-390A-4BF6-A277-2099897D2132}
|
||||
{DADD59B8-4B8B-4F2B-BFEB-0F93B8A6AA55} = {0C7D5C39-2067-4EF1-9C10-72A847FF9CB3}
|
||||
{F02CC33B-B479-480B-B886-25ED0E18494A} = {DADD59B8-4B8B-4F2B-BFEB-0F93B8A6AA55}
|
||||
{A1EB6145-FE32-45EC-8A1B-A76268B6DFDE} = {17FFA72B-390A-4BF6-A277-2099897D2132}
|
||||
{3EE3F061-CC63-41B0-84BA-C95A256E18A9} = {17FFA72B-390A-4BF6-A277-2099897D2132}
|
||||
{12381ECC-7ECD-4D13-911B-83A07BB58134} = {7EC6823E-99FB-403F-9941-0CB08C6BFFD4}
|
||||
{6CCA9961-D498-4E59-8781-D94AE8228200} = {7EC6823E-99FB-403F-9941-0CB08C6BFFD4}
|
||||
{71D20402-E03C-43EA-BFA2-DF16B32D623C} = {17FFA72B-390A-4BF6-A277-2099897D2132}
|
||||
{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}
|
||||
{1FF691E4-E27D-4A7E-861C-4D6291B6EE35} = {943853E7-513D-45EA-870F-549CFC0AF8E8}
|
||||
{4225F3BA-A39D-4680-945E-F2869E98AEA2} = {1FF691E4-E27D-4A7E-861C-4D6291B6EE35}
|
||||
{62F276F3-9184-4908-A7FB-065B4E491BE2} = {4225F3BA-A39D-4680-945E-F2869E98AEA2}
|
||||
{EF5DF177-F4F2-49D5-9E1C-2E37869238D8} = {943853E7-513D-45EA-870F-549CFC0AF8E8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
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: ""
|
|
@ -0,0 +1,14 @@
|
|||
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"
|
|
@ -0,0 +1,29 @@
|
|||
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; }
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using Dapr.Actors;
|
||||
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)
|
||||
{
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.ReadmeCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await Store(context.Org,context.Repo, context.ParentNumber.Value, context.IssueNumber, "readme", "md", "output", item.Data["readme"]);
|
||||
await PublishEvent(Consts.PubSub, Consts.MainTopic, new Event
|
||||
{
|
||||
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.Value, context.IssueNumber, "run", "sh", "output", item.Data["code"]);
|
||||
await RunInSandbox(context.Org,context.Repo, context.ParentNumber.Value, context.IssueNumber);
|
||||
await PublishEvent(Consts.PubSub, Consts.MainTopic, new Event
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
using Dapr.Actors;
|
||||
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 async override Task HandleEvent(Event 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(Consts.PubSub, Consts.MainTopic, new Event
|
||||
{
|
||||
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(Consts.PubSub, Consts.MainTopic, new Event
|
||||
{
|
||||
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 default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DeveloperState
|
||||
{
|
||||
public string Understanding { get; set; }
|
||||
}
|
||||
|
||||
public class UnderstandingResult
|
||||
{
|
||||
public string NewUnderstanding { get; set; }
|
||||
public string Explanation { get; set; }
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class DeveloperSkills {
|
||||
public static 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 static 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 static 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 static 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.
|
||||
""";
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class DevLeadSkills {
|
||||
public static 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 static 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}}
|
||||
""";
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
using Dapr.Actors;
|
||||
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 async override Task HandleEvent(Event 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(Consts.PubSub,Consts.MainTopic, new Event
|
||||
{
|
||||
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(Consts.PubSub,Consts.MainTopic, new Event
|
||||
{
|
||||
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 default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DevLeadPlanResponse
|
||||
{
|
||||
public List<Step> steps { get; set; }
|
||||
}
|
||||
|
||||
public class Step
|
||||
{
|
||||
public string description { get; set; }
|
||||
public string step { get; set; }
|
||||
public List<Subtask> subtasks { get; set; }
|
||||
}
|
||||
|
||||
public class Subtask
|
||||
{
|
||||
public string subtask { get; set; }
|
||||
public string prompt { get; set; }
|
||||
}
|
||||
|
||||
public class DeveloperLeadState
|
||||
{
|
||||
public string Plan { get; set; }
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
using System.Text.Json;
|
||||
using Dapr.Actors;
|
||||
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)
|
||||
{
|
||||
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.Value);
|
||||
var commentBody = $" - #{issue} - tracks {functionName}";
|
||||
await PostComment(context.Org, context.Repo, context.ParentNumber.Value, commentBody);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeStored):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber.Value, context.IssueNumber, "output", branch);
|
||||
await CreatePullRequest(context.Org, context.Repo, context.ParentNumber.Value, branch);
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.SandboxRunFinished):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber.Value, 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public static class PMSkills
|
||||
{
|
||||
public static 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 static 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 static 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}}
|
||||
""";
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using Dapr.Actors;
|
||||
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 async override Task HandleEvent(Event 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(Consts.PubSub,Consts.MainTopic, new Event {
|
||||
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(Consts.PubSub,Consts.MainTopic, new Event {
|
||||
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 default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductManagerState
|
||||
{
|
||||
public string Capabilities { get; set; }
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
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 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)
|
||||
{
|
||||
switch(item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.SandboxRunCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await ScheduleCommitSandboxRun(context.Org, context.Repo, context.ParentNumber.Value, 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(Consts.PubSub,Consts.MainTopic, new Event {
|
||||
Type = nameof(GithubFlowEventType.SandboxRunFinished),
|
||||
Subject = subject,
|
||||
Data = data
|
||||
});
|
||||
await Cleanup();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SandboxMetadata
|
||||
{
|
||||
public string Org { get; set; }
|
||||
public string Repo { get; set; }
|
||||
public long ParentIssueNumber { get; set; }
|
||||
public long IssueNumber { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
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"]
|
|
@ -0,0 +1,69 @@
|
|||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return new Dictionary<string, string> {
|
||||
{ "org", context.Org },
|
||||
{ "repo", context.Repo },
|
||||
{ "issueNumber", $"{context.IssueNumber}" },
|
||||
{ "parentNumber", context.ParentNumber.HasValue? context.ParentNumber.ToString(): default }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class GithubContext
|
||||
{
|
||||
public string Org { get; set; }
|
||||
public 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 Event Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.AI.Agents.Dapr\Microsoft.AI.Agents.Dapr.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<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" Version="2.0.3" />
|
||||
<PackageReference Include="Octokit" Version="10.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.10.0" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.10.0-alpha" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.10.0-alpha" />
|
||||
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.21.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0" />
|
||||
<PackageReference Include="Azure.ResourceManager.ContainerInstance" Version="1.2.0" />
|
||||
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.16.0-beta.1" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.8.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.11.0-beta.1" />
|
||||
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.4.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
<PackageReference Include="Dapr.Actors.AspNetCore" Version="1.13.0" />
|
||||
<PackageReference Include="Dapr.AspNetCore" Version="1.13.0" />
|
||||
<PackageReference Include="CloudNative.CloudEvents.SystemTextJson" Version="2.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,22 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class AzureOptions
|
||||
{
|
||||
[Required]
|
||||
public string SubscriptionId { get; set; }
|
||||
[Required]
|
||||
public string Location { get; set; }
|
||||
[Required]
|
||||
public string ContainerInstancesResourceGroup { get; set; }
|
||||
[Required]
|
||||
public string FilesShareName { get; set; }
|
||||
[Required]
|
||||
public string FilesAccountName { get; set; }
|
||||
[Required]
|
||||
public string FilesAccountKey { get; set; }
|
||||
[Required]
|
||||
public string SandboxImage { get; set; }
|
||||
public string ManagedIdentity { get; set; }
|
||||
public string CosmosConnectionString { get; set; }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
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";
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class GithubOptions
|
||||
{
|
||||
[Required]
|
||||
public string AppKey { get; set; }
|
||||
[Required]
|
||||
public int AppId { get; set; }
|
||||
[Required]
|
||||
public long InstallationId { get; set; }
|
||||
[Required]
|
||||
public string WebhookSecret { get; set; }
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class OpenAIOptions
|
||||
{
|
||||
[Required]
|
||||
public string ServiceType { get; set; }
|
||||
[Required]
|
||||
public string ServiceId { get; set; }
|
||||
[Required]
|
||||
public string DeploymentOrModelId { get; set; }
|
||||
[Required]
|
||||
public string EmbeddingDeploymentOrModelId { get; set; }
|
||||
[Required]
|
||||
public string Endpoint { get; set; }
|
||||
[Required]
|
||||
public string ApiKey { get; set; }
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class QdrantOptions
|
||||
{
|
||||
[Required]
|
||||
public string Endpoint { get; set; }
|
||||
[Required]
|
||||
public int VectorSize { get; set; }
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marketing.Options;
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
public class ServiceOptions
|
||||
{
|
||||
private string _ingesterUrl;
|
|
@ -0,0 +1,205 @@
|
|||
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.GetService<IOptions<GithubOptions>>();
|
||||
var logger = s.GetService<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.GetService<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)
|
||||
{
|
||||
try
|
||||
{
|
||||
var proxyOptions = new ActorProxyOptions
|
||||
{
|
||||
RequestTimeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
var proxy = proxyFactory.Create(new ActorId(evt.Data.Subject), type, proxyOptions);
|
||||
await proxy.InvokeMethodAsync(method, evt.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static ISemanticTextMemory CreateMemory(IServiceProvider provider)
|
||||
{
|
||||
var openAiConfig = provider.GetService<IOptions<OpenAIOptions>>().Value;
|
||||
var qdrantConfig = provider.GetService<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.GetService<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();
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
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) : 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);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AI.DevTeam.Dapr;
|
||||
|
||||
public interface IAnalyzeCode
|
||||
{
|
||||
Task<IEnumerable<CodeAnalysis>> 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)
|
||||
{
|
||||
_serviceOptions = serviceOptions.Value;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_httpClient.BaseAddress = new Uri(_serviceOptions.IngesterUrl);
|
||||
|
||||
}
|
||||
public async Task<IEnumerable<CodeAnalysis>> Analyze(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new CodeAnalysisRequest { Content = content };
|
||||
var body = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PostAsync("api/AnalyzeCode", body);
|
||||
var stringResult = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<IEnumerable<CodeAnalysis>>(stringResult);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error analyzing code");
|
||||
return Enumerable.Empty<CodeAnalysis>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CodeAnalysisRequest
|
||||
{
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class CodeAnalysis
|
||||
{
|
||||
public string Meaning { get; set; }
|
||||
public string CodeBlock { get; set; }
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
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)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
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)
|
||||
{
|
||||
_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}/", "")
|
||||
.Replace($"{dirName}/", "");
|
||||
var fileStream = await file.OpenReadAsync();
|
||||
using (var reader = new StreamReader(fileStream, Encoding.UTF8))
|
||||
{
|
||||
var value = reader.ReadToEnd();
|
||||
|
||||
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 {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)
|
||||
{
|
||||
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(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 string Name { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public interface IManageGithub
|
||||
{
|
||||
Task<int> CreateIssue(string org, string repo, string input, string function, 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);
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
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)
|
||||
{
|
||||
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.Where(k => k != "Parent").FirstOrDefault();
|
||||
long? parentNumber = labels.ContainsKey("Parent") ? long.Parse(labels["Parent"]) : 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)
|
||||
{
|
||||
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.Where(k => k != "Parent").FirstOrDefault();
|
||||
long? parentNumber = labels.ContainsKey("Parent") ? long.Parse(labels["Parent"]) : 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()}
|
||||
};
|
||||
|
||||
var evt = new Event
|
||||
{
|
||||
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()},
|
||||
{ "input" , input}
|
||||
};
|
||||
var evt = new Event
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"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,6 +1,7 @@
|
|||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.Agents.Orleans;
|
||||
using Microsoft.AI.DevTeam.Events;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AI.DevTeam;
|
||||
|
||||
|
@ -21,17 +22,22 @@ public class AzureGenie : Agent
|
|||
{
|
||||
case nameof(GithubFlowEventType.ReadmeCreated):
|
||||
{
|
||||
var parentNumber = long.Parse(item.Data["parentNumber"]);
|
||||
var issueNumber = long.Parse(item.Data["issueNumber"]);
|
||||
await Store(item.Data["org"], item.Data["repo"], parentNumber, issueNumber, "readme", "md", "output", item.Message);
|
||||
var data = item.Data;
|
||||
var parentNumber = long.Parse(data["parentNumber"].ToString());
|
||||
var issueNumber = long.Parse(data["issueNumber"].ToString());
|
||||
var org = data["org"].ToString();
|
||||
var repo = data["repo"].ToString();
|
||||
var subject = $"{org}/{repo}/{issueNumber}";
|
||||
await Store(org,repo, parentNumber, issueNumber, "readme", "md", "output", data["readme"].ToString());
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.ReadmeStored),
|
||||
Subject = subject,
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "parentNumber", item.Data["parentNumber"] }
|
||||
{ "org", org },
|
||||
{ "repo", repo },
|
||||
{ "issueNumber", $"{issueNumber}" },
|
||||
{ "parentNumber", $"{parentNumber}" }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -39,18 +45,23 @@ public class AzureGenie : Agent
|
|||
break;
|
||||
case nameof(GithubFlowEventType.CodeCreated):
|
||||
{
|
||||
var parentNumber = long.Parse(item.Data["parentNumber"]);
|
||||
var issueNumber = long.Parse(item.Data["issueNumber"]);
|
||||
await Store(item.Data["org"], item.Data["repo"], parentNumber, issueNumber, "run", "sh", "output", item.Message);
|
||||
await RunInSandbox(item.Data["org"], item.Data["repo"], parentNumber, issueNumber);
|
||||
var data = item.Data;
|
||||
var parentNumber = long.Parse(data["parentNumber"].ToString());
|
||||
var issueNumber = long.Parse(data["issueNumber"].ToString());
|
||||
var org = data["org"].ToString();
|
||||
var repo = data["repo"].ToString();
|
||||
var subject = $"{org}/{repo}/{issueNumber}";
|
||||
await Store(org,repo, parentNumber, issueNumber, "run", "sh", "output", data["code"].ToString());
|
||||
await RunInSandbox(org, repo, parentNumber, issueNumber);
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.SandboxRunCreated),
|
||||
Subject = subject,
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "parentNumber", item.Data["parentNumber"] }
|
||||
{ "org", org },
|
||||
{ "repo", repo },
|
||||
{ "issueNumber", $"{issueNumber}" },
|
||||
{ "parentNumber", $"{parentNumber}" }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ namespace Microsoft.AI.DevTeam;
|
|||
public class Dev : AiAgent<DeveloperState>, IDevelopApps
|
||||
{
|
||||
protected override string Namespace => Consts.MainNamespace;
|
||||
|
||||
|
||||
private readonly ILogger<Dev> _logger;
|
||||
|
||||
public Dev([PersistentState("state", "messages")] IPersistentState<AgentState<DeveloperState>> state, Kernel kernel, ISemanticTextMemory memory, ILogger<Dev> logger)
|
||||
public Dev([PersistentState("state", "messages")] IPersistentState<AgentState<DeveloperState>> state, Kernel kernel, ISemanticTextMemory memory, ILogger<Dev> logger)
|
||||
: base(state, memory, kernel)
|
||||
{
|
||||
_logger = logger;
|
||||
|
@ -25,33 +25,34 @@ public class Dev : AiAgent<DeveloperState>, IDevelopApps
|
|||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.CodeGenerationRequested):
|
||||
var code = await GenerateCode(item.Message);
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.CodeGenerated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "code", code }
|
||||
},
|
||||
Message = code
|
||||
});
|
||||
var context = item.ToGithubContext();
|
||||
var code = await GenerateCode(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"] = code;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.CodeGenerated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(GithubFlowEventType.CodeChainClosed):
|
||||
var lastCode = _state.State.History.Last().Message;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.CodeCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "code", lastCode },
|
||||
{ "parentNumber", item.Data["parentNumber"] }
|
||||
},
|
||||
Message = lastCode
|
||||
});
|
||||
var context = item.ToGithubContext();
|
||||
var lastCode = _state.State.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["code"] = lastCode;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.CodeCreated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -63,9 +64,9 @@ public class Dev : AiAgent<DeveloperState>, IDevelopApps
|
|||
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 context = new KernelArguments { ["input"] = AppendChatHistory(ask) };
|
||||
var instruction = "Consider the following architectural guidelines:!waf!";
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf",context);
|
||||
var enhancedContext = await AddKnowledge(instruction, "waf", context);
|
||||
return await CallFunction(DeveloperSkills.Implement, enhancedContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -8,7 +8,7 @@ public static class DeveloperSkills {
|
|||
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}}
|
||||
{{$wafContext}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public static string Improve = """
|
||||
|
@ -20,7 +20,7 @@ public static class DeveloperSkills {
|
|||
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}}
|
||||
{{$wafContext}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public static string Explain = """
|
||||
|
|
|
@ -32,7 +32,7 @@ public static class DevLeadSkills {
|
|||
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}}
|
||||
{{$wafContext}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public static string Explain = """
|
||||
|
|
|
@ -3,6 +3,7 @@ using Microsoft.AI.Agents.Orleans;
|
|||
using Microsoft.AI.DevTeam.Events;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Orleans.Runtime;
|
||||
|
||||
namespace Microsoft.AI.DevTeam;
|
||||
|
@ -23,33 +24,34 @@ public class DeveloperLead : AiAgent<DeveloperLeadState>, ILeadDevelopers
|
|||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.DevPlanRequested):
|
||||
var plan = await CreatePlan(item.Message);
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.DevPlanGenerated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "plan", plan }
|
||||
},
|
||||
Message = plan
|
||||
});
|
||||
var context = item.ToGithubContext();
|
||||
var plan = await CreatePlan(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"] = plan;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.DevPlanGenerated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(GithubFlowEventType.DevPlanChainClosed):
|
||||
var latestPlan = _state.State.History.Last().Message;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.DevPlanCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{"parentNumber", item.Data["parentNumber"]},
|
||||
{ "plan", latestPlan }
|
||||
},
|
||||
Message = latestPlan
|
||||
});
|
||||
var context = item.ToGithubContext();
|
||||
var latestPlan = _state.State.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["plan"] = latestPlan;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(GithubFlowEventType.DevPlanCreated),
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -22,49 +22,52 @@ public class Hubber : Agent
|
|||
{
|
||||
case nameof(GithubFlowEventType.NewAsk):
|
||||
{
|
||||
var parentNumber = long.Parse(item.Data["issueNumber"]);
|
||||
var pmIssue = await CreateIssue(item.Data["org"], item.Data["repo"], item.Message, "PM.Readme", parentNumber);
|
||||
var devLeadIssue = await CreateIssue(item.Data["org"], item.Data["repo"], item.Message, "DevLead.Plan", parentNumber);
|
||||
await PostComment(item.Data["org"], item.Data["repo"], parentNumber, $" - #{pmIssue} - tracks PM.Readme");
|
||||
await PostComment(item.Data["org"], item.Data["repo"], parentNumber, $" - #{devLeadIssue} - tracks DevLead.Plan");
|
||||
await CreateBranch(item.Data["org"], item.Data["repo"], $"sk-{parentNumber}");
|
||||
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 contents = string.IsNullOrEmpty(item.Message)? "Sorry, I got tired, can you try again please? ": item.Message;
|
||||
await PostComment(item.Data["org"], item.Data["repo"], long.Parse(item.Data["issueNumber"]), contents);
|
||||
{
|
||||
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));
|
||||
var parentNumber = long.Parse(item.Data["parentNumber"]);
|
||||
|
||||
foreach (var prompt in prompts)
|
||||
{
|
||||
var functionName = "Developer.Implement";
|
||||
var issue = await CreateIssue(item.Data["org"], item.Data["repo"], prompt, functionName, parentNumber);
|
||||
var issue = await CreateIssue(context.Org, context.Repo, prompt, functionName, context.ParentNumber.Value);
|
||||
var commentBody = $" - #{issue} - tracks {functionName}";
|
||||
await PostComment(item.Data["org"], item.Data["repo"], parentNumber, commentBody);
|
||||
await PostComment(context.Org, context.Repo, context.ParentNumber.Value, commentBody);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeStored):
|
||||
{
|
||||
var parentNumber = long.Parse(item.Data["parentNumber"]);
|
||||
var issueNumber = long.Parse(item.Data["issueNumber"]);
|
||||
var branch = $"sk-{parentNumber}";
|
||||
await CommitToBranch(item.Data["org"], item.Data["repo"], parentNumber, issueNumber, "output", branch);
|
||||
await CreatePullRequest(item.Data["org"], item.Data["repo"], parentNumber, branch);
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber.Value, context.IssueNumber, "output", branch);
|
||||
await CreatePullRequest(context.Org, context.Repo, context.ParentNumber.Value, branch);
|
||||
}
|
||||
break;
|
||||
case nameof(GithubFlowEventType.SandboxRunFinished):
|
||||
{
|
||||
var parentNumber = long.Parse(item.Data["parentNumber"]);
|
||||
var issueNumber = long.Parse(item.Data["issueNumber"]);
|
||||
var branch = $"sk-{parentNumber}";
|
||||
await CommitToBranch(item.Data["org"], item.Data["repo"], parentNumber, issueNumber, "output", branch);
|
||||
var context = item.ToGithubContext();
|
||||
var branch = $"sk-{context.ParentNumber}";
|
||||
await CommitToBranch(context.Org, context.Repo, context.ParentNumber.Value, context.IssueNumber, "output", branch);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -7,7 +7,7 @@ public static string BootstrapProject = """
|
|||
You may include commands to build the applications but do not run them.
|
||||
Do not include any git commands.
|
||||
Input: {{$input}}
|
||||
{{$wafContext}}
|
||||
{{$waf}}
|
||||
""";
|
||||
public static string Readme = """
|
||||
You are a program manager on a software development team. You are working on an app described below.
|
||||
|
@ -15,7 +15,7 @@ public static string BootstrapProject = """
|
|||
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}}
|
||||
{{$wafContext}}
|
||||
{{$waf}}
|
||||
""";
|
||||
|
||||
public static string Explain = """
|
||||
|
|
|
@ -24,31 +24,32 @@ public class ProductManager : AiAgent<ProductManagerState>, IManageProducts
|
|||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.ReadmeRequested):
|
||||
var readme = await CreateReadme(item.Message);
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var readme = await CreateReadme(item.Data["input"]);
|
||||
var data = context.ToData();
|
||||
data["result"]=readme;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event {
|
||||
Type = nameof(GithubFlowEventType.ReadmeGenerated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "readme", readme }
|
||||
},
|
||||
Message = readme
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case nameof(GithubFlowEventType.ReadmeChainClosed):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
var lastReadme = _state.State.History.Last().Message;
|
||||
var data = context.ToData();
|
||||
data["readme"] = lastReadme;
|
||||
await PublishEvent(Consts.MainNamespace, this.GetPrimaryKeyString(), new Event {
|
||||
Type = nameof(GithubFlowEventType.ReadmeCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "org", item.Data["org"] },
|
||||
{ "repo", item.Data["repo"] },
|
||||
{ "issueNumber", item.Data["issueNumber"] },
|
||||
{ "readme", lastReadme },
|
||||
{ "parentNumber", item.Data["parentNumber"] }
|
||||
},
|
||||
Message = lastReadme
|
||||
Subject = context.Subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
using Microsoft.AI.Agents.Orleans;
|
||||
using Microsoft.AI.DevTeam.Events;
|
||||
using Orleans.Runtime;
|
||||
using Orleans.Streams;
|
||||
using Orleans.Timers;
|
||||
|
||||
namespace Microsoft.AI.DevTeam;
|
||||
[ImplicitStreamSubscription(Consts.MainNamespace)]
|
||||
public class Sandbox : Agent, IRemindable
|
||||
{
|
||||
protected override string Namespace => Consts.MainNamespace;
|
||||
protected override string Namespace => Consts.MainNamespace;
|
||||
private const string ReminderName = "SandboxRunReminder";
|
||||
private readonly IManageAzure _azService;
|
||||
private readonly IReminderRegistry _reminderRegistry;
|
||||
|
@ -26,18 +25,18 @@ public class Sandbox : Agent, IRemindable
|
|||
}
|
||||
public override async Task HandleEvent(Event item)
|
||||
{
|
||||
switch(item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.SandboxRunCreated):
|
||||
var org = item.Data["org"];
|
||||
var repo = item.Data["repo"];
|
||||
var parentIssueNumber = long.Parse(item.Data["parentNumber"]);
|
||||
var issueNumber = long.Parse(item.Data["issueNumber"]);
|
||||
await ScheduleCommitSandboxRun(org, repo, parentIssueNumber, issueNumber);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (item.Type)
|
||||
{
|
||||
case nameof(GithubFlowEventType.SandboxRunCreated):
|
||||
{
|
||||
var context = item.ToGithubContext();
|
||||
await ScheduleCommitSandboxRun(context.Org, context.Repo, context.ParentNumber.Value, context.IssueNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
public async Task ScheduleCommitSandboxRun(string org, string repo, long parentIssueNumber, long issueNumber)
|
||||
{
|
||||
|
@ -54,6 +53,7 @@ public class Sandbox : Agent, IRemindable
|
|||
if (!_state.State.IsCompleted)
|
||||
{
|
||||
var sandboxId = $"sk-sandbox-{_state.State.Org}-{_state.State.Repo}-{_state.State.ParentIssueNumber}-{_state.State.IssueNumber}".ToLower();
|
||||
|
||||
if (await _azService.IsSandboxCompleted(sandboxId))
|
||||
{
|
||||
await _azService.DeleteSandbox(sandboxId);
|
||||
|
@ -94,7 +94,7 @@ public class Sandbox : Agent, IRemindable
|
|||
await _state.WriteStateAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,4 +21,42 @@ namespace Microsoft.AI.DevTeam.Events
|
|||
DevPlanCreated,
|
||||
SandboxRunCreated
|
||||
}
|
||||
}
|
||||
|
||||
public static class EventExtensions
|
||||
{
|
||||
public static GithubContext ToGithubContext(this Event 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)
|
||||
{
|
||||
return new Dictionary<string, string> {
|
||||
{ "org", context.Org },
|
||||
{ "repo", context.Repo },
|
||||
{ "issueNumber", $"{context.IssueNumber}" },
|
||||
{ "parentNumber", context.ParentNumber.HasValue? default: context.ParentNumber.ToString() }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class GithubContext
|
||||
{
|
||||
public string Org { get; set; }
|
||||
public string Repo { get; set; }
|
||||
public long IssueNumber { get; set; }
|
||||
public long? ParentNumber { get; set; }
|
||||
|
||||
public string Subject => $"{Org}/{Repo}/{IssueNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Octokit.Webhooks.AspNetCore" Version="2.0.3" />
|
||||
<PackageReference Include="Octokit" Version="10.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.10.0" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.10.0-alpha" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.10.0-alpha" />
|
||||
|
@ -44,9 +43,7 @@
|
|||
<PackageReference Include="Azure.Identity" Version="1.11.0-beta.1" />
|
||||
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.4.1" />
|
||||
<PackageReference Include="Microsoft.Orleans.Serialization.NewtonsoftJson" Version="8.0.0" />
|
||||
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -87,15 +87,16 @@ builder.Services.AddSingleton<IAnalyzeCode, CodeAnalyzer>();
|
|||
builder.Host.UseOrleans(siloBuilder =>
|
||||
{
|
||||
siloBuilder.UseDashboard(x => x.HostSelf = true);
|
||||
siloBuilder.Services.AddSerializer( sb => {
|
||||
sb.AddNewtonsoftJsonSerializer(isSupported: t => true);
|
||||
});
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
siloBuilder.UseLocalhostClustering()
|
||||
.AddMemoryStreams("StreamProvider")
|
||||
.AddMemoryGrainStorage("PubSubStore")
|
||||
.AddMemoryGrainStorage("messages");
|
||||
|
||||
siloBuilder.UseInMemoryReminderService();
|
||||
siloBuilder.UseDashboard(x => x.HostSelf = true);
|
||||
|
||||
siloBuilder.UseInMemoryReminderService();
|
||||
}
|
||||
else
|
||||
|
@ -144,7 +145,6 @@ builder.Host.UseOrleans(siloBuilder =>
|
|||
.AddMemoryStreams("StreamProvider")
|
||||
.AddMemoryGrainStorage("PubSubStore");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
builder.Services.Configure<JsonSerializerOptions>(options =>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.AI.Agents.Abstractions;
|
||||
using Microsoft.AI.DevTeam;
|
||||
using Microsoft.AI.DevTeam.Events;
|
||||
using Octokit.Webhooks;
|
||||
using Octokit.Webhooks.Events;
|
||||
|
@ -97,8 +96,9 @@ public sealed class GithubWebHookProcessor : WebhookEventProcessor
|
|||
|
||||
private async Task HandleClosingIssue(long issueNumber, long? parentNumber, string skillName, string functionName, string suffix, string org, string repo)
|
||||
{
|
||||
var subject = suffix+issueNumber.ToString();
|
||||
var streamProvider = _client.GetStreamProvider("StreamProvider");
|
||||
var streamId = StreamId.Create(Consts.MainNamespace, suffix+issueNumber.ToString());
|
||||
var streamId = StreamId.Create(Consts.MainNamespace, subject);
|
||||
var stream = streamProvider.GetStream<Event>(streamId);
|
||||
var eventType = (skillName, functionName) switch
|
||||
{
|
||||
|
@ -118,6 +118,7 @@ public sealed class GithubWebHookProcessor : WebhookEventProcessor
|
|||
await stream.OnNextAsync(new Event
|
||||
{
|
||||
Type = eventType,
|
||||
Subject = subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
@ -127,8 +128,9 @@ public sealed class GithubWebHookProcessor : WebhookEventProcessor
|
|||
try
|
||||
{
|
||||
_logger.LogInformation("Handling new ask");
|
||||
var subject = suffix+issueNumber.ToString();
|
||||
var streamProvider = _client.GetStreamProvider("StreamProvider");
|
||||
var streamId = StreamId.Create(Consts.MainNamespace, suffix+issueNumber.ToString());
|
||||
var streamId = StreamId.Create(Consts.MainNamespace, subject);
|
||||
var stream = streamProvider.GetStream<Event>(streamId);
|
||||
|
||||
var eventType = (skillName, functionName) switch
|
||||
|
@ -144,12 +146,14 @@ public sealed class GithubWebHookProcessor : WebhookEventProcessor
|
|||
{ "org", org },
|
||||
{ "repo", repo },
|
||||
{ "issueNumber", issueNumber.ToString() },
|
||||
{ "parentNumber", parentNumber?.ToString()}
|
||||
{ "parentNumber", parentNumber?.ToString()},
|
||||
{ "input", input}
|
||||
|
||||
};
|
||||
await stream.OnNextAsync(new Event
|
||||
{
|
||||
Type = eventType,
|
||||
Message = input,
|
||||
Subject = subject,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,17 +36,18 @@ public class CommunityManager : AiAgent<CommunityManagerState>
|
|||
await SendDesignedCreatedEvent(lastMessage, item.Data["UserId"]);
|
||||
break;
|
||||
|
||||
case nameof(EventTypes.ArticleCreated):
|
||||
//var lastCode = _state.State.History.Last().Message;
|
||||
case nameof(EventTypes.ArticleCreated):
|
||||
{
|
||||
var article = item.Data["article"];
|
||||
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.ArticleCreated)}. UserMessage: {item.Message}");
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.ArticleCreated)}. Article: {article}");
|
||||
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(item.Message) };
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(article) };
|
||||
string socialMediaPost = await CallFunction(CommunityManagerPrompts.WritePost, context);
|
||||
_state.State.Data.WrittenSocialMediaPost = socialMediaPost;
|
||||
await SendDesignedCreatedEvent(socialMediaPost, item.Data["UserId"]);
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -59,8 +60,8 @@ public class CommunityManager : AiAgent<CommunityManagerState>
|
|||
Type = nameof(EventTypes.SocialMediaPostCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "UserId", userId },
|
||||
},
|
||||
Message = socialMediaPost
|
||||
{ nameof(socialMediaPost), socialMediaPost}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -42,10 +42,11 @@ public class GraphicDesigner : AiAgent<GraphicDesignerState>
|
|||
|
||||
break;
|
||||
case nameof(EventTypes.ArticleCreated):
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.ArticleCreated)}. UserMessage: {item.Message}");
|
||||
|
||||
//TODO
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.ArticleCreated)}.");
|
||||
var article = item.Data["article"];
|
||||
var dallEService = _kernel.GetRequiredService<ITextToImageService>();
|
||||
var imageUri = await dallEService.GenerateImageAsync(item.Message, 1024, 1024);
|
||||
var imageUri = await dallEService.GenerateImageAsync(article, 1024, 1024);
|
||||
|
||||
_state.State.Data.imageUrl = imageUri;
|
||||
|
||||
|
@ -58,15 +59,15 @@ public class GraphicDesigner : AiAgent<GraphicDesignerState>
|
|||
}
|
||||
}
|
||||
|
||||
private async Task SendDesignedCreatedEvent(string AbsoluteImageUri, string userId)
|
||||
private async Task SendDesignedCreatedEvent(string imageUri, string userId)
|
||||
{
|
||||
await PublishEvent(Consts.OrleansNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(EventTypes.GraphicDesignCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "UserId", userId },
|
||||
},
|
||||
Message = AbsoluteImageUri
|
||||
{ nameof(imageUri), imageUri}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -27,17 +27,17 @@ public class SignalR : Agent
|
|||
switch (item.Type)
|
||||
{
|
||||
case nameof(EventTypes.ArticleCreated):
|
||||
var writenArticle = item.Message;
|
||||
var writenArticle = item.Data["article"];
|
||||
await _signalRClient.SendMessageToSpecificClient(item.Data["UserId"], writenArticle, AgentTypes.Chat);
|
||||
break;
|
||||
|
||||
case nameof(EventTypes.GraphicDesignCreated):
|
||||
var imageUrl = item.Message;
|
||||
var imageUrl = item.Data["imageUri"];
|
||||
await _signalRClient.SendMessageToSpecificClient(item.Data["UserId"], imageUrl, AgentTypes.GraphicDesigner);
|
||||
break;
|
||||
|
||||
case nameof(EventTypes.SocialMediaPostCreated):
|
||||
var post = item.Message;
|
||||
var post = item.Data["socialMediaPost"];
|
||||
await _signalRClient.SendMessageToSpecificClient(item.Data["UserId"], post, AgentTypes.CommunityManager);
|
||||
break;
|
||||
|
||||
|
|
|
@ -38,30 +38,31 @@ public class Writer : AiAgent<WriterState>, IWriter
|
|||
break;
|
||||
|
||||
case nameof(EventTypes.UserChatInput):
|
||||
{
|
||||
var userMessage = item.Data["userMessage"];
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.UserChatInput)}. UserMessage: {userMessage}");
|
||||
|
||||
_logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(EventTypes.UserChatInput)}. UserMessage: {item.Message}");
|
||||
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(item.Message) };
|
||||
string newArticle = await CallFunction(WriterPrompts.Write, context);
|
||||
var context = new KernelArguments { ["input"] = AppendChatHistory(userMessage) };
|
||||
string newArticle = await CallFunction(WriterPrompts.Write, context);
|
||||
|
||||
await SendDesignedCreatedEvent(newArticle, item.Data["UserId"]);
|
||||
|
||||
break;
|
||||
await SendDesignedCreatedEvent(newArticle, item.Data["UserId"]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendDesignedCreatedEvent(string writtenArticle, string userId)
|
||||
private async Task SendDesignedCreatedEvent(string article, string userId)
|
||||
{
|
||||
await PublishEvent(Consts.OrleansNamespace, this.GetPrimaryKeyString(), new Event
|
||||
{
|
||||
Type = nameof(EventTypes.ArticleCreated),
|
||||
Data = new Dictionary<string, string> {
|
||||
{ "UserId", userId },
|
||||
{ "UserMessage", writtenArticle },
|
||||
},
|
||||
Message = writtenArticle
|
||||
{ nameof(article), article },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,6 @@ namespace Marketing.Controller
|
|||
await stream.OnNextAsync(new Event
|
||||
{
|
||||
Type = nameof(EventTypes.UserChatInput),
|
||||
Message = userMessage,
|
||||
Data = data
|
||||
});
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.SemanticKernel.Connectors.OpenAI;
|
|||
using Marketing.SignalRHub;
|
||||
using Marketing.Options;
|
||||
using Marketing;
|
||||
using Orleans.Serialization;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddTransient(CreateKernel);
|
||||
|
@ -54,14 +55,6 @@ builder.Services.AddOptions<QdrantOptions>()
|
|||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<ServiceOptions>()
|
||||
.Configure<IConfiguration>((settings, configuration) =>
|
||||
{
|
||||
configuration.GetSection(nameof(ServiceOptions)).Bind(settings);
|
||||
})
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Host.UseOrleans(siloBuilder =>
|
||||
{
|
||||
siloBuilder.UseLocalhostClustering()
|
||||
|
|
|
@ -41,7 +41,6 @@ public class ArticleHub : Hub<IArticleHub>
|
|||
await stream.OnNextAsync(new Event
|
||||
{
|
||||
Type = nameof(EventTypes.UserChatInput),
|
||||
Message = frontEndMessage.Message,
|
||||
Data = data
|
||||
});
|
||||
|
||||
|
@ -70,7 +69,6 @@ public class ArticleHub : Hub<IArticleHub>
|
|||
await stream.OnNextAsync(new Event
|
||||
{
|
||||
Type = nameof(EventTypes.UserConnected),
|
||||
Message = frontEndMessage.Message,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,13 +14,15 @@
|
|||
},
|
||||
|
||||
"AllowedHosts": "*",
|
||||
"SANDBOX_IMAGE": "mcr.microsoft.com/dotnet/sdk:7.0",
|
||||
|
||||
"OpenAIOptions": {
|
||||
"ChatDeploymentOrModelId": "gpt-4-32",
|
||||
"EmbeddingDeploymentOrModelId": "text-embedding-ada-002",
|
||||
"ChatEndpoint": "https://<mandatory>.openai.azure.com/",
|
||||
"ChatApiKey": "<mandatory>",
|
||||
"EmbeddingsDeploymentOrModelId": "text-embedding-ada-002",
|
||||
"EmbeddingsEndpoint": "https://<mandatory>.openai.azure.com/",
|
||||
"EmbeddingsApiKey": "<mandatory>",
|
||||
"ImageDeploymentOrModelId":"dalle3",
|
||||
"ImageEndpoint": "https://<mandatory>.openai.azure.com/",
|
||||
"ImageApiKey": "<mandatory>"
|
||||
},
|
||||
|
@ -28,10 +30,5 @@
|
|||
"QdrantOptions": {
|
||||
"Endpoint": "http://qdrant:6333",
|
||||
"VectorSize": "1536"
|
||||
},
|
||||
|
||||
"ServiceOptions": {
|
||||
"IngesterUrl": "http://localhost:7071"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,27 @@
|
|||
namespace Microsoft.AI.Agents.Dapr;
|
||||
using Dapr.Actors.Runtime;
|
||||
using Dapr.Client;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
public class Agent
|
||||
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(string ns, string id, Event item)
|
||||
{
|
||||
var metadata = new Dictionary<string, string>() {
|
||||
{ "cloudevent.Type", item.Type },
|
||||
{ "cloudevent.Subject", item.Subject },
|
||||
{ "cloudevent.id", Guid.NewGuid().ToString()}
|
||||
};
|
||||
|
||||
await daprClient.PublishEventAsync(ns, id, item, metadata);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
using System.Text;
|
||||
using Dapr.Actors;
|
||||
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;
|
||||
|
||||
|
||||
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 == null) ? new OpenAIPromptExecutionSettings { MaxTokens = 18000, Temperature = 0.8, TopP = 1 }
|
||||
: settings;
|
||||
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($"{doc.Metadata.Text}");
|
||||
}
|
||||
arguments[index] = instruction.Replace($"!{index}!", $"{kbStringBuilder}");
|
||||
return arguments;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using Dapr.Actors;
|
||||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
namespace Microsoft.AI.Agents.Dapr;
|
||||
|
||||
public interface IDaprAgent : IActor
|
||||
{
|
||||
Task HandleEvent(Event item);
|
||||
}
|
|
@ -7,11 +7,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapr.Client" Version="1.13.0" />
|
||||
<PackageReference Include="Dapr.Actors" Version="1.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj" />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AI.Agents\Microsoft.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
using Microsoft.AI.Agents.Abstractions;
|
||||
|
||||
namespace Microsoft.AI.Agents.Orleans;
|
||||
|
||||
[GenerateSerializer]
|
||||
public struct EventSurrogate
|
||||
{
|
||||
[Id(0)]
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
[Id(1)]
|
||||
public string Type { get; set; }
|
||||
[Id(2)]
|
||||
public string Subject { get; set; }
|
||||
}
|
||||
|
||||
[RegisterConverter]
|
||||
public sealed class EventSurrogateConverter :
|
||||
IConverter<Event, EventSurrogate>
|
||||
{
|
||||
public Event ConvertFromSurrogate(
|
||||
in EventSurrogate surrogate) =>
|
||||
new Event { Data = surrogate.Data, Subject = surrogate.Subject, Type = surrogate.Type};
|
||||
|
||||
public EventSurrogate ConvertToSurrogate(
|
||||
in Event value) =>
|
||||
new EventSurrogate
|
||||
{
|
||||
Data = value.Data,
|
||||
Type = value.Type,
|
||||
Subject = value.Subject
|
||||
};
|
||||
}
|
|
@ -1,16 +1,12 @@
|
|||
using Orleans;
|
||||
using Orleans.CodeGeneration;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.AI.Agents.Abstractions
|
||||
{
|
||||
[GenerateSerializer]
|
||||
[DataContract]
|
||||
public class Event
|
||||
{
|
||||
[Id(0)]
|
||||
public string Message { get; set; }
|
||||
[Id(1)]
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
[Id(2)]
|
||||
public string Type { get; set; }
|
||||
public string Subject { get; set; }
|
||||
}
|
||||
}
|
|
@ -4,4 +4,4 @@ public interface IAgent
|
|||
{
|
||||
Task HandleEvent(Event item);
|
||||
Task PublishEvent(string ns, string id, Event item);
|
||||
}
|
||||
}
|
|
@ -7,21 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="8.1.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.Orleans.Server" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Runtime" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Persistence.Cosmos" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Clustering.Cosmos" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Reminders.Cosmos" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Streaming.EventHubs" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Reminders" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Orleans.Streaming" Version="8.1.0" />
|
||||
<PackageReference Include="OrleansDashboard" Version="8.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.9.0" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue