Merge branch 'SciSharp:master' into master

This commit is contained in:
C. Oceania 2024-04-26 09:37:22 -05:00 committed by GitHub
commit 7cfe1a1c01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 116 additions and 78 deletions

View file

@ -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;
}

View file

@ -14,6 +14,7 @@ public class ButtonTemplateMessage : IRichMessage, ITemplateMessage
[JsonPropertyName("text")]
[JsonProperty("text")]
[Translate]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("template_type")]

View file

@ -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;
/// <summary>
/// The reason why you select this function or agent
@ -30,32 +30,29 @@ public class RoutingArgs
/// </summary>
[JsonPropertyName("response")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Response { get; set; }
public string Response { get; set; } = string.Empty;
/// <summary>
/// Agent for next action based on user latest response
/// </summary>
[JsonPropertyName("next_action_agent")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string AgentName { get; set; }
public string AgentName { get; set; } = string.Empty;
/// <summary>
/// Agent who can achieve user original goal
/// </summary>
[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) ? "" : $"<Route to {AgentName.ToUpper()} because {NextActionReason}>";

View file

@ -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<ResponseToUserRoutingHandler> logger, RoutingSettings settings)

View file

@ -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<RouteToAgentRoutingHandler> logger, RoutingSettings settings)

View file

@ -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;

View file

@ -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<ITranslationService>();
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<ITranslationService>();
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);
}
}

View file

@ -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<TranslationService> logger,
@ -31,7 +34,7 @@ public class TranslationService : ITranslationService
var unique = new HashSet<string>();
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<string>();
var map = new Dictionary<string, string>();
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<string>))
{
@ -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<object>))
{
@ -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<string>();
foreach (var item in (data as IEnumerable<string>))
@ -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<object>))
{
@ -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
/// <param name="list"></param>
/// <param name="language"></param>
/// <returns></returns>
private async Task<Dictionary<string, string>> InnerTranslate(HashSet<string> list, string language)
private async Task<string> 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<string, object>
{
{ "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<ITemplateRender>();
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<string>();
var map = new Dictionary<string, string>();
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
}

View file

@ -1,4 +1,4 @@
{{ text_list }}
=====
Translate the sentences in the list into {{ language }}, only output the translated text in string list [""].
Translate the above sentences in the list into {{ language }}, output the translated text in JSON array [""].

View file

@ -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 =>
{