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:
Kosta Petan 2024-05-14 14:18:24 +02:00 committed by GitHub
parent 949520bba6
commit e9a7a07e13
74 changed files with 2429 additions and 403 deletions

View File

@ -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"
]
}
}

View File

@ -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

View File

@ -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

View File

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

26
.vscode/launch.json vendored
View File

@ -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"
}
]
}

View File

@ -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"
}

114
.vscode/tasks.json vendored
View File

@ -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"
}
]
}

View File

@ -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

View File

@ -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: ""

View File

@ -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"

View File

@ -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; }
}

View File

@ -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);
}
}

View File

@ -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; }
}

View File

@ -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.
""";
}

View File

@ -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}}
""";
}

View File

@ -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; }
}

View File

@ -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);
}
}

View File

@ -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}}
""";
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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"]

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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; }
}

View File

@ -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";
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace Marketing.Options;
namespace Microsoft.AI.DevTeam.Dapr;
public class ServiceOptions
{
private string _ingesterUrl;

View File

@ -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();
}

View File

@ -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"
}
}
}
}

View File

@ -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);
}

View File

@ -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; }
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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}" }
}
});
}

View File

@ -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)

View File

@ -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 = """

View File

@ -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 = """

View File

@ -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;

View File

@ -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:

View File

@ -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 = """

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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}";
}
}

View File

@ -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>

View File

@ -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 =>

View File

@ -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
});
}

View File

@ -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}
}
});
}

View File

@ -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}
}
});
}
}

View File

@ -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;

View File

@ -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 },
}
});
}

View File

@ -54,7 +54,6 @@ namespace Marketing.Controller
await stream.OnNextAsync(new Event
{
Type = nameof(EventTypes.UserChatInput),
Message = userMessage,
Data = data
});

View File

@ -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()

View File

@ -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
});
}

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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
};
}

View File

@ -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; }
}
}

View File

@ -4,4 +4,4 @@ public interface IAgent
{
Task HandleEvent(Event item);
Task PublishEvent(string ns, string id, Event item);
}
}

View File

@ -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>