diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 6f56d00b..80cd6984 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.MLTasks; namespace BotSharp.Abstraction.Conversations; diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs new file mode 100644 index 00000000..0adb2be8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Instructs; + +public interface IInstructService +{ + Task ExecuteInstructionRecursively(Agent agent, + List wholeDialogs, + Func onMessageReceived, + Func onFunctionExecuting, + Func onFunctionExecuted); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs new file mode 100644 index 00000000..f89bb8e0 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Instructs.Models; + +public class InstructResult +{ + public string Text { get; set; } + public string Function { get; set; } + public object Data { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 544fc826..9196a035 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using BotSharp.Abstraction.Routing.Settings; using BotSharp.Abstraction.Templating; +using BotSharp.Core.Instructs; +using BotSharp.Abstraction.Instructs; namespace BotSharp.Core; @@ -54,7 +56,6 @@ public static class BotSharpServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); // Register function callback services.AddScoped(); @@ -64,6 +65,8 @@ public static class BotSharpServiceCollectionExtensions services.AddScoped(); + services.AddScoped(); + return services; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs index 02a81350..fbd879ed 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; namespace BotSharp.Core.Conversations.Services; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index ea3c2603..98f2bedf 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -54,6 +54,11 @@ public class ConversationStateService : IConversationStateService, IDisposable _state = new ConversationState(); + if (_conversationId == null) + { + return _state; + } + _file = GetStorageFile(_conversationId); if (File.Exists(_file)) @@ -77,6 +82,11 @@ public class ConversationStateService : IConversationStateService, IDisposable public void Save() { + if (_conversationId == null) + { + return; + } + var states = new List(); foreach (var dic in _state) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.CallFunctions.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.CallFunctions.cs new file mode 100644 index 00000000..025b11e8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.CallFunctions.cs @@ -0,0 +1,50 @@ +using BotSharp.Abstraction.Functions; + +namespace BotSharp.Core.Instructs; + +public partial class InstructService +{ + private async Task CallFunctions(RoleDialogModel msg) + { + var hooks = _services.GetServices() + .OrderBy(x => x.Priority).ToList(); + + // Invoke functions + var functions = _services.GetServices() + .Where(x => x.Name == msg.FunctionName) + .ToList(); + + if (functions.Count == 0) + { + msg.Content = $"Can't find function implementation of {msg.FunctionName}."; + _logger.LogError(msg.Content); + return; + } + + foreach (var fn in functions) + { + // Before executing functions + foreach (var hook in hooks) + { + await hook.OnFunctionExecuting(msg); + } + + try + { + // Execute function + await fn.Execute(msg); + } + catch (Exception ex) + { + msg.ExecutionResult = ex.Message; + _logger.LogError(msg.ExecutionResult); + } + + // After functions have been executed + foreach (var hook in hooks) + { + await hook.OnFunctionExecuted(msg); + } + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs new file mode 100644 index 00000000..c3ad6346 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -0,0 +1,84 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Templating; + +namespace BotSharp.Core.Instructs; + +public partial class InstructService : IInstructService +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public InstructService(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task ExecuteInstructionRecursively(Agent agent, + List wholeDialogs, + Func onMessageReceived, + Func onFunctionExecuting, + Func onFunctionExecuted) + { + var chatCompletion = GetChatCompletion(); + + var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => + { + await onMessageReceived(msg); + }, async fn => + { + var preAgentId = agent.Id; + + await HandleFunctionMessage(fn, onFunctionExecuting, onFunctionExecuted); + + // Function executed has exception + if (fn.ExecutionResult == null || fn.StopCompletion) + { + await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, fn.Content)); + return; + } + + fn.Content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult; + + // Find response template + var templateService = _services.GetRequiredService(); + var response = await templateService.RenderFunctionResponse(agent.Id, fn); + if (!string.IsNullOrEmpty(response)) + { + await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, response)); + return; + } + + // After function is executed, pass the result to LLM to get a natural response + wholeDialogs.Add(fn); + + await ExecuteInstructionRecursively(agent, + wholeDialogs, + onMessageReceived, + onFunctionExecuting, + onFunctionExecuted); + }); + + return result; + } + + private async Task HandleFunctionMessage(RoleDialogModel msg, + Func onFunctionExecuting, + Func onFunctionExecuted) + { + // Call functions + await onFunctionExecuting(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.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs new file mode 100644 index 00000000..798d4786 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -0,0 +1,54 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.ApiAdapters; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.Instructs.Models; +using BotSharp.OpenAPI.ViewModels.Conversations; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class InstructModeController : ControllerBase, IApiAdapter +{ + private readonly IServiceProvider _services; + private readonly IUserIdentity _user; + + public InstructModeController(IServiceProvider services, + IUserIdentity user) + { + _services = services; + _user = user; + } + + [HttpPost("/instruct/{agentId}")] + public async Task NewConversation([FromRoute] string agentId, + [FromBody] NewMessageModel input) + { + var response = new InstructResult(); + var instructor = _services.GetRequiredService(); + var agentService = _services.GetRequiredService(); + Agent agent = await agentService.LoadAgent(agentId); + + await instructor.ExecuteInstructionRecursively(agent, + new List + { + new RoleDialogModel("user", input.Text) + }, + async msg => + { + response.Text = msg.Content; + }, + async fnExecuting => + { + + }, + async fnExecuted => + { + response.Function = fnExecuted.FunctionName; + response.Data = fnExecuted.ExecutionData; + }); + + return response; + } +}