├── .gitattributes ├── .gitignore ├── AgenticChatWorkflows.csproj ├── AgenticChatWorkflows.sln ├── AgenticChatWorkflows ├── AgenticWorkflowType.cs ├── ChatWorkflows │ ├── CodeCrafterWorkflowChatFactory.cs │ ├── NestedChatMiddlewareWorkflowFactory.cs │ ├── NestedChatWithGroupAgentChatWorkflowFactory.cs │ ├── SemanticKernelWithAutoGenPluginChatFactory.cs │ ├── SequentialAgentChatWorkflowFactory.cs │ ├── SequentialTwoAgentChatWorkflowFactory.cs │ ├── TestV2ChatWorkflowFactory.cs │ ├── TestWorkflowChatFactory.cs │ └── TwoAgentChatWorkflowFactory.cs ├── Context.cs ├── EnvironmentWellKnown.cs └── StringToFlowDocumentConverter.cs ├── AgenticWorkflowModel.cs ├── App.xaml ├── App.xaml.cs ├── ApprovalTerminationStrategy.cs ├── AssemblyInfo.cs ├── AutoGen ├── Agents │ ├── AutoGen_CriticWrapperAgent.cs │ ├── AutoGen_EthicsReviewerAgent.cs │ ├── AutoGen_LegalReviewerAgent.cs │ ├── AutoGen_MetaReviewerAgent.cs │ ├── AutoGen_SEOReviewerAgent.cs │ └── AutoGen_StyleCheckerAgent.cs ├── AskForFeedbackAutoGenPlugin.cs └── AutoGenChatWorkflow_AskForFeedback.cs ├── Helpers ├── EnvironmentWellKnown.cs └── WellKnown.cs ├── LICENSE ├── LICENSE.txt ├── README.md ├── SearchFunctionFilter.cs ├── SemanticKernel ├── AgentGroupChatExt.cs ├── BaseAgentGroupChat.cs ├── IAgentChat.cs ├── IAgentGroupChat.cs ├── IChatMiddleware.cs ├── MiddlewareAgentChat.cs ├── ResultProcessing.cs ├── SequentialAgentChat.cs ├── SequentialTwoAgentChat.cs ├── TwoAgentChat.cs └── TwoAgentChatConfiguration.cs ├── testWindow02.xaml └── testWindow02.xaml.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /AgenticChatWorkflows.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AgenticChatWorkflows.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgenticChatWorkflows", "AgenticChatWorkflows.csproj", "{71EB1B66-CF17-424D-821E-7E9150913A43}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {71EB1B66-CF17-424D-821E-7E9150913A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {71EB1B66-CF17-424D-821E-7E9150913A43}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {71EB1B66-CF17-424D-821E-7E9150913A43}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {71EB1B66-CF17-424D-821E-7E9150913A43}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {AE699C6D-C387-42B2-BED5-6B47FCF368BA} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/AgenticWorkflowType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AgenticChatWorkflows; 8 | 9 | public enum WorkflowType 10 | { 11 | TestWorkflow, 12 | TestV2Workflow, 13 | TwoAgentChatWorkflow, 14 | SequentialAgentChatWorkflow, 15 | SequentialTwoAgentChatWorkflow, 16 | NestedChatWithGroupAgentChatWorkflow, 17 | CodeCrafterAgentChatWorkflow, 18 | AutoGenPluginChatWorkflow 19 | } -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/CodeCrafterWorkflowChatFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.ComponentModel; 7 | using Microsoft.SemanticKernel; 8 | using Microsoft.SemanticKernel.Agents; 9 | using Microsoft.SemanticKernel.Agents.OpenAI; 10 | using Microsoft.SemanticKernel.ChatCompletion; 11 | using Microsoft.SemanticKernel.Connectors.OpenAI; 12 | using Microsoft.SemanticKernel.Agents.Chat; 13 | using Microsoft.SemanticKernel.Plugins.Web; 14 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 15 | using System.Net; 16 | using System.Threading; 17 | using Microsoft.Extensions.DependencyInjection; 18 | using System.Windows.Controls; 19 | using System.Windows.Documents; 20 | using System.Reflection.Metadata; 21 | using System.Windows.Media; 22 | 23 | 24 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 25 | 26 | namespace AgenticChatWorkflows; 27 | 28 | public static class CodeCrafterWorkflowChatFactory 29 | { 30 | // Lazy Kernel initialization 31 | private static Kernel? _kernel; 32 | public static Kernel Kernel => _kernel ??= CreateKernel(); 33 | 34 | // Create the Kernel lazily using the environment variables 35 | private static Kernel CreateKernel() 36 | { 37 | var builder = Kernel.CreateBuilder(); 38 | builder.Services.AddSingleton(); 39 | 40 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 41 | deploymentName: EnvironmentWellKnown.DeploymentName, 42 | endpoint: EnvironmentWellKnown.Endpoint, 43 | apiKey: EnvironmentWellKnown.ApiKey) 44 | .Build(); 45 | 46 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 47 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 48 | KernelPlugin updateConceptPlugin = KernelPluginFactory.CreateFromType(); 49 | kernel.Plugins.Add(updateConceptPlugin); 50 | 51 | return kernel; 52 | } 53 | 54 | public static IAgentGroupChat CreateChat(int characterLimit = 2000, int maxIterations = 1) 55 | { 56 | string projectDetails = Context.Facts; 57 | 58 | // Main coding agent with combined responsibilities 59 | ChatCompletionAgent CodeCrafterAgent = new() 60 | { 61 | Instructions = $""" 62 | Your name is CodeCrafterAgent, an expert in software development, clean coding principles, and code legibility. 63 | 64 | # Context 65 | ## Software Project 66 | The user is developing a software application and needs assistance in crafting the main components, including architecture, algorithms, writing clean, efficient code, and ensuring the code follows best practices. 67 | 68 | ## Task 69 | Your task is to help the user design and write high-quality, maintainable code. You will guide the user through the following tasks: 70 | 71 | ### 1. **Architecture** 72 | Help the user define the **software architecture** for the project. 73 | - Ask: "What is the core functionality and what architecture suits the project best (e.g., MVC, microservices, monolithic)?" 74 | - Provide recommendations based on scalability, maintainability, and performance. 75 | - **Request feedback**: Ask if the user is satisfied with the architecture before proceeding. 76 | 77 | ### 2. **Algorithm Design** 78 | Assist the user in designing **algorithms** for core functionalities. 79 | - Ask: "What problem are we solving? What is the most efficient way to solve it?" 80 | - Offer examples of algorithms (e.g., sorting, searching, dynamic programming). 81 | - **Request feedback**: Confirm that the user agrees with the proposed algorithms or if they would like to make adjustments. 82 | 83 | ### 3. **Clean Code Principles** 84 | Review the code for **clean coding principles**. Ensure the code adheres to best practices such as DRY (Don't Repeat Yourself), SRP (Single Responsibility Principle), and is easily maintainable. 85 | - Suggest improvements where necessary, especially focusing on modularity, readability, and maintainability. 86 | - **Request feedback**: Ask the user if they agree with the changes or if additional refactoring is needed. 87 | 88 | ### 4. **Naming Conventions** 89 | Ensure the code follows proper **naming conventions** for variables, functions, and classes. 90 | - Review the names to make sure they are clear, meaningful, and follow best practices for readability. 91 | - **Request feedback**: Ask the user if the naming conventions align with their preferences or if they want adjustments. 92 | 93 | ### 5. **Code Legibility** 94 | Ensure the code is **legible** for future developers. Focus on proper formatting, spacing, indentation, and consistency. 95 | - Suggest formatting improvements if the code is hard to read or lacks structure. 96 | - **Request feedback**: Confirm with the user if the code is legible enough for their team or future developers. 97 | 98 | ### 6. **Code Review and Final Approval** 99 | Conduct a final **code review** to ensure all the above aspects are met. 100 | - Ask: "Is the code following the project standards? Is there any redundancy or areas for optimization?" 101 | - Ensure the code adheres to best practices for security, performance, and maintainability. 102 | - **Request feedback**: Before finalizing the review, ensure the user approves the code quality. 103 | 104 | ## Update the Code 105 | Once the user is satisfied with the final version, use the **UpdateCode tool** to finalize the code. 106 | - **Ensure that the user has reviewed and approved** the final version before updating. 107 | 108 | 109 | ## Existing code 110 | The existing code is: 111 | {Context.Code} 112 | """, 113 | 114 | Name = "CodeCrafterAgent", 115 | Kernel = Kernel, 116 | Arguments = new KernelArguments( 117 | new OpenAIPromptExecutionSettings() 118 | { 119 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions 120 | }), 121 | }; 122 | 123 | IAgentGroupChat chat = new AgentGroupChatExt(CodeCrafterAgent) 124 | { 125 | ExecutionSettings = new() 126 | { 127 | TerminationStrategy = new ApprovalTerminationStrategy() 128 | { 129 | Agents = [CodeCrafterAgent], 130 | MaximumIterations = maxIterations, 131 | } 132 | } 133 | }; 134 | 135 | return chat; 136 | } 137 | 138 | private sealed class UpdateCode 139 | { 140 | [KernelFunction, Description("Updates the Code with a provided code segment.")] 141 | public void UpdateTheCode( 142 | [Description("The code segment.")] string code) 143 | { 144 | Context.Code = code; 145 | } 146 | } 147 | } 148 | 149 | 150 | // Based on the "base code", achieve the goal. Let's go back to basics with this simple card game: war! Let's go back to basics with this simple card game: war!Your goal is to write a program which finds out which player is the winner for a given card distribution of the "war" game. War is a card game played between two players. Each player gets a variable number of cards of the beginning of the game: that's the player's deck. Cards are placed face down on top of each deck. Step 1 : the fight At each game round, in unison, each player reveals the top card of their deck – this is a "battle" – and the player with the higher card takes both the cards played and moves them to the bottom of their stack. The cards are ordered by value as follows, from weakest to strongest: 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A. Step 2 : war If the two cards played are of equal value, then there is a "war". First, both players place the three next cards of their pile face down. Then they go back to step 1 to decide who is going to win the war (several "wars" can be chained). As soon as a player wins a "war", the winner adds all the cards from the "war" to their deck. Special cases If a player runs out of cards during a "war" (when giving up the three cards or when doing the battle), then the game ends and both players are placed equally first. The test cases provided in this puzzle are built in such a way that a game always ends (you do not have to deal with infinite games) Each card is represented by its value followed by its suit: D, H, C, S. For example: 4H, 8C, AS. When a player wins a battle, they put back the cards at the bottom of their deck in a precise order. First the cards from the first player, then the one from the second player (for a "war", all the cards from the first player then all the cards from the second player). Start from the existing code provided 151 | 152 | 153 | // Copy this in code view 154 | 155 | //using System; 156 | //using System.Linq; 157 | //using System.IO; 158 | //using System.Text; 159 | //using System.Collections; 160 | //using System.Collections.Generic; 161 | 162 | ///** 163 | // * Auto-generated code below aims at helping you parse 164 | // * the standard input according to the problem statement. 165 | // **/ 166 | //class Solution 167 | //{ 168 | // static void Main(string[] args) 169 | // { 170 | // int n = int.Parse(Console.ReadLine()); // the number of cards for player 1 171 | // for (int i = 0; i < n; i++) 172 | // { 173 | // string cardp1 = Console.ReadLine(); // the n cards of player 1 174 | // } 175 | // int m = int.Parse(Console.ReadLine()); // the number of cards for player 2 176 | // for (int i = 0; i < m; i++) 177 | // { 178 | // string cardp2 = Console.ReadLine(); // the m cards of player 2 179 | // } 180 | 181 | // // Write an answer using Console.WriteLine() 182 | // // To debug: Console.Error.WriteLine("Debug messages..."); 183 | 184 | // Console.WriteLine("PAT"); 185 | // } 186 | //} -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/NestedChatMiddlewareWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.SemanticKernel.ChatCompletion; 10 | 11 | namespace AgenticChatWorkflows; 12 | 13 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 14 | 15 | /// 16 | /// NestedChat Middleware Workflow Factory. 17 | /// Stopped implementation due time constrains and not fully fitting the Semantic Kernel pattern - they are used in an agent, not a chat, figured out that later. 18 | /// will come back to this a bit later. 19 | /// 20 | public static class NestedChatMiddlewareWorkflowFactory 21 | { 22 | private const string WriterName = "Writer"; 23 | private const string CriticName = "Critic"; 24 | private const string TerminationKeyword = "TERMINATE"; 25 | 26 | // Lazy Kernel initialization 27 | private static Kernel? _kernel; 28 | public static Kernel Kernel => _kernel ??= CreateKernel(); 29 | 30 | // Create the Kernel lazily using the environment variables 31 | private static Kernel CreateKernel() 32 | { 33 | var builder = Kernel.CreateBuilder(); 34 | // Add any required services or plugins to the kernel 35 | 36 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 37 | deploymentName: EnvironmentWellKnown.DeploymentName, 38 | endpoint: EnvironmentWellKnown.Endpoint, 39 | apiKey: EnvironmentWellKnown.ApiKey) 40 | .Build(); 41 | 42 | // Add any additional plugins if needed 43 | 44 | return kernel; 45 | } 46 | 47 | public static IAgentGroupChat CreateChat(int maxIterations = 5) 48 | { 49 | // Create agents 50 | var writerAgent = CreateWriterAgent(); 51 | var seoReviewerAgent = CreateSEOReviewerAgent(); 52 | var legalReviewerAgent = CreateLegalReviewerAgent(); 53 | var ethicsReviewerAgent = CreateEthicsReviewerAgent(); 54 | var factCheckerAgent = CreateFactCheckerAgent(); 55 | var styleCheckerAgent = CreateStyleCheckerAgent(); 56 | var metaReviewerAgent = CreateMetaReviewerAgent(); 57 | var criticAgent = CreateCriticAgent(); 58 | 59 | // Aggregate all agents into a list 60 | var allAgents = new List 61 | { 62 | writerAgent, 63 | seoReviewerAgent, 64 | legalReviewerAgent, 65 | ethicsReviewerAgent, 66 | factCheckerAgent, 67 | styleCheckerAgent, 68 | metaReviewerAgent, 69 | criticAgent 70 | }; 71 | 72 | // Create MiddlewareAgentChat without passing any agents 73 | var middlewareChat = new MiddlewareAgentChat(); 74 | 75 | // Add the NestedChatMultiCriticMiddleware, passing the writer and all agents 76 | middlewareChat.AddMiddleware(new NestedChatMultiCriticMiddleware()); 77 | 78 | return middlewareChat; 79 | } 80 | 81 | private static bool CheckTermination(ChatMessageContent message) 82 | { 83 | if (message.Content.ToLower().Contains(TerminationKeyword)) 84 | { 85 | return true; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | #region Agent Creation Methods 92 | 93 | /// 94 | /// Creates the Writer agent. 95 | /// 96 | /// Configured ChatCompletionAgent for writing. 97 | private static ChatCompletionAgent CreateWriterAgent() 98 | { 99 | const string writerInstructions = @" 100 | You are a writer. You write engaging and concise articles (with title) on given topics. 101 | You must polish your writing based on the feedback you receive and provide a refined version. 102 | Only return your final work without additional comments. 103 | "; 104 | 105 | return new ChatCompletionAgent 106 | { 107 | Instructions = writerInstructions, 108 | Name = "Writer", 109 | Kernel = Kernel, 110 | }; 111 | } 112 | 113 | /// 114 | /// Creates the SEO Reviewer agent. 115 | /// 116 | /// Configured ChatCompletionAgent for SEO review. 117 | private static ChatCompletionAgent CreateSEOReviewerAgent() 118 | { 119 | const string seoReviewerInstructions = @" 120 | You are an SEO reviewer, known for your ability to optimize content for search engines, ensuring that it ranks well and attracts organic traffic. 121 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 122 | Begin the review by stating your role. 123 | "; 124 | 125 | return new ChatCompletionAgent 126 | { 127 | Instructions = seoReviewerInstructions, 128 | Name = "SEO_Reviewer", 129 | Kernel = Kernel, 130 | }; 131 | } 132 | 133 | /// 134 | /// Creates the Legal Reviewer agent. 135 | /// 136 | /// Configured ChatCompletionAgent for legal review. 137 | private static ChatCompletionAgent CreateLegalReviewerAgent() 138 | { 139 | const string legalReviewerInstructions = @" 140 | You are a legal reviewer, known for your ability to ensure that content is legally compliant and free from any potential legal issues. 141 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 142 | Begin the review by stating your role. 143 | Also be aware of data privacy and GDPR compliance, which needs to be respected, so in doubt suggest the removal of PII information. 144 | Assume that the speakers have agreed to share their name and title, so there is no issue with sharing that in the article. 145 | "; 146 | 147 | return new ChatCompletionAgent 148 | { 149 | Instructions = legalReviewerInstructions, 150 | Name = "Legal_Reviewer", 151 | Kernel = Kernel, 152 | }; 153 | } 154 | 155 | /// 156 | /// Creates the Ethics Reviewer agent. 157 | /// 158 | /// Configured ChatCompletionAgent for ethics review. 159 | private static ChatCompletionAgent CreateEthicsReviewerAgent() 160 | { 161 | const string ethicsReviewerInstructions = @" 162 | You are an ethics reviewer, known for your ability to ensure that content is ethically sound and free from any potential ethical issues. 163 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 164 | Begin the review by stating your role. 165 | "; 166 | 167 | return new ChatCompletionAgent 168 | { 169 | Instructions = ethicsReviewerInstructions, 170 | Name = "Ethics_Reviewer", 171 | Kernel = Kernel, 172 | }; 173 | } 174 | 175 | /// 176 | /// Creates the FactChecker agent. 177 | /// 178 | /// Configured ChatCompletionAgent for fact checking. 179 | private static ChatCompletionAgent CreateFactCheckerAgent() 180 | { 181 | string conferenceDescription = GetConferenceDescription(); 182 | 183 | string factCheckerInstructions = $@" 184 | You are a FactChecker. Your job is to ensure that all facts mentioned in the article are accurate and derived from the provided source material. 185 | You will avoid any hallucinations and ensure that all the facts are cross-checked with the source material. 186 | 187 | Cross-check the content against the following source: 188 | 189 | {conferenceDescription} 190 | 191 | Make sure no invented information is included, and suggest corrections if any discrepancies are found. 192 | "; 193 | 194 | return new ChatCompletionAgent 195 | { 196 | Instructions = factCheckerInstructions, 197 | Name = "FactChecker", 198 | Kernel = Kernel, 199 | }; 200 | } 201 | 202 | /// 203 | /// Creates the StyleChecker agent. 204 | /// 205 | /// Configured ChatCompletionAgent for style checking. 206 | private static ChatCompletionAgent CreateStyleCheckerAgent() 207 | { 208 | const string styleCheckerInstructions = @" 209 | You are a Style Checker. Your task is to ensure that the article is written in a proper style. 210 | Check that the writing style is positive, engaging, motivational, original, and funny. 211 | The phrases should not be too complex, and the tone should be friendly, casual, yet polite. 212 | Provide suggestions to improve the style if necessary. 213 | Provide ONLY suggestions, do not rewrite the content or write any part of the content in the style suggested; this is the work of the writer. 214 | "; 215 | 216 | return new ChatCompletionAgent 217 | { 218 | Instructions = styleCheckerInstructions, 219 | Name = "StyleChecker", 220 | Kernel = Kernel, 221 | }; 222 | } 223 | 224 | /// 225 | /// Creates the MetaReviewer agent. 226 | /// 227 | /// Configured ChatCompletionAgent for meta reviewing. 228 | private static ChatCompletionAgent CreateMetaReviewerAgent() 229 | { 230 | const string metaReviewerInstructions = @" 231 | You are a meta reviewer. You aggregate and review the work of other reviewers and give final suggestions on the content. 232 | "; 233 | 234 | return new ChatCompletionAgent 235 | { 236 | Instructions = metaReviewerInstructions, 237 | Name = "Meta_Reviewer", 238 | Kernel = Kernel, 239 | }; 240 | } 241 | 242 | /// 243 | /// Creates the Critic agent. 244 | /// 245 | /// Configured ChatCompletionAgent for critiquing. 246 | private static ChatCompletionAgent CreateCriticAgent() 247 | { 248 | string criticInstructions = $@" 249 | You are a critic. You review the work of the writer and provide constructive feedback to help improve the quality of the content. 250 | If the work is already solid and convincing, like 80-90% perfect, you can respond with '{TerminationKeyword}' only. 251 | If you provide ANY feedback, DO NOT, I repeat, DO NOT respond or add '{TerminationKeyword}' in your feedback. 252 | After having replied 4 times, respond with '{TerminationKeyword}' to end the conversation. 253 | AGAIN DO NOT WRITE ANY PART OF THE WORK. ONLY PROVIDE FEEDBACK. 254 | IF THE WORK IS SOLID, RESPOND WITH '{TerminationKeyword}'. 255 | RESPOND WITH {TerminationKeyword} AFTER 4 REPLIES. 256 | "; 257 | 258 | return new ChatCompletionAgent 259 | { 260 | Instructions = criticInstructions, 261 | Name = "Critic", 262 | Kernel = Kernel, 263 | }; 264 | } 265 | 266 | /// 267 | /// Provides a description of the conference for fact checking. 268 | /// 269 | /// Conference description string. 270 | private static string GetConferenceDescription() 271 | { 272 | return @" 273 | The .NET Day Switzerland is a community-driven and independent .NET conference focused on .NET technologies, taking place on June 3rd, 2024, in Zürich, Switzerland. 274 | The conference features 3 parallel tracks with 15 sessions, covering topics like .NET, Azure, Blazor, WebAssembly, AI, and more. 275 | Speakers include renowned experts from the industry. 276 | The event is non-profit, organized by the .NET community, and aims to bring developers, architects, and experts together. 277 | "; 278 | } 279 | 280 | #endregion 281 | 282 | } 283 | 284 | public class NestedChatMultiCriticMiddleware : IChatMiddleware 285 | { 286 | public string? Name => nameof(NestedChatMultiCriticMiddleware); 287 | 288 | private readonly ChatCompletionAgent _writerAgent; 289 | private readonly IEnumerable _reviewerAgents; 290 | private readonly ChatCompletionAgent _metaReviewerAgent; 291 | private readonly ChatCompletionAgent _criticAgent; 292 | private readonly Func _terminationCondition; 293 | 294 | private readonly Kernel _kernel; 295 | 296 | public NestedChatMultiCriticMiddleware() 297 | { 298 | 299 | } 300 | 301 | public NestedChatMultiCriticMiddleware( 302 | ChatCompletionAgent writerAgent, 303 | IEnumerable reviewerAgents, 304 | ChatCompletionAgent metaReviewerAgent, 305 | ChatCompletionAgent criticAgent, 306 | Func terminationCondition) 307 | { 308 | _writerAgent = writerAgent ?? throw new ArgumentNullException(nameof(writerAgent)); 309 | _reviewerAgents = reviewerAgents ?? throw new ArgumentNullException(nameof(reviewerAgents)); 310 | _metaReviewerAgent = metaReviewerAgent ?? throw new ArgumentNullException(nameof(metaReviewerAgent)); 311 | _criticAgent = criticAgent ?? throw new ArgumentNullException(nameof(criticAgent)); 312 | _terminationCondition = terminationCondition ?? throw new ArgumentNullException(nameof(terminationCondition)); 313 | 314 | // Assuming all agents share the same Kernel 315 | _kernel = NestedChatMiddlewareWorkflowFactory.Kernel; 316 | } 317 | 318 | public async Task InvokeAsync( 319 | ChatMessageContent message, 320 | Func> next, 321 | CancellationToken cancellationToken = default) 322 | { 323 | // Only trigger when the last message is from the Writer 324 | if (!string.Equals(message?.AuthorName, _writerAgent.Name, StringComparison.OrdinalIgnoreCase)) 325 | { 326 | // Proceed to the next middleware or agent 327 | return await next(message); 328 | } 329 | 330 | // Step 1: Invoke the Writer Agent 331 | ChatMessageContent writerResponse = await InvokeAgentAsync(_writerAgent, message, cancellationToken); 332 | 333 | // Step 2: Invoke all Reviewer Agents with the Writer's output 334 | var reviewTasks = _reviewerAgents.Select(reviewer => InvokeReviewerAsync(reviewer, writerResponse.Content, cancellationToken)).ToList(); 335 | var reviewerFeedbacks = await Task.WhenAll(reviewTasks); 336 | 337 | // Step 3: Invoke the MetaReviewer Agent to aggregate feedback 338 | string aggregatedFeedbackPrompt = "Aggregate feedback from all reviewers and provide final suggestions on the writing."; 339 | ChatMessageContent aggregatedFeedback = await InvokeAgentAsync(_metaReviewerAgent, new ChatMessageContent(AuthorRole.User, aggregatedFeedbackPrompt), cancellationToken); 340 | 341 | // Step 4: Invoke the Critic Agent with the aggregated feedback 342 | ChatMessageContent criticResponse = await InvokeAgentAsync(_criticAgent, new ChatMessageContent(AuthorRole.User, aggregatedFeedback.Content), cancellationToken); 343 | 344 | // Step 5: Evaluate the Termination Strategy 345 | if (_terminationCondition(criticResponse)) 346 | { 347 | // Optionally, you can mark the chat as complete or perform other actions 348 | // For simplicity, we'll just return the Critic's response 349 | return criticResponse; 350 | } 351 | 352 | // If not terminating, you might want to continue the conversation 353 | // Depending on your workflow, you can decide what to do next 354 | return criticResponse; 355 | } 356 | 357 | private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatMessageContent message, CancellationToken cancellationToken) 358 | { 359 | ChatMessageContent? response = null; 360 | 361 | // AgentChat invokation wrong 362 | // Should beAddChatMessage() and InvokeAsync() 363 | //await foreach (var res in agent.InvokeAsync(message, cancellationToken)) 364 | //{ 365 | // response = res; 366 | // break; // Only take the first response 367 | //} 368 | 369 | if (response == null) 370 | { 371 | throw new InvalidOperationException($"Agent {agent.Name} did not produce a response."); 372 | } 373 | 374 | return response; 375 | } 376 | 377 | private async Task InvokeReviewerAsync(ChatCompletionAgent reviewer, string content, CancellationToken cancellationToken) 378 | { 379 | string reviewPrompt = $"Review the following content.\n\n{content}"; 380 | ChatMessageContent reviewMessage = new ChatMessageContent(AuthorRole.User, reviewPrompt); 381 | 382 | return await InvokeAgentAsync(reviewer, reviewMessage, cancellationToken); 383 | } 384 | 385 | // Methods to create reviewer agents 386 | private Agent CreateSEOReviewerAgent() 387 | { 388 | const string seoReviewerInstructions = """ 389 | You are an SEO reviewer, known for your ability to optimize content for search engines, ensuring that it ranks well and attracts organic traffic. 390 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 391 | Begin the review by stating your role. 392 | """; 393 | 394 | return new ChatCompletionAgent 395 | { 396 | Instructions = seoReviewerInstructions, 397 | Name = "SEO_Reviewer", 398 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 399 | }; 400 | } 401 | 402 | private Agent CreateLegalReviewerAgent() 403 | { 404 | const string legalReviewerInstructions = """ 405 | You are a legal reviewer, known for your ability to ensure that content is legally compliant and free from any potential legal issues. 406 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 407 | Begin the review by stating your role. 408 | Also be aware of data privacy and GDPR compliance, which needs to be respected, so in doubt suggest the removal of PII information. 409 | Assume that the speakers have agreed to share their name and title, so there is no issue with sharing that in the article. 410 | """; 411 | 412 | return new ChatCompletionAgent 413 | { 414 | Instructions = legalReviewerInstructions, 415 | Name = "Legal_Reviewer", 416 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 417 | }; 418 | } 419 | 420 | private Agent CreateEthicsReviewerAgent() 421 | { 422 | const string ethicsReviewerInstructions = """ 423 | You are an ethics reviewer, known for your ability to ensure that content is ethically sound and free from any potential ethical issues. 424 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 425 | Begin the review by stating your role. 426 | """; 427 | 428 | return new ChatCompletionAgent 429 | { 430 | Instructions = ethicsReviewerInstructions, 431 | Name = "Ethics_Reviewer", 432 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 433 | }; 434 | } 435 | 436 | private Agent CreateFactCheckerAgent() 437 | { 438 | string conferenceDescription = GetConferenceDescription(); 439 | 440 | string factCheckerInstructions = $""" 441 | You are a FactChecker. Your job is to ensure that all facts mentioned in the article are accurate and derived from the provided source material. 442 | You will avoid any hallucinations and ensure that all the facts are cross-checked with the source material. 443 | 444 | Cross-check the content against the following source: 445 | 446 | {conferenceDescription} 447 | 448 | Make sure no invented information is included, and suggest corrections if any discrepancies are found. 449 | """; 450 | 451 | return new ChatCompletionAgent 452 | { 453 | Instructions = factCheckerInstructions, 454 | Name = "FactChecker", 455 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 456 | }; 457 | } 458 | 459 | private Agent CreateStyleCheckerAgent() 460 | { 461 | const string styleCheckerInstructions = """ 462 | You are a Style Checker. Your task is to ensure that the article is written in a proper style. 463 | Check that the writing style is positive, engaging, motivational, original, and funny. 464 | The phrases should not be too complex, and the tone should be friendly, casual, yet polite. 465 | Provide suggestions to improve the style if necessary. 466 | Provide ONLY suggestions, do not rewrite the content or write any part of the content in the style suggested; this is the work of the writer. 467 | """; 468 | 469 | return new ChatCompletionAgent 470 | { 471 | Instructions = styleCheckerInstructions, 472 | Name = "StyleChecker", 473 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 474 | }; 475 | } 476 | 477 | private Agent CreateMetaReviewerAgent() 478 | { 479 | const string metaReviewerInstructions = """ 480 | You are a meta reviewer. You aggregate and review the work of other reviewers and give final suggestions on the content. 481 | """; 482 | 483 | return new ChatCompletionAgent 484 | { 485 | Instructions = metaReviewerInstructions, 486 | Name = "Meta_Reviewer", 487 | Kernel = NestedChatMiddlewareWorkflowFactory.Kernel, 488 | }; 489 | } 490 | 491 | private static string GetConferenceDescription() 492 | { 493 | return @" 494 | The .NET Day Switzerland is a community-driven and independent .NET conference focused on .NET technologies, taking place on June 3rd, 2024, in Zürich, Switzerland. 495 | The conference features 3 parallel tracks with 15 sessions, covering topics like .NET, Azure, Blazor, WebAssembly, AI, and more. 496 | Speakers include renowned experts from the industry. 497 | The event is non-profit, organized by the .NET community, and aims to bring developers, architects, and experts together. 498 | "; 499 | } 500 | } -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/NestedChatWithGroupAgentChatWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.SemanticKernel.Plugins.Web; 5 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.VisualBasic; 8 | using static System.Runtime.InteropServices.JavaScript.JSType; 9 | using System.Net.Sockets; 10 | using System.Net; 11 | using System.Reflection.Metadata; 12 | using System; 13 | 14 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 15 | 16 | namespace AgenticChatWorkflows; 17 | 18 | // Doesn't seem to fully work - reimplement without the AggregatorAgent seems the solution... something is amiss here... 19 | public static class NestedChatWithGroupAgentChatWorkflowFactory 20 | { 21 | // Lazy Kernel initialization 22 | private static Kernel? _kernel; 23 | public static Kernel Kernel => _kernel ??= CreateKernel(); 24 | 25 | // Create the Kernel lazily using the environment variables 26 | private static Kernel CreateKernel() 27 | { 28 | var builder = Kernel.CreateBuilder(); 29 | builder.Services.AddSingleton(); 30 | 31 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 32 | deploymentName: EnvironmentWellKnown.DeploymentName, 33 | endpoint: EnvironmentWellKnown.Endpoint, 34 | apiKey: EnvironmentWellKnown.ApiKey) 35 | .Build(); 36 | 37 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 38 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 39 | 40 | return kernel; 41 | } 42 | 43 | public static IAgentGroupChat CreateChat(int maxIterations = 5) 44 | { 45 | // Create worker agent 46 | var workerAgent = CreateWorkerAgent(); 47 | 48 | // Create all reviewer agents for the sequential process 49 | var seoReviewerAgent = CreateSEOReviewerAgent(); 50 | var legalReviewerAgent = CreateLegalReviewerAgent(); 51 | var ethicsReviewerAgent = CreateEthicsReviewerAgent(); 52 | var factCheckerAgent = CreateFactCheckerAgent(); 53 | var styleCheckerAgent = CreateStyleCheckerAgent(); 54 | var metaReviewerAgent = CreateMetaReviewerAgent(); 55 | 56 | // Sequential agents list for the SequentialAgentChat 57 | var sequentialAgents = new List 58 | { 59 | seoReviewerAgent, 60 | legalReviewerAgent, 61 | ethicsReviewerAgent, 62 | factCheckerAgent, 63 | styleCheckerAgent 64 | }; 65 | string OuterTerminationInstructions = 66 | $$$""" 67 | Determine if user request has been fully answered. 68 | Respond only with "APPROVED" if the user request has been fully answered 69 | """; 70 | 71 | KernelFunction outerTerminationFunction = KernelFunctionFactory.CreateFromPrompt(OuterTerminationInstructions); 72 | 73 | // Wrap SequentialAgentChat using AggregatorAgent with Nested Mode 74 | AggregatorAgent sequentialChatAgent = 75 | new(CreateSequentialAgentChat) 76 | { 77 | Name = "SequentialChatAgent", 78 | Mode = AggregatorMode.Nested, 79 | }; 80 | 81 | AgentGroupChat CreateSequentialAgentChat() => 82 | new(seoReviewerAgent, 83 | legalReviewerAgent, 84 | ethicsReviewerAgent, 85 | factCheckerAgent, 86 | styleCheckerAgent) 87 | { 88 | ExecutionSettings = 89 | new() 90 | { 91 | TerminationStrategy = 92 | new KernelFunctionTerminationStrategy(outerTerminationFunction, Kernel) 93 | { 94 | ResultParser = 95 | (result) => 96 | { 97 | var outcome = result.GetValue().ToLower(); 98 | 99 | return (outcome == "approved"); 100 | }, 101 | MaximumIterations = 1, 102 | }, 103 | } 104 | }; 105 | 106 | // Create the final critic agent 107 | var finalCriticAgent = CreateFinalCriticAgent(); 108 | 109 | // Create the AgentGroupChat combining the worker, sequential reviewers, and final critic 110 | AgentGroupChat groupChat = new(workerAgent, sequentialChatAgent, finalCriticAgent) 111 | { 112 | ExecutionSettings = new AgentGroupChatSettings 113 | { 114 | TerminationStrategy = CreateTerminationStrategy(3) 115 | } 116 | }; 117 | 118 | //return groupChat; does not work as AgentGroupChat is not IAgentGroupChat (does not implememt any interface but inherits from AgentChat - not coding vs interfaces...) I implemented IAGentGroupChat locally to create custom versions of AgentGroupChat :) 119 | 120 | // Solution: Create a custom version of AgentGroupChat that implements IAgentGroupChat and wraps it - AgentGroupChatExt 121 | AgentGroupChatExt agentGroupChatExt = new(groupChat); 122 | return agentGroupChatExt; 123 | } 124 | 125 | #region Agent Creation Methods 126 | 127 | private static ChatCompletionAgent CreateWorkerAgent() 128 | { 129 | const string workerInstructions = @" 130 | You are a skilled article writer. Write a detailed and engaging article on a given topic. 131 | Keep the language clear and concise. Only return the article without additional commentary. 132 | "; 133 | 134 | return new ChatCompletionAgent 135 | { 136 | Instructions = workerInstructions, 137 | Name = "WorkerAgent", 138 | Kernel = Kernel, 139 | }; 140 | } 141 | 142 | private static ChatCompletionAgent CreateSEOReviewerAgent() 143 | { 144 | const string seoReviewerInstructions = @" 145 | You are an SEO reviewer. Ensure the article is optimized for search engines, concise, and engaging. 146 | Provide suggestions in a concise manner (within 3 bullet points). 147 | "; 148 | 149 | return new ChatCompletionAgent 150 | { 151 | Instructions = seoReviewerInstructions, 152 | Name = "SEO_Reviewer", 153 | Kernel = Kernel, 154 | }; 155 | } 156 | 157 | private static ChatCompletionAgent CreateLegalReviewerAgent() 158 | { 159 | const string legalReviewerInstructions = @" 160 | You are a legal reviewer. Ensure the article complies with legal standards and is free from potential legal issues. 161 | Make suggestions concisely and focus on any legal problems. 162 | "; 163 | 164 | return new ChatCompletionAgent 165 | { 166 | Instructions = legalReviewerInstructions, 167 | Name = "Legal_Reviewer", 168 | Kernel = Kernel, 169 | }; 170 | } 171 | 172 | private static ChatCompletionAgent CreateEthicsReviewerAgent() 173 | { 174 | const string ethicsReviewerInstructions = @" 175 | You are an ethics reviewer. Ensure the article adheres to ethical standards and avoids any controversial topics. 176 | Suggest ethical improvements concisely. 177 | "; 178 | 179 | return new ChatCompletionAgent 180 | { 181 | Instructions = ethicsReviewerInstructions, 182 | Name = "Ethics_Reviewer", 183 | Kernel = Kernel, 184 | }; 185 | } 186 | 187 | private static ChatCompletionAgent CreateFactCheckerAgent() 188 | { 189 | const string factCheckerInstructions = @" 190 | You are a fact-checker. Ensure that all factual claims in the article are accurate and backed by reliable sources. 191 | Suggest corrections if there are any factual inaccuracies. 192 | "; 193 | 194 | return new ChatCompletionAgent 195 | { 196 | Instructions = factCheckerInstructions, 197 | Name = "FactChecker", 198 | Kernel = Kernel, 199 | }; 200 | } 201 | 202 | private static ChatCompletionAgent CreateStyleCheckerAgent() 203 | { 204 | const string styleCheckerInstructions = @" 205 | You are a style checker. Ensure the writing style is engaging, clear, and suitable for the target audience. 206 | Provide feedback on style issues concisely. 207 | "; 208 | 209 | return new ChatCompletionAgent 210 | { 211 | Instructions = styleCheckerInstructions, 212 | Name = "StyleChecker", 213 | Kernel = Kernel, 214 | }; 215 | } 216 | 217 | private static ChatCompletionAgent CreateMetaReviewerAgent() 218 | { 219 | const string metaReviewerInstructions = @" 220 | You are a meta reviewer. You aggregate and review the work of other reviewers and give a summarized final review from each reviewer on the content. 221 | Ensure that all feedback is constructive and actionable. 222 | "; 223 | 224 | return new ChatCompletionAgent 225 | { 226 | Instructions = metaReviewerInstructions, 227 | Name = "Meta_Reviewer", 228 | Kernel = Kernel, 229 | }; 230 | } 231 | 232 | private static ChatCompletionAgent CreateFinalCriticAgent() 233 | { 234 | const string finalCriticInstructions = @" 235 | You are the final critic. Review the article holistically and approve it for publication if it meets all standards. 236 | Provide any final feedback or approve it for publishing. 237 | "; 238 | 239 | return new ChatCompletionAgent 240 | { 241 | Instructions = finalCriticInstructions, 242 | Name = "FinalCritic", 243 | Kernel = Kernel, 244 | }; 245 | } 246 | 247 | #endregion 248 | 249 | #region Strategy Creation Methods 250 | 251 | private static TerminationStrategy CreateTerminationStrategy(int maxIterations) 252 | { 253 | return new AgentTerminationStrategy 254 | { 255 | MaximumIterations = maxIterations 256 | }; 257 | } 258 | 259 | 260 | #endregion 261 | 262 | 263 | 264 | } 265 | 266 | 267 | public sealed class AgentTerminationStrategy : TerminationStrategy 268 | { 269 | /// 270 | protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken = default) 271 | { 272 | return Task.FromResult(true); 273 | } 274 | } 275 | 276 | 277 | // write me an article about .NET Day Switzerland 278 | //.NET Day Switzerland takes place on Tuesday, the 27.08.2024 at the Arena Cinemas at Sihlcity in Zürich and is an independent technology conference for developers, architects and experts to discuss about and get to know.NET technologies all around.NET, .NET Core, C#, ASP.NET Core, Azure and more. Experienced speakers share their knowledge on the latest topics and give you deep insights into the new world of Microsoft software development and beyond. In addition to the technical talks, the .NET Day provides a space for discussions with the speakers and other attendees. 279 | 280 | //The .NET Day is your place for networking, discussions and questions! 281 | 282 | //.NET Day Switzerland is a non-profit community conference.All the speakers and staff engage on a voluntary basis because they are good people and want to support the Swiss.NET Community. Any positive financial balance from the ticket sales will be used to support non-profit organizations either involved in charity projects or the Swiss software developer community. 283 | 284 | //Questions, input or improvements can be dropped at any time via info[at] dotnetday.ch 285 | 286 | //If you want to get the hottest news delivered to your inbox, sign up for the.NET Day Newsletter here. Your email address will not be abused or given to anybody outside. That’s our promise! 287 | 288 | //If you are interested in the history and background of.NET Day Switzerland read this blog post: here 289 | 290 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/SemanticKernelWithAutoGenPluginChatFactory.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.SemanticKernel.Agents; 4 | using Microsoft.SemanticKernel.Connectors.OpenAI; 5 | using Microsoft.SemanticKernel.Plugins.Web; 6 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using AgenticChatWorkflows.AutoGen; 9 | 10 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 11 | 12 | namespace AgenticChatWorkflows; 13 | 14 | public static class SemanticKernelWithAutoGenPluginChatFactory 15 | { 16 | // Lazy Kernel initialization 17 | private static Kernel? _kernel; 18 | public static Kernel Kernel => _kernel ??= CreateKernel(); 19 | 20 | // Create the Kernel lazily using the environment variables 21 | private static Kernel CreateKernel() 22 | { 23 | var builder = Kernel.CreateBuilder(); 24 | builder.Services.AddSingleton(); 25 | 26 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 27 | deploymentName: EnvironmentWellKnown.DeploymentName, 28 | endpoint: EnvironmentWellKnown.Endpoint, 29 | apiKey: EnvironmentWellKnown.ApiKey) 30 | .Build(); 31 | 32 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 33 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 34 | KernelPlugin updateArticlePlugin = KernelPluginFactory.CreateFromType(); 35 | kernel.Plugins.Add(updateArticlePlugin); 36 | 37 | // Add the AutoGen plugin 38 | KernelPlugin askForFeedbackAutoGen = KernelPluginFactory.CreateFromType(); 39 | kernel.Plugins.Add(askForFeedbackAutoGen); 40 | 41 | return kernel; 42 | } 43 | 44 | public static IAgentGroupChat CreateChat(int characterLimit = 2000, int maxIterations = 1) 45 | { 46 | string projectDetails = Context.Facts; 47 | 48 | // Main coding agent with combined responsibilities 49 | ChatCompletionAgent ArticleWriterAgent = new() 50 | { 51 | Instructions = $""" 52 | You are a writer. You write engaging and concise articles (with title) on given topics. 53 | You must polish your writing based on the feedback you receive and provide a refined version. 54 | Only return your final work without additional comments. 55 | Also you will always follow the same process when writing articles: 56 | 1. Research using the bing plugin search engine on the topic (or topics) of the article. 57 | 2. Write the article based on the research and the input. 58 | 3. Ask for feedback on the article by using the AskForFeedback function in the askForFeedbackAutoGenPlugin plugin, providing the article. 59 | 4. Update the article based on the feedback. 60 | 5. Update the article article by using UpdateArticle plugin and ask the user for feedback. 61 | 6. Update the article based on the user's feedback. 62 | 7. If the user is satisfied, you are done. If not, go back to step 3 unless the user asks for more research - then go back to step 1. 63 | 64 | """, 65 | 66 | Name = "CodeCrafterAgent", 67 | Kernel = Kernel, 68 | Arguments = new KernelArguments( 69 | new OpenAIPromptExecutionSettings() 70 | { 71 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions 72 | }), 73 | }; 74 | 75 | IAgentGroupChat chat = new AgentGroupChatExt(ArticleWriterAgent) 76 | { 77 | ExecutionSettings = new() 78 | { 79 | TerminationStrategy = new ApprovalTerminationStrategy() 80 | { 81 | Agents = [ArticleWriterAgent], 82 | MaximumIterations = maxIterations, 83 | } 84 | } 85 | }; 86 | 87 | return chat; 88 | } 89 | 90 | private sealed class UpdateArticle 91 | { 92 | [KernelFunction, Description("Updates the article with the provided article.")] 93 | public void UpdateTheArticle( 94 | [Description("The article")] string article) 95 | { 96 | Context.Code = article; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/SequentialAgentChatWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.SemanticKernel.Plugins.Web; 5 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 9 | 10 | namespace AgenticChatWorkflows; 11 | 12 | public static class SequentialAgentChatWorkflowFactory 13 | { 14 | // Lazy Kernel initialization 15 | private static Kernel? _kernel; 16 | public static Kernel Kernel => _kernel ??= CreateKernel(); 17 | 18 | // Create the Kernel lazily using the environment variables 19 | private static Kernel CreateKernel() 20 | { 21 | var builder = Kernel.CreateBuilder(); 22 | builder.Services.AddSingleton(); 23 | 24 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 25 | deploymentName: EnvironmentWellKnown.DeploymentName, 26 | endpoint: EnvironmentWellKnown.Endpoint, 27 | apiKey: EnvironmentWellKnown.ApiKey) 28 | .Build(); 29 | 30 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 31 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 32 | 33 | return kernel; 34 | } 35 | 36 | //public static AgentGroupChat CreateChat() => (AgentGroupChat)CreateChat(2000); 37 | 38 | public static IAgentGroupChat CreateChat(int characterLimit = 2000) 39 | { 40 | // Create agents using separate methods 41 | var seoReviewerAgent = CreateSEOReviewerAgent(); 42 | var legalReviewerAgent = CreateLegalReviewerAgent(); 43 | var ethicsReviewerAgent = CreateEthicsReviewerAgent(); 44 | var factCheckerAgent = CreateFactCheckerAgent(); 45 | var styleCheckerAgent = CreateStyleCheckerAgent(); 46 | var metaReviewerAgent = CreateMetaReviewerAgent(); 47 | 48 | // List of agents to invoke sequentially 49 | var agents = new List 50 | { 51 | seoReviewerAgent, 52 | legalReviewerAgent, 53 | ethicsReviewerAgent, 54 | factCheckerAgent, 55 | styleCheckerAgent 56 | }; 57 | 58 | // Create the SequentialAgentChat with a list of agents and a summarizer agent 59 | SequentialAgentChat sequentialAgentChat = new SequentialAgentChat( 60 | agents, 61 | metaReviewerAgent); 62 | 63 | sequentialAgentChat.IsComplete = false; 64 | 65 | return sequentialAgentChat; 66 | } 67 | 68 | #region Agent Creation Methods 69 | private static ChatCompletionAgent CreateSEOReviewerAgent() 70 | { 71 | const string seoReviewerInstructions = @" 72 | You are an SEO reviewer, known for your ability to optimize content for search engines, ensuring that it ranks well and attracts organic traffic. 73 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 74 | Begin the review by stating your role. 75 | "; 76 | 77 | return new ChatCompletionAgent 78 | { 79 | Instructions = seoReviewerInstructions, 80 | Name = "SEO_Reviewer", 81 | Kernel = Kernel, 82 | }; 83 | } 84 | 85 | private static ChatCompletionAgent CreateLegalReviewerAgent() 86 | { 87 | const string legalReviewerInstructions = @" 88 | You are a legal reviewer, known for your ability to ensure that content is legally compliant and free from any potential legal issues. 89 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 90 | Begin the review by stating your role. 91 | Also be aware of data privacy and GDPR compliance, which needs to be respected, so in doubt suggest the removal of PII information. 92 | Assume that the speakers have agreed to share their name and title, so there is no issue with sharing that in the article. 93 | "; 94 | 95 | return new ChatCompletionAgent 96 | { 97 | Instructions = legalReviewerInstructions, 98 | Name = "Legal_Reviewer", 99 | Kernel = Kernel, 100 | }; 101 | } 102 | 103 | private static ChatCompletionAgent CreateEthicsReviewerAgent() 104 | { 105 | const string ethicsReviewerInstructions = @" 106 | You are an ethics reviewer, known for your ability to ensure that content is ethically sound and free from any potential ethical issues. 107 | Make sure your suggestion is concise (within 3 bullet points), concrete, and to the point. 108 | Begin the review by stating your role. 109 | "; 110 | 111 | return new ChatCompletionAgent 112 | { 113 | Instructions = ethicsReviewerInstructions, 114 | Name = "Ethics_Reviewer", 115 | Kernel = Kernel, 116 | }; 117 | } 118 | 119 | private static ChatCompletionAgent CreateFactCheckerAgent() 120 | { 121 | string conferenceDescription = GetConferenceDescription(); 122 | 123 | string factCheckerInstructions = $@" 124 | You are a FactChecker. Your job is to ensure that all facts mentioned in the article are accurate and derived from the provided source material. 125 | You will avoid any hallucinations and ensure that all the facts are cross-checked with the source material. 126 | 127 | Cross-check the content against the following source: 128 | 129 | {conferenceDescription} 130 | 131 | Make sure no invented information is included, and suggest corrections if any discrepancies are found. 132 | "; 133 | 134 | return new ChatCompletionAgent 135 | { 136 | Instructions = factCheckerInstructions, 137 | Name = "FactChecker", 138 | Kernel = Kernel, 139 | }; 140 | } 141 | 142 | private static ChatCompletionAgent CreateStyleCheckerAgent() 143 | { 144 | const string styleCheckerInstructions = @" 145 | You are a Style Checker. Your task is to ensure that the article is written in a proper style. 146 | Check that the writing style is positive, engaging, motivational, original, and funny. 147 | The phrases should not be too complex, and the tone should be friendly, casual, yet polite. 148 | Provide suggestions to improve the style if necessary. 149 | Provide ONLY suggestions, do not rewrite the content or write any part of the content in the style suggested; this is the work of the writer. 150 | "; 151 | 152 | return new ChatCompletionAgent 153 | { 154 | Instructions = styleCheckerInstructions, 155 | Name = "StyleChecker", 156 | Kernel = Kernel, 157 | }; 158 | } 159 | 160 | private static ChatCompletionAgent CreateMetaReviewerAgent() 161 | { 162 | const string metaReviewerInstructions = @" 163 | You are a meta reviewer. You aggregate and review the work of other reviewers and give a summarized final review from each reviewer on the content. Do not output the content, just provide a summary of the feedback from each reviewer. 164 | Ensure that all feedback is constructive and actionable. 165 | "; 166 | 167 | return new ChatCompletionAgent 168 | { 169 | Instructions = metaReviewerInstructions, 170 | Name = "Meta_Reviewer", 171 | Kernel = Kernel, 172 | }; 173 | } 174 | 175 | private static string GetConferenceDescription() 176 | { 177 | return @" 178 | The .NET Day Switzerland is a community-driven and independent .NET conference focused on .NET technologies, taking place on June 3rd, 2024, in Zürich, Switzerland. 179 | The conference features 3 parallel tracks with 15 sessions, covering topics like .NET, Azure, Blazor, WebAssembly, AI, and more. 180 | Speakers include renowned experts from the industry. 181 | The event is non-profit, organized by the .NET community, and aims to bring developers, architects, and experts together. 182 | "; 183 | } 184 | 185 | #endregion 186 | 187 | } -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/SequentialTwoAgentChatWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.SemanticKernel.Plugins.Web; 5 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 9 | 10 | namespace AgenticChatWorkflows; 11 | 12 | public static class SequentialTwoAgentChatWorkflowFactory 13 | { 14 | private static Kernel? _kernel; 15 | public static Kernel Kernel => _kernel ??= CreateKernel(); 16 | 17 | // Initialize Kernel lazily using environment variables 18 | private static Kernel CreateKernel() 19 | { 20 | var builder = Kernel.CreateBuilder(); 21 | 22 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 23 | deploymentName: EnvironmentWellKnown.DeploymentName, 24 | endpoint: EnvironmentWellKnown.Endpoint, 25 | apiKey: EnvironmentWellKnown.ApiKey) 26 | .Build(); 27 | 28 | return kernel; 29 | } 30 | 31 | public static IAgentGroupChat CreateChat() 32 | { 33 | // Create the TwoAgentChat configurations 34 | List chatConfigurations = new() 35 | { 36 | CreateTranslationReviewConfig(), 37 | CreateLegalReviewConfig(), 38 | CreateFactCheckReviewConfig() 39 | }; 40 | 41 | // Create the SequentialTwoAgentChat with these configurations 42 | SequentialTwoAgentChat sequentialTwoAgentChat = new SequentialTwoAgentChat(chatConfigurations); 43 | 44 | sequentialTwoAgentChat.IsComplete = false; 45 | 46 | return sequentialTwoAgentChat; 47 | } 48 | 49 | // Translation Worker + Critic Configuration 50 | private static TwoAgentChatConfiguration CreateTranslationReviewConfig() 51 | { 52 | var translatorAgent = CreateTranslatorAgent(); 53 | var translationCriticAgent = CreateTranslationCriticAgent(); 54 | 55 | return new TwoAgentChatConfiguration( 56 | workerAgent: translatorAgent, 57 | criticAgent: translationCriticAgent, 58 | maxIterations: 3, 59 | terminationKeyword: "APPROVE", 60 | carryover: true, 61 | resultProcessing: ResultProcessing.Replace 62 | ); 63 | } 64 | 65 | // Legal Review Worker + Critic Configuration 66 | private static TwoAgentChatConfiguration CreateLegalReviewConfig() 67 | { 68 | var legalReviewerAgent = CreateLegalReviewerAgent(); 69 | var legalCriticAgent = CreateLegalCriticAgent(); 70 | 71 | return new TwoAgentChatConfiguration( 72 | workerAgent: legalReviewerAgent, 73 | criticAgent: legalCriticAgent, 74 | maxIterations: 3, 75 | terminationKeyword: "APPROVE", 76 | carryover: true, 77 | resultProcessing: ResultProcessing.Append 78 | ); 79 | } 80 | 81 | // Fact-Checking Worker + Critic Configuration 82 | private static TwoAgentChatConfiguration CreateFactCheckReviewConfig() 83 | { 84 | var factCheckerAgent = CreateFactCheckerAgent(); 85 | var factCheckCriticAgent = CreateFactCheckCriticAgent(); 86 | 87 | return new TwoAgentChatConfiguration( 88 | workerAgent: factCheckerAgent, 89 | criticAgent: factCheckCriticAgent, 90 | maxIterations: 3, 91 | terminationKeyword: "APPROVE", 92 | carryover: true, 93 | resultProcessing: ResultProcessing.Append 94 | ); 95 | } 96 | 97 | // Methods to Create Agents 98 | 99 | private static ChatCompletionAgent CreateTranslatorAgent() 100 | { 101 | const string translatorInstructions = @" 102 | You are a skilled translator. Translate the content provided to you accurately. 103 | Ensure the translation is precise and free from bias or subjective interpretation. 104 | "; 105 | 106 | return new ChatCompletionAgent 107 | { 108 | Instructions = translatorInstructions, 109 | Name = "Translator", 110 | Kernel = Kernel, 111 | }; 112 | } 113 | 114 | private static ChatCompletionAgent CreateTranslationCriticAgent() 115 | { 116 | const string translationCriticInstructions = @" 117 | You are a critic reviewing the translation. Your task is to find any incorrect or subjective points in the translation. 118 | Review the translation critically, and if it meets the standards, respond with 'APPROVE'. 119 | "; 120 | 121 | return new ChatCompletionAgent 122 | { 123 | Instructions = translationCriticInstructions, 124 | Name = "TranslationCritic", 125 | Kernel = Kernel, 126 | }; 127 | } 128 | 129 | private static ChatCompletionAgent CreateLegalReviewerAgent() 130 | { 131 | const string legalReviewerInstructions = @" 132 | You are a legal reviewer. Your job is to ensure that the content complies with legal regulations. 133 | Ensure the content is clear, legally accurate, and free of any potential legal issues. 134 | you are only to check the last provided translation, in English. 135 | Elaborate your output in markdown format as output and suggest improvements. 136 | "; 137 | 138 | return new ChatCompletionAgent 139 | { 140 | Instructions = legalReviewerInstructions, 141 | Name = "LegalReviewer", 142 | Kernel = Kernel, 143 | }; 144 | } 145 | 146 | private static ChatCompletionAgent CreateLegalCriticAgent() 147 | { 148 | const string legalCriticInstructions = @" 149 | You are a critic reviewing the legal review. Your task is to find any inaccuracies or subjective interpretations in the legal review. 150 | Review the legal analysis carefully, and if it meets the standards, respond with 'APPROVE'. 151 | "; 152 | 153 | return new ChatCompletionAgent 154 | { 155 | Instructions = legalCriticInstructions, 156 | Name = "LegalCritic", 157 | Kernel = Kernel, 158 | }; 159 | } 160 | 161 | private static ChatCompletionAgent CreateFactCheckerAgent() 162 | { 163 | const string factCheckerInstructions = @" 164 | You are a fact checker. Your task is to verify that all the factual claims in the content are accurate. 165 | you are only to check the last provided translation, in English. 166 | Elaborate your output in markdown format as output and suggest improvements. 167 | Cross-check the information with trusted sources and flag any discrepancies. 168 | "; 169 | 170 | return new ChatCompletionAgent 171 | { 172 | Instructions = factCheckerInstructions, 173 | Name = "FactChecker", 174 | Kernel = Kernel, 175 | }; 176 | } 177 | 178 | private static ChatCompletionAgent CreateFactCheckCriticAgent() 179 | { 180 | const string factCheckCriticInstructions = @" 181 | You are a critic reviewing the fact-checking process. Your task is to find any missed points or subjective conclusions in the fact check. 182 | If the fact-check is thorough and accurate, respond with 'APPROVE'. 183 | "; 184 | 185 | return new ChatCompletionAgent 186 | { 187 | Instructions = factCheckCriticInstructions, 188 | Name = "FactCheckCritic", 189 | Kernel = Kernel, 190 | }; 191 | } 192 | } -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/TestV2ChatWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.SemanticKernel.Plugins.Web; 5 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | 9 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 10 | 11 | namespace AgenticChatWorkflows; 12 | 13 | public static class TestV2ChatWorkflowFactory 14 | { 15 | 16 | private const string ReviewerName = "ArtDirector"; 17 | private const string ReviewerInstructions = 18 | """ 19 | You are an art director who has opinions about copywriting born of a love for David Ogilvy. 20 | The goal is to determine if the given copy is acceptable to print. 21 | If so, state that it is approved. 22 | If not, provide insight on how to refine suggested copy without examples. 23 | """; 24 | 25 | private const string CopyWriterName = "CopyWriter"; 26 | private const string CopyWriterInstructions = 27 | """ 28 | You are a copywriter with ten years of experience and are known for brevity and a dry humor. 29 | The goal is to refine and decide on the single best copy as an expert in the field. 30 | Only provide a single proposal per response. 31 | You're laser focused on the goal at hand. 32 | Don't waste time with chit chat. 33 | Consider suggestions when refining an idea. 34 | """; 35 | 36 | // Lazy Kernel initialization 37 | private static Kernel? _kernel; 38 | public static Kernel Kernel => _kernel ??= CreateKernel(); 39 | 40 | // Create the Kernel lazily using the environment variables 41 | private static Kernel CreateKernel() 42 | { 43 | var builder = Kernel.CreateBuilder(); 44 | builder.Services.AddSingleton(); 45 | 46 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 47 | deploymentName: EnvironmentWellKnown.DeploymentName, 48 | endpoint: EnvironmentWellKnown.Endpoint, 49 | apiKey: EnvironmentWellKnown.ApiKey) 50 | .Build(); 51 | 52 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 53 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 54 | 55 | return kernel; 56 | } 57 | 58 | 59 | public static IAgentGroupChat CreateChat(int characterLimit = 2000, int maxIterations = 4) 60 | { 61 | // Define the agents 62 | ChatCompletionAgent agentReviewer = 63 | new() 64 | { 65 | Instructions = ReviewerInstructions, 66 | Name = ReviewerName, 67 | Kernel = Kernel, 68 | }; 69 | 70 | ChatCompletionAgent agentWriter = 71 | new() 72 | { 73 | Instructions = CopyWriterInstructions, 74 | Name = CopyWriterName, 75 | Kernel = Kernel, 76 | }; 77 | 78 | KernelFunction terminationFunction = 79 | KernelFunctionFactory.CreateFromPrompt( 80 | """ 81 | Determine if the copy has been approved. If so, respond with a single word: yes 82 | 83 | History: 84 | {{$history}} 85 | """); 86 | 87 | KernelFunction selectionFunction = 88 | KernelFunctionFactory.CreateFromPrompt( 89 | $$$""" 90 | Determine which participant takes the next turn in a conversation based on the the most recent participant. 91 | State only the name of the participant to take the next turn. 92 | No participant should take more than one turn in a row. 93 | 94 | Choose only from these participants: 95 | - {{{ReviewerName}}} 96 | - {{{CopyWriterName}}} 97 | 98 | Always follow these rules when selecting the next participant: 99 | - After {{{CopyWriterName}}}, it is {{{ReviewerName}}}'s turn. 100 | - After {{{ReviewerName}}}, it is {{{CopyWriterName}}}'s turn. 101 | 102 | History: 103 | {{$history}} 104 | """); 105 | 106 | // Create a chat for agent interaction. 107 | IAgentGroupChat chatExt = 108 | new AgentGroupChatExt(agentWriter, agentReviewer) 109 | { 110 | ExecutionSettings = 111 | new() 112 | { 113 | // Here KernelFunctionTerminationStrategy will terminate 114 | // when the art-director has given their approval. 115 | TerminationStrategy = 116 | new KernelFunctionTerminationStrategy(terminationFunction, Kernel) 117 | { 118 | // Only the art-director may approve. 119 | Agents = [agentReviewer], 120 | // Customer result parser to determine if the response is "yes" 121 | ResultParser = (result) => result.GetValue()?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false, 122 | // The prompt variable name for the history argument. 123 | HistoryVariableName = "history", 124 | // Limit total number of turns 125 | MaximumIterations = 10, 126 | }, 127 | // Here a KernelFunctionSelectionStrategy selects agents based on a prompt function. 128 | SelectionStrategy = 129 | new KernelFunctionSelectionStrategy(selectionFunction, Kernel) 130 | { 131 | // Always start with the writer agent. 132 | InitialAgent = agentWriter, 133 | // Returns the entire result value as a string. 134 | ResultParser = (result) => result.GetValue() ?? CopyWriterName, 135 | // The prompt variable name for the agents argument. 136 | AgentsVariableName = "agents", 137 | // The prompt variable name for the history argument. 138 | HistoryVariableName = "history", 139 | }, 140 | } 141 | }; 142 | 143 | return chatExt; 144 | } 145 | 146 | 147 | } 148 | 149 | 150 | ///create a concept idea for a story regarding Generative AI in the future, the rise of the machines, AI Agents and a developer that saves the world. 151 | /// Science fiction thriller with romance, so he saves the girl 152 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/TestWorkflowChatFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.ComponentModel; 7 | using Microsoft.SemanticKernel; 8 | using Microsoft.SemanticKernel.Agents; 9 | using Microsoft.SemanticKernel.Agents.OpenAI; 10 | using Microsoft.SemanticKernel.ChatCompletion; 11 | using Microsoft.SemanticKernel.Connectors.OpenAI; 12 | using Microsoft.SemanticKernel.Agents.Chat; 13 | using Microsoft.SemanticKernel.Plugins.Web; 14 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 15 | using System.Net; 16 | using System.Threading; 17 | using Microsoft.Extensions.DependencyInjection; 18 | using System.Windows.Controls; 19 | using System.Windows.Documents; 20 | using System.Reflection.Metadata; 21 | using System.Windows.Media; 22 | 23 | 24 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 25 | 26 | namespace AgenticChatWorkflows; 27 | 28 | public static class TestWorkflowChatFactory 29 | { 30 | // Lazy Kernel initialization 31 | private static Kernel? _kernel; 32 | public static Kernel Kernel => _kernel ??= CreateKernel(); 33 | 34 | // Create the Kernel lazily using the environment variables 35 | private static Kernel CreateKernel() 36 | { 37 | var builder = Kernel.CreateBuilder(); 38 | builder.Services.AddSingleton(); 39 | 40 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 41 | deploymentName: EnvironmentWellKnown.DeploymentName, 42 | endpoint: EnvironmentWellKnown.Endpoint, 43 | apiKey: EnvironmentWellKnown.ApiKey) 44 | .Build(); 45 | 46 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 47 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 48 | 49 | return kernel; 50 | } 51 | 52 | public static IAgentGroupChat CreateChat(int characterLimit = 2000, int maxIterations = 25) 53 | { 54 | string facts = Context.Facts; 55 | 56 | ChatCompletionAgent questionAnswererAgent = new() 57 | { 58 | Instructions = $""" 59 | You are a question answerer for {facts}. 60 | You take in questions from a questionnaire and emit the answers from the perspective of {facts}, 61 | using documentation from the public web. You also emit links to any websites you find that help answer the questions. 62 | Do not address the user as 'you' - make all responses solely in the third person. 63 | If you do not find information on a topic, you simply respond that there is no information available on that topic. 64 | You will emit an answer that is no greater than {characterLimit} characters in length. 65 | """, 66 | Name = "QuestionAnswererAgent", 67 | Kernel = Kernel, 68 | Arguments = new KernelArguments( 69 | new OpenAIPromptExecutionSettings() 70 | { 71 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions 72 | }), 73 | }; 74 | 75 | ChatCompletionAgent answerCheckerAgent = new() 76 | { 77 | Instructions = $""" 78 | You are an answer checker for {facts}. Your responses always start with either the words ANSWER CORRECT or ANSWER INCORRECT. 79 | Given a question and an answer, you check the answer for accuracy regarding {facts}, 80 | using public web sources when necessary. If everything in the answer is true, you verify the answer by responding "ANSWER CORRECT." with no further explanation. 81 | You also ensure that the answer is no greater than {characterLimit} characters in length. 82 | Otherwise, you respond "ANSWER INCORRECT - " and add the portion that is incorrect. 83 | You do not output anything other than "ANSWER CORRECT" or "ANSWER INCORRECT - ". 84 | """, 85 | Name = "AnswerCheckerAgent", 86 | Kernel = Kernel, 87 | Arguments = new KernelArguments( 88 | new OpenAIPromptExecutionSettings() 89 | { 90 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions 91 | }), 92 | }; 93 | 94 | ChatCompletionAgent linkCheckerAgent = new() 95 | { 96 | Instructions = """ 97 | You are a link checker. Your responses always start with either the words LINKS CORRECT or LINK INCORRECT. 98 | Given a question and an answer that contains links, you verify that the links are working, 99 | using public web sources when necessary. If all links are working, you verify the answer by responding "LINKS CORRECT" with no further explanation. 100 | Otherwise, for each bad link, you respond "LINK INCORRECT - " and add the link that is incorrect. 101 | You do not output anything other than "LINKS CORRECT" or "LINK INCORRECT - ". 102 | """, 103 | Name = "LinkCheckerAgent", 104 | Kernel = Kernel 105 | }; 106 | 107 | ChatCompletionAgent managerAgent = new() 108 | { 109 | Instructions = """ 110 | You are a manager which reviews the question, the answer to the question, and the links. 111 | If the answer checker replies "ANSWER INCORRECT", or the link checker replies "LINK INCORRECT," you can reply "reject" and ask the question answerer to correct the answer. 112 | Once the question has been answered properly, you can approve the request by just responding "approve". 113 | You do not output anything other than "reject" or "approve". 114 | """, 115 | Name = "ManagerAgent", 116 | Kernel = Kernel 117 | }; 118 | 119 | IAgentGroupChat chat = new AgentGroupChatExt(questionAnswererAgent, answerCheckerAgent, linkCheckerAgent, managerAgent) 120 | { 121 | ExecutionSettings = new() 122 | { 123 | TerminationStrategy = new ApprovalTerminationStrategy() 124 | { 125 | Agents = [managerAgent], 126 | MaximumIterations = maxIterations, 127 | } 128 | } 129 | }; 130 | 131 | return chat; 132 | } 133 | 134 | 135 | 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/ChatWorkflows/TwoAgentChatWorkflowFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel.Agents.Chat; 4 | using Microsoft.SemanticKernel.Plugins.Web; 5 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | 9 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 10 | 11 | namespace AgenticChatWorkflows; 12 | 13 | public static class TwoAgentChatWorkflowFactory 14 | { 15 | private const string ReviewerName = "ArtDirector"; 16 | private const string CopyWriterName = "CopyWriter"; 17 | private const string TerminationKeyword = "approved"; 18 | 19 | // Lazy Kernel initialization 20 | private static Kernel? _kernel; 21 | public static Kernel Kernel => _kernel ??= CreateKernel(); 22 | 23 | // Create the Kernel lazily using the environment variables 24 | private static Kernel CreateKernel() 25 | { 26 | var builder = Kernel.CreateBuilder(); 27 | builder.Services.AddSingleton(); 28 | 29 | Kernel kernel = builder.AddAzureOpenAIChatCompletion( 30 | deploymentName: EnvironmentWellKnown.DeploymentName, 31 | endpoint: EnvironmentWellKnown.Endpoint, 32 | apiKey: EnvironmentWellKnown.ApiKey) 33 | .Build(); 34 | 35 | BingConnector bing = new BingConnector(EnvironmentWellKnown.BingApiKey); 36 | kernel.ImportPluginFromObject(new WebSearchEnginePlugin(bing), "bing"); 37 | 38 | return kernel; 39 | } 40 | 41 | public static IAgentGroupChat CreateChat(int characterLimit = 2000, int maxIterations = 4) 42 | { 43 | // Create agents using separate methods 44 | ChatCompletionAgent agentReviewer = CreateReviewerAgent(); 45 | ChatCompletionAgent agentWriter = CreateCopyWriterAgent(); 46 | 47 | // Create an instance of TwoAgentChat 48 | TwoAgentChat twoAgentChat = new TwoAgentChat( 49 | agentWriter, 50 | agentReviewer, 51 | maxIterations, 52 | TerminationKeyword); 53 | 54 | twoAgentChat.IsComplete = false; 55 | 56 | return twoAgentChat; 57 | } 58 | 59 | // Method to create the Reviewer Agent 60 | private static ChatCompletionAgent CreateReviewerAgent() 61 | { 62 | const string reviewerInstructions = $""" 63 | You are an art director who has opinions about copywriting born of a love for David Ogilvy. 64 | The goal is to determine if the given copy is acceptable to print. 65 | If so, state that it is approved. Say "{TerminationKeyword}" to approve the copy. 66 | If not, provide insight on how to refine suggested copy without examples. 67 | """; 68 | 69 | return new ChatCompletionAgent 70 | { 71 | Instructions = reviewerInstructions, 72 | Name = ReviewerName, 73 | Kernel = Kernel, 74 | }; 75 | } 76 | 77 | // Method to create the CopyWriter Agent 78 | private static ChatCompletionAgent CreateCopyWriterAgent() 79 | { 80 | const string copyWriterInstructions = """ 81 | You are a copywriter with ten years of experience and are known for brevity and a dry humor. 82 | The goal is to refine and decide on the single best copy as an expert in the field. 83 | Only provide a single proposal per response. 84 | You're laser focused on the goal at hand. 85 | Don't waste time with chit chat. 86 | Consider suggestions when refining an idea. 87 | """; 88 | 89 | return new ChatCompletionAgent 90 | { 91 | Instructions = copyWriterInstructions, 92 | Name = CopyWriterName, 93 | Kernel = Kernel, 94 | }; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /AgenticChatWorkflows/Context.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AgenticChatWorkflows; 8 | 9 | public static class Context 10 | { 11 | // Event to notify when any property changes 12 | public static event Action? PropertyChanged; 13 | 14 | private static string _facts = "Microsoft Azure AI"; 15 | public static string Facts 16 | { 17 | get => _facts; 18 | set 19 | { 20 | if (_facts != value) 21 | { 22 | _facts = value; 23 | NotifyPropertyChanged(nameof(Facts)); 24 | } 25 | } 26 | } 27 | 28 | // Workflow-specific properties with change notification 29 | private static string _code = ""; 30 | public static string Code 31 | { 32 | get => _code; 33 | set 34 | { 35 | if (_code != value) 36 | { 37 | _code = value; 38 | NotifyPropertyChanged(nameof(Code)); 39 | } 40 | } 41 | } 42 | 43 | // Method to trigger the PropertyChanged event 44 | private static void NotifyPropertyChanged(string propertyName) 45 | { 46 | PropertyChanged?.Invoke(propertyName); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/EnvironmentWellKnown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AgenticChatWorkflows; 8 | 9 | public static class EnvironmentWellKnown 10 | { 11 | private static string? _deploymentName; 12 | public static string DeploymentName => _deploymentName ??= Environment.GetEnvironmentVariable("AzureOpenAI_Model"); 13 | 14 | private static string? _endpoint; 15 | public static string Endpoint => _endpoint ??= Environment.GetEnvironmentVariable("AzureOpenAI_Endpoint"); 16 | 17 | private static string? _apiKey; 18 | public static string ApiKey => _apiKey ??= Environment.GetEnvironmentVariable("AzureOpenAI_ApiKey"); 19 | 20 | private static string? _bingApiKey; 21 | public static string BingApiKey => _bingApiKey ??= Environment.GetEnvironmentVariable("Bing_ApiKey"); 22 | } 23 | -------------------------------------------------------------------------------- /AgenticChatWorkflows/StringToFlowDocumentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | using System.Windows.Documents; 9 | 10 | namespace AgenticChatWorkflows; 11 | 12 | public class StringToFlowDocumentConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value is string stringValue) 17 | { 18 | FlowDocument doc = new FlowDocument(); 19 | doc.Blocks.Add(new Paragraph(new Run(stringValue))); 20 | return doc; 21 | } 22 | return null; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | if (value is FlowDocument document) 28 | { 29 | TextRange textRange = new TextRange(document.ContentStart, document.ContentEnd); 30 | return textRange.Text.Trim(); // return string representation of FlowDocument 31 | } 32 | return string.Empty; 33 | } 34 | } -------------------------------------------------------------------------------- /AgenticWorkflowModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.ComponentModel; 7 | using Microsoft.SemanticKernel; 8 | using Microsoft.SemanticKernel.Agents; 9 | using Microsoft.SemanticKernel.Agents.OpenAI; 10 | using Microsoft.SemanticKernel.ChatCompletion; 11 | using Microsoft.SemanticKernel.Connectors.OpenAI; 12 | using Microsoft.SemanticKernel.Agents.Chat; 13 | using Microsoft.SemanticKernel.Plugins.Web; 14 | using Microsoft.SemanticKernel.Plugins.Web.Bing; 15 | using System.Net; 16 | using System.Threading; 17 | using Microsoft.Extensions.DependencyInjection; 18 | using System.Windows.Controls; 19 | using System.Windows.Documents; 20 | using System.Reflection.Metadata; 21 | using System.Windows.Media; 22 | 23 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 24 | 25 | namespace AgenticChatWorkflows; 26 | 27 | class AgenticWorkflowModel : INotifyPropertyChanged 28 | { 29 | testWindow02? mainWindow; 30 | 31 | private int _CharacterLimit = 2000; 32 | public IAgentGroupChat? ChatWorkflow { get; private set; } 33 | 34 | public int CharacterLimit 35 | { 36 | get { return _CharacterLimit; } 37 | set 38 | { 39 | if (_CharacterLimit != value) 40 | { 41 | _CharacterLimit = value; 42 | OnPropertyChanged("CharacterLimit"); 43 | } 44 | } 45 | } 46 | 47 | public string Facts 48 | { 49 | get { return Context.Facts; } 50 | set 51 | { 52 | if (Context.Facts != value) 53 | { 54 | Context.Facts = value; 55 | OnPropertyChanged("Facts"); 56 | } 57 | } 58 | } 59 | 60 | public string Concept 61 | { 62 | get { return Context.Code; } 63 | set 64 | { 65 | if (Context.Code != value) 66 | { 67 | Context.Code = value; 68 | UpdateConceptRTB(); // Manually update RichTextBox content 69 | OnPropertyChanged("Concept"); 70 | } 71 | } 72 | } 73 | 74 | private Dictionary _workflowOptions; 75 | private WorkflowType _selectedWorkflow; 76 | 77 | public Dictionary WorkflowOptions 78 | { 79 | get => _workflowOptions; 80 | set 81 | { 82 | _workflowOptions = value; 83 | OnPropertyChanged(nameof(WorkflowOptions)); 84 | } 85 | } 86 | 87 | public WorkflowType SelectedWorkflow 88 | { 89 | get => _selectedWorkflow; 90 | set 91 | { 92 | _selectedWorkflow = value; 93 | OnPropertyChanged(nameof(SelectedWorkflow)); 94 | } 95 | } 96 | 97 | public event PropertyChangedEventHandler? PropertyChanged; 98 | 99 | protected virtual void OnPropertyChanged(string propertyName) 100 | { 101 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 102 | } 103 | 104 | private string _Question = "Does your service offer video generative AI?"; 105 | public string Question 106 | { 107 | get { return _Question; } 108 | set 109 | { 110 | if (_Question != value) 111 | { 112 | _Question = value; 113 | OnPropertyChanged("Question"); 114 | } 115 | } 116 | } 117 | 118 | string? QuestionAnswererPrompt; 119 | string? AnswerCheckerPrompt; 120 | string? LinkCheckerPrompt; 121 | string? ManagerPrompt; 122 | 123 | public AgenticWorkflowModel(testWindow02 mainWindow) 124 | { 125 | this.mainWindow = mainWindow; 126 | PopulateWorkflowOptions(); 127 | InitializeChatWorkflow(); 128 | UpdateContext(); 129 | } 130 | 131 | // Method to populate the WorkflowOptions dictionary 132 | private void PopulateWorkflowOptions() 133 | { 134 | WorkflowOptions = new Dictionary 135 | { 136 | { WorkflowType.TestWorkflow, "Test Workflow" }, 137 | { WorkflowType.CodeCrafterAgentChatWorkflow, "Code Crafter Single Agent Workflow" }, 138 | { WorkflowType.TestV2Workflow, "Test V2 Workflow" }, 139 | { WorkflowType.TwoAgentChatWorkflow, "Two Agent Workflow" }, 140 | { WorkflowType.SequentialAgentChatWorkflow, "Sequential Agent Workflow" }, 141 | { WorkflowType.SequentialTwoAgentChatWorkflow, "Sequential Two Agent Workflow" }, 142 | { WorkflowType.NestedChatWithGroupAgentChatWorkflow, "Nested Chat with Group Agent Chat Agent Workflow" }, 143 | { WorkflowType.AutoGenPluginChatWorkflow, "AutoGen Plugin Chat Workflow" } 144 | }; 145 | } 146 | 147 | public void InitializeChatWorkflow() 148 | { 149 | switch (SelectedWorkflow) 150 | { 151 | case WorkflowType.TestWorkflow: 152 | ChatWorkflow = TestWorkflowChatFactory.CreateChat(CharacterLimit); 153 | break; 154 | 155 | case WorkflowType.TestV2Workflow: 156 | ChatWorkflow = TestV2ChatWorkflowFactory.CreateChat(CharacterLimit); 157 | break; 158 | 159 | case WorkflowType.TwoAgentChatWorkflow: 160 | ChatWorkflow = TwoAgentChatWorkflowFactory.CreateChat(CharacterLimit); 161 | break; 162 | 163 | case WorkflowType.SequentialAgentChatWorkflow: 164 | ChatWorkflow = SequentialAgentChatWorkflowFactory.CreateChat(CharacterLimit); 165 | break; 166 | 167 | case WorkflowType.SequentialTwoAgentChatWorkflow: 168 | ChatWorkflow = SequentialTwoAgentChatWorkflowFactory.CreateChat(); 169 | break; 170 | 171 | case WorkflowType.NestedChatWithGroupAgentChatWorkflow: 172 | ChatWorkflow = NestedChatWithGroupAgentChatWorkflowFactory.CreateChat(); 173 | break; 174 | 175 | case WorkflowType.CodeCrafterAgentChatWorkflow: 176 | ChatWorkflow = CodeCrafterWorkflowChatFactory.CreateChat(CharacterLimit); 177 | break; 178 | 179 | case WorkflowType.AutoGenPluginChatWorkflow: 180 | ChatWorkflow = SemanticKernelWithAutoGenPluginChatFactory.CreateChat(CharacterLimit); 181 | break; 182 | 183 | default: 184 | // Handle default case if no workflow matches 185 | updateResponseBox("Error", "No valid workflow selected."); 186 | ChatWorkflow = null; 187 | break; 188 | } 189 | } 190 | 191 | public async Task askQuestion() 192 | { 193 | if (ChatWorkflow == null) 194 | { 195 | updateResponseBox("Error", "No chat workflow is initialized for the selected workflow."); 196 | return; 197 | } 198 | 199 | string input = Question; 200 | ChatWorkflow.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); 201 | 202 | updateResponseBox("Question", input); 203 | 204 | string finalAnswer = ""; 205 | 206 | await foreach (var content in ChatWorkflow.InvokeAsync()) 207 | { 208 | Color color; 209 | switch (content.AuthorName) 210 | { 211 | case "QuestionAnswererAgent": 212 | color = Colors.Black; 213 | finalAnswer = content.Content; // Assume last time it's called, it's the final answer 214 | break; 215 | case "AnswerCheckerAgent": 216 | color = Colors.Blue; 217 | break; 218 | case "LinkCheckerAgent": 219 | color = Colors.DarkGoldenrod; 220 | break; 221 | case "ManagerAgent": 222 | color = Colors.DarkGreen; 223 | break; 224 | case "IdeaCrafterAgent": 225 | color = Colors.Yellow; 226 | break; 227 | case "ArtDirector": 228 | color = Colors.DarkRed; 229 | break; 230 | case "CopyWriter": 231 | color = Colors.DarkBlue; 232 | break; 233 | 234 | default: 235 | color = Colors.DarkSlateBlue; 236 | break; 237 | } 238 | 239 | updateResponseBox(content.AuthorName, content.Content, color); 240 | } 241 | } 242 | 243 | public void updateResponseBox(string sender, string response) 244 | { 245 | updateResponseBox(sender, response, Colors.Black); 246 | } 247 | 248 | public void updateResponseBox(string sender, string response, Color color) 249 | { 250 | //Update mainWindow.ResponseBox to add the sender in bold, a colon, a space, and the response in normal text 251 | Paragraph paragraph = new Paragraph(); 252 | Bold bold = new Bold(new Run(sender + ": ")); 253 | 254 | bold.Foreground = new SolidColorBrush(color); 255 | 256 | paragraph.Inlines.Add(bold); 257 | Run run = new Run(response); 258 | paragraph.Inlines.Add(run); 259 | mainWindow.ResponseBox.Document.Blocks.Add(paragraph); 260 | 261 | // Scroll to the end after adding new content 262 | ScrollToEnd(mainWindow.ResponseBox); 263 | } 264 | 265 | private void ScrollToEnd(RichTextBox richTextBox) 266 | { 267 | if (richTextBox != null) 268 | { 269 | // Use Dispatcher to ensure it's invoked on the UI thread 270 | richTextBox.Dispatcher.BeginInvoke(new Action(() => 271 | { 272 | richTextBox.ScrollToEnd(); 273 | })); 274 | } 275 | } 276 | 277 | #region Context changes and updates 278 | private void UpdateContext() 279 | { 280 | Context.PropertyChanged -= OnContextPropertyChanged; 281 | 282 | UpdateConceptRTB(); 283 | // Subscribe to static Context property changes 284 | Context.PropertyChanged += OnContextPropertyChanged; 285 | } 286 | 287 | // Update RichTextBox with the Concept content 288 | //public void UpdateConceptRTB() 289 | //{ 290 | // if (mainWindow != null && mainWindow.ConceptRTB != null) 291 | // { 292 | // mainWindow.ConceptRTB.Document.Blocks.Clear(); 293 | // mainWindow.ConceptRTB.Document.Blocks.Add(new Paragraph(new Run(Concept))); 294 | // } 295 | //} 296 | public void UpdateConceptRTB() 297 | { 298 | if (mainWindow != null && mainWindow.ConceptRTB != null) 299 | { 300 | // Check if we're on the UI thread 301 | if (mainWindow.Dispatcher.CheckAccess()) 302 | { 303 | // We are on the UI thread, so we can directly update the RichTextBox 304 | mainWindow.ConceptRTB.Document.Blocks.Clear(); 305 | mainWindow.ConceptRTB.Document.Blocks.Add(new Paragraph(new Run(Concept))); 306 | } 307 | else 308 | { 309 | // We're not on the UI thread, so we must use the Dispatcher to update the UI 310 | mainWindow.Dispatcher.BeginInvoke(new Action(() => 311 | { 312 | mainWindow.ConceptRTB.Document.Blocks.Clear(); 313 | mainWindow.ConceptRTB.Document.Blocks.Add(new Paragraph(new Run(Concept))); 314 | })); 315 | } 316 | } 317 | } 318 | 319 | 320 | 321 | #endregion 322 | 323 | private void OnContextPropertyChanged(string propertyName) 324 | { 325 | // Raise the property changed event for the respective property 326 | UpdateConceptRTB(); 327 | OnPropertyChanged(propertyName); 328 | 329 | } 330 | 331 | ~AgenticWorkflowModel() 332 | { 333 | // Unsubscribe from event to avoid memory leaks 334 | Context.PropertyChanged -= OnContextPropertyChanged; 335 | } 336 | 337 | } 338 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.Data; 3 | using System.Windows; 4 | 5 | namespace AgenticChatWorkflows 6 | { 7 | /// 8 | /// Interaction logic for App.xaml 9 | /// 10 | public partial class App : Application 11 | { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /ApprovalTerminationStrategy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel.Agents.Chat; 2 | using Microsoft.SemanticKernel.Agents; 3 | using Microsoft.SemanticKernel; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | #pragma warning disable SKEXP0110, SKEXP0001 11 | 12 | namespace AgenticChatWorkflows; 13 | 14 | class ApprovalTerminationStrategy : TerminationStrategy 15 | { 16 | // Terminate when the final message contains the term "approve" 17 | protected override Task ShouldAgentTerminateAsync(Agent agent, IReadOnlyList history, CancellationToken cancellationToken) 18 | => Task.FromResult(history[history.Count - 1].Content?.Contains("approve", StringComparison.OrdinalIgnoreCase) ?? false); 19 | } 20 | -------------------------------------------------------------------------------- /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_CriticWrapperAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_CriticWrapperAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string article, string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var CriticWrapperAgent = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "Critic_Wrapper", 26 | systemMessage: $""" 27 | You are a meta reviewer, you aggregate and review the work of other reviewers and give a final suggestion on the content. 28 | 29 | You are a critic wrapper. You delegate the review work of the article to a set of specialized committee of expert reviewers and provide their feedback "as is" to help improve the quality of the content. 30 | you will not change at all the feedback provided by the reviewers, it will come to you aggregated and formatted. 31 | you will not change the format. You will not change the feedback. You will not change the content. 32 | you will hand over the article "as is" to the expert reviewers. 33 | The Article is: 34 | --- 35 | {article} 36 | --- 37 | """) 38 | .RegisterMessageConnector() 39 | .RegisterPrintMessage(); 40 | 41 | return CriticWrapperAgent; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_EthicsReviewerAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_EthicsReviewerAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var AutoGen_EthicsReviewerAgent = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "EthicsReviewer", 26 | systemMessage: $""" 27 | You are an ethics reviewer, known for your ability to ensure that content is ethically sound and free from any potential ethical issues. 28 | Make sure your suggestion is concise (within 3 bullet points), concrete and to the point. 29 | Begin the review by stating your role. 30 | """) 31 | .RegisterMessageConnector() 32 | .RegisterPrintMessage(); 33 | 34 | return AutoGen_EthicsReviewerAgent; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_LegalReviewerAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_LegalReviewerAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var AutoGen_LegalReviewerAgent = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "LegalReviewer", 26 | systemMessage: $""" 27 | You are a legal reviewer, known for your ability to ensure that content is legally compliant and free from any potential legal issues. 28 | Make sure your suggestion is concise (within 3 bullet points), concrete and to the point. 29 | Begin the review by stating your role. 30 | Also be aware of data privacy and GDPR compliance which needs to be respected, so in doubt suggest the removal of PII information. 31 | Asume that the speakers have agreed to share their name and title, so there is no issue with sharing that in the article. 32 | """) 33 | .RegisterMessageConnector() 34 | .RegisterPrintMessage(); 35 | 36 | return AutoGen_LegalReviewerAgent; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_MetaReviewerAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_MetaReviewerAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var AutoGen_MetaReviewer = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "MetaReviewer", 26 | systemMessage: $""" 27 | You are a meta reviewer, you aggregate and review the work of other reviewers and give a final suggestion on the content." 28 | 29 | """) 30 | .RegisterMessageConnector() 31 | .RegisterPrintMessage(); 32 | 33 | return AutoGen_MetaReviewer; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_SEOReviewerAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_SEOReviewerAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var SEOReviewerAgent = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "SEOReviewer", 26 | systemMessage: $""" 27 | You are an SEO reviewer, known for your ability to optimize content for search engines, ensuring that it ranks well and attracts organic traffic. 28 | Make sure your suggestion is concise (within 3 bullet points), concrete and to the point. 29 | Begin the review by stating your role. 30 | """) 31 | .RegisterMessageConnector() 32 | .RegisterPrintMessage(); 33 | 34 | return SEOReviewerAgent; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AutoGen/Agents/AutoGen_StyleCheckerAgent.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.Helpers; 2 | using AutoGen; 3 | using AutoGen.Core; 4 | using AutoGen.OpenAI; 5 | using AutoGen.OpenAI.Extension; 6 | using Azure.AI.OpenAI; 7 | 8 | namespace AgenticChatWorkflows.AutoGen.Agents; 9 | 10 | public static class AutoGen_StyleCheckerAgent 11 | { 12 | public static MiddlewareStreamingAgent CreateAgent(string modelOverride = null) 13 | { 14 | string model; 15 | AzureOpenAIClient client; 16 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 17 | 18 | if (!string.IsNullOrEmpty(modelOverride)) 19 | { 20 | model = modelOverride; 21 | } 22 | 23 | var AutoGen_StyleCheckerAgent = new OpenAIChatAgent( 24 | chatClient: client.GetChatClient(model), 25 | name: "FactChecker", 26 | systemMessage: $""" 27 | You are a Style Checker. Your task is to ensure that the article is written in a proper style. 28 | Check that the writing style is positive, engaging, motivational, original, and funny. 29 | The phrases should not be too complex, and the tone should be friendly, casual, yet polite. 30 | Provide suggestions to improve the style if necessary. 31 | Provide ONLY Suggestions, do not rewrite the content or write any part of the content in the style suggested, this is the work of the writer. 32 | You are a Style Checker. Your task is to ensure that the article is written in a proper style. 33 | Check that the writing style is positive, engaging, motivational, original, and funny. 34 | The phrases should not be too complex, and the tone should be friendly, casual, yet polite. 35 | Provide suggestions to improve the style if necessary. 36 | Provide ONLY Suggestions, do not rewrite the content or write any part of the content in the style suggested, this is the work of the writer. 37 | """) 38 | .RegisterMessageConnector() 39 | .RegisterPrintMessage(); 40 | 41 | return AutoGen_StyleCheckerAgent; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AutoGen/AskForFeedbackAutoGenPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using AutoGen.Core; 3 | using Microsoft.SemanticKernel; 4 | using OpenAI; 5 | 6 | namespace AgenticChatWorkflows.AutoGen; 7 | 8 | public class askForFeedbackAutoGenPlugin 9 | { 10 | [KernelFunction, Description("Performs a code review through different experts")] 11 | public async Task AskForFeedback(string article) 12 | { 13 | string articleReview = await AutoGenChatWorkflow_AskForFeedback.Execute(article); 14 | 15 | return articleReview; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AutoGen/AutoGenChatWorkflow_AskForFeedback.cs: -------------------------------------------------------------------------------- 1 | using AgenticChatWorkflows.AutoGen.Agents; 2 | using AgenticChatWorkflows.Helpers; 3 | using AutoGen.Core; 4 | using Azure.AI.OpenAI; 5 | using Google.Rpc; 6 | using OpenAI; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace AgenticChatWorkflows.AutoGen; 14 | 15 | public static class AutoGenChatWorkflow_AskForFeedback 16 | { 17 | public static async Task Execute(string article) 18 | { 19 | // Use AzureOpenAI 20 | string model; 21 | AzureOpenAIClient client; 22 | WellKnown.GetAutoGenModelAndConnectionToLLM(out model, out client); 23 | 24 | // Agent creation 25 | var CriticWrapperAgent = AutoGen_CriticWrapperAgent.CreateAgent(article); 26 | var EthicsReviewerAgent = AutoGen_EthicsReviewerAgent.CreateAgent(); 27 | var LegalReviewerAgent = AutoGen_LegalReviewerAgent.CreateAgent(); 28 | var SEOReviewerAgent = AutoGen_SEOReviewerAgent.CreateAgent(); 29 | var StyleCheckerAgent = AutoGen_StyleCheckerAgent.CreateAgent(); 30 | var MetaReviewerAgent = AutoGen_MetaReviewerAgent.CreateAgent(); 31 | 32 | // Middleware creation 33 | var middleware = new NestedChatReviewerMiddleware( 34 | CriticWrapperAgent, 35 | EthicsReviewerAgent, 36 | LegalReviewerAgent, 37 | SEOReviewerAgent, 38 | StyleCheckerAgent, 39 | MetaReviewerAgent); 40 | 41 | var nestedChatCriticWrapperAgent = AutoGen_CriticWrapperAgent.CreateAgent(article); 42 | 43 | // Register the middleware and setup message printing 44 | var middlewareAgent = nestedChatCriticWrapperAgent 45 | .RegisterMiddleware(middleware) 46 | .RegisterPrintMessage(); 47 | 48 | // https://microsoft.github.io/autogen-for-net/articles/Agent-overview.html 49 | var message = new TextMessage(Role.User, $"The code to review is: {article}"); 50 | 51 | IMessage reply = await middlewareAgent.GenerateReplyAsync([message]); 52 | 53 | return reply.GetContent(); 54 | } 55 | } 56 | 57 | public class NestedChatReviewerMiddleware : IMiddleware 58 | { 59 | private readonly IAgent CriticWrapperAgent; 60 | private readonly IAgent EthicsReviewerAgent; 61 | private readonly IAgent LegalReviewerAgent; 62 | private readonly IAgent SEOReviewerAgent; 63 | private readonly IAgent StyleCheckerAgent; 64 | private readonly IAgent MetaReviewerAgent; 65 | 66 | public NestedChatReviewerMiddleware( 67 | IAgent criticWrapperAgent, 68 | IAgent ethicsReviewerAgent, 69 | IAgent legalReviewerAgent, 70 | IAgent seoReviewerAgent, 71 | IAgent styleCheckerAgent, 72 | IAgent metaReviewerAgent) 73 | { 74 | CriticWrapperAgent = criticWrapperAgent; 75 | EthicsReviewerAgent = ethicsReviewerAgent; 76 | LegalReviewerAgent = legalReviewerAgent; 77 | SEOReviewerAgent = seoReviewerAgent; 78 | StyleCheckerAgent = styleCheckerAgent; 79 | MetaReviewerAgent = metaReviewerAgent; 80 | } 81 | 82 | public string? Name => nameof(NestedChatReviewerMiddleware); 83 | 84 | public async Task InvokeAsync( 85 | MiddlewareContext context, 86 | IAgent critic, 87 | CancellationToken cancellationToken = default) 88 | { 89 | var messageToReview = context.Messages.Last(); 90 | var reviewPrompt = $""" 91 | Review the following article: 92 | {messageToReview.GetContent()} 93 | """; 94 | 95 | var ethicsReviewTask = critic.SendAsync( 96 | receiver: EthicsReviewerAgent, 97 | message: reviewPrompt, 98 | maxRound: 1) 99 | .ToListAsync() 100 | .AsTask(); 101 | 102 | var legalReviewTask = critic.SendAsync( 103 | receiver: LegalReviewerAgent, 104 | message: reviewPrompt, 105 | maxRound: 1) 106 | .ToListAsync() 107 | .AsTask(); 108 | 109 | var seoReviewTask = critic.SendAsync( 110 | receiver: SEOReviewerAgent, 111 | message: reviewPrompt, 112 | maxRound: 1) 113 | .ToListAsync() 114 | .AsTask(); 115 | 116 | var styleReviewTask = critic.SendAsync( 117 | receiver: StyleCheckerAgent, 118 | message: reviewPrompt, 119 | maxRound: 1) 120 | .ToListAsync() 121 | .AsTask(); 122 | 123 | // Await all review tasks to enable parallel execution 124 | await Task.WhenAll( 125 | ethicsReviewTask, 126 | legalReviewTask, 127 | seoReviewTask, 128 | styleReviewTask); 129 | 130 | var ethicsReview = await ethicsReviewTask; 131 | var legalReview = await legalReviewTask; 132 | var seoReview = await seoReviewTask; 133 | var styleReview = await styleReviewTask; 134 | 135 | // Combine reviews from all agents 136 | var allReviews = ethicsReview 137 | .Concat(legalReview) 138 | .Concat(seoReview) 139 | .Concat(styleReview); 140 | 141 | var metaReview = await critic.SendAsync( 142 | receiver: MetaReviewerAgent, 143 | message: "Aggregate feedback from all reviewers and give final suggestions on the article.", 144 | chatHistory: allReviews, 145 | maxRound: 1) 146 | .ToListAsync(); 147 | 148 | var lastReview = metaReview.Last(); 149 | lastReview.From = critic.Name; 150 | 151 | // return the summarized reviews 152 | return lastReview; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Helpers/EnvironmentWellKnown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AgenticChatWorkflows.Helpers; 8 | 9 | public static class EnvironmentWellKnown 10 | { 11 | private static string? _deploymentName; 12 | public static string DeploymentName => _deploymentName ??= Environment.GetEnvironmentVariable("AzureOpenAI_Model"); 13 | 14 | private static string? _endpoint; 15 | public static string Endpoint => _endpoint ??= Environment.GetEnvironmentVariable("AzureOpenAI_Endpoint"); 16 | 17 | private static string? _apiKey; 18 | public static string ApiKey => _apiKey ??= Environment.GetEnvironmentVariable("AzureOpenAI_ApiKey"); 19 | 20 | private static string? _bingApiKey; 21 | public static string BingApiKey => _bingApiKey ??= Environment.GetEnvironmentVariable("Bing_ApiKey"); 22 | } -------------------------------------------------------------------------------- /Helpers/WellKnown.cs: -------------------------------------------------------------------------------- 1 | using AutoGen.Core; 2 | using Azure.AI.OpenAI; 3 | using Azure; 4 | 5 | namespace AgenticChatWorkflows.Helpers; 6 | 7 | public static class WellKnown 8 | { 9 | public static void GetAutoGenModelAndConnectionToLLM( 10 | out string model, 11 | out AzureOpenAIClient client) 12 | { 13 | var key = EnvironmentWellKnown.ApiKey; 14 | var endpoint = EnvironmentWellKnown.Endpoint; 15 | model = EnvironmentWellKnown.DeploymentName; // "gpt-4o-mini"; 16 | 17 | client = new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(key)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jose Luis Latorre Millas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SemanticKernelAgenticChatWorkflowsExtension 2 | Playing around with extending Semantic Kernel and implementing several agentic chat workflow patterns. 3 | 4 | I wanted to implement some advanced patterns with Semantic Kernel and got stuck with some things I could not extend. 5 | Essentially some classes are sealed and do not have any interfaces to implement. 6 | 7 | I have made an issue and a PR to get this in a better, more extensible state. 8 | - Issue: https://github.com/microsoft/semantic-kernel/issues/8719 9 | - PR: https://github.com/microsoft/semantic-kernel/pull/8720 10 | 11 | ## Semantic Kernel extensions 12 | I have implemented some interfaces so to extend Semantic Kernel and implement several agentic chat workflow patterns. 13 | - IAgentGroupChat & IAgentChat so I can create customized chat workflows. 14 | - Implemented a wrapper for the AgentGroupChat, AgentGroupChatEx so I can inject any kind of IAgentGroupChat and extend it 15 | - Created a AgentGroupChatExt which wraps the AgentGroupChat and implements IAgentGroupChat 16 | - Created the BaseAgentGroupChat which is a base class for other AgentGroupChat implementations 17 | - Implemented the SequAgentChat which is a sequential chat workflow. 18 | - Implemented the TwoAgentChat which is a chat between two agents managed through code (fast). 19 | - Implemented a SequeentialTwoAgentChat which is a sequential chat between several two agent workflows, through code (fast). 20 | - Tried to implement a Middleware pattern as it is in AutoGen.NET but did not fully work. Got stuck with some things I could not extend... 21 | 22 | ## Application 23 | I have created a WPF application based on a sample from Marco Casalaina exhibited in the Cozy AI Kitchen series from John Maeda. 24 | Sources: 25 | https://www.youtube.com/watch?v=7VCkdxKNBl4 26 | https://techcommunity.microsoft.com/t5/ai-ai-platform-blog/the-future-of-ai-exploring-multi-agent-ai-systems/ba-p/4226593 27 | https://github.com/mcasalaina/QuestionnaireMultiagent 28 | 29 | I have extended a bit to plug dynamically different chat workflows and to be able to change the chat workflow on the fly. 30 | Also improved the UI to my liking and needs. 31 | 32 | ## Idea 33 | I'd like to thank Chris Rickman, https://github.com/crickman, a Microsoft Principal Software Engineer working on the Semantic Kernel team, 34 | for the great discussions and his suggestions on how to extend Semantic Kernel which leaded to this repository. 35 | 36 | 37 | ## Current implementations (in UI, and workflow providers) 38 | - Code Crafter workflow (single - agent) - to improve or program some code 39 | - Two Agent Chat Workflow - chat between two agents 40 | - - Sequential Agent Chat Workflow - sequential chat between agents 41 | - Sequential Two Agent Chat Workflow - sequential chat between a set of a number of two agent chats 42 | - TestWorkflow - a test workflow to test the chat workflows with a "normal chat of X Agents" 43 | - TestV2ChatWorkflow - IAgentGroupChat Chat group with termination function - just based on the AgentGroupChatExt wrapper -------------------------------------------------------------------------------- /SearchFunctionFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | #pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 9 | #pragma warning disable SKEXP0050 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 10 | #pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 11 | 12 | namespace AgenticChatWorkflows; 13 | 14 | class SearchFunctionFilter : IFunctionInvocationFilter 15 | { 16 | public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next) 17 | { 18 | // We'll restore the color after we're done 19 | var prevColor = Console.ForegroundColor; 20 | 21 | // Indicate that the assistant is calling a function, but only if it's not an internal function 22 | var isInternal = context.Function.Name.StartsWith("internal_"); 23 | if (!isInternal) 24 | { 25 | var args = context.Arguments.Select(x => $"\"{x.Key}\": \"{x.Value}\"") ?? new List(); 26 | var json = "{" + string.Join(",", args) + "}"; 27 | 28 | Console.ForegroundColor = ConsoleColor.DarkGray; 29 | Console.Write($"\rassistant-function: {context.Function.Name}({json}) = "); 30 | } 31 | 32 | // Call the next middleware in the pipeline 33 | await next(context); 34 | 35 | // Indicate that the assistant has finished calling the function, but only if it's not an internal function 36 | if (!isInternal) 37 | { 38 | try 39 | { 40 | var result = context.Result.GetValue() ?? string.Empty; 41 | Console.WriteLine(result); 42 | 43 | Console.ForegroundColor = prevColor; 44 | Console.Write("\nAssistant: "); 45 | } 46 | catch (Exception) 47 | { 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SemanticKernel/AgentGroupChatExt.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel.Agents.Chat; 2 | using Microsoft.SemanticKernel.ChatCompletion; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.SemanticKernel.Agents; 9 | 10 | #pragma warning disable SKEXP0110 11 | 12 | public class AgentGroupChatExt : IAgentGroupChat 13 | { 14 | private readonly AgentGroupChat _agentGroupChat; 15 | 16 | public AgentGroupChatExt(params Agent[] agents) 17 | { 18 | _agentGroupChat = new AgentGroupChat(agents); 19 | } 20 | 21 | public AgentGroupChatExt(AgentGroupChat agentGroupChat) 22 | { 23 | _agentGroupChat = agentGroupChat; 24 | } 25 | 26 | // Expose the ChatHistory from the underlying _agentGroupChat 27 | //public ChatHistory History => _agentGroupChat.History; //doesn't work as History is protected 28 | public ChatHistory History => _agentGroupChat.GetType().GetProperty("History", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(_agentGroupChat) as ChatHistory; 29 | 30 | // Properties from IAgentChat 31 | public bool IsActive => _agentGroupChat.IsActive; 32 | 33 | public bool IsComplete 34 | { 35 | get => _agentGroupChat.IsComplete; 36 | set => _agentGroupChat.IsComplete = value; 37 | } 38 | 39 | public AgentGroupChatSettings ExecutionSettings 40 | { 41 | get => _agentGroupChat.ExecutionSettings; 42 | set => _agentGroupChat.ExecutionSettings = value; 43 | } 44 | 45 | public IReadOnlyList Agents => _agentGroupChat.Agents; 46 | 47 | // Methods from IAgentChat 48 | public Task ResetAsync(CancellationToken cancellationToken = default) 49 | { 50 | return _agentGroupChat.ResetAsync(cancellationToken); 51 | } 52 | 53 | public void AddChatMessage(ChatMessageContent message) 54 | { 55 | _agentGroupChat.AddChatMessage(message); 56 | } 57 | 58 | public void AddChatMessages(IReadOnlyList messages) 59 | { 60 | _agentGroupChat.AddChatMessages(messages); 61 | } 62 | 63 | public IAsyncEnumerable GetChatMessagesAsync(CancellationToken cancellationToken = default) 64 | { 65 | return _agentGroupChat.GetChatMessagesAsync(cancellationToken); 66 | } 67 | 68 | public IAsyncEnumerable GetChatMessagesAsync(Agent agent, CancellationToken cancellationToken = default) 69 | { 70 | return _agentGroupChat.GetChatMessagesAsync(agent, cancellationToken); 71 | } 72 | 73 | // Methods from IAgentGroupChat 74 | public void AddAgent(Agent agent) 75 | { 76 | _agentGroupChat.AddAgent(agent); 77 | } 78 | 79 | public async IAsyncEnumerable InvokeAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 80 | { 81 | await foreach (var item in _agentGroupChat.InvokeAsync(cancellationToken).WithCancellation(cancellationToken)) 82 | { 83 | yield return item; 84 | } 85 | } 86 | 87 | public async IAsyncEnumerable InvokeStreamingAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 88 | { 89 | await foreach (var item in _agentGroupChat.InvokeStreamingAsync(cancellationToken).WithCancellation(cancellationToken)) 90 | { 91 | yield return item; 92 | } 93 | } 94 | 95 | public async IAsyncEnumerable InvokeStreamingAsync(Agent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) 96 | { 97 | await foreach (var item in _agentGroupChat.InvokeStreamingAsync(agent, cancellationToken).WithCancellation(cancellationToken)) 98 | { 99 | yield return item; 100 | } 101 | } 102 | 103 | public async IAsyncEnumerable InvokeAsync(Agent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) 104 | { 105 | await foreach (var item in _agentGroupChat.InvokeAsync(agent, cancellationToken).WithCancellation(cancellationToken)) 106 | { 107 | yield return item; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SemanticKernel/BaseAgentGroupChat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.SemanticKernel.Agents.Chat; 8 | using Microsoft.SemanticKernel.ChatCompletion; 9 | 10 | #pragma warning disable SKEXP0110 11 | 12 | namespace Microsoft.SemanticKernel.Agents; 13 | 14 | public abstract class BaseAgentGroupChat : IAgentGroupChat 15 | { 16 | protected readonly IAgentGroupChat _agentGroupChat; 17 | 18 | protected BaseAgentGroupChat(params ChatCompletionAgent[] agents) 19 | { 20 | _agentGroupChat = new AgentGroupChatExt(agents); 21 | } 22 | 23 | // Properties from IAgentChat 24 | public bool IsActive => _agentGroupChat.IsActive; 25 | 26 | // Properties from IAgentGroupChat 27 | public ChatHistory History => _agentGroupChat.History; 28 | 29 | public bool IsComplete 30 | { 31 | get => _agentGroupChat.IsComplete; 32 | set => _agentGroupChat.IsComplete = value; 33 | } 34 | 35 | public AgentGroupChatSettings ExecutionSettings 36 | { 37 | get => _agentGroupChat.ExecutionSettings; 38 | set => _agentGroupChat.ExecutionSettings = value; 39 | } 40 | 41 | public IReadOnlyList Agents => _agentGroupChat.Agents; 42 | 43 | // Methods from IAgentChat 44 | public virtual Task ResetAsync(CancellationToken cancellationToken = default) 45 | { 46 | return _agentGroupChat.ResetAsync(cancellationToken); 47 | } 48 | 49 | public virtual void AddChatMessage(ChatMessageContent message) 50 | { 51 | _agentGroupChat.AddChatMessage(message); 52 | } 53 | 54 | public virtual void AddChatMessages(IReadOnlyList messages) 55 | { 56 | _agentGroupChat.AddChatMessages(messages); 57 | } 58 | 59 | public virtual IAsyncEnumerable GetChatMessagesAsync(CancellationToken cancellationToken = default) 60 | { 61 | return _agentGroupChat.GetChatMessagesAsync(cancellationToken); 62 | } 63 | 64 | public virtual IAsyncEnumerable GetChatMessagesAsync(Agent agent, CancellationToken cancellationToken = default) 65 | { 66 | return _agentGroupChat.GetChatMessagesAsync(agent, cancellationToken); 67 | } 68 | 69 | // Methods from IAgentGroupChat 70 | public virtual void AddAgent(Agent agent) 71 | { 72 | _agentGroupChat.AddAgent(agent); 73 | } 74 | 75 | // Abstract methods to be implemented by derived classes 76 | public abstract IAsyncEnumerable InvokeAsync(CancellationToken cancellationToken = default); 77 | 78 | public abstract IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default); 79 | 80 | public virtual IAsyncEnumerable InvokeAsync(Agent agent, CancellationToken cancellationToken = default) 81 | { 82 | return _agentGroupChat.InvokeAsync(agent, cancellationToken); 83 | } 84 | 85 | public virtual IAsyncEnumerable InvokeStreamingAsync(Agent agent, CancellationToken cancellationToken = default) 86 | { 87 | return _agentGroupChat.InvokeStreamingAsync(agent, cancellationToken); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /SemanticKernel/IAgentChat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel.ChatCompletion; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.SemanticKernel.Agents; 7 | 8 | #pragma warning disable SKEXP0110 9 | public interface IAgentChat 10 | { 11 | bool IsActive { get; } 12 | 13 | Task ResetAsync(CancellationToken cancellationToken = default); 14 | 15 | IAsyncEnumerable GetChatMessagesAsync(CancellationToken cancellationToken = default); 16 | IAsyncEnumerable GetChatMessagesAsync(Agent agent, CancellationToken cancellationToken = default); 17 | 18 | void AddChatMessage(ChatMessageContent message); 19 | void AddChatMessages(IReadOnlyList messages); 20 | 21 | IAsyncEnumerable InvokeAsync(CancellationToken cancellationToken = default); 22 | IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default); 23 | } 24 | -------------------------------------------------------------------------------- /SemanticKernel/IAgentGroupChat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel.Agents.Chat; 2 | using Microsoft.SemanticKernel.ChatCompletion; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | //namespace NovelCrafter.SemanticKernel; 9 | namespace Microsoft.SemanticKernel.Agents; 10 | 11 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 12 | 13 | public interface IAgentGroupChat : IAgentChat 14 | { 15 | bool IsComplete { get; set; } 16 | ChatHistory History { get; } 17 | 18 | AgentGroupChatSettings ExecutionSettings { get; set; } 19 | IReadOnlyList Agents { get; } 20 | 21 | void AddAgent(Agent agent); 22 | 23 | IAsyncEnumerable InvokeAsync(CancellationToken cancellationToken = default); 24 | 25 | IAsyncEnumerable InvokeAsync(Agent agent, CancellationToken cancellationToken = default); 26 | 27 | IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default); 28 | 29 | IAsyncEnumerable InvokeStreamingAsync(Agent agent, CancellationToken cancellationToken = default); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /SemanticKernel/IChatMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.SemanticKernel.Agents; 8 | 9 | public interface IChatMiddleware 10 | { 11 | /// 12 | /// the name of the middleware 13 | /// 14 | public string? Name { get; } 15 | 16 | /// 17 | /// Processes a message and optionally invokes the next middleware or the target agent. 18 | /// 19 | /// The message to process. 20 | /// Delegate to invoke the next middleware or agent. 21 | /// Cancellation token. 22 | /// The processed message. 23 | Task InvokeAsync( 24 | ChatMessageContent message, 25 | Func> next, 26 | CancellationToken cancellationToken = default); 27 | } 28 | -------------------------------------------------------------------------------- /SemanticKernel/MiddlewareAgentChat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.SemanticKernel.Agents; 9 | 10 | #pragma warning disable SKEXP0110 11 | 12 | /// 13 | /// Inspired by the AutoGen.net Middleware implementation 14 | /// Represents a middleware-based agent chat that processes messages through a pipeline of middleware components before starting the chat. 15 | /// those are processed before the agents in a group chat. 16 | /// Stopped the implementation due to the complexity of the middleware implementation and having it fit Semantic Kernel. 17 | /// 18 | public class MiddlewareAgentChat : BaseAgentGroupChat, IAgentGroupChat 19 | { 20 | private readonly List _middlewares = new(); 21 | 22 | public MiddlewareAgentChat() 23 | : base() // Pass an empty array to the base constructor 24 | { 25 | // No agents are passed; all agent interactions are handled via middleware 26 | } 27 | 28 | // Method to add middleware 29 | public void AddMiddleware(IChatMiddleware middleware) 30 | { 31 | if (middleware == null) throw new ArgumentNullException(nameof(middleware)); 32 | _middlewares.Add(middleware); 33 | } 34 | 35 | public override async IAsyncEnumerable InvokeAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 36 | { 37 | if (IsComplete) 38 | { 39 | yield break; 40 | } 41 | 42 | // Retrieve the latest message from chat history as the initial message 43 | var initialMessages = await GetChatMessagesAsync(cancellationToken).ToListAsync(cancellationToken); 44 | var initialMessage = initialMessages.LastOrDefault(); 45 | 46 | // If there's no initial message, you might want to define a default user prompt or handle accordingly 47 | // For this example, we'll assume that an initial message exists 48 | if (initialMessage == null) 49 | { 50 | throw new InvalidOperationException("No initial message found in chat history."); 51 | } 52 | 53 | // Build the middleware pipeline 54 | Func> handler = async (message) => 55 | { 56 | // If no middleware is present, simply return the message 57 | return message; 58 | }; 59 | 60 | // Wrap each middleware around the handler, starting from the last added middleware 61 | foreach (var middleware in _middlewares.AsEnumerable().Reverse()) 62 | { 63 | var next = handler; 64 | handler = async (msg) => await middleware.InvokeAsync(msg, next, cancellationToken); 65 | } 66 | 67 | // Execute the middleware pipeline with the initial message 68 | var result = await handler(initialMessage); 69 | 70 | // Add the result to the chat history 71 | AddChatMessage(result); 72 | 73 | // Yield the result 74 | yield return result; 75 | 76 | // Check for termination based on the termination strategy 77 | if (CheckTermination(result)) 78 | { 79 | IsComplete = true; 80 | yield break; 81 | } 82 | } 83 | private bool CheckTermination(ChatMessageContent message) 84 | { 85 | if (ExecutionSettings.TerminationStrategy != null) 86 | { 87 | // Evaluate the termination strategy with the latest message 88 | return ExecutionSettings.TerminationStrategy.ShouldTerminateAsync(null, new List { message }, CancellationToken.None).Result; 89 | } 90 | return false; 91 | } 92 | 93 | public override IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default) 94 | { 95 | throw new NotImplementedException("Streaming is not implemented in MiddlewareAgentChat."); 96 | } 97 | } -------------------------------------------------------------------------------- /SemanticKernel/ResultProcessing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.SemanticKernel.Agents; 8 | 9 | public enum ResultProcessing 10 | { 11 | Append, 12 | Replace 13 | } 14 | -------------------------------------------------------------------------------- /SemanticKernel/SequentialAgentChat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.SemanticKernel.Agents; 9 | 10 | #pragma warning disable SKEXP0110 11 | 12 | /// 13 | /// It implements a sequence of agents that are executed in order. 14 | /// At the end the outputs from all agents are concatenated and passed to a summarizer agent. 15 | /// 16 | public class SequentialAgentChat : BaseAgentGroupChat, IAgentGroupChat 17 | { 18 | private readonly List _agents; 19 | private readonly ChatCompletionAgent _summarizerAgent; 20 | private readonly int _maxIterations = 1; 21 | private readonly string? _terminationKeyword; 22 | 23 | public SequentialAgentChat( 24 | List agents, 25 | ChatCompletionAgent summarizerAgent) 26 | : base(agents.ToArray()) 27 | { 28 | if (agents == null) 29 | { 30 | throw new ArgumentException("There must be at least one agent."); 31 | } 32 | 33 | _agents = agents; 34 | _summarizerAgent = summarizerAgent; 35 | _maxIterations = 1; // only one iteration is supported 36 | } 37 | 38 | public override async IAsyncEnumerable InvokeAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 39 | { 40 | var history = new List(); 41 | for (int iteration = 0; iteration < _maxIterations || _maxIterations == 0; iteration++) 42 | { 43 | foreach (var agent in _agents) 44 | { 45 | // Invoke each agent in sequence 46 | await foreach (var agentMessage in _agentGroupChat.InvokeAsync(agent, cancellationToken).WithCancellation(cancellationToken)) 47 | { 48 | yield return agentMessage; 49 | history.Add(agentMessage); 50 | } 51 | } 52 | } 53 | 54 | // Summarizer Agent 55 | if (_summarizerAgent != null) 56 | { 57 | var concatenatedMessages = string.Join("\n", history.Select(msg => msg.Content)); 58 | 59 | var summaryMessage = new ChatMessageContent 60 | { 61 | Content = concatenatedMessages 62 | }; 63 | 64 | // Pass the concatenated messages to the summarizer 65 | await foreach (var summaryResponse in _agentGroupChat.InvokeAsync(_summarizerAgent, cancellationToken).WithCancellation(cancellationToken)) 66 | { 67 | yield return summaryResponse; 68 | } 69 | 70 | IsComplete = true; 71 | yield break; 72 | } 73 | } 74 | 75 | public override IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default) 76 | { 77 | throw new NotImplementedException(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /SemanticKernel/SequentialTwoAgentChat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.SemanticKernel.Agents; 9 | 10 | #pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 11 | 12 | public class SequentialTwoAgentChat : BaseAgentGroupChat, IAgentGroupChat 13 | { 14 | private readonly List _chatConfigurations; 15 | private ChatMessageContent? _lastWorkerMessage; 16 | 17 | public SequentialTwoAgentChat(List chatConfigurations) 18 | : base(chatConfigurations.Select(c => c.WorkerAgent).Concat(chatConfigurations.Select(c => c.CriticAgent)).ToArray()) 19 | { 20 | _chatConfigurations = chatConfigurations ?? throw new ArgumentNullException(nameof(chatConfigurations)); 21 | } 22 | 23 | public override async IAsyncEnumerable InvokeAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 24 | { 25 | _lastWorkerMessage = this.History.LastOrDefault(); 26 | 27 | foreach (var config in _chatConfigurations) 28 | { 29 | bool shouldTerminate = false; // Flag to signal termination 30 | var workerAgent = config.WorkerAgent; 31 | var criticAgent = config.CriticAgent; 32 | var maxIterations = config.MaxIterations; 33 | var terminationKeyword = config.TerminationKeyword?.ToLower(); 34 | var carryover = config.Carryover; 35 | var resultProcessing = config.ResultProcessing; 36 | 37 | // Initialize the message for worker-agent to process, respecting carryover flag 38 | var workerMessage = _lastWorkerMessage; 39 | 40 | for (int iteration = 0; iteration < maxIterations || maxIterations == 0; iteration++) 41 | { 42 | if (shouldTerminate) 43 | { 44 | shouldTerminate = false; 45 | break; // Exit the inner loop if termination has been triggered 46 | } 47 | 48 | await foreach (var message in _agentGroupChat.InvokeAsync(workerAgent, cancellationToken).WithCancellation(cancellationToken)) 49 | { 50 | yield return message; 51 | 52 | // Handle ResultProcessing based on Append or Replace 53 | if (resultProcessing == ResultProcessing.Append) 54 | { 55 | // Append previous worker's content to the new message's content 56 | workerMessage = new ChatMessageContent 57 | { 58 | AuthorName = message.AuthorName, 59 | Content = _lastWorkerMessage.Content + "\n" + message?.Content 60 | }; 61 | } 62 | else if (resultProcessing == ResultProcessing.Replace) 63 | { 64 | // Replace the initial message with the last worker message 65 | workerMessage = message; 66 | } 67 | } 68 | 69 | // CriticAgent responds to the WorkerAgent's message 70 | await foreach (var criticMessage in _agentGroupChat.InvokeAsync(criticAgent, cancellationToken).WithCancellation(cancellationToken)) 71 | { 72 | yield return criticMessage; 73 | 74 | if (CheckTermination(criticMessage, terminationKeyword)) 75 | { 76 | shouldTerminate = true; 77 | break; 78 | } 79 | } 80 | } 81 | 82 | // Store the last worker message if carryover is enabled for the next agent sequence 83 | if (carryover) 84 | { 85 | _lastWorkerMessage = workerMessage; 86 | } 87 | } 88 | 89 | //output the final review 90 | yield return _lastWorkerMessage; 91 | 92 | IsComplete = true; 93 | yield break; 94 | } 95 | 96 | private bool CheckTermination(ChatMessageContent message, string? terminationKeyword) 97 | { 98 | if (!string.IsNullOrEmpty(terminationKeyword) && message.Content.ToLower().Contains(terminationKeyword)) 99 | { 100 | return true; 101 | } 102 | 103 | return false; 104 | } 105 | 106 | public override IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default) 107 | { 108 | // Streaming is not supported for this implementation 109 | throw new NotImplementedException(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /SemanticKernel/TwoAgentChat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.SemanticKernel.Agents; 9 | 10 | #pragma warning disable SKEXP0110 11 | 12 | public class TwoAgentChat : BaseAgentGroupChat, IAgentGroupChat 13 | { 14 | private readonly ChatCompletionAgent _workerAgent; 15 | private readonly ChatCompletionAgent _criticAgent; 16 | private readonly int _maxIterations; 17 | private readonly string _terminationKeyword; 18 | 19 | public TwoAgentChat( 20 | ChatCompletionAgent workerAgent, 21 | ChatCompletionAgent criticAgent, 22 | int maxIterations, 23 | string terminationKeyword) 24 | : base(workerAgent, criticAgent) 25 | { 26 | _workerAgent = workerAgent; 27 | _criticAgent = criticAgent; 28 | _maxIterations = maxIterations; 29 | _terminationKeyword = terminationKeyword.ToLower(); 30 | } 31 | 32 | public override async IAsyncEnumerable InvokeAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) 33 | { 34 | for (int iteration = 0; iteration < _maxIterations; iteration++) 35 | { 36 | // WorkerAgent produces a message 37 | await foreach (var workerMessage in _agentGroupChat.InvokeAsync(_workerAgent, cancellationToken).WithCancellation(cancellationToken)) 38 | { 39 | yield return workerMessage; 40 | } 41 | 42 | // CriticAgent responds to the WorkerAgent's message 43 | await foreach (var criticMessage in _agentGroupChat.InvokeAsync(_criticAgent, cancellationToken).WithCancellation(cancellationToken)) 44 | { 45 | yield return criticMessage; 46 | 47 | if (CheckTermination(criticMessage)) 48 | { 49 | IsComplete = true; 50 | yield break; 51 | } 52 | } 53 | 54 | if (IsComplete) 55 | { 56 | break; 57 | } 58 | } 59 | } 60 | 61 | public override IAsyncEnumerable InvokeStreamingAsync(CancellationToken cancellationToken = default) 62 | { 63 | // Not implemented 64 | throw new NotImplementedException(); 65 | } 66 | 67 | private bool CheckTermination(ChatMessageContent message) 68 | { 69 | if (message.Content.ToLower().Contains(_terminationKeyword)) 70 | { 71 | IsComplete = true; 72 | 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /SemanticKernel/TwoAgentChatConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.SemanticKernel.Agents; 8 | 9 | #pragma warning disable SKEXP0110 10 | 11 | public class TwoAgentChatConfiguration 12 | { 13 | public ChatCompletionAgent WorkerAgent { get; set; } 14 | public ChatCompletionAgent CriticAgent { get; set; } 15 | public int MaxIterations { get; set; } 16 | public string? TerminationKeyword { get; set; } 17 | public bool Carryover { get; set; } 18 | public ResultProcessing ResultProcessing { get; set; } 19 | 20 | 21 | public TwoAgentChatConfiguration( 22 | ChatCompletionAgent workerAgent, 23 | ChatCompletionAgent criticAgent, 24 | int maxIterations, 25 | string terminationKeyword, 26 | bool carryover, 27 | ResultProcessing resultProcessing) 28 | { 29 | WorkerAgent = workerAgent; 30 | CriticAgent = criticAgent; 31 | MaxIterations = maxIterations; 32 | TerminationKeyword = terminationKeyword; 33 | Carryover = carryover; 34 | ResultProcessing = resultProcessing; 35 | } 36 | } -------------------------------------------------------------------------------- /testWindow02.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |