diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..cab3edc --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-repl": { + "version": "0.1.205", + "commands": [ + "dotnet-repl" + ] + }, + "docfx": { + "version": "2.67.5", + "commands": [ + "docfx" + ] + } + } + } \ No newline at end of file diff --git a/.github/workflows/gh-page.yml b/.github/workflows/gh-page.yml new file mode 100644 index 0000000..df40252 --- /dev/null +++ b/.github/workflows/gh-page.yml @@ -0,0 +1,79 @@ +# Your GitHub workflow file under .github/workflows/ +# Trigger the action on push to main +on: + workflow_dispatch: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '30 5,17 * * *' + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + actions: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + publish-docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + # checkout autogen library + # url: https://github.com/microsoft/autogen/tree/dotnet + - name: Checkout + uses: actions/checkout@v3 + - name: ls + run: ls + - name: Dotnet Setup + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - run: dotnet tool update -g docfx + - run: docfx website/docfx.json + + - name: insert clarity snippet to website/_site/index.html + run: | + # import os + # clarity_script = """ + # + # """ + + # site_folder = 'website/_site/' + + # for root, dirs, files in os.walk(site_folder): + # for file in files: + # if file.endswith('.html'): + # html_path = os.path.join(root, file) + + # # insert the script into the html's head section + # with open(html_path, 'r') as file: + # html = file.read() + # html = html.replace('', clarity_script + '') + + # with open(html_path, 'w') as file: + # file.write(html) + + # print(f'Clarity script inserted into {html_path}') + shell: python + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'dotnet/website/_site' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 68ef025..61f8a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -402,4 +402,7 @@ FodyWeavers.xsd # bin bin -obj \ No newline at end of file +obj + +# website +_site \ No newline at end of file diff --git a/ChatRoom.sln b/ChatRoom.sln index 3d1af3d..661efac 100644 --- a/ChatRoom.sln +++ b/ChatRoom.sln @@ -26,6 +26,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatRoom.Tests", "test\Chat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatRoom.OpenAI", "ChatRoom\ChatRoom.OpenAI\ChatRoom.OpenAI.csproj", "{575F8BEE-0573-4DA2-9642-3AA266D9DA7F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRoom.Client.Tests", "test\ChatRoom.Client.Tests\ChatRoom.Client.Tests.csproj", "{6D582F66-BCD2-4295-B1CE-31E17A8684EA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRoom.OpenAI.Tests", "test\ChatRoom.OpenAI.Tests\ChatRoom.OpenAI.Tests.csproj", "{57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -120,6 +124,30 @@ Global {575F8BEE-0573-4DA2-9642-3AA266D9DA7F}.Release|x64.Build.0 = Release|Any CPU {575F8BEE-0573-4DA2-9642-3AA266D9DA7F}.Release|x86.ActiveCfg = Release|Any CPU {575F8BEE-0573-4DA2-9642-3AA266D9DA7F}.Release|x86.Build.0 = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|x64.Build.0 = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Debug|x86.Build.0 = Debug|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|Any CPU.Build.0 = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|x64.ActiveCfg = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|x64.Build.0 = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|x86.ActiveCfg = Release|Any CPU + {6D582F66-BCD2-4295-B1CE-31E17A8684EA}.Release|x86.Build.0 = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|x64.Build.0 = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Debug|x86.Build.0 = Debug|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|Any CPU.Build.0 = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|x64.ActiveCfg = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|x64.Build.0 = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|x86.ActiveCfg = Release|Any CPU + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +160,8 @@ Global {D7C64BB5-CCB5-4597-8DE2-1DAE05B79918} = {C0FE0076-894A-4462-8B31-3099EDBCE0A7} {6AEA6834-F104-4C10-92F7-CBFB62F5B807} = {4503B3D3-D2C7-4FE8-A450-5A051089635C} {575F8BEE-0573-4DA2-9642-3AA266D9DA7F} = {C0FE0076-894A-4462-8B31-3099EDBCE0A7} + {6D582F66-BCD2-4295-B1CE-31E17A8684EA} = {4503B3D3-D2C7-4FE8-A450-5A051089635C} + {57D8D4B6-619F-4CAA-A118-DF89AB68DE1E} = {4503B3D3-D2C7-4FE8-A450-5A051089635C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E2F0A4B1-A064-41DD-B0C3-A4FF892EF46A} diff --git a/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj b/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj index a4c8d41..8d93065 100644 --- a/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj +++ b/ChatRoom/ChatRoom.Client/ChatRoom.Client.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -18,12 +18,14 @@ + + diff --git a/ChatRoom/ChatRoom.Client/ChatRoomClientCommand.cs b/ChatRoom/ChatRoom.Client/ChatRoomClientCommand.cs new file mode 100644 index 0000000..7e55997 --- /dev/null +++ b/ChatRoom/ChatRoom.Client/ChatRoomClientCommand.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Spectre.Console.Cli; + +namespace ChatRoom.Client; + +public class ChatRoomClientCommandSettings : CommandSettings +{ + [Description("The room name to create.")] + [CommandOption("-r|--room ")] + public string? Room { get; init; } = null; + + [Description("The port to listen.")] + [CommandOption("-p|--port ")] + public int? Port { get; init; } = null; + + [Description("Your name in the room")] + [CommandOption("-n|--name ")] + public string YourName { get; init; } = "User"; + + [Description("Configuration file")] + [CommandOption("-c|--config ")] + public string? ConfigFile { get; init; } = null; +} + +public class ChatRoomClientCommand : AsyncCommand +{ + public override async Task ExecuteAsync(CommandContext context, ChatRoomClientCommandSettings command) + { + var config = command.ConfigFile is not null + ? JsonSerializer.Deserialize(File.ReadAllText(command.ConfigFile))! + : new ChatRoomClientConfiguration(); + + config.RoomConfig.Room = command.Room ?? config.RoomConfig.Room; + config.RoomConfig.Port = command.Port ?? config.RoomConfig.Port; + config.YourName = command.YourName; + + var host = Host.CreateDefaultBuilder() + .UseOrleans(siloBuilder => + { + siloBuilder + .UseLocalhostClustering(config.RoomConfig.Port) + .AddMemoryGrainStorage("PubSubStore") + .ConfigureLogging(logBuilder => + { + logBuilder + .ClearProviders(); + }); + }) + .ConfigureServices(serviceCollection => + { + serviceCollection.AddSingleton(config); + serviceCollection.AddSingleton(config.RoomConfig); + serviceCollection.AddSingleton(config.ChannelConfig); + serviceCollection.AddHostedService(); + }) + .Build(); + + await host.RunAsync(); + + return 0; + } +} diff --git a/ChatRoom/ChatRoom.Client/ChatRoomClientConfiguration.cs b/ChatRoom/ChatRoom.Client/ChatRoomClientConfiguration.cs new file mode 100644 index 0000000..3bee977 --- /dev/null +++ b/ChatRoom/ChatRoom.Client/ChatRoomClientConfiguration.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; +using ChatRoom.Room; +using Json.Schema.Generation; + +namespace ChatRoom.Client; + +public class ChatRoomClientConfiguration +{ + [Description("The configuration for the chat room")] + [JsonPropertyName("room_config")] + public RoomConfiguration RoomConfig { get; set; } = new RoomConfiguration(); + + [Description("The configuration for the chat channel")] + [JsonPropertyName("channel_config")] + public ChannelConfiguration ChannelConfig { get; set; } = new ChannelConfiguration(); + + [JsonPropertyName("agent_extensions")] + public List AgentExtensions { get; set; } = []; + + [JsonPropertyName("name")] + [Description("Your name in the chat room")] + public string YourName { get; set; } = "User"; +} + +public class AgentExtensionConfiguration +{ + [JsonPropertyName("name")] + public string Name { get; init; } = null!; +} diff --git a/ChatRoom/ChatRoom.Client/ConsoleChatRoomService.cs b/ChatRoom/ChatRoom.Client/ConsoleChatRoomService.cs index 3f0317c..a1a6fb1 100644 --- a/ChatRoom/ChatRoom.Client/ConsoleChatRoomService.cs +++ b/ChatRoom/ChatRoom.Client/ConsoleChatRoomService.cs @@ -13,14 +13,15 @@ public class ConsoleChatRoomService : IHostedService private Task? _processTask = null; private readonly ConsoleRoomObserver _roomObserver; private readonly IRoomObserver _roomObserverRef; - private readonly Dictionary _channelObservers = new(); + private readonly ChatRoomClientConfiguration _configuration; - public ConsoleChatRoomService(IClusterClient clsterClient) + public ConsoleChatRoomService(ChatRoomClientConfiguration configuration, IClusterClient clsterClient) { + _configuration = configuration; _roomObserver = new ConsoleRoomObserver(); _roomObserverRef = clsterClient.CreateObjectReference(_roomObserver); _clusterClient = clsterClient; - _clientContext = new ClientContext(_clusterClient, UserName: "Zhang", CurrentChannel: "General", CurrentRoom: "room"); + _clientContext = new ClientContext(_clusterClient, UserName: configuration.YourName, CurrentChannel: "General", CurrentRoom: configuration.RoomConfig.Room); } public async Task StartAsync(CancellationToken cancellationToken) @@ -145,13 +146,6 @@ async Task ProcessLoopAsync(ClientContext context, CancellationToken ct) } var firstTwoCharacters = input.Length >= 2 ? input[..2] : string.Empty; - if (firstTwoCharacters is "/n") - { - context = context with { UserName = input.Replace("/n", "").Trim() }; - AnsiConsole.MarkupLine( - "[dim][[STATUS]][/] Set username to [lime]{0}[/]", context.UserName); - continue; - } if (firstTwoCharacters switch { @@ -198,14 +192,17 @@ private void PrintUsage() }.HideHeaders(); table.AddColumn(new TableColumn("One")); - var header = new FigletText("Agent Chat Room") + var header = new FigletText("Agent") + { + Color = Color.Aqua + }; + var header2 = new FigletText("ChatRoom") { Color = Color.Aqua }; var markup = new Markup( "[bold fuchsia]/j[/] [aqua][/] to [underline green]join[/] a specific channel\n" - + "[bold fuchsia]/n[/] [aqua][/] to set your [underline green]name[/]\n" + "[bold fuchsia]/l[/] to [underline green]leave[/] the current channel\n" + "[bold fuchsia]/h[/] to re-read channel [underline green]history[/]\n" + "[bold fuchsia]/m[/] to query [underline green]members[/] in the current channel\n" @@ -224,10 +221,11 @@ private void PrintUsage() .AddColumn(new TableColumn("Content")); rightTable.AddRow(header) + .AddRow(header2) .AddEmptyRow() .AddEmptyRow() .AddRow(markup); - table.AddRow(logo, rightTable); + table.AddRow(rightTable); AnsiConsole.Write(table); AnsiConsole.WriteLine(); diff --git a/ChatRoom/ChatRoom.Client/Program.cs b/ChatRoom/ChatRoom.Client/Program.cs index 4b4aa34..61ca2a1 100644 --- a/ChatRoom/ChatRoom.Client/Program.cs +++ b/ChatRoom/ChatRoom.Client/Program.cs @@ -2,21 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Spectre.Console.Cli; -var channelHost = Host.CreateDefaultBuilder(args) - .UseOrleans(siloBuilder => - { - siloBuilder - .UseLocalhostClustering() - .AddMemoryGrainStorage("PubSubStore") - .ConfigureLogging(logBuilder => - { - logBuilder - .ClearProviders(); - }); - }) - .ConfigureServices(serviceCollection => serviceCollection.AddHostedService()) - .Build(); - -await channelHost.StartAsync(); -await channelHost.WaitForShutdownAsync(); +var app = new CommandApp(); +await app.RunAsync(args); diff --git a/ChatRoom/ChatRoom.Common/AgentCollectionService.cs b/ChatRoom/ChatRoom.Common/AgentCollectionService.cs new file mode 100644 index 0000000..fa55b36 --- /dev/null +++ b/ChatRoom/ChatRoom.Common/AgentCollectionService.cs @@ -0,0 +1,8 @@ +using AutoGen.Core; + +namespace ChatRoom.SDK +{ + internal class AgentCollectionService : List + { + } +} diff --git a/ChatRoom/ChatRoom.Common/ChatRoom.SDK.csproj b/ChatRoom/ChatRoom.Common/ChatRoom.SDK.csproj index cfaaf88..9347d34 100644 --- a/ChatRoom/ChatRoom.Common/ChatRoom.SDK.csproj +++ b/ChatRoom/ChatRoom.Common/ChatRoom.SDK.csproj @@ -10,6 +10,7 @@ + diff --git a/ChatRoom/ChatRoom.Common/ChatRoomAgentClientCommandSettings.cs b/ChatRoom/ChatRoom.Common/ChatRoomAgentClientCommandSettings.cs new file mode 100644 index 0000000..a777515 --- /dev/null +++ b/ChatRoom/ChatRoom.Common/ChatRoomAgentClientCommandSettings.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace ChatRoom.SDK; +public class ChatRoomAgentClientCommandSettings : CommandSettings +{ + [Description("The room name to create.")] + [CommandOption("-r|--room ")] + public string? Room { get; init; } = null; + + [Description("The port to listen.")] + [CommandOption("-p|--port ")] + public int? Port { get; init; } = null; + + [Description("Configuration file")] + [CommandOption("-c|--config ")] + public string? ConfigFile { get; init; } = null; +} + +public abstract class ChatRoomAgentCommand : AsyncCommand +{} diff --git a/ChatRoom/ChatRoom.Common/HostBuilderExtension.cs b/ChatRoom/ChatRoom.Common/HostBuilderExtension.cs index 20c259a..f76e91e 100644 --- a/ChatRoom/ChatRoom.Common/HostBuilderExtension.cs +++ b/ChatRoom/ChatRoom.Common/HostBuilderExtension.cs @@ -53,11 +53,36 @@ public static IHostBuilder UseChatRoom( var configuration = ctx.Configuration; var port = configuration.GetValue("Port") ?? 30000; var roomName = configuration.GetValue("Room") ?? "room"; - serviceCollections.UseChatRoom(roomName, port); }); } + public static IHostBuilder AddAgentAsync( + this IHostBuilder hostBuilder, + Func> agentFactory) + where TAgent : IAgent + { + return hostBuilder + .ConfigureServices(async (ctx, serviceCollections) => + { + serviceCollections.AddSingleton(sp => + { + var agent = agentFactory(sp).Result; + + return agent; + }); + }); + } + + public static async Task WaitForAgentsJoinRoomAsync(this IHost host) + { + var agentCollection = host.Services.GetServices(); + foreach (var agent in agentCollection) + { + await host.JoinRoomAsync(agent); + } + } + public static async Task JoinRoomAsync( this IHost host, IAgent agent, diff --git a/ChatRoom/ChatRoom.OpenAI/ChatRoom.OpenAI.csproj b/ChatRoom/ChatRoom.OpenAI/ChatRoom.OpenAI.csproj index 438ae0c..588ad2e 100644 --- a/ChatRoom/ChatRoom.OpenAI/ChatRoom.OpenAI.csproj +++ b/ChatRoom/ChatRoom.OpenAI/ChatRoom.OpenAI.csproj @@ -13,7 +13,9 @@ + + diff --git a/ChatRoom/ChatRoom.OpenAI/OpenAIAgentConfiguration.cs b/ChatRoom/ChatRoom.OpenAI/OpenAIAgentConfiguration.cs new file mode 100644 index 0000000..d713f10 --- /dev/null +++ b/ChatRoom/ChatRoom.OpenAI/OpenAIAgentConfiguration.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Json.Schema.Generation; +using Spectre.Console.Cli; + +namespace ChatRoom.OpenAI; + +public class OpenAIAgentConfiguration +{ + [Description("Use AOAI, default is true")] + [JsonPropertyName("use_aoai")] + public bool UseAOAI { get; set; } = true; + + [Description("OpenAI API key, default is $env:OPENAI_API_KEY")] + [JsonPropertyName("openai_api_key")] + public string? OpenAIApiKey { get; set; } = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + [Description("OpenAI model ID, default is gpt-3.5-turbo")] + [JsonPropertyName("openai_model_id")] + public string? OpenAIModelId { get; set; } = "gpt-3.5-turbo"; + + [Description("Azure OpenAI endpoint, default is $env:AZURE_OPENAI_ENDPOINT")] + [JsonPropertyName("azure_openai_endpoint")] + public string? AzureOpenAIEndpoint { get; set; } = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); + + [Description("Azure OpenAI key, default is $env:AZURE_OPENAI_API_KEY")] + [JsonPropertyName("azure_openai_key")] + public string? AzureOpenAIKey { get; set; } = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + + [Description("Azure OpenAI deploy name, default is $env:AZURE_OPENAI_DEPLOY_NAME")] + [JsonPropertyName("azure_openai_deploy_name")] + public string? AzureOpenAIDeployName { get; set; } = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOY_NAME"); + + [Description("System message used in gpt agent, default is 'You are a helpful AI assistant'")] + [JsonPropertyName("system_message")] + public string SystemMessage { get; init; } = "You are a helpful AI assistant"; + + [Description("Name of the agent, default is 'gpt'")] + [JsonPropertyName("name")] + public string Name { get; init; } = "gpt"; +} diff --git a/ChatRoom/ChatRoom.OpenAI/OpenAIAgentFactory.cs b/ChatRoom/ChatRoom.OpenAI/OpenAIAgentFactory.cs new file mode 100644 index 0000000..fbffb0b --- /dev/null +++ b/ChatRoom/ChatRoom.OpenAI/OpenAIAgentFactory.cs @@ -0,0 +1,69 @@ +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; + +namespace ChatRoom.OpenAI; + +public class OpenAIAgentFactory +{ + private readonly OpenAIAgentConfiguration _config; + public OpenAIAgentFactory(OpenAIAgentConfiguration config) + { + _config = config; + } + + public IAgent CreateAgent() + { + IAgent? agent = default; + OpenAIClient? openaiClient = default; + string? deployModelName = default; + bool useAzure = _config.UseAOAI; + if (_config.UseAOAI) + { + if (_config.AzureOpenAIDeployName is string + && _config.AzureOpenAIKey is string + && _config.AzureOpenAIEndpoint is string) + { + openaiClient = new OpenAIClient(new Uri(_config.AzureOpenAIEndpoint), new Azure.AzureKeyCredential(_config.AzureOpenAIKey)); + deployModelName = _config.AzureOpenAIDeployName; + } + else + { + var defaultReply = "Please provide either (AzureOpenAIEndpoint, AzureOpenAIKey, AzureOpenAIDeployName)"; + + agent = new DefaultReplyAgent(_config.Name, defaultReply); + } + } + else + { + if (_config.OpenAIApiKey is string && _config.OpenAIModelId is string) + { + openaiClient = new OpenAIClient(_config.OpenAIApiKey); + deployModelName = _config.OpenAIModelId; + } + else + { + var defaultReply = "Please provide either (OpenAIApiKey, OpenAIModelId)"; + agent = new DefaultReplyAgent(_config.Name, defaultReply); + } + } + + if (agent is not DefaultReplyAgent && openaiClient is not null && deployModelName is not null) + { + agent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: _config.Name, + modelName: deployModelName, + systemMessage: _config.SystemMessage) + .RegisterMessageConnector(); + } + else + { + var defaultReply = "Please provide either (AzureOpenAIEndpoint, AzureOpenAIKey, AzureOpenAIDeployName) or (OpenAIApiKey, OpenAIModelId)"; + agent = new DefaultReplyAgent(_config.Name, defaultReply); + } + + return agent; + } +} diff --git a/ChatRoom/ChatRoom.OpenAI/OpenAICommand.cs b/ChatRoom/ChatRoom.OpenAI/OpenAICommand.cs new file mode 100644 index 0000000..7ea98d9 --- /dev/null +++ b/ChatRoom/ChatRoom.OpenAI/OpenAICommand.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using ChatRoom.SDK; +using Microsoft.Extensions.Hosting; +using Spectre.Console.Cli; +namespace ChatRoom.OpenAI; + +internal class OpenAICommand : ChatRoomAgentCommand +{ + public override async Task ExecuteAsync(CommandContext context, ChatRoomAgentClientCommandSettings setting) + { + var config = setting.ConfigFile is not null + ? JsonSerializer.Deserialize(File.ReadAllText(setting.ConfigFile))! + : new OpenAIAgentConfiguration(); + + var agentFactory = new OpenAIAgentFactory(config); + + var host = Host.CreateDefaultBuilder() + .AddAgentAsync(async (_) => agentFactory.CreateAgent()) + .UseChatRoom(roomName: setting.Room ?? "room", port: setting.Port ?? 30000) + .Build(); + + await host.StartAsync(); + await host.WaitForAgentsJoinRoomAsync(); + await host.WaitForShutdownAsync(); + + return 0; + } +} diff --git a/ChatRoom/ChatRoom.OpenAI/Program.cs b/ChatRoom/ChatRoom.OpenAI/Program.cs index d603af9..5199e09 100644 --- a/ChatRoom/ChatRoom.OpenAI/Program.cs +++ b/ChatRoom/ChatRoom.OpenAI/Program.cs @@ -1,46 +1,6 @@ // See https://aka.ms/new-console-template for more information -using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; -using Azure.AI.OpenAI; -using ChatRoom.SDK; -using Microsoft.Extensions.Hosting; +using ChatRoom.OpenAI; +using Spectre.Console.Cli; -var AZURE_OPENAI_ENDPOINT = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); -var AZURE_OPENAI_KEY = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); -var AZURE_DEPLOYMENT_NAME = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOY_NAME"); -var OPENAI_API_KEY = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); -var OPENAI_MODEL_ID = Environment.GetEnvironmentVariable("OPENAI_MODEL_ID") ?? "gpt-3.5-turbo-0125"; - -OpenAIClient openaiClient; -bool useAzure = false; -if (AZURE_OPENAI_ENDPOINT is string && AZURE_OPENAI_KEY is string && AZURE_DEPLOYMENT_NAME is string) -{ - openaiClient = new OpenAIClient(new Uri(AZURE_OPENAI_ENDPOINT), new Azure.AzureKeyCredential(AZURE_OPENAI_KEY)); - useAzure = true; -} -else if (OPENAI_API_KEY is string) -{ - openaiClient = new OpenAIClient(OPENAI_API_KEY); -} -else -{ - throw new ArgumentException("Please provide either (AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_KEY, AZURE_DEPLOYMENT_NAME) or OPENAI_API_KEY"); -} - -var deployModelName = useAzure ? AZURE_DEPLOYMENT_NAME! : OPENAI_MODEL_ID; - -var agent = new OpenAIChatAgent( - openAIClient: openaiClient, - name: "gpt", - modelName: deployModelName, - systemMessage: "You are a helpful AI assistant") - .RegisterMessageConnector(); - -var host = Host.CreateDefaultBuilder(args) - .UseChatRoom() - .Build(); - -await host.StartAsync(); -await host.JoinRoomAsync(agent); -await host.WaitForShutdownAsync(); +var app = new CommandApp(); +await app.RunAsync(args); diff --git a/ChatRoom/ChatRoom.Powershell/AgentFactory.cs b/ChatRoom/ChatRoom.Powershell/AgentFactory.cs new file mode 100644 index 0000000..7208a4f --- /dev/null +++ b/ChatRoom/ChatRoom.Powershell/AgentFactory.cs @@ -0,0 +1,41 @@ +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; + +namespace ChatRoom.Powershell; + +internal static class AgentFactory +{ + public static IAgent CreatePwshDeveloperAgent( + OpenAIClient client, + string cwd, + string name = "powershell", + string modelName = "gpt-35-turbo-0125") + { + var agent = new OpenAIChatAgent( + openAIClient: client, + modelName: modelName, + name: name, + systemMessage: $""" + You are a powershell developer. You need to convert the task assigned to you to a powershell script. + + If there is bug in the script, you need to fix it. + + The current working directory is {cwd} + + You need to write powershell script to resolve task. Put the script between ```pwsh and ```. + The script should always write the result to the output stream using Write-Host command. + + e.g. + ```pwsh + # This is a powershell script + Write-Host "Hello, World!" + ``` + """) + .RegisterMessageConnector() + .RegisterPrintMessage(); + + return agent; + } +} diff --git a/ChatRoom/ChatRoom.Service/ChannelConfiguration.cs b/ChatRoom/ChatRoom.Service/ChannelConfiguration.cs new file mode 100644 index 0000000..c3fac95 --- /dev/null +++ b/ChatRoom/ChatRoom.Service/ChannelConfiguration.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Json.Schema.Generation; + +namespace ChatRoom.Room; + +public class ChannelConfiguration +{ + [Description("Wheather to use AOAI or not. Default is true")] + [JsonPropertyName("use_aoai")] + public bool UseAOAI { get; set; } = true; + + [Description("The OpenAI API key. Default is the value of the env:OPENAI_API_KEY")] + [JsonPropertyName("openai_api_key")] + public string? OpenAIApiKey { get; set; } = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + [Description("The OpenAI model id. Default is 'gpt-3.5-turbo'")] + [JsonPropertyName("openai_model_id")] + public string? OpenAIModelId { get; set; } = "gpt-3.5-turbo"; + + [Description("The Azure OpenAI endpoint. Default is the value of the env:AZURE_OPENAI_ENDPOINT")] + [JsonPropertyName("azure_openai_endpoint")] + public string? AzureOpenAIEndpoint { get; set; } = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); + + [Description("The Azure OpenAI key. Default is the value of the env:AZURE_OPENAI_KEY")] + [JsonPropertyName("azure_openai_key")] + public string? AzureOpenAIKey { get; set; } = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); + + [Description("The Azure OpenAI deploy name. Default is the value of the env:AZURE_DEPLOYMENT_NAME")] + [JsonPropertyName("azure_openai_deploy_name")] + public string? AzureOpenAIDeployName { get; set; } = Environment.GetEnvironmentVariable("AZURE_DEPLOYMENT_NAME"); +} + +public class RoomConfiguration +{ + [Description("The name of the room. Default is 'room'")] + [JsonPropertyName("room")] + public string Room { get; set; } = "room"; + + [Description("The port number where the room is hosted. Default is 30000")] + [JsonPropertyName("port")] + public int Port { get; set; } = 30000; +} diff --git a/ChatRoom/ChatRoom.Service/ChannelGrain.cs b/ChatRoom/ChatRoom.Service/ChannelGrain.cs index 7747adf..fa86ca8 100644 --- a/ChatRoom/ChatRoom.Service/ChannelGrain.cs +++ b/ChatRoom/ChatRoom.Service/ChannelGrain.cs @@ -3,9 +3,6 @@ using ChatRoom.Common; using Microsoft.Extensions.Logging; using Orleans.Concurrency; -using Orleans.Runtime; -using Orleans.Streams; -using Orleans.Utilities; namespace ChatRoom.Room; @@ -15,17 +12,20 @@ internal class ChannelGrain : Grain, IChannelGrain private ChannelInfo _channelInfo = null!; private readonly ILogger _logger; private readonly Dictionary _agents = new(); - - public ChannelGrain(ILogger logger) + private readonly ChannelConfiguration _config; + private readonly RoomConfiguration _roomConfig; + public ChannelGrain(RoomConfiguration roomConfig, ChannelConfiguration config, ILogger logger) { + _roomConfig = roomConfig; _logger = logger; + _config = config; } public override async Task OnActivateAsync(CancellationToken cancellationToken) { _channelInfo = new ChannelInfo(this.GetPrimaryKeyString()); await base.OnActivateAsync(cancellationToken); - var roomGrain = this.GrainFactory.GetGrain("room"); + var roomGrain = this.GrainFactory.GetGrain(_roomConfig.Room); await roomGrain.CreateChannel(_channelInfo); } @@ -114,11 +114,11 @@ public async Task GetOnlineMembers() var notHumanAgents = notHumanMembers.Select(x => new DummyAgent(x)).ToArray(); var agents = humanAgents.Concat(notHumanAgents).ToArray(); // create agents - var AZURE_OPENAI_ENDPOINT = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); - var AZURE_OPENAI_KEY = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); - var AZURE_DEPLOYMENT_NAME = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOY_NAME"); - var OPENAI_API_KEY = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); - var OPENAI_MODEL_ID = Environment.GetEnvironmentVariable("OPENAI_MODEL_ID") ?? "gpt-3.5-turbo-0125"; + var AZURE_OPENAI_ENDPOINT = _config.AzureOpenAIEndpoint; + var AZURE_OPENAI_KEY = _config.AzureOpenAIKey; + var AZURE_DEPLOYMENT_NAME = _config.AzureOpenAIDeployName; + var OPENAI_API_KEY = _config.OpenAIApiKey; + var OPENAI_MODEL_ID = _config.OpenAIModelId; OpenAIClient openaiClient; bool useAzure = false; diff --git a/Directory.Packages.props b/Directory.Packages.props index 4fe87be..001ba89 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,10 +3,12 @@ true + + diff --git a/configuration/chatroom-client.json b/configuration/chatroom-client.json new file mode 100644 index 0000000..5670b82 --- /dev/null +++ b/configuration/chatroom-client.json @@ -0,0 +1,13 @@ +{ + "$schema": "./../schema/client_configuration_schema.json", + "name": "Alice", + "room_config": { + "room": "room", + "port": 30000 + }, + "channel_config": { + "use_aoai": false, + "openai_api_key": "your-api-key", + "openai_model_id": "gpt-3.5-turbo" + } +} \ No newline at end of file diff --git a/configuration/chatroom-openai.json b/configuration/chatroom-openai.json new file mode 100644 index 0000000..9ad5437 --- /dev/null +++ b/configuration/chatroom-openai.json @@ -0,0 +1,8 @@ +{ + "$schema": "./../schema/chatroom_openai_configuration_schema.json", + "name": "openai", + "system_message": "You are a helpful AI assistant", + "openai_api_key": "sk-", + "use_aoai": false, + "openai_model_id": "gpt-3.5-turbo" +} \ No newline at end of file diff --git a/schema/chatroom_openai_configuration_schema.json b/schema/chatroom_openai_configuration_schema.json new file mode 100644 index 0000000..a4cc5a6 --- /dev/null +++ b/schema/chatroom_openai_configuration_schema.json @@ -0,0 +1,37 @@ +{ + "type": "object", + "properties": { + "use_aoai": { + "type": "boolean", + "description": "Use AOAI, default is true" + }, + "openai_api_key": { + "type": "string", + "description": "OpenAI API key, default is $env:OPENAI_API_KEY" + }, + "openai_model_id": { + "type": "string", + "description": "OpenAI model ID, default is gpt-3.5-turbo" + }, + "azure_openai_endpoint": { + "type": "string", + "description": "Azure OpenAI endpoint, default is $env:AZURE_OPENAI_ENDPOINT" + }, + "azure_openai_key": { + "type": "string", + "description": "Azure OpenAI key, default is $env:AZURE_OPENAI_API_KEY" + }, + "azure_openai_deploy_name": { + "type": "string", + "description": "Azure OpenAI deploy name, default is $env:AZURE_OPENAI_DEPLOY_NAME" + }, + "system_message": { + "type": "string", + "description": "System message used in gpt agent, default is \u0027You are a helpful AI assistant\u0027" + }, + "name": { + "type": "string", + "description": "Name of the agent, default is \u0027gpt\u0027" + } + } + } \ No newline at end of file diff --git a/schema/client_configuration_schema.json b/schema/client_configuration_schema.json new file mode 100644 index 0000000..52eb1b3 --- /dev/null +++ b/schema/client_configuration_schema.json @@ -0,0 +1,90 @@ +{ + "type": "object", + "properties": { + "room_config": { + "type": "object", + "properties": { + "room": { + "$ref": "#/$defs/string" + }, + "port": { + "$ref": "#/$defs/integer" + } + }, + "description": "The configuration for the chat room" + }, + "channel_config": { + "type": "object", + "properties": { + "use_aoai": { + "$ref": "#/$defs/boolean" + }, + "openai_api_key": { + "$ref": "#/$defs/string1" + }, + "openai_model_id": { + "$ref": "#/$defs/string2" + }, + "azure_openai_endpoint": { + "$ref": "#/$defs/string3" + }, + "azure_openai_key": { + "$ref": "#/$defs/string4" + }, + "azure_openai_deploy_name": { + "$ref": "#/$defs/string5" + } + }, + "description": "The configuration for the chat channel" + }, + "agent_extensions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "name": { + "type": "string", + "description": "Your name in the chat room" + } + }, + "$defs": { + "string": { + "type": "string", + "description": "The name of the room. Default is \u0027room\u0027" + }, + "integer": { + "type": "integer", + "description": "The port number where the room is hosted. Default is 30000" + }, + "boolean": { + "type": "boolean", + "description": "Wheather to use AOAI or not. Default is true" + }, + "string1": { + "type": "string", + "description": "The OpenAI API key. Default is the value of the env:OPENAI_API_KEY" + }, + "string2": { + "type": "string", + "description": "The OpenAI model id. Default is \u0027gpt-3.5-turbo\u0027" + }, + "string3": { + "type": "string", + "description": "The Azure OpenAI endpoint. Default is the value of the env:AZURE_OPENAI_ENDPOINT" + }, + "string4": { + "type": "string", + "description": "The Azure OpenAI key. Default is the value of the env:AZURE_OPENAI_KEY" + }, + "string5": { + "type": "string", + "description": "The Azure OpenAI deploy name. Default is the value of the env:AZURE_DEPLOYMENT_NAME" + } + } + } \ No newline at end of file diff --git a/test/ChatRoom.Client.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt b/test/ChatRoom.Client.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt new file mode 100644 index 0000000..8d42558 --- /dev/null +++ b/test/ChatRoom.Client.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt @@ -0,0 +1,90 @@ +{ + "type": "object", + "properties": { + "room_config": { + "type": "object", + "properties": { + "room": { + "$ref": "#/$defs/string" + }, + "port": { + "$ref": "#/$defs/integer" + } + }, + "description": "The configuration for the chat room" + }, + "channel_config": { + "type": "object", + "properties": { + "use_aoai": { + "$ref": "#/$defs/boolean" + }, + "openai_api_key": { + "$ref": "#/$defs/string1" + }, + "openai_model_id": { + "$ref": "#/$defs/string2" + }, + "azure_openai_endpoint": { + "$ref": "#/$defs/string3" + }, + "azure_openai_key": { + "$ref": "#/$defs/string4" + }, + "azure_openai_deploy_name": { + "$ref": "#/$defs/string5" + } + }, + "description": "The configuration for the chat channel" + }, + "agent_extensions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "name": { + "type": "string", + "description": "Your name in the chat room" + } + }, + "$defs": { + "string": { + "type": "string", + "description": "The name of the room. Default is \u0027room\u0027" + }, + "integer": { + "type": "integer", + "description": "The port number where the room is hosted. Default is 30000" + }, + "boolean": { + "type": "boolean", + "description": "Wheather to use AOAI or not. Default is true" + }, + "string1": { + "type": "string", + "description": "The OpenAI API key. Default is the value of the env:OPENAI_API_KEY" + }, + "string2": { + "type": "string", + "description": "The OpenAI model id. Default is \u0027gpt-3.5-turbo\u0027" + }, + "string3": { + "type": "string", + "description": "The Azure OpenAI endpoint. Default is the value of the env:AZURE_OPENAI_ENDPOINT" + }, + "string4": { + "type": "string", + "description": "The Azure OpenAI key. Default is the value of the env:AZURE_OPENAI_KEY" + }, + "string5": { + "type": "string", + "description": "The Azure OpenAI deploy name. Default is the value of the env:AZURE_DEPLOYMENT_NAME" + } + } +} \ No newline at end of file diff --git a/test/ChatRoom.Client.Tests/ChatRoom.Client.Tests.csproj b/test/ChatRoom.Client.Tests/ChatRoom.Client.Tests.csproj new file mode 100644 index 0000000..927e9e1 --- /dev/null +++ b/test/ChatRoom.Client.Tests/ChatRoom.Client.Tests.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + True + + + + + + + + + + + diff --git a/test/ChatRoom.Client.Tests/ClientConfigurationTests.cs b/test/ChatRoom.Client.Tests/ClientConfigurationTests.cs new file mode 100644 index 0000000..1efd71e --- /dev/null +++ b/test/ChatRoom.Client.Tests/ClientConfigurationTests.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using Json.Schema; +using Json.Schema.Generation; +using Xunit; + +namespace ChatRoom.Client.Tests; + +public class ClientConfigurationTests +{ + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void VerifyConfigurationSchema() + { + var schema = new JsonSchemaBuilder() + .FromType() + .Build(); + + var json = JsonSerializer.Serialize(schema, new JsonSerializerOptions { WriteIndented = true }); + + Approvals.Verify(json); + } +} diff --git a/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt b/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt new file mode 100644 index 0000000..060b599 --- /dev/null +++ b/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.approved.txt @@ -0,0 +1,37 @@ +{ + "type": "object", + "properties": { + "use_aoai": { + "type": "boolean", + "description": "Use AOAI, default is true" + }, + "openai_api_key": { + "type": "string", + "description": "OpenAI API key, default is $env:OPENAI_API_KEY" + }, + "openai_model_id": { + "type": "string", + "description": "OpenAI model ID, default is gpt-3.5-turbo" + }, + "azure_openai_endpoint": { + "type": "string", + "description": "Azure OpenAI endpoint, default is $env:AZURE_OPENAI_ENDPOINT" + }, + "azure_openai_key": { + "type": "string", + "description": "Azure OpenAI key, default is $env:AZURE_OPENAI_API_KEY" + }, + "azure_openai_deploy_name": { + "type": "string", + "description": "Azure OpenAI deploy name, default is $env:AZURE_OPENAI_DEPLOY_NAME" + }, + "system_message": { + "type": "string", + "description": "System message used in gpt agent, default is \u0027You are a helpful AI assistant\u0027" + }, + "name": { + "type": "string", + "description": "Name of the agent, default is \u0027gpt\u0027" + } + } +} \ No newline at end of file diff --git a/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.received.txt b/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.received.txt new file mode 100644 index 0000000..060b599 --- /dev/null +++ b/test/ChatRoom.OpenAI.Tests/ApprovalTests/ClientConfigurationTests.VerifyConfigurationSchema.received.txt @@ -0,0 +1,37 @@ +{ + "type": "object", + "properties": { + "use_aoai": { + "type": "boolean", + "description": "Use AOAI, default is true" + }, + "openai_api_key": { + "type": "string", + "description": "OpenAI API key, default is $env:OPENAI_API_KEY" + }, + "openai_model_id": { + "type": "string", + "description": "OpenAI model ID, default is gpt-3.5-turbo" + }, + "azure_openai_endpoint": { + "type": "string", + "description": "Azure OpenAI endpoint, default is $env:AZURE_OPENAI_ENDPOINT" + }, + "azure_openai_key": { + "type": "string", + "description": "Azure OpenAI key, default is $env:AZURE_OPENAI_API_KEY" + }, + "azure_openai_deploy_name": { + "type": "string", + "description": "Azure OpenAI deploy name, default is $env:AZURE_OPENAI_DEPLOY_NAME" + }, + "system_message": { + "type": "string", + "description": "System message used in gpt agent, default is \u0027You are a helpful AI assistant\u0027" + }, + "name": { + "type": "string", + "description": "Name of the agent, default is \u0027gpt\u0027" + } + } +} \ No newline at end of file diff --git a/test/ChatRoom.OpenAI.Tests/ChatRoom.OpenAI.Tests.csproj b/test/ChatRoom.OpenAI.Tests/ChatRoom.OpenAI.Tests.csproj new file mode 100644 index 0000000..15cff4f --- /dev/null +++ b/test/ChatRoom.OpenAI.Tests/ChatRoom.OpenAI.Tests.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + True + + + + + + + + + + + diff --git a/test/ChatRoom.OpenAI.Tests/ClientConfigurationTests.cs b/test/ChatRoom.OpenAI.Tests/ClientConfigurationTests.cs new file mode 100644 index 0000000..cab1565 --- /dev/null +++ b/test/ChatRoom.OpenAI.Tests/ClientConfigurationTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using ChatRoom.OpenAI; +using Json.Schema; +using Json.Schema.Generation; +using Xunit; + +namespace ChatRoom.Client.Tests; + +public class ClientConfigurationTests +{ + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void VerifyConfigurationSchema() + { + var schema = new JsonSchemaBuilder() + .FromType() + .Build(); + + var json = JsonSerializer.Serialize(schema, new JsonSerializerOptions { WriteIndented = true }); + + Approvals.Verify(json); + } +} diff --git a/website/articles/toc.yml b/website/articles/toc.yml new file mode 100644 index 0000000..6aa7903 --- /dev/null +++ b/website/articles/toc.yml @@ -0,0 +1 @@ +- name: Getting Start \ No newline at end of file diff --git a/website/docfx.json b/website/docfx.json new file mode 100644 index 0000000..4e990ec --- /dev/null +++ b/website/docfx.json @@ -0,0 +1,44 @@ +{ + "build": { + "content": [ + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "**/toc.yml", + "get_start/**.md", + "release_notes/**.md", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "modern", + "template" + ], + "globalMetadata":{ + "_appTitle": "Agent Chatroom", + "_appName": "Agent Chatroom", + "_appFooter": "Powered by AutoGen.Net and Orleans", + "_gitContribute": { + "repo": "https://github.com/microsoft/autogen.git", + "branch": "main" + } + }, + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false + } + } \ No newline at end of file diff --git a/website/get_start/Install_agents.md b/website/get_start/Install_agents.md new file mode 100644 index 0000000..fea8177 --- /dev/null +++ b/website/get_start/Install_agents.md @@ -0,0 +1,7 @@ +Agent Chatroom provides the following built-in agents where you can easily install them as dotnet tools from NuGet: +- `ChatRoom.OpenAI`: OpenAI agent. +- `ChatRoom.Powershell`: Powershell GPT agent and Powershell executor agent. +- `ChatRoom.BingSearch`: Bing search agent. + +Please refer to each agent's documentation for how to install, configure and run them. +- [OpenAI agent](./install_openai_agent.md) \ No newline at end of file diff --git a/website/get_start/configure_client.md b/website/get_start/configure_client.md new file mode 100644 index 0000000..fdc48e5 --- /dev/null +++ b/website/get_start/configure_client.md @@ -0,0 +1,9 @@ +After [install the client](install_client.md), you can configure the client by creating a configuration file. Below is an example of a configuration file for the client: + +[!code-json[](../../configuration/chatroom-client.json)] + +After creating the configuration and saving it to `chatroom-client.json`, you can start the client with the following command: + +```bash +chatroom --config chatroom-client.json +``` \ No newline at end of file diff --git a/website/get_start/install_client.md b/website/get_start/install_client.md new file mode 100644 index 0000000..f5bfee3 --- /dev/null +++ b/website/get_start/install_client.md @@ -0,0 +1,39 @@ +You can install the latest chatroom client from nuget.org by running the following command: +```bash +dotnet tool install --global ChatRoom.Client +``` + +To start the chatroom client, run: +```bash +chatroom +``` + +After the chatroom client is started, you will see the following output from the console: + +```bash + _ _ + / \ __ _ ___ _ __ | |_ + / _ \ / _` | / _ \ | '_ \ | __| + / ___ \ | (_| | | __/ | | | | | |_ + /_/ \_\ \__, | \___| |_| |_| \__| + |___/ + ____ _ _ ____ + / ___| | |__ __ _ | |_ | _ \ ___ ___ _ __ ___ + | | | '_ \ / _` | | __| | |_) | / _ \ / _ \ | '_ ` _ \ + | |___ | | | | | (_| | | |_ | _ < | (_) | | (_) | | | | | | | + \____| |_| |_| \__,_| \__| |_| \_\ \___/ \___/ |_| |_| |_| + + + +/j to join a specific channel +/l to leave the current channel +/h to re-read channel history +/m to query members in the current channel +/lm to query members in the room +/lc to query all channels in the room +/rc to remove channel from the room +/am to add member to the current channel +/rm to remove member from the current channel +/exit to exit + to send a message +``` \ No newline at end of file diff --git a/website/get_start/install_openai_agent.md b/website/get_start/install_openai_agent.md new file mode 100644 index 0000000..162f88d --- /dev/null +++ b/website/get_start/install_openai_agent.md @@ -0,0 +1,27 @@ +To install `ChatRoom.OpenAI` agent, run the following command: +```bash +dotnet tool install --global ChatRoom.OpenAI +``` + +After the agent is installed, you can start the agent with the following command: +```bash +chatroom-openai +``` + +## Configuration +You can configure `ChatRoom.OpenAI` agent by creating a configuration file and pass it to the agent. Below is an example of a configuration file for the agent: + +[!code-json[](../../configuration/chatroom-openai.json)] + +After creating the configuration and saving it to `chatroom-openai.json`, you can start the agent with the following command: + +> [!NOTE] +> Before starting the agent, make sure the `ChatRoom.Client` is running. +> You can refer to the [use client](./use_client.md) for more information. + +```bash +chatroom-openai --room "room" --port 30000 --config chatroom-openai.json +``` + +## Usage +After agent is started, you can chat with the agent from the client by adding this agent to the current channel and send message to it. You can refer to the [use client](./use_client.md) for more information. \ No newline at end of file diff --git a/website/get_start/toc.yml b/website/get_start/toc.yml new file mode 100644 index 0000000..2d29464 --- /dev/null +++ b/website/get_start/toc.yml @@ -0,0 +1,14 @@ +- name: Install client + href: install_client.md + +- name: Configure client + href: configure_client.md + +- name: Use client + href: use_client.md + +- name: Install agents + href: install_agents.md + +- name: Install OpenAI agent + href: install_openai_agent.md \ No newline at end of file diff --git a/website/get_start/use_client.md b/website/get_start/use_client.md new file mode 100644 index 0000000..7a84342 --- /dev/null +++ b/website/get_start/use_client.md @@ -0,0 +1,66 @@ +Once you [configure the client](./configure_client.md), you can start chatting with the agents in the chatroom. + +To start the chatroom client, run the following command: + +```bash +chatroom +``` + +After the chatroom client is started, you will see the following output from the console: + +```bash + _ _ + / \ __ _ ___ _ __ | |_ + / _ \ / _` | / _ \ | '_ \ | __| + / ___ \ | (_| | | __/ | | | | | |_ + /_/ \_\ \__, | \___| |_| |_| \__| + |___/ + ____ _ _ ____ + / ___| | |__ __ _ | |_ | _ \ ___ ___ _ __ ___ + | | | '_ \ / _` | | __| | |_) | / _ \ / _ \ | '_ ` _ \ + | |___ | | | | | (_| | | |_ | _ < | (_) | | (_) | | | | | | | + \____| |_| |_| \__,_| \__| |_| \_\ \___/ \___/ |_| |_| |_| + + + +/j to join a specific channel +/l to leave the current channel +/h to re-read channel history +/m to query members in the current channel +/lm to query members in the room +/lc to query all channels in the room +/rc to remove channel from the room +/am to add member to the current channel +/rm to remove member from the current channel +/exit to exit + to send a message +``` + +Then you can use `/lm` to list all the agents in the chatroom. For how to install agents, please refer to the [install agents](./install_agent.md) page. + +```bash +/lm +────────────────────────────────────────────────── Members for 'room' ────────────────────────────────────────────────── +User - Human user +gpt - You are a helpful AI assistant +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +``` + +To chat with an agent, you need to add the agent to the current channel first. Channel is similar to a group chat in a message app where you can chat with multiple agents simultaneously. To add an agent to the current channel, use the `/am` command followed by the agent name. For example, to add the GPT agent to the current channel, use the following command. + +```bash +/am gpt +gpt joins the General channel. +``` + +After the agent is added to the channel, you can start chatting with the agent. + +```bash +Tell me a joke +[5/24/2024 10:21:08 PM] User: Tell me a joke +[5/24/2024 10:21:09 PM] gpt: Sure, here's a joke for you: + +Why couldn't the bicycle stand up by itself? + +Because it was two-tired! +``` diff --git a/website/icon.png b/website/icon.png new file mode 100644 index 0000000..b7c0c7a Binary files /dev/null and b/website/icon.png differ diff --git a/website/images/chatroom-client.png b/website/images/chatroom-client.png new file mode 100644 index 0000000..3cbfac4 Binary files /dev/null and b/website/images/chatroom-client.png differ diff --git a/website/images/preface.png b/website/images/preface.png new file mode 100644 index 0000000..2abf8ae Binary files /dev/null and b/website/images/preface.png differ diff --git a/website/index.md b/website/index.md new file mode 100644 index 0000000..4d237c1 --- /dev/null +++ b/website/index.md @@ -0,0 +1,65 @@ +

