diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs index 0c9a7436..566f7bef 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs @@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Conversations.Settings; public class RateLimitSetting { public int MaxConversationPerDay { get; set; } = 100; - public int MaxInputLengthPerRequest { get; set; } = 256; + public int MaxInputLengthPerRequest { get; set; } = 512; public int MinTimeSecondsBetweenMessages { get; set; } = 2; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs index ce8a5a93..647ebc98 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs @@ -14,6 +14,7 @@ public class ButtonTemplateMessage : IRichMessage, ITemplateMessage [JsonPropertyName("text")] [JsonProperty("text")] + [Translate] public string Text { get; set; } = string.Empty; [JsonPropertyName("template_type")] diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs index 105c0c57..f68b7231 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs @@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Routing.Models; public class RoutingArgs { [JsonPropertyName("function")] - public string Function { get; set; } + public string Function { get; set; } = string.Empty; /// /// The reason why you select this function or agent @@ -30,32 +30,29 @@ public class RoutingArgs /// [JsonPropertyName("response")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string Response { get; set; } + public string Response { get; set; } = string.Empty; /// /// Agent for next action based on user latest response /// [JsonPropertyName("next_action_agent")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string AgentName { get; set; } + public string AgentName { get; set; } = string.Empty; /// /// Agent who can achieve user original goal /// [JsonPropertyName("user_goal_agent")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string OriginalAgent { get; set; } + public string OriginalAgent { get; set; } = string.Empty; [JsonPropertyName("user_goal_description")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string UserGoal { get; set; } + public string UserGoal { get; set; } = string.Empty; [JsonPropertyName("language")] public string Language { get; set; } = LanguageType.ENGLISH; - [JsonPropertyName("lastest_message_translated_to_english")] - public string UserMessageInEnglish { get; set; } - public override string ToString() { var route = string.IsNullOrEmpty(AgentName) ? "" : $""; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs index 92a3b137..00c8c616 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs @@ -22,9 +22,7 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler type: "boolean"), new ParameterPropertyDef("language", "User preferred language, considering the whole conversation. Language could be English, Spanish or Chinese.", - required: true), - new ParameterPropertyDef("lastest_message_translated_to_english", - "Translate user lastest message in [CONVERSATION] to English"), + required: true) }; public ResponseToUserRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 5c08d679..59561461 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -31,9 +31,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler type: "boolean"), new ParameterPropertyDef("language", "User preferred language, considering the whole conversation. Language could be English, Spanish or Chinese.", - required: true), - new ParameterPropertyDef("lastest_message_translated_to_english", - "Translate lastest user message in [CONVERSATION] to English"), + required: true) }; public RouteToAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs index bd883314..5825a2c0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs @@ -16,7 +16,7 @@ public partial class RoutingService role = agent.Name; } - conversation += $"{role}: {dialog.Content}\r\n"; + conversation += $"{role}: {dialog.SecondaryContent ?? dialog.Content}\r\n"; } return conversation; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 36fb21be..c139ceab 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -100,11 +100,17 @@ public partial class RoutingService : IRoutingService var inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs); // Handle multi-language for input + var translator = _services.GetRequiredService(); - if (inst.Language != LanguageType.UNKNOWN && inst.Language != LanguageType.ENGLISH) + var language = states.GetState("language", inst.Language); + if (language != LanguageType.UNKNOWN && language != LanguageType.ENGLISH) { - message.Content = inst.UserMessageInEnglish; + message.SecondaryContent = message.Content; + message.Content = await translator.Translate(_router, message.MessageId, message.Content, + language: LanguageType.ENGLISH, + clone: false); } + storage.Append(convService.ConversationId, message); int loopCount = 1; @@ -150,22 +156,26 @@ public partial class RoutingService : IRoutingService } // Handle multi-language for output - if (inst.Language != LanguageType.UNKNOWN && inst.Language != LanguageType.ENGLISH) + if (language != LanguageType.UNKNOWN && language != LanguageType.ENGLISH) { - var translator = _services.GetRequiredService(); if (response.RichContent != null) { - response.RichContent.Message = await translator.Translate(_router, + if (string.IsNullOrEmpty(response.RichContent.Message.Text)) + { + response.RichContent.Message.Text = response.Content; + } + + response.SecondaryRichContent = await translator.Translate(_router, message.MessageId, - response.RichContent.Message, - language: inst.Language); + response.RichContent, + language: language); } else { response.SecondaryContent = await translator.Translate(_router, message.MessageId, response.Content, - language: inst.Language); + language: language); } } diff --git a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs index 815b2dc9..0e44e3e2 100644 --- a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs +++ b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs @@ -1,8 +1,10 @@ +using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Templating; using BotSharp.Abstraction.Translation.Attributes; using Newtonsoft.Json; using System.Collections; +using System.Collections.Generic; using System.Reflection; namespace BotSharp.Core.Translation; @@ -14,6 +16,7 @@ public class TranslationService : ITranslationService private readonly BotSharpOptions _options; private Agent _router; private string _messageId; + private IChatCompletion _completion; public TranslationService(IServiceProvider services, ILogger logger, @@ -31,7 +34,7 @@ public class TranslationService : ITranslationService var unique = new HashSet(); Collect(data, ref unique); - if (unique.Count == 0) + if (unique.IsNullOrEmpty()) { return data; } @@ -42,8 +45,31 @@ public class TranslationService : ITranslationService cloned = Clone(data); } - var map = await InnerTranslate(unique, language); - cloned = Assign(cloned, map); + // chat completion + _completion = CompletionProvider.GetChatCompletion(_services, + provider: _router?.LlmConfig?.Provider, + model: _router?.LlmConfig?.Model); + var template = _router.Templates.First(x => x.Name == "translation_prompt").Content; + + var texts = unique.ToArray(); + var translatedStringList = await InnerTranslate(JsonConvert.SerializeObject(texts), language, template); + + try + { + var translatedTexts = translatedStringList.JsonArrayContent(); + var map = new Dictionary(); + + for (var i = 0; i < texts.Length; i++) + { + map.Add(texts[i], translatedTexts[i]); + } + + cloned = Assign(cloned, map); + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } return cloned; } @@ -68,23 +94,21 @@ public class TranslationService : ITranslationService if (data == null) return; var dataType = data.GetType(); - if (dataType == typeof(string)) + if (IsStringType(dataType)) { res.Add(data.ToString()); return; } - var interfaces = dataType.GetTypeInfo().ImplementedInterfaces; - if (interfaces.Any(x => x.Name == typeof(IDictionary<,>).Name)) + if (IsDictionaryType(dataType)) { return; } - var isList = interfaces.Any(x => x.Name == typeof(IEnumerable).Name); - if (dataType.IsArray || isList) + if (IsListType(dataType)) { var elementType = dataType.IsArray ? dataType.GetElementType() : dataType.GetGenericArguments().FirstOrDefault(); - if (elementType == typeof(string)) + if (IsStringType(elementType)) { foreach (var item in (data as IEnumerable)) { @@ -92,7 +116,7 @@ public class TranslationService : ITranslationService res.Add(item); } } - else if (elementType != null && (elementType.IsClass || elementType.IsInterface)) + else if (IsTrackToNextLevel(elementType)) { foreach (var item in (data as IEnumerable)) { @@ -113,32 +137,30 @@ public class TranslationService : ITranslationService if (value == null) continue; - if (propType == typeof(string)) + if (IsStringType(propType)) { if (translate != null) { Collect(value, ref res); } } - else if (propType.IsClass || propType.IsInterface) + else if (IsTrackToNextLevel(propType)) { - interfaces = propType.GetTypeInfo().ImplementedInterfaces; - isList = interfaces.Any(x => x.Name == typeof(IEnumerable).Name); - if (interfaces.Any(x => x.Name == typeof(IDictionary<,>).Name)) + if (IsDictionaryType(propType)) { Collect(value, ref res); } - else if (propType.IsArray || isList) + else if (IsListType(propType)) { var elementType = propType.IsArray ? propType.GetElementType() : propType.GetGenericArguments().FirstOrDefault(); - if (elementType == typeof(string)) + if (IsStringType(elementType)) { if (translate != null) { Collect(value, ref res); } } - else if (elementType != null && (elementType.IsClass || elementType.IsInterface)) + else if (IsTrackToNextLevel(elementType)) { Collect(value, ref res); } @@ -163,22 +185,20 @@ public class TranslationService : ITranslationService if (data == null) return data; var dataType = data.GetType(); - if (dataType == typeof(string) && map.TryGetValue(data.ToString(), out var target)) + if (IsStringType(dataType) && map.TryGetValue(data.ToString(), out var target)) { return target as T; } - var interfaces = dataType.GetTypeInfo().ImplementedInterfaces; - if (interfaces.Any(x => x.Name == typeof(IDictionary<,>).Name)) + if (IsDictionaryType(dataType)) { return data; } - var isList = interfaces.Any(x => x.Name == typeof(IEnumerable).Name); - if (dataType.IsArray || isList) + if (IsListType(dataType)) { var elementType = dataType.IsArray ? dataType.GetElementType() : dataType.GetGenericArguments().FirstOrDefault(); - if (elementType == typeof(string)) + if (IsStringType(elementType)) { var list = new List(); foreach (var item in (data as IEnumerable)) @@ -195,7 +215,7 @@ public class TranslationService : ITranslationService data = dataType.IsArray ? list.ToArray() as T : list as T; } - else if (elementType != null && (elementType.IsClass || elementType.IsInterface)) + else if (IsTrackToNextLevel(elementType)) { foreach (var item in (data as IEnumerable)) { @@ -216,25 +236,23 @@ public class TranslationService : ITranslationService if (value == null) continue; - if (propType == typeof(string)) + if (IsStringType(propType)) { if (translate != null) { prop.SetValue(data, Assign(value, map)); } } - else if (propType.IsClass || propType.IsInterface) + else if (IsTrackToNextLevel(propType)) { - interfaces = propType.GetTypeInfo().ImplementedInterfaces; - isList = interfaces.Any(x => x.Name == typeof(IEnumerable).Name); - if (interfaces.Any(x => x.Name == typeof(IDictionary<,>).Name)) + if (IsDictionaryType(propType)) { Assign(value, map); } - else if (propType.IsArray || isList) + else if (IsListType(propType)) { var elementType = propType.IsArray ? propType.GetElementType() : propType.GetGenericArguments().FirstOrDefault(); - if (elementType == typeof(string)) + if (IsStringType(elementType)) { if (translate != null) { @@ -242,7 +260,7 @@ public class TranslationService : ITranslationService prop.SetValue(data, targetValue); } } - else if (elementType != null && (elementType.IsClass || elementType.IsInterface)) + else if (IsTrackToNextLevel(elementType)) { prop.SetValue(data, Assign(value, map)); } @@ -263,26 +281,19 @@ public class TranslationService : ITranslationService /// /// /// - private async Task> InnerTranslate(HashSet list, string language) + private async Task InnerTranslate(string texts, string language, string template) { - // chat completion - var completion = CompletionProvider.GetChatCompletion(_services, - provider: _router?.LlmConfig?.Provider, - model: _router?.LlmConfig?.Model); - - var texts = list.ToArray(); var translator = new Agent { Id = Guid.Empty.ToString(), Name = "Translator", TemplateDict = new Dictionary { - { "text_list", JsonConvert.SerializeObject(texts) }, + { "text_list", texts }, { "language", language } } }; - var template = _router.Templates.First(x => x.Name == "translation_prompt").Content; var render = _services.GetRequiredService(); var prompt = render.Render(template, translator.TemplateDict); @@ -294,15 +305,39 @@ public class TranslationService : ITranslationService MessageId = _messageId } }; - var translationResponse = await completion.GetChatCompletions(translator, translationDialogs); - var translatedTexts = translationResponse.Content.JsonArrayContent(); - var map = new Dictionary(); - - for (var i = 0; i < list.Count; i++) - { - map.Add(texts[i], translatedTexts[i]); - } - - return map; + var response = await _completion.GetChatCompletions(translator, translationDialogs); + return response.Content; } + + #region Type methods + private static bool IsStringType(Type? type) + { + if (type == null) return false; + + return type == typeof(string); + } + + private static bool IsListType(Type? type) + { + if (type == null) return false; + + var interfaces = type.GetTypeInfo().ImplementedInterfaces; + return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name); + } + + private static bool IsDictionaryType(Type? type) + { + if (type == null) return false; + + var underlyingInterfaces = type.UnderlyingSystemType.GetTypeInfo().ImplementedInterfaces; + return underlyingInterfaces.Any(x => x.Name == typeof(IDictionary).Name); + } + + private static bool IsTrackToNextLevel(Type? type) + { + if (type == null) return false; + + return type.IsClass || type.IsInterface || type.IsAbstract; + } + #endregion } 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 729f0f72..a3052187 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,4 +1,4 @@ {{ text_list }} ===== -Translate the sentences in the list into {{ language }}, only output the translated text in string list [""]. \ No newline at end of file +Translate the above sentences in the list into {{ language }}, output the translated text in JSON array [""]. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index 820dcb84..a9fe828a 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -300,7 +300,6 @@ public class ChatCompletionProvider : IChatCompletion })); prompt += $"{verbose}\r\n"; - prompt += "\r\n[CONVERSATION]\r\n"; verbose = string.Join("\r\n", chatCompletionsOptions.Messages .Where(x => x.Role != AgentRole.System).Select(x => {