mirror of
https://github.com/microsoft/autogen.git
synced 2025-07-19 15:01:52 +00:00
379 lines
15 KiB
C#
379 lines
15 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// RolePlayOrchestratorTests.cs
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AutoGen.Anthropic;
|
|
using AutoGen.Anthropic.Extensions;
|
|
using AutoGen.Anthropic.Utils;
|
|
using AutoGen.AzureAIInference;
|
|
using AutoGen.AzureAIInference.Extension;
|
|
using AutoGen.Gemini;
|
|
using AutoGen.Mistral;
|
|
using AutoGen.Mistral.Extension;
|
|
using AutoGen.OpenAI;
|
|
using AutoGen.OpenAI.Extension;
|
|
using Azure.AI.Inference;
|
|
using Azure.AI.OpenAI;
|
|
using FluentAssertions;
|
|
using Moq;
|
|
using OpenAI;
|
|
using Xunit;
|
|
|
|
namespace AutoGen.Tests;
|
|
|
|
[Trait("Category", "UnitV1")]
|
|
public class RolePlayOrchestratorTests
|
|
{
|
|
[Fact]
|
|
public async Task ItReturnNextSpeakerTestAsync()
|
|
{
|
|
var admin = Mock.Of<IAgent>();
|
|
Mock.Get(admin).Setup(x => x.Name).Returns("Admin");
|
|
Mock.Get(admin).Setup(x => x.GenerateReplyAsync(
|
|
It.IsAny<IEnumerable<IMessage>>(),
|
|
It.IsAny<GenerateReplyOptions>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<IEnumerable<IMessage>, GenerateReplyOptions, CancellationToken>((messages, option, _) =>
|
|
{
|
|
// verify prompt
|
|
var rolePlayPrompt = messages.First().GetContent();
|
|
rolePlayPrompt.Should().Contain("You are in a role play game. Carefully read the conversation history and carry on the conversation");
|
|
rolePlayPrompt.Should().Contain("The available roles are:");
|
|
rolePlayPrompt.Should().Contain("Alice,Bob");
|
|
rolePlayPrompt.Should().Contain("From Alice:");
|
|
option.StopSequence.Should().BeEquivalentTo([":"]);
|
|
option.Temperature.Should().Be(0);
|
|
option.MaxToken.Should().Be(128);
|
|
option.Functions.Should().BeNull();
|
|
})
|
|
.ReturnsAsync(new TextMessage(Role.Assistant, "From Alice"));
|
|
|
|
var alice = new EchoAgent("Alice");
|
|
var bob = new EchoAgent("Bob");
|
|
|
|
var orchestrator = new RolePlayOrchestrator(admin);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice, bob],
|
|
ChatHistory = [],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().Be(alice);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItReturnNullWhenNoCandidateIsAvailableAsync()
|
|
{
|
|
var admin = Mock.Of<IAgent>();
|
|
var orchestrator = new RolePlayOrchestrator(admin);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [],
|
|
ChatHistory = [],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItReturnCandidateWhenOnlyOneCandidateIsAvailableAsync()
|
|
{
|
|
var admin = Mock.Of<IAgent>();
|
|
var alice = new EchoAgent("Alice");
|
|
var orchestrator = new RolePlayOrchestrator(admin);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice],
|
|
ChatHistory = [],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().Be(alice);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItThrowExceptionWhenAdminFailsToFollowPromptAsync()
|
|
{
|
|
var admin = Mock.Of<IAgent>();
|
|
Mock.Get(admin).Setup(x => x.Name).Returns("Admin");
|
|
Mock.Get(admin).Setup(x => x.GenerateReplyAsync(
|
|
It.IsAny<IEnumerable<IMessage>>(),
|
|
It.IsAny<GenerateReplyOptions>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new TextMessage(Role.Assistant, "I don't know")); // admin fails to follow the prompt and returns an invalid message
|
|
|
|
var alice = new EchoAgent("Alice");
|
|
var bob = new EchoAgent("Bob");
|
|
|
|
var orchestrator = new RolePlayOrchestrator(admin);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice, bob],
|
|
ChatHistory = [],
|
|
};
|
|
|
|
var action = async () => await orchestrator.GetNextSpeakerAsync(context);
|
|
|
|
await action.Should().ThrowAsync<Exception>()
|
|
.WithMessage("The response from admin is 't know, which is either not in the candidates list or not in the correct format.");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItSelectNextSpeakerFromWorkflowIfProvided()
|
|
{
|
|
var workflow = new Graph();
|
|
var alice = new EchoAgent("Alice");
|
|
var bob = new EchoAgent("Bob");
|
|
var charlie = new EchoAgent("Charlie");
|
|
workflow.AddTransition(Transition.Create(alice, bob));
|
|
workflow.AddTransition(Transition.Create(bob, charlie));
|
|
workflow.AddTransition(Transition.Create(charlie, alice));
|
|
|
|
var admin = Mock.Of<IAgent>();
|
|
var orchestrator = new RolePlayOrchestrator(admin, workflow);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice, bob, charlie],
|
|
ChatHistory =
|
|
[
|
|
new TextMessage(Role.User, "Hello, Bob", from: "Alice"),
|
|
],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().Be(bob);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItReturnNullIfNoAvailableAgentFromWorkflowAsync()
|
|
{
|
|
var workflow = new Graph();
|
|
var alice = new EchoAgent("Alice");
|
|
var bob = new EchoAgent("Bob");
|
|
workflow.AddTransition(Transition.Create(alice, bob));
|
|
|
|
var admin = Mock.Of<IAgent>();
|
|
var orchestrator = new RolePlayOrchestrator(admin, workflow);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice, bob],
|
|
ChatHistory =
|
|
[
|
|
new TextMessage(Role.User, "Hello, Alice", from: "Bob"),
|
|
],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ItUseCandidatesFromWorflowAsync()
|
|
{
|
|
var workflow = new Graph();
|
|
var alice = new EchoAgent("Alice");
|
|
var bob = new EchoAgent("Bob");
|
|
var charlie = new EchoAgent("Charlie");
|
|
workflow.AddTransition(Transition.Create(alice, bob));
|
|
workflow.AddTransition(Transition.Create(alice, charlie));
|
|
|
|
var admin = Mock.Of<IAgent>();
|
|
Mock.Get(admin).Setup(x => x.GenerateReplyAsync(
|
|
It.IsAny<IEnumerable<IMessage>>(),
|
|
It.IsAny<GenerateReplyOptions>(),
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<IEnumerable<IMessage>, GenerateReplyOptions, CancellationToken>((messages, option, _) =>
|
|
{
|
|
messages.First().IsSystemMessage().Should().BeTrue();
|
|
|
|
// verify prompt
|
|
var rolePlayPrompt = messages.First().GetContent();
|
|
rolePlayPrompt.Should().Contain("Bob,Charlie");
|
|
rolePlayPrompt.Should().Contain("From Bob:");
|
|
option.StopSequence.Should().BeEquivalentTo([":"]);
|
|
option.Temperature.Should().Be(0);
|
|
option.MaxToken.Should().Be(128);
|
|
option.Functions.Should().BeEmpty();
|
|
})
|
|
.ReturnsAsync(new TextMessage(Role.Assistant, "From Bob"));
|
|
var orchestrator = new RolePlayOrchestrator(admin, workflow);
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [alice, bob],
|
|
ChatHistory =
|
|
[
|
|
new TextMessage(Role.User, "Hello, Bob", from: "Alice"),
|
|
],
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker.Should().Be(bob);
|
|
}
|
|
|
|
[ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_DEPLOY_NAME")]
|
|
public async Task GPT_3_5_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable.");
|
|
var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable.");
|
|
var deployName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOY_NAME") ?? throw new Exception("Please set AZURE_OPENAI_DEPLOY_NAME environment variable.");
|
|
var openaiClient = new AzureOpenAIClient(new Uri(endpoint), new System.ClientModel.ApiKeyCredential(key));
|
|
var openAIChatAgent = new OpenAIChatAgent(
|
|
chatClient: openaiClient.GetChatClient(deployName),
|
|
name: "assistant")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(openAIChatAgent);
|
|
}
|
|
|
|
[ApiKeyFact("OPENAI_API_KEY")]
|
|
public async Task GPT_4o_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set");
|
|
var model = "gpt-4o";
|
|
var openaiClient = new OpenAIClient(apiKey);
|
|
var openAIChatAgent = new OpenAIChatAgent(
|
|
chatClient: openaiClient.GetChatClient(model),
|
|
name: "assistant")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(openAIChatAgent);
|
|
}
|
|
|
|
[ApiKeyFact("OPENAI_API_KEY")]
|
|
public async Task GPT_4o_mini_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set");
|
|
var model = "gpt-4o-mini";
|
|
var openaiClient = new OpenAIClient(apiKey);
|
|
var openAIChatAgent = new OpenAIChatAgent(
|
|
chatClient: openaiClient.GetChatClient(model),
|
|
name: "assistant")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(openAIChatAgent);
|
|
}
|
|
|
|
[ApiKeyFact("GOOGLE_GEMINI_API_KEY")]
|
|
public async Task GoogleGemini_1_5_flash_001_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("GOOGLE_GEMINI_API_KEY") ?? throw new InvalidOperationException("GOOGLE_GEMINI_API_KEY is not set");
|
|
var geminiAgent = new GeminiChatAgent(
|
|
name: "gemini",
|
|
model: "gemini-1.5-flash-001",
|
|
apiKey: apiKey)
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(geminiAgent);
|
|
}
|
|
|
|
[ApiKeyFact("ANTHROPIC_API_KEY")]
|
|
public async Task Claude3_Haiku_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new Exception("Please set ANTHROPIC_API_KEY environment variable.");
|
|
var client = new AnthropicClient(new HttpClient(), AnthropicConstants.Endpoint, apiKey);
|
|
|
|
var agent = new AnthropicClientAgent(
|
|
client,
|
|
name: "AnthropicAgent",
|
|
AnthropicConstants.Claude3Haiku,
|
|
systemMessage: "You are a helpful AI assistant that convert user message to upper case")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(agent);
|
|
}
|
|
|
|
[ApiKeyFact("MISTRAL_API_KEY")]
|
|
public async Task Mistra_7b_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set.");
|
|
var client = new MistralClient(apiKey: apiKey);
|
|
|
|
var agent = new MistralClientAgent(
|
|
client: client,
|
|
name: "MistralClientAgent",
|
|
model: "open-mistral-7b")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(agent);
|
|
}
|
|
|
|
[ApiKeyFact("GH_API_KEY")]
|
|
public async Task LLaMA_3_1_CoderReviewerRunnerTestAsync()
|
|
{
|
|
var apiKey = Environment.GetEnvironmentVariable("GH_API_KEY") ?? throw new InvalidOperationException("GH_API_KEY is not set.");
|
|
var endPoint = "https://models.inference.ai.azure.com";
|
|
|
|
var chatCompletionClient = new ChatCompletionsClient(new Uri(endPoint), new Azure.AzureKeyCredential(apiKey));
|
|
var agent = new ChatCompletionsClientAgent(
|
|
chatCompletionsClient: chatCompletionClient,
|
|
name: "assistant",
|
|
modelName: "Meta-Llama-3.1-70B-Instruct")
|
|
.RegisterMessageConnector();
|
|
|
|
await CoderReviewerRunnerTestAsync(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This test is to mimic the conversation among coder, reviewer and runner.
|
|
/// The coder will write the code, the reviewer will review the code, and the runner will run the code.
|
|
/// </summary>
|
|
/// <param name="admin"></param>
|
|
/// <returns></returns>
|
|
public async Task CoderReviewerRunnerTestAsync(IAgent admin)
|
|
{
|
|
var coder = new EchoAgent("Coder");
|
|
var reviewer = new EchoAgent("Reviewer");
|
|
var runner = new EchoAgent("Runner");
|
|
var user = new EchoAgent("User");
|
|
var initializeMessage = new List<IMessage>
|
|
{
|
|
new TextMessage(Role.User, "Hello, I am user, I will provide the coding task, please write the code first, then review and run it", from: "User"),
|
|
new TextMessage(Role.User, "Hello, I am coder, I will write the code", from: "Coder"),
|
|
new TextMessage(Role.User, "Hello, I am reviewer, I will review the code", from: "Reviewer"),
|
|
new TextMessage(Role.User, "Hello, I am runner, I will run the code", from: "Runner"),
|
|
new TextMessage(Role.User, "how to print 'hello world' using C#", from: user.Name),
|
|
};
|
|
|
|
var chatHistory = new List<IMessage>()
|
|
{
|
|
new TextMessage(Role.User, """
|
|
```csharp
|
|
Console.WriteLine("Hello World");
|
|
```
|
|
""", from: coder.Name),
|
|
new TextMessage(Role.User, "The code looks good", from: reviewer.Name),
|
|
new TextMessage(Role.User, "The code runs successfully, the output is 'Hello World'", from: runner.Name),
|
|
};
|
|
|
|
var orchestrator = new RolePlayOrchestrator(admin);
|
|
foreach (var message in chatHistory)
|
|
{
|
|
var context = new OrchestrationContext
|
|
{
|
|
Candidates = [coder, reviewer, runner, user],
|
|
ChatHistory = initializeMessage,
|
|
};
|
|
|
|
var speaker = await orchestrator.GetNextSpeakerAsync(context);
|
|
speaker!.Name.Should().Be(message.From);
|
|
initializeMessage.Add(message);
|
|
}
|
|
|
|
// the last next speaker should be the user
|
|
var lastSpeaker = await orchestrator.GetNextSpeakerAsync(new OrchestrationContext
|
|
{
|
|
Candidates = [coder, reviewer, runner, user],
|
|
ChatHistory = initializeMessage,
|
|
});
|
|
|
|
lastSpeaker!.Name.Should().Be(user.Name);
|
|
}
|
|
}
|