+
Agent Chatroom
+

+ +

+ Agent Chatroom +

+ +

+ An extensible multi-agent platform built on top of AutoGen.Net and Orleans. +

+ +--- + +## 🌟 Highlights +- **Multi-Agent Chat**: Chat with multiple agents simultaneously. +- **Extensible**: Create your own agents and integrate them into the chatroom. + +--- + +## 🚀 Quick Start + +- 🛠️ Install the Client +To install the client, run the following command: +```bash +dotnet tool install --global ChatRoom.Client --version 0.0.7 +``` + +- 🧩 Install the Agent +To install the OpenAI agent, run the following command: +```bash +dotnet tool install --global ChatRoom.OpenAI --version 0.0.7 +``` + +> [!Note] +> As of 2024/05/24, the following agents are available as dotnet tools from NuGet: +> - `ChatRoom.OpenAI`: OpenAI agent. +> - `ChatRoom.Powershell`: Powershell GPT agent and Powershell executor agent. +> - `ChatRoom.BingSearch`: Bing search agent. + +You can search for and install these agents from [nuget.org](https://www.nuget.org/). + +- 🚪 Start the Chatroom +To start the chatroom service as an Orleans silo, run: +```bash +chatroom +``` + +- 🤖 Start the OpenAI Agent and Join the Chatroom +To start the OpenAI agent, run: +```bash +chatroom-openai +``` + +After the OpenAI agent is started, you will see the following message in the chatroom: +```bash +gpt joined the chatroom. +``` + +- 💬 Add the OpenAI Agent to the Current Channel and Start Chatting +Once the GPT agent is in the chatroom, you can add it to the current channel and start chatting with it using the following command: +```bash +/am gpt +Hey, tell me a joke. +``` \ No newline at end of file diff --git a/website/toc.yml b/website/toc.yml new file mode 100644 index 0000000..40e606b --- /dev/null +++ b/website/toc.yml @@ -0,0 +1,5 @@ +- name: Get Start + href: get_start/ + +- name: Release Notes + href: release_note/ \ No newline at end of file