2024-09-30 16:32:48 -07:00
// Copyright (c) Microsoft Corporation. All rights reserved.
2024-04-26 09:21:46 -07:00
// Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs
using System.Text ;
using System.Text.Json ;
2025-01-28 17:13:36 -05:00
using AutoGen.Basic.Sample ;
2024-04-26 09:21:46 -07:00
using AutoGen.Core ;
2024-05-09 15:36:20 -07:00
using AutoGen.DotnetInteractive ;
2024-08-08 10:49:58 -07:00
using AutoGen.DotnetInteractive.Extension ;
2024-08-27 14:37:47 -07:00
using AutoGen.OpenAI ;
using AutoGen.OpenAI.Extension ;
2024-08-08 10:49:58 -07:00
using Microsoft.DotNet.Interactive ;
2024-08-27 14:37:47 -07:00
using OpenAI.Chat ;
2024-04-26 09:21:46 -07:00
public partial class Example07_Dynamic_GroupChat_Calculate_Fibonacci
{
#region reviewer_function
public struct CodeReviewResult
{
public bool HasMultipleCodeBlocks { get ; set ; }
public bool IsTopLevelStatement { get ; set ; }
public bool IsDotnetCodeBlock { get ; set ; }
public bool IsPrintResultToConsole { get ; set ; }
}
/// <summary>
/// review code block
/// </summary>
/// <param name="hasMultipleCodeBlocks">true if there're multipe csharp code blocks</param>
/// <param name="isTopLevelStatement">true if the code is in top level statement</param>
/// <param name="isDotnetCodeBlock">true if the code block is csharp code block</param>
/// <param name="isPrintResultToConsole">true if the code block print out result to console</param>
[Function]
public async Task < string > ReviewCodeBlock (
bool hasMultipleCodeBlocks ,
bool isTopLevelStatement ,
bool isDotnetCodeBlock ,
bool isPrintResultToConsole )
{
var obj = new CodeReviewResult
{
HasMultipleCodeBlocks = hasMultipleCodeBlocks ,
IsTopLevelStatement = isTopLevelStatement ,
IsDotnetCodeBlock = isDotnetCodeBlock ,
IsPrintResultToConsole = isPrintResultToConsole ,
} ;
return JsonSerializer . Serialize ( obj ) ;
}
#endregion reviewer_function
#region create_coder
2024-08-27 14:37:47 -07:00
public static async Task < IAgent > CreateCoderAgentAsync ( ChatClient client )
2024-04-26 09:21:46 -07:00
{
2024-07-29 09:32:45 -07:00
var coder = new OpenAIChatAgent (
2024-08-27 14:37:47 -07:00
chatClient : client ,
2024-04-26 09:21:46 -07:00
name : "coder" ,
systemMessage : @ "You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you.
Here ' re some rules to follow on writing dotnet code :
- put code between ` ` ` csharp and ` ` `
- Avoid adding ` using ` keyword when creating disposable object . e . g ` var httpClient = new HttpClient ( ) `
- Try to use ` var ` instead of explicit type .
- Try avoid using external library , use . NET Core library instead .
- Use top level statement to write code .
- Always print out the result to console . Don ' t write code that doesn ' t print out anything .
If you need to install nuget packages , put nuget packages in the following format :
` ` ` nuget
nuget_package_name
` ` `
If your code is incorrect , runner will tell you the error message . Fix the error and send the code again . ",
temperature : 0.4f )
2024-07-29 09:32:45 -07:00
. RegisterMessageConnector ( )
2024-04-26 09:21:46 -07:00
. RegisterPrintMessage ( ) ;
return coder ;
}
#endregion create_coder
#region create_runner
2024-08-08 10:49:58 -07:00
public static async Task < IAgent > CreateRunnerAgentAsync ( Kernel kernel )
2024-04-26 09:21:46 -07:00
{
2024-07-29 09:32:45 -07:00
var runner = new DefaultReplyAgent (
2024-04-26 09:21:46 -07:00
name : "runner" ,
defaultReply : "No code available." )
2024-05-05 07:51:00 -07:00
. RegisterMiddleware ( async ( msgs , option , agent , _ ) = >
2024-04-26 09:21:46 -07:00
{
2024-09-30 16:32:48 -07:00
if ( msgs . Any ( ) | | msgs . All ( msg = > msg . From ! = "coder" ) )
2024-04-26 09:21:46 -07:00
{
return new TextMessage ( Role . Assistant , "No code available. Coder please write code" ) ;
}
else
{
2024-05-05 07:51:00 -07:00
var coderMsg = msgs . Last ( msg = > msg . From = = "coder" ) ;
2024-08-08 10:49:58 -07:00
if ( coderMsg . ExtractCodeBlock ( "```csharp" , "```" ) is string code )
{
var codeResult = await kernel . RunSubmitCodeCommandAsync ( code , "csharp" ) ;
codeResult = $"" "
[RUNNER_RESULT]
{ codeResult }
"" ";
return new TextMessage ( Role . Assistant , codeResult )
{
From = "runner" ,
} ;
}
else
{
return new TextMessage ( Role . Assistant , "No code available. Coder please write code" ) ;
}
2024-04-26 09:21:46 -07:00
}
} )
. RegisterPrintMessage ( ) ;
return runner ;
}
#endregion create_runner
#region create_admin
2024-08-27 14:37:47 -07:00
public static async Task < IAgent > CreateAdminAsync ( ChatClient client )
2024-04-26 09:21:46 -07:00
{
2024-07-29 09:32:45 -07:00
var admin = new OpenAIChatAgent (
2024-08-27 14:37:47 -07:00
chatClient : client ,
2024-04-26 09:21:46 -07:00
name : "admin" ,
2024-07-29 09:32:45 -07:00
temperature : 0 )
. RegisterMessageConnector ( )
. RegisterPrintMessage ( ) ;
2024-04-26 09:21:46 -07:00
return admin ;
}
#endregion create_admin
#region create_reviewer
2024-08-27 14:37:47 -07:00
public static async Task < IAgent > CreateReviewerAgentAsync ( ChatClient chatClient )
2024-04-26 09:21:46 -07:00
{
var functions = new Example07_Dynamic_GroupChat_Calculate_Fibonacci ( ) ;
2024-07-29 09:32:45 -07:00
var functionCallMiddleware = new FunctionCallMiddleware (
functions : [ functions . ReviewCodeBlockFunctionContract ] ,
2024-04-26 09:21:46 -07:00
functionMap : new Dictionary < string , Func < string , Task < string > > > ( )
{
2024-07-29 09:32:45 -07:00
{ nameof ( functions . ReviewCodeBlock ) , functions . ReviewCodeBlockWrapper } ,
} ) ;
var reviewer = new OpenAIChatAgent (
2024-08-27 14:37:47 -07:00
chatClient : chatClient ,
2024-07-29 09:32:45 -07:00
name : "code_reviewer" ,
2024-08-27 14:37:47 -07:00
systemMessage : @"You review code block from coder" )
2024-07-29 09:32:45 -07:00
. RegisterMessageConnector ( )
. RegisterStreamingMiddleware ( functionCallMiddleware )
2024-04-26 09:21:46 -07:00
. RegisterMiddleware ( async ( msgs , option , innerAgent , ct ) = >
{
var maxRetry = 3 ;
var reply = await innerAgent . GenerateReplyAsync ( msgs , option , ct ) ;
while ( maxRetry - - > 0 )
{
2024-09-30 16:32:48 -07:00
if ( reply . GetToolCalls ( ) is var toolCalls & & toolCalls . Count = = 1 & & toolCalls [ 0 ] . FunctionName = = nameof ( ReviewCodeBlock ) )
2024-04-26 09:21:46 -07:00
{
var toolCallResult = reply . GetContent ( ) ;
var reviewResultObj = JsonSerializer . Deserialize < CodeReviewResult > ( toolCallResult ) ;
var reviews = new List < string > ( ) ;
if ( reviewResultObj . HasMultipleCodeBlocks )
{
var fixCodeBlockPrompt = @"There're multiple code blocks, please combine them into one code block" ;
reviews . Add ( fixCodeBlockPrompt ) ;
}
if ( reviewResultObj . IsDotnetCodeBlock is false )
{
var fixCodeBlockPrompt = @"The code block is not csharp code block, please write dotnet code only" ;
reviews . Add ( fixCodeBlockPrompt ) ;
}
if ( reviewResultObj . IsTopLevelStatement is false )
{
var fixCodeBlockPrompt = @"The code is not top level statement, please rewrite your dotnet code using top level statement" ;
reviews . Add ( fixCodeBlockPrompt ) ;
}
if ( reviewResultObj . IsPrintResultToConsole is false )
{
var fixCodeBlockPrompt = @"The code doesn't print out result to console, please print out result to console" ;
reviews . Add ( fixCodeBlockPrompt ) ;
}
if ( reviews . Count > 0 )
{
var sb = new StringBuilder ( ) ;
sb . AppendLine ( "There're some comments from code reviewer, please fix these comments" ) ;
foreach ( var review in reviews )
{
sb . AppendLine ( $"- {review}" ) ;
}
return new TextMessage ( Role . Assistant , sb . ToString ( ) , from : "code_reviewer" ) ;
}
else
{
var msg = new TextMessage ( Role . Assistant , "The code looks good, please ask runner to run the code for you." )
{
From = "code_reviewer" ,
} ;
return msg ;
}
}
else
{
var originalContent = reply . GetContent ( ) ;
var prompt = $ @ "Please convert the content to ReviewCodeBlock function arguments.
# # Original Content
{ originalContent } ";
reply = await innerAgent . SendAsync ( prompt , msgs , ct ) ;
}
}
throw new Exception ( "Failed to review code block" ) ;
} )
. RegisterPrintMessage ( ) ;
return reviewer ;
}
#endregion create_reviewer
public static async Task RunWorkflowAsync ( )
{
long the39thFibonacciNumber = 63245986 ;
2024-08-09 18:53:48 -07:00
var kernel = DotnetInteractiveKernelBuilder
. CreateDefaultInProcessKernelBuilder ( )
. Build ( ) ;
2024-04-26 09:21:46 -07:00
2024-08-27 14:37:47 -07:00
var gpt4o = LLMConfiguration . GetOpenAIGPT4o_mini ( ) ;
2024-07-29 09:32:45 -07:00
2024-04-26 09:21:46 -07:00
#region create_workflow
2024-08-27 14:37:47 -07:00
var reviewer = await CreateReviewerAgentAsync ( gpt4o ) ;
var coder = await CreateCoderAgentAsync ( gpt4o ) ;
2024-08-08 10:49:58 -07:00
var runner = await CreateRunnerAgentAsync ( kernel ) ;
2024-08-27 14:37:47 -07:00
var admin = await CreateAdminAsync ( gpt4o ) ;
2024-04-26 09:21:46 -07:00
var admin2CoderTransition = Transition . Create ( admin , coder ) ;
var coder2ReviewerTransition = Transition . Create ( coder , reviewer ) ;
var reviewer2RunnerTransition = Transition . Create (
from : reviewer ,
to : runner ,
canTransitionAsync : async ( from , to , messages ) = >
{
var lastMessage = messages . Last ( ) ;
if ( lastMessage is TextMessage textMessage & & textMessage . Content . ToLower ( ) . Contains ( "the code looks good, please ask runner to run the code for you." ) is true )
{
// ask runner to run the code
return true ;
}
return false ;
} ) ;
var reviewer2CoderTransition = Transition . Create (
from : reviewer ,
to : coder ,
canTransitionAsync : async ( from , to , messages ) = >
{
var lastMessage = messages . Last ( ) ;
if ( lastMessage is TextMessage textMessage & & textMessage . Content . ToLower ( ) . Contains ( "there're some comments from code reviewer, please fix these comments" ) is true )
{
// ask coder to fix the code based on reviewer's comments
return true ;
}
return false ;
} ) ;
var runner2CoderTransition = Transition . Create (
from : runner ,
to : coder ,
canTransitionAsync : async ( from , to , messages ) = >
{
var lastMessage = messages . Last ( ) ;
if ( lastMessage is TextMessage textMessage & & textMessage . Content . ToLower ( ) . Contains ( "error" ) is true )
{
// ask coder to fix the error
return true ;
}
return false ;
} ) ;
var runner2AdminTransition = Transition . Create ( runner , admin ) ;
var workflow = new Graph (
[
admin2CoderTransition ,
coder2ReviewerTransition ,
reviewer2RunnerTransition ,
reviewer2CoderTransition ,
runner2CoderTransition ,
runner2AdminTransition ,
] ) ;
#endregion create_workflow
#region create_group_chat_with_workflow
var groupChat = new GroupChat (
admin : admin ,
workflow : workflow ,
members :
[
admin ,
coder ,
runner ,
reviewer ,
] ) ;
2024-08-08 10:49:58 -07:00
#endregion create_group_chat_with_workflow
2024-04-26 09:21:46 -07:00
admin . SendIntroduction ( "Welcome to my group, work together to resolve my task" , groupChat ) ;
coder . SendIntroduction ( "I will write dotnet code to resolve task" , groupChat ) ;
reviewer . SendIntroduction ( "I will review dotnet code" , groupChat ) ;
runner . SendIntroduction ( "I will run dotnet code once the review is done" , groupChat ) ;
2024-08-08 10:49:58 -07:00
var task = "What's the 39th of fibonacci number?" ;
2024-04-26 09:21:46 -07:00
2024-08-08 10:49:58 -07:00
var taskMessage = new TextMessage ( Role . User , task , from : admin . Name ) ;
await foreach ( var message in groupChat . SendAsync ( [ taskMessage ] , maxRound : 10 ) )
{
// teminate chat if message is from runner and run successfully
if ( message . From = = "runner" & & message . GetContent ( ) . Contains ( the39thFibonacciNumber . ToString ( ) ) )
{
Console . WriteLine ( $"The 39th of fibonacci number is {the39thFibonacciNumber}" ) ;
break ;
}
}
2024-04-26 09:21:46 -07:00
}
public static async Task RunAsync ( )
{
long the39thFibonacciNumber = 63245986 ;
var workDir = Path . Combine ( Path . GetTempPath ( ) , "InteractiveService" ) ;
if ( ! Directory . Exists ( workDir ) )
2024-07-15 12:33:10 -07:00
{
2024-04-26 09:21:46 -07:00
Directory . CreateDirectory ( workDir ) ;
2024-07-15 12:33:10 -07:00
}
2024-04-26 09:21:46 -07:00
2024-08-27 14:37:47 -07:00
var gpt4o = LLMConfiguration . GetOpenAIGPT4o_mini ( ) ;
2024-07-29 09:32:45 -07:00
2024-08-09 18:53:48 -07:00
var kernel = DotnetInteractiveKernelBuilder
. CreateDefaultInProcessKernelBuilder ( )
. Build ( ) ;
2024-04-26 09:21:46 -07:00
#region create_group_chat
2024-08-27 14:37:47 -07:00
var reviewer = await CreateReviewerAgentAsync ( gpt4o ) ;
var coder = await CreateCoderAgentAsync ( gpt4o ) ;
2024-08-08 10:49:58 -07:00
var runner = await CreateRunnerAgentAsync ( kernel ) ;
2024-08-27 14:37:47 -07:00
var admin = await CreateAdminAsync ( gpt4o ) ;
2024-04-26 09:21:46 -07:00
var groupChat = new GroupChat (
admin : admin ,
members :
[
coder ,
runner ,
reviewer ,
] ) ;
coder . SendIntroduction ( "I will write dotnet code to resolve task" , groupChat ) ;
reviewer . SendIntroduction ( "I will review dotnet code" , groupChat ) ;
runner . SendIntroduction ( "I will run dotnet code once the review is done" , groupChat ) ;
2024-07-29 09:32:45 -07:00
var task = "What's the 39th of fibonacci number?" ;
var taskMessage = new TextMessage ( Role . User , task ) ;
await foreach ( var message in groupChat . SendAsync ( [ taskMessage ] , maxRound : 10 ) )
{
// teminate chat if message is from runner and run successfully
if ( message . From = = "runner" & & message . GetContent ( ) . Contains ( the39thFibonacciNumber . ToString ( ) ) )
{
Console . WriteLine ( $"The 39th of fibonacci number is {the39thFibonacciNumber}" ) ;
break ;
}
}
2024-04-26 09:21:46 -07:00
#endregion create_group_chat
}
}