├── .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 |
79 |
80 |
81 |
82 |
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 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/testWindow02.xaml.cs:
--------------------------------------------------------------------------------
1 | using AgenticChatWorkflows;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 |
16 | namespace AgenticChatWorkflows;
17 |
18 | ///
19 | /// Interaction logic for testWindow02.xaml
20 | ///
21 | public partial class testWindow02 : Window
22 | {
23 |
24 | private CancellationTokenSource _cts; // For debouncing
25 |
26 | // Store the widths for each expander
27 | private Dictionary expanderWidths = new Dictionary
28 | {
29 | { "Code", 400 },
30 | { "Configuration", 250 }
31 | };
32 | private readonly double _collapsedWidth = 56; // Width for the collapsed header
33 | private AgenticWorkflowModel? agenticWorkflowModel;
34 |
35 | public testWindow02()
36 | {
37 | InitializeComponent();
38 | _cts = new CancellationTokenSource();
39 | agenticWorkflowModel = new AgenticWorkflowModel(this);
40 |
41 | // Default selected workflow is set to ConceptWorkflow
42 | agenticWorkflowModel.SelectedWorkflow = WorkflowType.CodeCrafterAgentChatWorkflow;
43 |
44 | this.DataContext = agenticWorkflowModel;
45 | }
46 |
47 | private void Expander_Collapsed(object sender, RoutedEventArgs e)
48 | {
49 | var expander = sender as Expander;
50 | if (expander == null) return;
51 |
52 | // Locate the column associated with the expander
53 | var column = FindColumnForExpander(expander);
54 | if (column != null)
55 | {
56 | // Store the current width before collapsing
57 | double currentWidth = expander.ActualWidth;
58 | StoreExpanderWidth(expander.Header.ToString(), currentWidth);
59 |
60 | // Set the column width to the collapsed width
61 | column.Width = new GridLength(_collapsedWidth);
62 | }
63 | }
64 |
65 | private void Expander_Expanded(object sender, RoutedEventArgs e)
66 | {
67 | var expander = sender as Expander;
68 | if (expander == null) return;
69 |
70 | // Locate the column and retrieve the stored width for the expander
71 | var column = FindColumnForExpander(expander);
72 | if (column != null)
73 | {
74 | // Restore the previously stored width after expanding
75 | double storedWidth = expanderWidths[expander.Header.ToString()];
76 | column.Width = new GridLength(storedWidth);
77 | }
78 | }
79 |
80 | // Function to store the width for each expander in the dictionary
81 | private void StoreExpanderWidth(string expanderHeader, double width)
82 | {
83 | if (expanderWidths.ContainsKey(expanderHeader))
84 | {
85 | expanderWidths[expanderHeader] = width;
86 | }
87 | }
88 |
89 | // Function to return the corresponding column for the expander
90 | private ColumnDefinition FindColumnForExpander(Expander expander)
91 | {
92 | switch (expander.Header.ToString())
93 | {
94 | case "Configuration":
95 | return PanelColumn1;
96 | case "Code":
97 | return ExpanderColumnCode;
98 | default:
99 | return null;
100 | }
101 | }
102 |
103 | private async void AskButton_Click(object sender, RoutedEventArgs e)
104 | {
105 | // Disable the Ask button
106 | AskButton.IsEnabled = false;
107 |
108 | // Call the askQuestion method
109 | await agenticWorkflowModel?.askQuestion();
110 |
111 | // Re-enable the Ask button after the process is finished
112 | AskButton.IsEnabled = true;
113 |
114 | }
115 |
116 | private void QuestionBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
117 | {
118 | if (e.Key == System.Windows.Input.Key.Enter)
119 | {
120 | AskButton_Click(sender, e);
121 | }
122 | }
123 |
124 | public void AgenticWorkflow_SelectionChanged(object sender, SelectionChangedEventArgs e)
125 | {
126 | agenticWorkflowModel?.InitializeChatWorkflow(); // Re-initialize the chat workflow based on the selected workflow
127 | }
128 |
129 | // Debounce the TextChanged event
130 | public async void ConceptRTB_TextChanged(object sender, TextChangedEventArgs e)
131 | {
132 | // Cancel the previous task if a new TextChanged event occurs
133 | _cts.Cancel();
134 | _cts = new CancellationTokenSource();
135 |
136 | try
137 | {
138 | // Wait for 300ms (adjust the delay as needed)
139 | await Task.Delay(400, _cts.Token);
140 |
141 | // Update the Concept property after the delay
142 | if (ConceptRTB != null)
143 | {
144 | TextRange textRange = new TextRange(ConceptRTB.Document.ContentStart, ConceptRTB.Document.ContentEnd);
145 | Context.Code = textRange.Text.Trim(); // Sync RichTextBox content with Concept property at source
146 | }
147 | }
148 | catch (TaskCanceledException)
149 | {
150 | // Task was canceled due to new input, so we don't update anything
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------