diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index 34a657b1..6953dee9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Conversations.Models; + namespace BotSharp.Abstraction.Agents; public abstract class AgentHookBase : IAgentHook @@ -33,8 +35,12 @@ public abstract class AgentHookBase : IAgentHook return true; } - public virtual Agent OnAgentLoaded() + public virtual void OnAgentLoaded(Agent agent) { - return _agent; + } + + public virtual bool OnAgentRouting(RoleDialogModel message, ref string id) + { + return true; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index b91fc073..7b0b73dd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -24,5 +24,5 @@ public interface IAgentHook /// /// /// - Agent OnAgentLoaded(); + void OnAgentLoaded(Agent agent); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs new file mode 100644 index 00000000..2a1f18fa --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Agents; + +public interface IAgentRouting +{ + Task LoadCurrentAgent(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 122a14be..1e22d6f6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Abstraction.Agents.Models; public class Agent @@ -29,4 +27,7 @@ public class Agent /// Domain knowledges /// public string Knowledges { get; set; } + + public override string ToString() + => $"{Name} {Id}"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs index f51e1941..6b092805 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs @@ -2,5 +2,9 @@ namespace BotSharp.Abstraction.Agents.Settings; public class AgentSettings { + /// + /// Router Agent Id + /// + public string RouterId { get; set; } public string DataDir { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index dc883adf..3886445a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -8,25 +8,25 @@ public class RoleDialogModel public string Role { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public string Content { get; set; } + public string CurrentAgentId { get; set; } /// /// Function name if LLM response function call /// public string? FunctionName { get; set; } + public string? FunctionArgs { get; set; } + /// /// Function execution result /// public string? ExecutionResult { get; set; } - /// - /// When function callback has been executed, system will pass result to LLM again, - /// Set this property to True to stop calling LLM. - /// - public bool StopSubsequentInteraction { get; set; } - public bool IsConversationEnd { get; set; } + public bool NeedReloadAgent { get; set; } + public bool StopPropagate { get; set; } + /// /// Channel name /// diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs index 2e8f6438..26fecf8a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs @@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.MLTasks; public interface IChatCompletion { - string GetChatCompletions(Agent agent, List conversations); + // string GetChatCompletions(Agent agent, List conversations, Func onMessageReceived); Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived); Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs index 0da63437..651adad7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs @@ -1,3 +1,6 @@ +using System.Linq; +using System.Text.RegularExpressions; + namespace BotSharp.Abstraction.Utilities; public static class StringExtensions @@ -15,4 +18,19 @@ public static class StringExtensions else return str; } + + public static string CleanPhoneNumber(this string phoneNumber) + { + if (phoneNumber != null && !phoneNumber.All(char.IsDigit)) + { + phoneNumber = Regex.Replace(phoneNumber, @"[^\d]", ""); + } + + if (phoneNumber != null && phoneNumber.Length > 10) + { + phoneNumber = phoneNumber.Substring(1); + } + + return phoneNumber; + } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs new file mode 100644 index 00000000..8f112392 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs @@ -0,0 +1,37 @@ +using BotSharp.Abstraction.Agents.Models; + +namespace BotSharp.Core.Agents.Services; + +public class AgentRouter : IAgentRouting +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly AgentSettings _settings; + + public AgentRouter(IServiceProvider services, + ILogger logger, + AgentSettings settings) + { + _services = services; + _logger = logger; + _settings = settings; + } + + public async Task LoadCurrentAgent() + { + // Load current agent from state + var stateService = _services.GetRequiredService(); + var currentAgentId = stateService.GetState("agentId"); + if (string.IsNullOrEmpty(currentAgentId)) + { + currentAgentId = _settings.RouterId; + } + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(currentAgentId); + + // Set agent and trigger state changed + stateService.SetState("agentId", currentAgentId); + + return agent; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 5d9d7b4f..09c463ee 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -39,9 +39,11 @@ public partial class AgentService hook.OnSamplesLoaded(ref samples); } - hook.OnAgentLoaded(); + hook.OnAgentLoaded(agent); } + _logger.LogInformation($"Loaded agent {agent}."); + return agent; } } diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 66d8d4a3..c746438e 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -33,6 +33,8 @@ public static class BotSharpServiceCollectionExtensions RegisterPlugins(services, config); + services.AddScoped(); + return services; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs index a576c26c..e9dec02b 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs @@ -51,7 +51,7 @@ public class ConversationController : ControllerBase, IApiAdapter await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text), async msg => - response.Text += msg.Content, + response.Text = msg.Content, async fn => await Task.CompletedTask); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 8cdfdb23..12b6fed7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -1,3 +1,6 @@ +using Amazon.SecurityToken.Model.Internal.MarshallTransformations; +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.MLTasks; @@ -12,7 +15,7 @@ public class ConversationService : IConversationService private readonly ConversationSetting _settings; private readonly IConversationStorage _storage; - public ConversationService(IServiceProvider services, + public ConversationService(IServiceProvider services, IUserIdentity user, ConversationSetting settings, IConversationStorage storage, @@ -69,11 +72,12 @@ public class ConversationService : IConversationService return record.ToConversation(); } - public async Task SendMessage(string agentId, string conversationId, - RoleDialogModel lastDalog, - Func onMessageReceived, + public async Task SendMessage(string agentId, string conversationId, + RoleDialogModel lastDalog, + Func onMessageReceived, Func onFunctionExecuting) { + lastDalog.CurrentAgentId = agentId; _storage.Append(conversationId, lastDalog); var wholeDialogs = GetDialogHistory(conversationId); @@ -95,11 +99,9 @@ public class ConversationService : IConversationService var stateService = _services.GetRequiredService(); stateService.SetConversation(conversationId); stateService.Load(); - stateService.SetState("agentId", agentId); - // load agent - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(agentId); + var router = _services.GetRequiredService(); + var agent = await router.LoadCurrentAgent(); // Get relevant domain knowledge /*if (_settings.EnableKnowledgeBase) @@ -127,39 +129,100 @@ public class ConversationService : IConversationService var chatCompletion = GetChatCompletion(); var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => { - if (msg.Role == "function") + await HandleMessage(conversationId, agent, msg, onMessageReceived, onFunctionExecuting); + + if (msg.NeedReloadAgent) { - // Save states - SaveStateByArgs(msg.Content); - - // Call functions - await onFunctionExecuting(msg); - await CallFunctions(conversationId, msg); - } - else - { - // Add to dialog history - _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content)); - - // After chat completion hook - foreach (var hook in hooks) - { - await hook.AfterCompletion(msg); - } - - await onMessageReceived(msg); - } - - // Clean conversation - if (msg.IsConversationEnd) - { - stateService.CleanState(); + await HandleMessageIfAgentReloaded(conversationId, agent, msg, wholeDialogs, onMessageReceived, onFunctionExecuting); } }); return result; } + private async Task HandleMessage(string conversationId, Agent agent, RoleDialogModel msg, + Func onMessageReceived, + Func onFunctionExecuting) + { + if (msg.Role == "function") + { + // Save states + SaveStateByArgs(msg.FunctionArgs); + + // Call functions + await onFunctionExecuting(msg); + await CallFunctions(msg); + + // Add to dialog history + if (msg.ExecutionResult != null) + { + if (msg.NeedReloadAgent) + { + _logger.LogInformation($"Skipped append dialog log: {msg.FunctionName}\n{msg.FunctionArgs}\n{msg.ExecutionResult}"); + return; + } + + _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content) + { + CurrentAgentId = agent.Id, + FunctionName = msg.FunctionName, + FunctionArgs = msg.FunctionArgs, + ExecutionResult = msg.ExecutionResult + }); + } + } + else + { + // Add to dialog history + _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content) + { + CurrentAgentId = agent.Id + }); + + var hooks = _services.GetServices().ToList(); + // After chat completion hook + foreach (var hook in hooks) + { + await hook.AfterCompletion(msg); + } + + await onMessageReceived(msg); + } + } + + private async Task HandleMessageIfAgentReloaded(string conversationId, Agent agent, + RoleDialogModel msg, + List wholeDialogs, + Func onMessageReceived, + Func onFunctionExecuting) + { + var state = _services.GetRequiredService(); + var currentAgentId = state.GetState("agentId"); + + // Send to LLM to get final response when agent is switched. + var conv = _services.GetRequiredService(); + var chatCompletion = conv.GetChatCompletion(); + var agentService = _services.GetRequiredService(); + var newAgent = await agentService.LoadAgent(currentAgentId); + await chatCompletion.GetChatCompletionsAsync(newAgent, wholeDialogs, async newMsg => + { + if (newMsg.Role == AgentRole.Function) + { + await HandleMessage(conversationId, agent, newMsg, onMessageReceived, onFunctionExecuting); + } + else + { + msg.StopPropagate = true; + await onMessageReceived(newMsg); + + _storage.Append(conversationId, new RoleDialogModel(newMsg.Role, newMsg.Content) + { + CurrentAgentId = agent.Id + }); + } + }); + } + private void SaveStateByArgs(string args) { var stateService = _services.GetRequiredService(); @@ -173,7 +236,7 @@ public class ConversationService : IConversationService } } - private async Task CallFunctions(string conversationId, RoleDialogModel msg) + private async Task CallFunctions(RoleDialogModel msg) { var hooks = _services.GetServices().ToList(); @@ -182,6 +245,12 @@ public class ConversationService : IConversationService .Where(x => x.Name == msg.FunctionName) .ToList(); + if (functions.Count == 0) + { + _logger.LogError($"Can't find function implementation of {msg.FunctionName}."); + return; + } + foreach (var fn in functions) { // Before executing functions @@ -193,12 +262,6 @@ public class ConversationService : IConversationService // Execute function await fn.Execute(msg); - // Add to dialog history - _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.ExecutionResult) - { - FunctionName = msg.FunctionName, - }); - // After functions have been executed foreach (var hook in hooks) { diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index ca98268c..0d2d249e 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Conversations.Models; using System.IO; @@ -15,15 +16,40 @@ public class ConversationStorage : IConversationStorage { var conversationFile = GetStorageFile(conversationId); var sb = new StringBuilder(); - sb.AppendLine($"{dialog.Role}|{dialog.CreatedAt}|{dialog.FunctionName}"); - var content = dialog.Content.Trim().Replace("\r", " ").Replace("\n", " "); - if (string.IsNullOrEmpty(content)) + if (dialog.Role == AgentRole.Function) { - return; - } + var args = dialog.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim(); - sb.AppendLine($" - {content}"); + sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{dialog.CurrentAgentId}|{dialog.FunctionName}|{args}"); + + var content = dialog.ExecutionResult.Replace("\r", " ").Replace("\n", " ").Trim(); + if (string.IsNullOrEmpty(content)) + { + return; + } + sb.AppendLine($" - {content}"); + } + else if (dialog.Role == AgentRole.Assistant) + { + sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|||"); + var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim(); + if (string.IsNullOrEmpty(content)) + { + return; + } + sb.AppendLine($" - {content}"); + } + else + { + sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{dialog.CurrentAgentId}||"); + var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim(); + if (string.IsNullOrEmpty(content)) + { + return; + } + sb.AppendLine($" - {content}"); + } var conversation = sb.ToString(); File.AppendAllText(conversationFile, conversation); @@ -39,13 +65,18 @@ public class ConversationStorage : IConversationStorage { var meta = dialogs[i]; var dialog = dialogs[i + 1]; - var role = meta.Split('|')[0]; - var createdAt = DateTime.Parse(meta.Split('|')[1]); + var createdAt = DateTime.Parse(meta.Split('|')[0]); + var role = meta.Split('|')[1]; + var currentAgentId = meta.Split('|')[2]; + var funcName = meta.Split('|')[3]; + var funcArgs= meta.Split('|')[4]; var text = dialog.Substring(4); - var funcName = meta.Split('|')[2]; + results.Add(new RoleDialogModel(role, text) { + CurrentAgentId = currentAgentId, FunctionName = funcName, + FunctionArgs = funcArgs, CreatedAt = createdAt }); } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs index 02516b9a..20d67820 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs @@ -14,7 +14,7 @@ public class ChatCompletionProvider : IChatCompletion _services = services; } - public string GetChatCompletions(Agent agent, List conversations) + public string GetChatCompletions(Agent agent, List conversations, Func onMessageReceived) { throw new NotImplementedException(); } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index e3e146f4..f71475e3 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -25,27 +25,41 @@ public class ChatCompletionProvider : IChatCompletion _logger = logger; } - public string GetChatCompletions(Agent agent, List conversations) + private OpenAIClient GetClient() { var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); + return client; + } + + /*public string GetChatCompletions(Agent agent, List conversations, Func onMessageReceived) + { + var client = GetClient(); var chatCompletionsOptions = PrepareOptions(agent, conversations); var response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); + var choice = response.Value.Choices[0]; + var message = choice.Message; - string output = ""; - foreach (var choice in response.Value.Choices) + if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { - var message = choice.Message; - if (message.Content == null) - continue; - Console.Write(message.Content); - output += message.Content; + response = HandleFunctionCall(message, + onMessageReceived, + chatCompletionsOptions).Result; } - _logger.LogInformation(output); + choice = response.Value.Choices[0]; + message = choice.Message; - return output.Trim(); - } + _logger.LogInformation(message.Content); + + if (!string.IsNullOrEmpty(message.Content)) + { + onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content)) + .Wait(); + } + + return message.Content.Trim(); + }*/ public List GetChatSamples(string sampleText) { @@ -95,7 +109,7 @@ public class ChatCompletionProvider : IChatCompletion public async Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived) { - var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); + var client = GetClient(); var chatCompletionsOptions = PrepareOptions(agent, conversations); var response = await client.GetChatCompletionsAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); @@ -104,51 +118,24 @@ public class ChatCompletionProvider : IChatCompletion if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { - if (message.FunctionCall == null || message.FunctionCall.Arguments == null) - { - return false; - } - _logger.LogInformation($"{message.FunctionCall.Name}: {message.FunctionCall.Arguments}"); - var funcContextIn = new RoleDialogModel(ChatRole.Function.ToString(), message.FunctionCall.Arguments) - { - FunctionName = message.FunctionCall.Name - }; - - // Execute functions - await onMessageReceived(funcContextIn); - - if (funcContextIn.StopSubsequentInteraction) - { - // Emit a fake message that should be populated by whom set StopSubsequentInteraction as True. - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), "")); - return true; - } - - if (funcContextIn.IsConversationEnd) - { - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), funcContextIn.Content) - { - IsConversationEnd = true - }); - return true; - } - - // After function is executed, pass the result to LLM - chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.Function, funcContextIn.ExecutionResult) - { - Name = funcContextIn.FunctionName - }); - response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); + response = await HandleFunctionCall(agent, + message, + onMessageReceived, + chatCompletionsOptions); } - choice = response.Value.Choices[0]; - message = choice.Message; - - _logger.LogInformation(message.Content); - - if (!string.IsNullOrEmpty(message.Content)) + if (response != null) { - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content)); + choice = response.Value.Choices[0]; + message = choice.Message; + + _logger.LogInformation(message.Content); + + if (!string.IsNullOrEmpty(message.Content)) + { + var msgByLlm = new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content); + await onMessageReceived(msgByLlm); + } } return true; @@ -198,10 +185,61 @@ public class ChatCompletionProvider : IChatCompletion return true; } + private async Task> HandleFunctionCall(Agent agent, + ChatMessage message, + Func onMessageReceived, + ChatCompletionsOptions chatCompletionsOptions) + { + Response response = default; + + if (message.FunctionCall == null || message.FunctionCall.Arguments == null) + { + return response; + } + + _logger.LogInformation($"{message.FunctionCall.Name}: {message.FunctionCall.Arguments}"); + var funcContextIn = new RoleDialogModel(ChatRole.Function.ToString(), message.Content) + { + CurrentAgentId = agent.Id, + FunctionName = message.FunctionCall.Name, + FunctionArgs = message.FunctionCall.Arguments + }; + + // Execute functions + await onMessageReceived(funcContextIn); + + if (funcContextIn.StopPropagate) + { + return response; + } + + if (funcContextIn.IsConversationEnd) + { + await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), funcContextIn.Content) + { + IsConversationEnd = true + }); + return response; + } + + // After function is executed, pass the result to LLM + if (funcContextIn.ExecutionResult != null) + { + chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.Function, funcContextIn.ExecutionResult) + { + Name = funcContextIn.FunctionName + }); + var client = GetClient(); + response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); + } + + return response; + } + 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)); @@ -244,6 +282,10 @@ public class ChatCompletionProvider : IChatCompletion } } + // 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; + _logger.LogInformation(string.Join("\n", chatCompletionsOptions.Messages.Select(x => $"{x.Role}: {x.Content}"))); return chatCompletionsOptions; }