diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 7ddf8ad86..a0171a857 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.MLTasks; - namespace BotSharp.Abstraction.Conversations; public interface IConversationService @@ -11,8 +9,6 @@ public interface IConversationService Task> GetConversations(); Task DeleteConversation(string id); - IChatCompletion GetChatCompletion(); - /// /// Send message to LLM /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 2740c8c88..eda9445cb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -11,6 +11,8 @@ public class RoleDialogModel public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public string Content { get; set; } public string CurrentAgentId { get; set; } + public string ModelName { get; set; } = "gpt-3.5-turbo"; + public float Temperature { get; set; } = 0.5f; /// /// Function name if LLM response function call diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs index be3a170b8..f347a8a75 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs @@ -1,9 +1,8 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Abstraction.MLTasks; public interface IChatCompletion { + string ModelName { get; } Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived, diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs index 73fd4d1e8..225785151 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs @@ -5,19 +5,19 @@ namespace BotSharp.Abstraction.Routing.Models; public class RoutingRecord { [JsonPropertyName("agent_id")] - public string AgentId { get; set; } + public string AgentId { get; set; } = string.Empty; [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [JsonPropertyName("description")] - public string Description { get; set; } + public string Description { get; set; } = string.Empty; [JsonPropertyName("required")] public List RequiredFields { get; set; } = new List(); [JsonPropertyName("redirect_to")] - public string RedirectTo { get; set; } + public string? RedirectTo { get; set; } [JsonPropertyName("disabled")] public bool Disabled { get; set; } diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index b9af74c55..a6165320c 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -83,4 +83,9 @@ + + + + + diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 1dda05a67..5d0b89f02 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -1,6 +1,4 @@ using BotSharp.Abstraction.Functions; -using BotSharp.Core.Functions; -using BotSharp.Core.Hooks; using BotSharp.Core.Routing; using BotSharp.Core.Templating; using Microsoft.AspNetCore.Builder; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index c42d63fef..d1f10e03a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -9,13 +9,14 @@ public partial class ConversationService { int currentRecursiveDepth = 0; - private async Task GetChatCompletionsAsyncRecursively(IChatCompletion chatCompletion, - Agent agent, + private async Task GetChatCompletionsAsyncRecursively(Agent agent, List wholeDialogs, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted) { + var chatCompletion = CompletionProvider.GetChatCompletion(_services, wholeDialogs.Last().ModelName); + currentRecursiveDepth++; if (currentRecursiveDepth > _settings.MaxRecursiveDepth) { @@ -28,11 +29,16 @@ private async Task GetChatCompletionsAsyncRecursively(IChatCompletion chat text = latestResponse.Content.Split("=>").Last(); } - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, text) + var msg = new RoleDialogModel(AgentRole.Assistant, text) { CurrentAgentId = agent.Id, Channel = wholeDialogs.Last().Channel - }, onMessageReceived); + }; + + await HandleAssistantMessage(msg, onMessageReceived); + + // Add to dialog history + _storage.Append(_conversationId, agent.Id, msg); return false; } @@ -85,8 +91,7 @@ await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content wholeDialogs.Add(fn); - await GetChatCompletionsAsyncRecursively(chatCompletion, - agent, + await GetChatCompletionsAsyncRecursively(agent, wholeDialogs, onMessageReceived, onFunctionExecuting, @@ -115,8 +120,7 @@ await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, response) // After function is executed, pass the result to LLM to get a natural response wholeDialogs.Add(fn); - await GetChatCompletionsAsyncRecursively(chatCompletion, - agent, + await GetChatCompletionsAsyncRecursively(agent, wholeDialogs, onMessageReceived, onFunctionExecuting, diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 11ecb1f23..b8fd9b104 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Routing.Settings; using BotSharp.Core.Routing; @@ -92,9 +91,7 @@ await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, reasonedCo }); } - var chatCompletion = GetChatCompletion(); - var result = await GetChatCompletionsAsyncRecursively(chatCompletion, - agent, + var result = await GetChatCompletionsAsyncRecursively(agent, wholeDialogs, onMessageReceived, onFunctionExecuting, @@ -136,16 +133,4 @@ private void SaveStateByArgs(string args) } } } - - public IChatCompletion GetChatCompletion() - { - var completions = _services.GetServices(); - return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.ChatCompletion)); - } - - public IChatCompletion GetGpt4ChatCompletion() - { - var completions = _services.GetServices(); - return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider")); - } } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs new file mode 100644 index 000000000..d23401355 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.MLTasks; + +namespace BotSharp.Core.Infrastructures; + +public class CompletionProvider +{ + public static IChatCompletion GetChatCompletion(IServiceProvider services, string modelName = "gpt-3.5-turbo") + { + var completions = services.GetServices(); + var settings = services.GetRequiredService(); + // completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(settings.ChatCompletion)); + return completions.FirstOrDefault(x => x.ModelName == modelName); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index 2d0f5a5dc..a6f2366fe 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -29,7 +29,7 @@ public async Task ExecuteInstruction(Agent agent, var wholeDialogs = new List { - new RoleDialogModel("user", message.Content) + message }; // Trigger before completion hooks @@ -71,7 +71,7 @@ private async Task ExecuteInstructionRecursively(Agent agent, Func onFunctionExecuting, Func onFunctionExecuted) { - var chatCompletion = GetChatCompletion(); + var chatCompletion = CompletionProvider.GetChatCompletion(_services, wholeDialogs.Last().ModelName); var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => { @@ -124,11 +124,4 @@ private async Task HandleFunctionMessage(RoleDialogModel msg, await CallFunctions(msg); await onFunctionExecuted(msg); } - - public IChatCompletion GetChatCompletion() - { - var completions = _services.GetServices(); - var settings = _services.GetRequiredService(); - return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(settings.ChatCompletion)); - } } diff --git a/src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs b/src/Infrastructure/BotSharp.Core/Routing/ReasoningHook.cs similarity index 89% rename from src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs rename to src/Infrastructure/BotSharp.Core/Routing/ReasoningHook.cs index f754733f7..63112d4dd 100644 --- a/src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/ReasoningHook.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Core.Hooks; +namespace BotSharp.Core.Routing; public class ReasoningHook : AgentHookBase { diff --git a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/RouteToAgentFn.cs similarity index 62% rename from src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs rename to src/Infrastructure/BotSharp.Core/Routing/RouteToAgentFn.cs index 4991dc99e..ef7f295ce 100644 --- a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RouteToAgentFn.cs @@ -1,9 +1,8 @@ -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Routing.Models; -using System.IO; -namespace BotSharp.Core.Functions; +namespace BotSharp.Core.Routing; /// /// Router calls this function to set the Active Agent according to the context @@ -63,26 +62,36 @@ private bool HasMissingRequiredField(RoleDialogModel message, out string agentId agentId = routingRule.AgentId; // Check required fields - var jo = JsonSerializer.Deserialize(message.FunctionArgs); + var root = JsonSerializer.Deserialize(message.FunctionArgs); bool hasMissingField = false; + string missingFieldName = ""; foreach (var field in routingRule.RequiredFields) { - if (jo is JsonElement root) + if (!root.EnumerateObject().Any(x => x.Name == field)) { - if (!root.EnumerateObject().Any(x => x.Name == field)) - { - message.ExecutionResult = $"missing {field}."; - hasMissingField = true; - break; - } - else if (root.EnumerateObject().Any(x => x.Name == field) && - string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) - { - message.ExecutionResult = $"missing {field}."; - hasMissingField = true; - break; - } + message.ExecutionResult = $"missing {field}."; + hasMissingField = true; + missingFieldName = field; + break; } + else if (root.EnumerateObject().Any(x => x.Name == field) && + string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) + { + message.ExecutionResult = $"missing {field}."; + hasMissingField = true; + missingFieldName = field; + break; + } + } + + // Check if states contains the field according conversation context. + var states = _services.GetRequiredService(); + if (!string.IsNullOrEmpty(states.GetState(missingFieldName))) + { + var value = states.GetState(missingFieldName); + message.FunctionArgs = message.FunctionArgs.Substring(0, message.FunctionArgs.Length - 1) + $", \"{missingFieldName}\": \"{value}\"" + "}"; + hasMissingField = false; + missingFieldName = ""; } if (hasMissingField && !string.IsNullOrEmpty(routingRule.RedirectTo)) diff --git a/src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingHook.cs similarity index 93% rename from src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs rename to src/Infrastructure/BotSharp.Core/Routing/RoutingHook.cs index aba0ffc21..851a9a60d 100644 --- a/src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingHook.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Core.Hooks; +namespace BotSharp.Core.Routing; public class RoutingHook : AgentHookBase { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs index 3bbbf8ff0..579a1a9aa 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs @@ -64,7 +64,7 @@ private async Task SendMessageToReasoner(Agent reasoner) new RoleDialogModel(AgentRole.User, @"What's the next step, your response must be in JSON format with ""function"" and ""parameters"". ") }; - var chatCompletion = GetGpt4ChatCompletion(); + var chatCompletion = CompletionProvider.GetChatCompletion(_services, "gpt-4"); RoleDialogModel response = null; await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg @@ -111,7 +111,7 @@ private async Task SendMessageToAgent(string agentId, List(); var agent = await agentService.LoadAgent(agentId); - var chatCompletion = GetChatCompletion(); + var chatCompletion = CompletionProvider.GetChatCompletion(_services, wholeDialogs.Last().ModelName); RoleDialogModel response = null; await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg @@ -132,19 +132,6 @@ await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg return response; } - public IChatCompletion GetChatCompletion() - { - var completions = _services.GetServices(); - var settings = _services.GetRequiredService(); - return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(settings.ChatCompletion)); - } - - public IChatCompletion GetGpt4ChatCompletion() - { - var completions = _services.GetServices(); - return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider")); - } - private void SaveStateByArgs(JsonDocument args) { var stateService = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 5ad55c78f..fdf6d256c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -40,11 +40,10 @@ public async Task DeleteConversation([FromRoute] string agentId, [FromRoute] str [HttpPost("/conversation/{agentId}/{conversationId}")] public async Task SendMessage([FromRoute] string agentId, [FromRoute] string conversationId, - [FromBody] NewMessageModel input, - [FromQuery] string? channel = "openapi") + [FromBody] NewMessageModel input) { var conv = _services.GetRequiredService(); - conv.SetConversationId(conversationId, channel); + conv.SetConversationId(conversationId, input.Channel); input.States.ForEach(x => conv.States.SetState(x.Split('=')[0], x.Split('=')[1])); var response = new MessageResponseModel(); @@ -53,7 +52,8 @@ public async Task SendMessage([FromRoute] string agentId, await conv.SendMessage(agentId, new RoleDialogModel("user", input.Text) { - Channel = channel + Channel = input.Channel, + ModelName = input.ModelName }, async msg => { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index 3eeac672f..18da09acd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -36,7 +36,10 @@ public async Task NewConversation([FromRoute] string agentId, } return await instructor.ExecuteInstruction(agent, - new RoleDialogModel(AgentRole.User, input.Text), + new RoleDialogModel(AgentRole.User, input.Text) + { + ModelName = input.ModelName + }, fn => Task.CompletedTask, fn => Task.CompletedTask, fn => Task.CompletedTask); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs index 0f66d7be9..18bb130ff 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs @@ -3,6 +3,8 @@ namespace BotSharp.OpenAPI.ViewModels.Conversations; public class NewMessageModel { public string Text { get; set; } + public string ModelName { get; set; } = "gpt-3.5-turbo"; + public string Channel { get; set; } = "openapi"; /// /// Conversation states from input diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index cd0f2206b..f4f935e1e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -23,6 +23,8 @@ public class ChatCompletionProvider : IChatCompletion private readonly IServiceProvider _services; private readonly ILogger _logger; + public virtual string ModelName => "gpt-3.5-turbo"; + public ChatCompletionProvider(AzureOpenAiSettings settings, ILogger logger, IServiceProvider services) @@ -32,10 +34,10 @@ public ChatCompletionProvider(AzureOpenAiSettings settings, _services = services; } - private OpenAIClient GetClient() + protected virtual (OpenAIClient, string) GetClient() { var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); - return client; + return (client, _settings.DeploymentModel.ChatCompletionModel); } public List GetChatSamples(string sampleText) @@ -89,10 +91,10 @@ public async Task GetChatCompletionsAsync(Agent agent, Func onMessageReceived, Func onFunctionExecuting) { - var client = GetClient(); + var (client, deploymentModel) = GetClient(); var chatCompletionsOptions = PrepareOptions(agent, conversations); - var response = await client.GetChatCompletionsAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); + var response = await client.GetChatCompletionsAsync(deploymentModel, chatCompletionsOptions); var choice = response.Value.Choices[0]; var message = choice.Message; @@ -110,6 +112,12 @@ public async Task GetChatCompletionsAsync(Agent agent, Channel = conversations.Last().Channel }; + // Somethings LLM will generate a function name with agent name. + if (!string.IsNullOrEmpty(funcContextIn.FunctionName)) + { + funcContextIn.FunctionName = funcContextIn.FunctionName.Split('.').Last(); + } + // Execute functions await onFunctionExecuting(funcContextIn); } @@ -175,7 +183,7 @@ public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations) + protected ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) { var chatCompletionsOptions = new ChatCompletionsOptions(); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs index 8767e7dfe..21fa75723 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs @@ -1,240 +1,31 @@ using Azure; using Azure.AI.OpenAI; -using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Conversations.Settings; -using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.MLTasks; using BotSharp.Plugin.AzureOpenAI.Settings; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; namespace BotSharp.Plugin.AzureOpenAI.Providers; -public class GPT4CompletionProvider : IChatCompletion +public class GPT4CompletionProvider : ChatCompletionProvider { private readonly AzureOpenAiSettings _settings; private readonly IServiceProvider _services; private readonly ILogger _logger; + public override string ModelName => "gpt-4"; + public GPT4CompletionProvider(AzureOpenAiSettings settings, ILogger logger, - IServiceProvider services) + IServiceProvider services) : base(settings, logger, services) { _settings = settings; _logger = logger; _services = services; } - private OpenAIClient GetClient() + protected override (OpenAIClient, string) GetClient() { var client = new OpenAIClient(new Uri(_settings.GPT4.Endpoint), new AzureKeyCredential(_settings.GPT4.ApiKey)); - return client; - } - - public List GetChatSamples(string sampleText) - { - var samples = new List(); - if (string.IsNullOrEmpty(sampleText)) - { - return samples; - } - - var lines = sampleText.Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - var line = lines[i]; - if (string.IsNullOrEmpty(line.Trim())) - { - continue; - } - var role = line.Substring(0, line.IndexOf(' ') - 1).Trim(); - var content = line.Substring(line.IndexOf(' ') + 1).Trim(); - - // comments - if (role == "##") - { - continue; - } - - samples.Add(new RoleDialogModel(role, content)); - } - - return samples; - } - - public List GetFunctions(string functionsJson) - { - var functions = new List(); - if (!string.IsNullOrEmpty(functionsJson)) - { - functions = JsonSerializer.Deserialize>(functionsJson, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - AllowTrailingCommas = true - }); - } - - return functions; - } - - public async Task GetChatCompletionsAsync(Agent agent, - List conversations, - Func onMessageReceived, - Func onFunctionExecuting) - { - var client = GetClient(); - var chatCompletionsOptions = PrepareOptions(agent, conversations); - - var response = await client.GetChatCompletionsAsync(_settings.GPT4.DeploymentModel, chatCompletionsOptions); - var choice = response.Value.Choices[0]; - var message = choice.Message; - - if (choice.FinishReason == CompletionsFinishReason.FunctionCall) - { - _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}"); - - var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content) - { - CurrentAgentId = agent.Id, - FunctionName = message.FunctionCall.Name, - FunctionArgs = message.FunctionCall.Arguments, - Channel = conversations.Last().Channel - }; - - // Execute functions - await onFunctionExecuting(funcContextIn); - } - else - { - _logger.LogInformation($"[{agent.Name}] {message.Role}: {message.Content}"); - - var msg = new RoleDialogModel(AgentRole.Assistant, message.Content) - { - CurrentAgentId= agent.Id, - Channel = conversations.Last().Channel - }; - - // Text response received - await onMessageReceived(msg); - } - - return true; - } - - public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) - { - var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); - var chatCompletionsOptions = PrepareOptions(agent, conversations); - - var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); - using StreamingChatCompletions streaming = response.Value; - - string output = ""; - await foreach (var choice in streaming.GetChoicesStreaming()) - { - if (choice.FinishReason == CompletionsFinishReason.FunctionCall) - { - var args = ""; - await foreach (var message in choice.GetMessageStreaming()) - { - if (message.FunctionCall == null || message.FunctionCall.Arguments == null) - continue; - Console.Write(message.FunctionCall.Arguments); - args += message.FunctionCall.Arguments; - - } - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), args)); - continue; - } - - await foreach (var message in choice.GetMessageStreaming()) - { - if (message.Content == null) - continue; - Console.Write(message.Content); - output += message.Content; - - _logger.LogInformation(message.Content); - - await onMessageReceived(new RoleDialogModel(message.Role.ToString(), message.Content)); - } - - output = ""; - } - - return true; - } - - - private ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) - { - var chatCompletionsOptions = new ChatCompletionsOptions(); - - if (!string.IsNullOrEmpty(agent.Instruction)) - { - chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction)); - } - - if (!string.IsNullOrEmpty(agent.Knowledges)) - { - chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges)); - } - - var samples = GetChatSamples(agent.Samples); - foreach (var message in samples) - { - chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); - } - - var functions = GetFunctions(agent.Functions); - foreach (var function in functions) - { - chatCompletionsOptions.Functions.Add(new FunctionDefinition - { - Name = function.Name, - Description = function.Description, - Parameters = BinaryData.FromObjectAsJson(function.Parameters) - }); - } - - foreach (var message in conversations) - { - if (message.Role == ChatRole.Function) - { - chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content) - { - Name = message.FunctionName - }); - } - else - { - chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); - } - } - - // https://community.openai.com/t/cheat-sheet-mastering-temperature-and-top-p-in-chatgpt-api-a-few-tips-and-tricks-on-controlling-the-creativity-deterministic-output-of-prompt-responses/172683 - chatCompletionsOptions.Temperature = 0.5f; - chatCompletionsOptions.NucleusSamplingFactor = 0.5f; - - var convSetting = _services.GetRequiredService(); - if (convSetting.ShowVerboseLog) - { - var verbose = string.Join("\n", chatCompletionsOptions.Messages.Select(x => - { - return x.Role == ChatRole.Function ? - $"{x.Role}: {x.Name} {x.Content}" : - $"{x.Role}: {x.Content}"; - })); - _logger.LogInformation(verbose); - } - - return chatCompletionsOptions; + return (client, _settings.GPT4.DeploymentModel); } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs index 9eb5f63ea..ad634d1b1 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs @@ -2,7 +2,7 @@ namespace BotSharp.Plugin.AzureOpenAI.Settings; public class DeploymentModelSetting { - public string? ChatCompletionModel { get; set; } + public string ChatCompletionModel { get; set; } = string.Empty; public string? TextCompletionModel { get; set; } public override string ToString() diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index cee7428dc..9230bec34 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -44,9 +44,17 @@ public OpenAiModels GetOpenAiModels() { Id = "gpt-3.5-turbo", Model = "gpt-3.5-turbo", - Name = "Default (GPT-3.5)", - MaxLength = 4000, - TokenLimit = 4000 + Name = "GPT-3.5 Turbo", + MaxLength = 4 * 1024, + TokenLimit = 4 * 1024 + }, + new AiModel + { + Id = "gpt-4", + Model = "gpt-4", + Name = "GPT-4", + MaxLength = 8 * 1024, + TokenLimit = 8 * 1024 } } }; @@ -62,7 +70,7 @@ public async Task SendMessage([FromBody] OpenAiMessageInput input) var outputStream = Response.Body; var channel = "webchat"; - var conversation = input.Messages + var message = input.Messages .Where(x => x.Role == AgentRole.User) .Select(x => new RoleDialogModel(x.Role, x.Content) { @@ -74,7 +82,7 @@ public async Task SendMessage([FromBody] OpenAiMessageInput input) input.States.ForEach(x => conv.States.SetState(x.Split('=')[0], x.Split('=')[1])); var result = await conv.SendMessage(input.AgentId, - conversation, + message, async msg => await OnChunkReceived(outputStream, msg), async fn diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs index e0b135647..c40bf1892 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs @@ -27,10 +27,8 @@ public ChatCompletionProvider(IServiceProvider services, _logger = logger; } - public string GetChatCompletions(Agent agent, List conversations, Func onMessageReceived) - { - throw new NotImplementedException(); - } + public string ModelName => "llama-2"; + public async Task GetChatCompletionsAsync(Agent agent, List conversations, diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 96631e576..dcd1d1389 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -26,9 +26,7 @@ "Conversation": { "DataDir": "conversations", - "ShowVerboseLog": false, - "ChatCompletion": "AzureOpenAI.Providers.ChatCompletionProvider" - // "ChatCompletion": "LLamaSharp.ChatCompletionProvider" + "ShowVerboseLog": false }, "LlamaSharp": {