diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs index d260a09d0..aa4154ffd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs @@ -43,4 +43,6 @@ public class PageActionArgs /// Wait time in seconds after page is opened /// public int WaitTime { get; set; } + + public bool ReadInnerHTMLAsBody { get; set; } = false; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index f0890ce9b..dd2648d8b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -27,6 +27,7 @@ public interface IBotSharpRepository User? GetUserByAffiliateId(string affiliateId) => throw new NotImplementedException(); User? GetUserByUserName(string userName) => throw new NotImplementedException(); void CreateUser(User user) => throw new NotImplementedException(); + void UpdateExistUser(string userId, User user) => throw new NotImplementedException(); void UpdateUserVerified(string userId) => throw new NotImplementedException(); void UpdateUserVerificationCode(string userId, string verficationCode) => throw new NotImplementedException(); void UpdateUserPassword(string userId, string password) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs index 588dedfcf..d3d6a453f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Infrastructures.Enums; - namespace BotSharp.OpenAPI.ViewModels.Translations; public class TranslationRequestModel @@ -7,3 +5,15 @@ public class TranslationRequestModel public string Text { get; set; } = null!; public string ToLang { get; set; } = LanguageType.CHINESE; } + +public class TranslationScriptTimestamp +{ + public string Text { set; get; } = null!; + public string Timestamp { get; set; } = null!; +} + +public class TranslationLongTextRequestModel +{ + public TranslationScriptTimestamp[] Texts { get; set; } = null!; + public string ToLang { get; set; } = LanguageType.CHINESE; +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs index 22a8eb956..cddabe10e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs @@ -33,4 +33,6 @@ public class UserRole /// AI Assistant /// public const string Assistant = "assistant"; + + public const string Root = "root"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 750858a78..5a3315b77 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -13,7 +13,8 @@ public interface IUserService Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); - Task SendVerificationCodeResetPassword(User user); + Task SendVerificationCodeResetPasswordNoLogin(User user); + Task SendVerificationCodeResetPasswordLogin(); Task ResetUserPassword(User user); Task ModifyUserEmail(string email); Task ModifyUserPhone(string phone); diff --git a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs index f9aa73e3b..453b22774 100644 --- a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs +++ b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs @@ -101,12 +101,12 @@ public async Task Translate(Agent router, string messageId, T data, string { var translatedStringList = await InnerTranslate(texts, language, template); - int retry = 0; + /*int retry = 0; while (translatedStringList.Texts.Length != texts.Count && retry < 3) { translatedStringList = await InnerTranslate(texts, language, template); retry++; - } + }*/ // Override language if it's Unknown, it's used to output the corresponding language. var states = _services.GetRequiredService(); @@ -119,7 +119,7 @@ public async Task Translate(Agent router, string messageId, T data, string var translatedTexts = translatedStringList.Texts; var memoryInputs = new List(); - for (var i = 0; i < texts.Count; i++) + for (var i = 0; i < Math.Min(texts.Count, translatedTexts.Length); i++) { map[outOfMemoryList[i].OriginalText] = translatedTexts[i].Text; memoryInputs.Add(new TranslationMemoryInput @@ -375,6 +375,8 @@ private async Task InnerTranslate(List text var render = _services.GetRequiredService(); var prompt = render.Render(template, translator.TemplateDict); + _logger.LogInformation($"Translation prompt: {prompt}"); + var translationDialogs = new List { new RoleDialogModel(AgentRole.User, prompt) @@ -384,6 +386,8 @@ private async Task InnerTranslate(List text } }; var response = await _completion.GetChatCompletions(translator, translationDialogs); + + _logger.LogInformation(response.Content); return response.Content.JsonContent(); } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index c0cf95888..ef4a77c75 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -32,6 +32,7 @@ public UserService(IServiceProvider services, public async Task CreateUser(User user) { + string hasRegisterId = null; if (string.IsNullOrEmpty(user.UserName)) { // generate unique name @@ -48,7 +49,7 @@ public async Task CreateUser(User user) if (record != null) { - return record; + hasRegisterId = record.Id; } if (string.IsNullOrEmpty(user.Id)) @@ -71,7 +72,14 @@ record = user; record.Verified = false; } - db.CreateUser(record); + if (hasRegisterId == null) + { + db.CreateUser(record); + } + else + { + db.UpdateExistUser(hasRegisterId, record); + } _logger.LogWarning($"Created new user account: {record.Id} {record.UserName}"); Utilities.ClearCache(); @@ -386,8 +394,9 @@ public async Task VerifyUserNameExisting(string userName) } var db = _services.GetRequiredService(); + var user = db.GetUserByUserName(userName); - if (user != null) + if (user != null && user.Verified) { return true; } @@ -404,7 +413,7 @@ public async Task VerifyEmailExisting(string email) var db = _services.GetRequiredService(); var emailName = db.GetUserByEmail(email); - if (emailName != null) + if (emailName != null && emailName.Verified) { return true; } @@ -412,32 +421,56 @@ public async Task VerifyEmailExisting(string email) return false; } - public async Task SendVerificationCodeResetPassword(User user) + public async Task SendVerificationCodeResetPasswordNoLogin(User user) { var db = _services.GetRequiredService(); User? record = null; - if (!string.IsNullOrWhiteSpace(_user.Id)) + if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone)) { - record = db.GetUserById(_user.Id); + return false; } - else + + if (!string.IsNullOrEmpty(user.Phone)) { - if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone)) - { - return false; - } + record = db.GetUserByPhone(user.Phone); + } - if (!string.IsNullOrEmpty(user.Email)) - { - record = db.GetUserByEmail(user.Email); - } + if (!string.IsNullOrEmpty(user.Email)) + { + record = db.GetUserByEmail(user.Email); + } - if (!string.IsNullOrEmpty(user.Phone)) - { - record = db.GetUserByPhone(user.Phone); - } + if (record == null) + { + return false; + } + + record.VerificationCode = Nanoid.Generate(alphabet: "0123456789", size: 6); + + //update current verification code. + db.UpdateUserVerificationCode(record.Id, record.VerificationCode); + + //send code to user Email. + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.VerificationCodeResetPassword(record); + } + + return true; + } + + public async Task SendVerificationCodeResetPasswordLogin() + { + var db = _services.GetRequiredService(); + + User? record = null; + + if (!string.IsNullOrWhiteSpace(_user.Id)) + { + record = db.GetUserById(_user.Id); } if (record == null) @@ -520,6 +553,11 @@ public async Task ModifyUserPhone(string phone) return false; } + if ((record.UserName.Substring(0, 3) == "+86" || record.FirstName.Substring(0, 3) == "+86") && phone.Substring(0, 3) != "+86") + { + phone = $"+86{phone}"; + } + db.UpdateUserPhone(record.Id, phone); return true; } diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid index 6b3a8677e..fd67a5fd1 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid @@ -1,6 +1,19 @@ {{ text_list }} ===== +{% if language == "Chinese" %} +将以上所有句子翻译成中文。 + +要求: +* 以 JSON 格式输出翻译后的文本 {"input_lang":"原始文本语言", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}。 +* output_count 必须等于输出中texts数组的长度。 +{% else %} Translate all the above sentences into {{ language }}. -Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}. -The "output_count" must equal the length of the "texts" array in the output. + +Requirements: +* Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}. +* The "output_count" must equal the length of the "texts" array in the output. +{% endif %} + + + diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs index ac7686a1c..7845e8cf6 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Translation; using BotSharp.OpenAPI.ViewModels.Translations; @@ -9,10 +9,13 @@ namespace BotSharp.OpenAPI.Controllers; public class TranslationController : ControllerBase { private readonly IServiceProvider _services; + private readonly JsonSerializerOptions _jsonOptions; - public TranslationController(IServiceProvider services) + public TranslationController(IServiceProvider services, + BotSharpOptions options) { _services = services; + _jsonOptions = InitJsonOptions(options); } [HttpPost("/translate")] @@ -21,10 +24,79 @@ public async Task Translate([FromBody] TranslationRequ var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); var translator = _services.GetRequiredService(); - var text = await translator.Translate(agent, Guid.NewGuid().ToString(), model.Text, language: model.ToLang); + var states = _services.GetRequiredService(); + states.SetState("max_tokens", "8192"); + var text = await translator.Translate(agent, Guid.NewGuid().ToString(), model.Text.Split("\r\n"), language: model.ToLang); return new TranslationResponseModel { - Text = text + Text = string.Join("\r\n", text) }; } + + [HttpPost("/translate/long-text")] + public async Task SendMessageSse([FromBody] TranslationLongTextRequestModel model) + { + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); + var translator = _services.GetRequiredService(); + + Response.StatusCode = 200; + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream"); + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache"); + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive"); + + foreach (var script in model.Texts) + { + var translatedText = await translator.Translate(agent, Guid.NewGuid().ToString(), script.Text, language: model.ToLang); + + var json = JsonSerializer.Serialize(new TranslationScriptTimestamp + { + Text = translatedText, + Timestamp = script.Timestamp + }, _jsonOptions); + + await OnChunkReceived(Response, json); + } + + await OnEventCompleted(Response); + } + + private async Task OnChunkReceived(HttpResponse response, string text) + { + var buffer = Encoding.UTF8.GetBytes($"data:{text}\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + await Task.Delay(10); + + buffer = Encoding.UTF8.GetBytes("\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + } + + private async Task OnEventCompleted(HttpResponse response) + { + var buffer = Encoding.UTF8.GetBytes("data:[DONE]\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + + buffer = Encoding.UTF8.GetBytes("\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + } + + private JsonSerializerOptions InitJsonOptions(BotSharpOptions options) + { + var jsonOption = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true + }; + + if (options?.JsonSerializerOptions != null) + { + foreach (var option in options.JsonSerializerOptions.Converters) + { + jsonOption.Converters.Add(option); + } + } + + return jsonOption; + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 281de3e85..bf15a33b5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -108,12 +108,20 @@ public async Task VerifyEmailExisting([FromQuery] string email) { return await _userService.VerifyEmailExisting(email); } + [AllowAnonymous] - [HttpPost("/user/verifycode")] + [HttpPost("/user/verifycode-out")] public async Task SendVerificationCodeResetPassword([FromBody] UserCreationModel user) { - return await _userService.SendVerificationCodeResetPassword(user.ToUser()); + return await _userService.SendVerificationCodeResetPasswordNoLogin(user.ToUser()); } + + [HttpPost("/user/verifycode-in")] + public async Task SendVerificationCodeResetPasswordLogined() + { + return await _userService.SendVerificationCodeResetPasswordLogin(); + } + [AllowAnonymous] [HttpPost("/user/resetpassword")] public async Task ResetUserPassword([FromBody] UserResetPasswordModel user) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 9b6b62c1a..db40705db 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -79,6 +79,18 @@ public void CreateUser(User user) _dc.Users.InsertOne(userCollection); } + public void UpdateExistUser(string userId, User user) + { + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update + .Set(x => x.Email, user.Email) + .Set(x => x.Phone, user.Phone) + .Set(x => x.Salt, user.Salt) + .Set(x => x.Password, user.Password) + .Set(x => x.VerificationCode, user.VerificationCode); + _dc.Users.UpdateOne(filter, update); + } + public void UpdateUserVerified(string userId) { var filter = Builders.Filter.Eq(x => x.Id, userId); diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs index 918b33e59..e228d6075 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs @@ -1,4 +1,5 @@ using BotSharp.Plugin.Planner.TwoStaging.Models; +using System.Threading.Tasks; namespace BotSharp.Plugin.Planner.Functions; @@ -27,14 +28,18 @@ public async Task Execute(RoleDialogModel message) var planPrimary = states.GetState("planning_result"); var taskSecondary = JsonSerializer.Deserialize(msgSecondary.FunctionArgs); - + // Search knowledgebase - var knowledges = await knowledgeService.SearchVectorKnowledge(taskSecondary.SolutionQuestion, collectionName, new VectorSearchOptions + var hooks = _services.GetServices(); + var knowledges = new List(); + foreach (var hook in hooks) { - Confidence = 0.7f - }); + var k = await hook.GetRelevantKnowledges(message, taskSecondary.SolutionQuestion); + knowledges.AddRange(k); + } + knowledges = knowledges.Distinct().ToList(); - var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges.Select(x => x.ToQuestionAnswer())); + var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges); // Get second stage planning prompt var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs index 22ddc3c32..e9fd3b378 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs @@ -1,8 +1,6 @@ -using BotSharp.Abstraction.Knowledges; using BotSharp.Abstraction.Planning; using BotSharp.Plugin.Planner.TwoStaging; using BotSharp.Plugin.Planner.TwoStaging.Models; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace BotSharp.Plugin.Planner.Functions; diff --git a/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs b/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs index 1a85e1c0a..503de3d53 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs @@ -2,13 +2,32 @@ namespace BotSharp.Plugin.Planner.Hooks; public class PlannerAgentHook : AgentHookBase { - public override string SelfId => string.Empty; + public override string SelfId => BuiltInAgentId.Planner; public PlannerAgentHook(IServiceProvider services, AgentSettings settings) : base(services, settings) { } + public override bool OnInstructionLoaded(string template, Dictionary dict) + { + var knowledgeHooks = _services.GetServices(); + + // Get global knowledges + var Knowledges = new List(); + foreach (var hook in knowledgeHooks) + { + var k = hook.GetGlobalKnowledges(new RoleDialogModel(AgentRole.User, template) + { + CurrentAgentId = BuiltInAgentId.Planner + }).Result; + Knowledges.AddRange(k); + } + dict["global_knowledges"] = Knowledges; + + return true; + } + public override void OnAgentLoaded(Agent agent) { var conv = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid index 5dda81e3a..d15329a89 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid @@ -1,4 +1,6 @@ +The user is dealing with a complex problem, and you need to break this complex problem into several small tasks to more easily solve the user's needs. Use the TwoStagePlanner approach to plan the overall implementation steps, follow the below steps strictly. + 1. Call plan_primary_stage to generate the primary plan. If you've already got the plan to meet the user goal, directly go to step 5. 2. If need_lookup_dictionary is True, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name. @@ -15,6 +17,7 @@ Don't run the planning process repeatedly if you have already got the result of {% if global_knowledges != empty -%} ===== Global Knowledge: +Current date time is: {{ "now" | date: "%Y-%m-%d %H:%M" }} {% for k in global_knowledges %} {{ k }} {% endfor %} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid index 113767aed..81e22fe8b 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid @@ -6,7 +6,6 @@ Reference to "Primary Planning" and the additional knowledge included. Breakdown * If need_lookup_dictionary is true, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name/code. * Output all the steps as much detail as possible in JSON: [{{ response_format }}] - Additional Requirements: * "output_results" is variable name that needed to be used in the next step. diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj index 4f93597a9..d4d0a2439 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj @@ -30,6 +30,7 @@ + @@ -69,6 +70,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs index 78ab85414..e3d8e57e0 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs @@ -1,8 +1,10 @@ using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Routing; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.SqlDriver.Models; using Dapper; using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Logging; using MySqlConnector; namespace BotSharp.Plugin.SqlDriver.Functions; @@ -13,32 +15,48 @@ public class ExecuteQueryFn : IFunctionCallback public string Indication => "Performing data retrieval operation."; private readonly SqlDriverSetting _setting; private readonly IServiceProvider _services; + private readonly ILogger _logger; - public ExecuteQueryFn(IServiceProvider services, SqlDriverSetting setting) + public ExecuteQueryFn(IServiceProvider services, SqlDriverSetting setting, ILogger logger) { _services = services; _setting = setting; + _logger = logger; } public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var refinedArgs = await RefineSqlStatement(message, args); + var settings = _services.GetRequiredService(); - var results = settings.DatabaseType switch + + try { - "MySql" => RunQueryInMySql(args.SqlStatements), - "SqlServer" => RunQueryInSqlServer(args.SqlStatements), - _ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.") - }; - - if (results.Count() == 0) + var results = settings.DatabaseType switch + { + "MySql" => RunQueryInMySql(refinedArgs.SqlStatements), + "SqlServer" => RunQueryInSqlServer(refinedArgs.SqlStatements), + _ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.") + }; + + if (results.Count() == 0) + { + message.Content = "No record found"; + return true; + } + + message.Content = JsonSerializer.Serialize(results); + } + catch (Exception ex) { - message.Content = "No record found"; - return true; + _logger.LogError(ex, "Error occurred while executing SQL query."); + message.Content = "Error occurred while retrieving information."; + message.StopCompletion = true; + return false; } - message.Content = JsonSerializer.Serialize(results); - if (args.FormattingResult) { var conv = _services.GetRequiredService(); @@ -59,6 +77,7 @@ public async Task Execute(RoleDialogModel message) }); message.Content = result.Content; + message.StopCompletion = true; } return true; @@ -77,4 +96,54 @@ private IEnumerable RunQueryInSqlServer(string[] sqlTexts) using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString); return connection.Query(string.Join("\r\n", sqlTexts)); } + + private async Task RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args) + { + // get table DDL + var fn = _services.GetRequiredService(); + var msg = RoleDialogModel.From(message); + await fn.InvokeFunction("sql_table_definition", msg); + + // refine SQL + var agentService = _services.GetRequiredService(); + var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); + var dictionarySqlPrompt = await GetDictionarySQLPrompt(string.Join("\r\n\r\n", args.SqlStatements), msg.Content); + var agent = new Agent + { + Id = message.CurrentAgentId ?? string.Empty, + Name = "sqlDriver_ExecuteQuery", + Instruction = dictionarySqlPrompt, + TemplateDict = new Dictionary(), + LlmConfig = currentAgent.LlmConfig + }; + + var completion = CompletionProvider.GetChatCompletion(_services, + provider: agent.LlmConfig.Provider, + model: agent.LlmConfig.Model); + + var refinedMessage = await completion.GetChatCompletions(agent, new List + { + new RoleDialogModel(AgentRole.User, "Check and output the correct SQL statements") + }); + + return refinedMessage.Content.JsonContent(); + } + + private async Task GetDictionarySQLPrompt(string originalSql, string tableStructure) + { + var agentService = _services.GetRequiredService(); + var render = _services.GetRequiredService(); + var knowledgeHooks = _services.GetServices(); + + var agent = await agentService.GetAgent(BuiltInAgentId.SqlDriver); + var template = agent.Templates.FirstOrDefault(x => x.Name == "sql_statement_correctness")?.Content ?? string.Empty; + var responseFormat = JsonSerializer.Serialize(new ExecuteQueryArgs { }); + + return render.Render(template, new Dictionary + { + { "original_sql", originalSql }, + { "table_structure", tableStructure }, + { "response_format", responseFormat } + }); + } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs index ac99c699f..291607809 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs @@ -33,20 +33,25 @@ public async Task OnPlanningCompleted(string planner, RoleDialogModel msg) var conv = _services.GetRequiredService(); var wholeDialogs = conv.GetDialogHistory(); wholeDialogs.Add(RoleDialogModel.From(msg)); - wholeDialogs.Add(RoleDialogModel.From(msg, AgentRole.User, "use execute_sql to run query")); - - var agent = await _services.GetRequiredService().LoadAgent("beda4c12-e1ec-4b4b-b328-3df4a6687c4f"); + wholeDialogs.Add(RoleDialogModel.From(msg, AgentRole.User, $"call execute_sql to run query, set formatting_result as {settings.FormattingResult}")); + var agent = await _services.GetRequiredService().LoadAgent(BuiltInAgentId.SqlDriver); var completion = CompletionProvider.GetChatCompletion(_services, provider: agent.LlmConfig.Provider, model: agent.LlmConfig.Model); var response = await completion.GetChatCompletions(agent, wholeDialogs); + + // Invoke "execute_sql" var routing = _services.GetRequiredService(); await routing.InvokeFunction(response.FunctionName, response); msg.CurrentAgentId = agent.Id; msg.FunctionName = response.FunctionName; msg.FunctionArgs = response.FunctionArgs; msg.Content = response.Content; + msg.StopCompletion = response.StopCompletion; + + /*var routing = _services.GetRequiredService(); + await routing.InvokeAgent(BuiltInAgentId.SqlDriver, wholeDialogs);*/ } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs index ac2279ea5..bc5c6fbdd 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs @@ -7,8 +7,12 @@ public class ExecuteQueryArgs [JsonPropertyName("sql_statements")] public string[] SqlStatements { get; set; } = []; + [JsonPropertyName("tables")] + public string[] Tables { get; set; } = []; + /// /// Beautifying query result /// + [JsonPropertyName("formatting_result")] public bool FormattingResult { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs index 5815151c6..3a4095a69 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs @@ -10,4 +10,5 @@ public class SqlDriverSetting public string SqlServerExecutionConnectionString { get; set; } = null!; public string SqlLiteConnectionString { get; set; } = null!; public bool ExecuteSqlSelectAutonomous { get; set; } = false; + public bool FormattingResult { get; set; } = true; } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json index 60309dffa..eb79816f8 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json @@ -10,7 +10,7 @@ "profiles": [ "database" ], "llmConfig": { "provider": "openai", - "model": "gpt-4o-mini" + "model": "gpt-4o" }, "routingRules": [ { diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json index 15e6d281d..9cdafc04a 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json @@ -11,8 +11,22 @@ "type": "string", "description": "sql statement" } + }, + + "formatting_result": { + "type": "boolean", + "description": "formatting the results" + }, + + "tables": { + "type": "array", + "description": "all related tables", + "items": { + "type": "string", + "description": "table name" + } } }, - "required": [ "sql_statement" ] + "required": [ "sql_statement", "tables", "formatting_result" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid index 7c40b11a1..5d07a941e 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid @@ -1 +1,5 @@ -Output in human readable format. If there is large amount of information, shape it in tabular. \ No newline at end of file +Output in human readable format. If there is large amount of rows, shape it in tabular, otherwise, output in plain text. +Put user task description in the first line in the same language, for example, user is using Chinese, you have to output the result in Chinese. + +User Task Description: +{{ requirement_detail }} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid new file mode 100644 index 000000000..cf61eb1b6 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid @@ -0,0 +1,11 @@ +You are a sql statement corrector. You will need to refer to the table structure and rewrite the original sql statement so it's using the correct information, e.g. column name. +Output the sql statement only without comments, in JSON format: {{ response_format }} +Make sure all the column names are defined in the Table Structure. + +===== +Original SQL statements: +{{ original_sql }} + +===== +Table Structure: +{{ table_structure }} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index cc1a00094..108ff4332 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -67,9 +67,13 @@ await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCall result.ResponseStatusCode = response.Status; if (response.Status == 200) { - // Disable this due to performance issue, some page is too large - // result.Body = await page.InnerHTMLAsync("body"); result.IsSuccess = true; + + // Be careful if page is too large, it will cause performance issue + if (args.ReadInnerHTMLAsBody) + { + result.Body = await page.InnerHTMLAsync("body"); + } } else {